参考: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设备。
(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"); // 遵循的协议