S3C2440 字符设备驱动程序之中断方式的按键驱动_编写代码(七)
参考:https://blog.csdn.net/fengyuwuzu0519/article/details/71046343
字符设备驱动程序之中断方式的按键驱动_编写代码
使用中断方式,那么肯定有一个中断的初始化注册,就是告诉内核,我按下按键的时候会触发一个中断,同时一定有一个中断处理函数来处理中断发生时应该做什么。
linux内核中 如何告诉内核我按下按键了给我触发中断并实现中断处理函数呢。
注册中断(open驱动程序时调用):int request_irq(unsigned int irq, irq_handler_t handler,unsigned long irqflags, const char *devname, void *dev_id)
request_irq()函数参数解析:
※※※重要!!void *:void即“无类型”,void *则为“无类型指针”,可以指向任何数据类型。所以后面的代码传进来结构体。
1、从原理图可知IRQ中断号irq:(IRQ_EINT0……)
2、向系统注册的中断处理函数,中断发生时,系统调用这个函数,dev_id参数被传递给它,中断处理函数handler的格式:
3、触发方式:irqflags:type(IRQT_BOTHEDGE双边沿触发:上升沿和下降沿都可以触发中断)。
4、devname:中断名称,可以使用cat /proc/interrupts 查看此名称
5、dev_id:用法很简单。在free_irq卸载时,通过irq与dev_id结合在一起,来确定卸载哪一个irqaction结构。
释放中断(卸载驱动程序时,解除按键中断):
void free_irq(unsigned int irq, void *dev_id)
参数:irq中断号。dev_id用法很简单,在free_irq卸载时,通过irq与dev_id结合在一起,来确定卸载哪一个irqaction结构。
驱动程序:third_drv.c
/*
一、驱动框架:
1.先定义file_operations结构体,其中有对设备的打开,读和写的操作函数。
2.分别定义相关的操作函数
3.定义好对设备的操作函数的结构体(file_operations)后,将其注册到内核的file_operations结构数组中。
此设置的主设备号为此结构在数组中的下标。
4.定义出口函数:卸载注册到内核中的设备相关资源
5.修饰 入口 和 出口函数
6.给系统提供更多的内核消息,在sys目录下提供设备的相关信息。应用程序udev可以据此自动创建设备节点,
创建一个class设备类,在此类下创建设备
*/
#include <linux/module.h> //内涵头文件,含有一些内核常用函数的原形定义。
#include <linux/kernel.h> //最基本的文件,支持动态添加和卸载模块。Hello World驱动要这一个文件就可以。
#include <linux/fs.h> //包含了文件操作相关的struct的定义,例如struct file_operations
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h> //包含了copy_to_user、copy_from_user等内核访问用户进程内存地址的函数定义
#include <asm/irq.h>
#include <asm/io.h> //包含了ioremap、ioread等内核访问IO内存等函数的定义
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
static struct class *thirddrv_class; //一个类
static struct class_device *thirddrv_class_dev; //一个类里面再建立一个设备
volatile unsigned long *gpfcon;
volatile unsigned long *gpfdat;
volatile unsigned long *gpgcon;
volatile unsigned long *gpgdat;
static irqreturn_t button_irq(int irq, void *dev_id)
{
printk("irq = %d\n",irq);
return IRQ_HANDLED;
}
static int third_drv_open(struct inode *inode, struct file *file)
{
/* 配置GPF0,2为输入引脚 */
/* 配置GPF3,11为输入引脚 */
request_irq(IRQ_EINT0, button_irq, IRQT_BOTHEDGE, "S2", 1); //配置为中断引脚
request_irq(IRQ_EINT2, button_irq, IRQT_BOTHEDGE, "S3", 1);
request_irq(IRQ_EINT11, button_irq, IRQT_BOTHEDGE, "S4", 1);
request_irq(IRQ_EINT19, button_irq, IRQT_BOTHEDGE, "S5", 1);
return 0;
}
ssize_t third_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
/* 返回4个引脚的电平 */
unsigned char key_vals[4];
int regval;
//如果传进来的size不等于我们返回的4个字节,返回一个错误值
if (size != sizeof(key_vals))
return -EINVAL;
/* 读GPF0,2为输入引脚 */
regval = *gpfdat;
key_vals[0]=(regval & (1<<0)) ? 1 : 0;
key_vals[1]=(regval & (1<<2)) ? 1 : 0;
/* 读GPG3,11为输入引脚 */
regval = *gpgdat;
key_vals[2]=(regval & (1<<3)) ? 1 : 0;
key_vals[3]=(regval & (1<<11)) ? 1 : 0;
copy_to_user(buf, key_vals, sizeof(key_vals));
return sizeof(key_vals);
}
int third_drv_close(struct inode *inode, struct file *file)
{
free_irq(IRQ_EINT0, 1);
free_irq(IRQ_EINT2, 1);
free_irq(IRQ_EINT11, 1);
free_irq(IRQ_EINT19, 1);
return 0;
}
static struct file_operations third_drv_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = third_drv_open,
.read = third_drv_read,
.release = third_drv_close,
};
int major;
static int third_drv_init(void)
{
major = register_chrdev(0, "third_drv", &third_drv_fops);
//创建一个类
thirddrv_class = class_create(THIS_MODULE, "firstdrv");
//在这个类下面再创建一个设备
//mdev是udev的一个简化版本
//mdev应用程序,就会被内核调用,会根据类和类下面的设备这些信息
thirddrv_class_dev = class_device_create(thirddrv_class, NULL, MKDEV(major, 0), NULL, "buttons");/* /dev/buttons */
//建立地址映射:物理地址->虚拟地址
gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16); //指向的是虚拟地址,第一个参数是物理开始地址,第二个是长度(字节)
gpfdat = gpfcon + 1; //加1,实际加4个字节
gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16); //指向的是虚拟地址,第一个参数是物理开始地址,第二个是长度(字节)
gpgdat = gpgcon + 1; //加1,实际加4个字节
return 0;
}
static void third_drv_exit(void)
{
unregister_chrdev(major, "third_drv");
class_device_unregister(thirddrv_class_dev);
class_destroy(thirddrv_class);
iounmap(gpfcon);
iounmap(gpgcon);
return 0;
}
module_init(third_drv_init);
module_exit(third_drv_exit);
MODULE_LICENSE("GPL");
验证驱动中断:(下面步骤,不写应用程序来验证驱动程序)
使用命令:exec 5</dev/buttons,打开/dev/buttons这个设备,定位到文件描述符fd5,挂载到5下,会调用open)
cat /proc/interrupts (产生的中断的信息)
使用ps命令查看,当前进程是-sh(shell),PID是772。
使用命令,ls -l /proc/772/fd,文件描述符fd5指向/dev/buttons,以后通过文件描述符5来访问buttons设备。
(exec是用来执行一个进程的)
(linux中,所有设备都是文件,/dev/buttons也是文件,有文件描述符exec 5</dev/button 将 /dev/button文件关联到文件描述符5,以后对5的操作,就是对设备文件的操作)
(在Shell里执行exec 5</dev/button,因此5是Shell新打开的文件描述符,Shell的进程ID是772)
(在proc文件系统里772的fd目录,表示772进程打开的文件描述符)
(ps查看当前进程(可以查看进程状态s:休眠))
使用命令,exec 5<&-,关闭文件描述符fd5,释放中断。(会调用release)
测试:(按下按键)
IRQ_EINT0:16=16+0,IRQ_EINT2:18=16+2,IRQ_EINT11:55=16+39,IRQ_EINT19(复位键):63=16+47。
因为是双边沿触发,所以每次按下按键,松开按键,打印两次。
优化上面的程序(读出按键值)
1、内核有一个系统函数s3c2410_gpio_getpin(引脚PIN):读出引脚的值。
2、定义了一个结构体pin_desc:
这个结构体在request_irq函数里传进去。
3、在read函数中,如果没有按键动作发生,休眠(让出CPU,不返回);如果有按键动作发生,直接返回。
休眠:wait_event_interruptible(button_waitq, ev_press)
(把进程挂在button_waitq队列里面)
如果ev_press=0,让应用程序休眠,不返回,程序停止在此处。当被唤醒时,从此处继续执行。
定义上面两个参数:
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
/* 中断时间标志,中断服务程序将它置1,third_drv_read将它清0 */
static volatile int ev_press=0;
4、当中断发生,执行中断处理函数,此时唤醒队列中次应用的进程,继续执行,返回结果。
唤醒:(去button_waitq队列,把挂在这个队列的进程唤醒)
ev_press = 1; /* 表示中断发生了 */
wake_up_interruptible(&button_waitq); /* 唤醒休眠的进程,去button_wq队列的进程唤醒 */
完整的驱动代码:third_drv.c
/*
一、驱动框架:
1.先定义file_operations结构体,其中有对设备的打开,读和写的操作函数。
2.分别定义相关的操作函数
3.定义好对设备的操作函数的结构体(file_operations)后,将其注册到内核的file_operations结构数组中。
此设置的主设备号为此结构在数组中的下标。
4.定义出口函数:卸载注册到内核中的设备相关资源
5.修饰 入口 和 出口函数
6.给系统提供更多的内核消息,在sys目录下提供设备的相关信息。应用程序udev可以据此自动创建设备节点,
创建一个class设备类,在此类下创建设备
*/
#include <linux/module.h> //内涵头文件,含有一些内核常用函数的原形定义。
#include <linux/kernel.h> //最基本的文件,支持动态添加和卸载模块。Hello World驱动要这一个文件就可以。
#include <linux/fs.h> //包含了文件操作相关的struct的定义,例如struct file_operations
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h> //包含了copy_to_user、copy_from_user等内核访问用户进程内存地址的函数定义
#include <asm/irq.h>
#include <asm/io.h> //包含了ioremap、ioread等内核访问IO内存等函数的定义
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
static struct class *thirddrv_class; //一个类
static struct class_device *thirddrv_class_dev; //一个类里面再建立一个设备
volatile unsigned long *gpfcon;
volatile unsigned long *gpfdat;
volatile unsigned long *gpgcon;
volatile unsigned long *gpgdat;
/* 下面两个是定义休眠函数的参数 */
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
/* 中断时间标志,中断服务程序将它置1,third_drv_read将它清0 */
static volatile int ev_press=0;
/* 引脚描述的结构体 */
struct pin_desc{
unsigned int pin;
unsigned int key_val;
};
/* 键值:按下时,0x01,0x02,0x03,0x04 */
/* 键值:松开时,0x81,0x82,0x83,0x84 */
static unsigned char keyval; //键值
/* 在request_irq函数中把结构体传进去 */
struct pin_desc pins_desc[4] = { //键值先赋初始值0x01,0x02,0x03,0x04
{S3C2410_GPF0, 0x01}, //pin=S3C2410_GPF0, key_val(按键值)=0x01
{S3C2410_GPF2, 0x02}, //pin=S3C2410_GPF2, key_val(按键值)=0x02
{S3C2410_GPG3, 0x03}, //pin=S3C2410_GPF3, key_val(按键值)=0x03
{S3C2410_GPG11, 0x04}, //pin=S3C2410_GPF11, key_val(按键值)=0x04
};
/*
* 确定按键值
*/
static irqreturn_t button_irq(int irq, void *dev_id) //中断处理函数
{
/* irq = IRQ_EINT0 …… */
/* dev_id = 结构体struct pins_desc */
struct pin_desc * pindesc = (struct pin_desc *)dev_id;
unsigned int pinval;
/* 读取引脚PIN值 */
pinval = s3c2410_gpio_getpin(pindesc->pin);
/* 确定按键值,按下管脚低电平,松开管脚高电平 */
if(pinval)
{
/* 松开 */
keyval = 0x80 | pindesc->key_val; //规定的:0x8X
}
else
{
/* 按下 */
keyval = pindesc->key_val; //0x0X
}
/* 唤醒 */
ev_press = 1; /* 表示中断发生了 */
wake_up_interruptible(&button_waitq); /* 唤醒休眠的进程,去button_wq队列,把挂在队列下的进程唤醒 */
return IRQ_RETVAL(IRQ_HANDLED);
}
static int third_drv_open(struct inode *inode, struct file *file)
{
/* 配置GPF0,2为输入引脚 */
/* 配置GPF3,11为输入引脚 */
/* request_irq函数的第五个参数是void *,为无类型指针,可以指向任何数据类型 */
request_irq(IRQ_EINT0, button_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]);
request_irq(IRQ_EINT2, button_irq, IRQT_BOTHEDGE, "S3", &pins_desc[1]);
request_irq(IRQ_EINT11, button_irq, IRQT_BOTHEDGE, "S4", &pins_desc[2]);
request_irq(IRQ_EINT19, button_irq, IRQT_BOTHEDGE, "S5", &pins_desc[3]);
return 0;
}
ssize_t third_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
if (size != 1)
return -EINVAL;
/* 如果没有按键动作,休眠,休眠:让出CPU */
/* 休眠时,把进程挂在button_wq 队列里 */
/* 如果休眠后被唤醒,就会从这里继续往下执行 */
/* 一开始没有按键按下,ev_press = 0 */
wait_event_interruptible(button_waitq, ev_press);//ev_press=0,休眠,让我们的测试程序休眠;ev_press!=0,直接往下运行
/* 如果有按键动作,返回键值 */
copy_to_user(buf, &keyval, 1); //把键值 拷回去
ev_press = 0; //清零,如果不清零,下次再读,立马往下执行,返回原来的值
return 1;
}
int third_drv_close(struct inode *inode, struct file *file)
{
free_irq(IRQ_EINT0, &pins_desc[0]);
free_irq(IRQ_EINT2, &pins_desc[1]);
free_irq(IRQ_EINT11, &pins_desc[2]);
free_irq(IRQ_EINT19, &pins_desc[3]);
return 0;
}
static struct file_operations third_drv_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = third_drv_open,
.read = third_drv_read,
.release = third_drv_close,
};
int major;
static int third_drv_init(void)
{
major = register_chrdev(0, "third_drv", &third_drv_fops);
//创建一个类
thirddrv_class = class_create(THIS_MODULE, "firstdrv");
//在这个类下面再创建一个设备
//mdev是udev的一个简化版本
//mdev应用程序,就会被内核调用,会根据类和类下面的设备这些信息
thirddrv_class_dev = class_device_create(thirddrv_class, NULL, MKDEV(major, 0), NULL, "buttons");/* /dev/buttons */
//建立地址映射:物理地址->虚拟地址
gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16); //指向的是虚拟地址,第一个参数是物理开始地址,第二个是长度(字节)
gpfdat = gpfcon + 1; //加1,实际加4个字节
gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16); //指向的是虚拟地址,第一个参数是物理开始地址,第二个是长度(字节)
gpgdat = gpgcon + 1; //加1,实际加4个字节
return 0;
}
static void third_drv_exit(void)
{
unregister_chrdev(major, "third_drv");
class_device_unregister(thirddrv_class_dev);
class_destroy(thirddrv_class);
iounmap(gpfcon);
iounmap(gpgcon);
return 0;
}
module_init(third_drv_init);
module_exit(third_drv_exit);
MODULE_LICENSE("GPL");
测试程序:thirddrvtest.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
/* thirddrvtest
*/
int main(int argc, char **argv)
{
int fd;
unsigned char key_val;
int cnt = 0;
fd = open("/dev/buttons", O_RDWR);
if (fd < 0)
{
printf("can't open!\n");
}
while (1)
{
//用查询方式读按键坏处:占用CPU大
//根本不知道按键什么时候按下,不可预料,只能不断地读,知道它返回
read(fd, &key_val, 1);
printf("key_val = 0x%x\n", key_val);
}
return 0;
}
Makefile文件
KERN_DIR = /work/system/linux-2.6.22.6
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += third_drv.o
最后进行测试:
insmod third_drv.ko
./thirddrvtest & (在后台执行)
然后按下开发板的四个按键,再松开。。
怎么卸载模块呢?
因为模块正在被使用
所以,先找到在后台运行的程序thirddrvtest,查看它的进程号为845,
用命令kill -9 845杀死进程,
再卸载模块。
书上或者光盘的参考代码:s3c24xx_button.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#define DEVICE_NAME "buttons" /* 加载模式后,执行”cat /proc/devices”命令看到的设备名称 */
#define BUTTON_MAJOR 232 /* 主设备号 */
struct button_irq_desc {
int irq;
unsigned long flags;
char *name;
};
/* 用来指定按键所用的外部中断引脚及中断触发方式, 名字 */
static struct button_irq_desc button_irqs [] = {
{IRQ_EINT19, IRQF_TRIGGER_FALLING, "KEY1"}, /* K1 */
{IRQ_EINT11, IRQF_TRIGGER_FALLING, "KEY2"}, /* K2 */
{IRQ_EINT2, IRQF_TRIGGER_FALLING, "KEY3"}, /* K3 */
{IRQ_EINT0, IRQF_TRIGGER_FALLING, "KEY4"}, /* K4 */
};
/* 按键被按下的次数(准确地说,是发生中断的次数) */
static volatile int press_cnt [] = {0, 0, 0, 0};
/* 等待队列:
* 当没有按键被按下时,如果有进程调用s3c24xx_buttons_read函数,
* 它将休眠
*/
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
/* 中断事件标志, 中断服务程序将它置1,s3c24xx_buttons_read将它清0 */
static volatile int ev_press = 0;
static irqreturn_t buttons_interrupt(int irq, void *dev_id)
{
volatile int *press_cnt = (volatile int *)dev_id;
*press_cnt = *press_cnt + 1; /* 按键计数加1 */
ev_press = 1; /* 表示中断发生了 */
wake_up_interruptible(&button_waitq); /* 唤醒休眠的进程 */
return IRQ_RETVAL(IRQ_HANDLED);
}
/* 应用程序对设备文件/dev/buttons执行open(...)时,
* 就会调用s3c24xx_buttons_open函数
*/
static int s3c24xx_buttons_open(struct inode *inode, struct file *file)
{
int i;
int err;
for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) {
// 注册中断处理函数
err = request_irq(button_irqs[i].irq, buttons_interrupt, button_irqs[i].flags,
button_irqs[i].name, (void *)&press_cnt[i]);
if (err)
break;
}
if (err) {
// 释放已经注册的中断
i--;
for (; i >= 0; i--)
free_irq(button_irqs[i].irq, (void *)&press_cnt[i]);
return -EBUSY;
}
return 0;
}
/* 应用程序对设备文件/dev/buttons执行close(...)时,
* 就会调用s3c24xx_buttons_close函数
*/
static int s3c24xx_buttons_close(struct inode *inode, struct file *file)
{
int i;
for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) {
// 释放已经注册的中断
free_irq(button_irqs[i].irq, (void *)&press_cnt[i]);
}
return 0;
}
/* 应用程序对设备文件/dev/buttons执行read(...)时,
* 就会调用s3c24xx_buttons_read函数
*/
static int s3c24xx_buttons_read(struct file *filp, char __user *buff,
size_t count, loff_t *offp)
{
unsigned long err;
/* 如果ev_press等于0,休眠 */
wait_event_interruptible(button_waitq, ev_press);
/* 执行到这里时,ev_press等于1,将它清0 */
ev_press = 0;
/* 将按键状态复制给用户,并清0 */
err = copy_to_user(buff, (const void *)press_cnt, min(sizeof(press_cnt), count));
memset((void *)press_cnt, 0, sizeof(press_cnt));
return err ? -EFAULT : 0;
}
/* 这个结构是字符设备驱动程序的核心
* 当应用程序操作设备文件时所调用的open、read、write等函数,
* 最终会调用这个结构中的对应函数
*/
static struct file_operations s3c24xx_buttons_fops = {
.owner = THIS_MODULE, /* 这是一个宏,指向编译模块时自动创建的__this_module变量 */
.open = s3c24xx_buttons_open,
.release = s3c24xx_buttons_close,
.read = s3c24xx_buttons_read,
};
/*
* 执行“insmod s3c24xx_buttons.ko”命令时就会调用这个函数
*/
static int __init s3c24xx_buttons_init(void)
{
int ret;
/* 注册字符设备驱动程序
* 参数为主设备号、设备名字、file_operations结构;
* 这样,主设备号就和具体的file_operations结构联系起来了,
* 操作主设备为BUTTON_MAJOR的设备文件时,就会调用s3c24xx_buttons_fops中的相关成员函数
* BUTTON_MAJOR可以设为0,表示由内核自动分配主设备号
*/
ret = register_chrdev(BUTTON_MAJOR, DEVICE_NAME, &s3c24xx_buttons_fops);
if (ret < 0) {
printk(DEVICE_NAME " can't register major number\n");
return ret;
}
printk(DEVICE_NAME " initialized\n");
return 0;
}
/*
* 执行”rmmod s3c24xx_buttons.ko”命令时就会调用这个函数
*/
static void __exit s3c24xx_buttons_exit(void)
{
/* 卸载驱动程序 */
unregister_chrdev(BUTTON_MAJOR, DEVICE_NAME);
}
/* 这两行指定驱动程序的初始化函数和卸载函数 */
module_init(s3c24xx_buttons_init);
module_exit(s3c24xx_buttons_exit);
/* 描述驱动程序的一些信息,不是必须的 */
MODULE_AUTHOR("http://www.100ask.net"); // 驱动程序的作者
MODULE_DESCRIPTION("S3C2410/S3C2440 BUTTON Driver"); // 一些描述信息
MODULE_LICENSE("GPL"); // 遵循的协议
上一篇: 初学yii,有哪些教程可以快速上手
下一篇: PHP学习宝典-第五章