按键驱动程序
本文学习主要包含按键硬件的实现、中断分层管理、按键定时器去抖、阻塞性驱动程序设计。这里面需要使用到混杂设备驱动和中断处理程序的内容。
一、创建按键混杂设备驱动模型
1 int key_open(struct inode *node,struct file *filp) 2 { 3 return 0; 4 } 5 struct file_operations key_fops = 6 { 7 .open = key_open, 8 }; 9 struct miscdevice key_miscdev = { 10 .minor = 200, //次设备号 11 .name = "6410key", //设备名 12 .fops = &key_fops, 13 };
二、按键硬件的实现
首先是按键的初始化,按键的初始化可以选择在open函数,和模块的初始化函数当中完成硬件的初始化。下面我们是选择在模块的初始化函数进行按键的初始化。按键的初始化,主要涉及对GPIO的引脚的功能进行相应的设置。我用的是ok6410A开发板上面有六个按键,核心板原理图如下:
2.1硬件初始化
s3c6410芯片手册对GPNCON的IO功能定义如下因此对按键硬件的初始化如下:
1 #define GPNCON 0x7f008830 2 #define GPNDAT 0x7f008834 3 4 void key_hw_init(void) //硬件初始化 5 { 6 unsigned int *gpio_config; 7 unsigned short data; 8 gpio_config = ioremap(GPNCON,4); //动态映射虚拟地址 9 data = readw(gpio_config); 10 data &= ~0xfff; 11 data |= 0xaaa; 12 writew(data,gpio_config); 13 gpio_dat = ioremap(GPNDAT,4); //将数据寄存器地址转化为虚拟地址 14 }
2.2按键中断程序(暂时注册第一个按键)
来源参照:中断处理程序
1 #include <linux/module.h> 2 #include <linux/init.h> 3 #include <linux/miscdevice.h> 4 #include <linux/interrupt.h> 5 #include <linux/io.h> 6 #include <linux/fs.h> 7 8 #define GPNCON 0x7f008830 9 #define GPNDAT 0x7f008834 10 11 MODULE_LICENSE("GPL"); 12 13 irqreturn_t key_init(int irp,void *dev_id) 14 { 15 //1.检测是否发生中断 16 //2.清除已经发生的按键中断 17 18 //3.打印按键值 19 printk(KERN_EMERG"key down! "); 20 return 0; 21 } 22 23 void key_hw_init(void) //硬件初始化 24 { 25 unsigned int *gpio_config; 26 unsigned short data; 27 gpio_config = ioremap(GPNCON,4); 28 data = readw(gpio_config); 29 data &= ~0xfff; 30 data |= 0xaaa; 31 writew(data,gpio_config); 32 gpio_dat = ioremap(GPNDAT,4); //将数据寄存器地址转化为虚拟地址 33 } 34 35 int key_open(struct inode *node,struct file *filp) 36 { 37 return 0; 38 } 39 struct file_operations key_fops = 40 { 41 .open = key_open, 42 }; 43 44 struct miscdevice key_miscdev = 45 { 46 .minor = 200, 47 .name = "key", 48 .fops = &key_fops, 49 }; 50 51 static int button_init(void) 52 { 53 misc_register(&key_miscdev); //注册混杂设备 54 //按键硬件初始化 55 key_hw_init(); 56 request_irq(IRQ_EINT(0),key_init,IRQF_TRIGGER_FALLING,"key",0); //注册中断处理程序 57 return 0; 58 } 59 60 static void button_exit(void) 61 { 62 misc_deregister(&key_miscdev); //注销混杂设备 63 64 //注销中断处理程序 65 free_irq(IRQ_EINT(0),0); 66 } 67 68 module_init(button_init); 69 module_exit(button_exit);
这里需要注意上面代码特殊标记的内容:IRQ_EINT(0)中断号、IRQF_TRIGGER_FALLING标志的来源
标志来源:
按键中断的处理,对于按键而言,可以在按下的时候产生中断,也可以在弹起的时候产生中断。需要通过一个标志来指定:IRQF_TRIGGER_FALLING,这个是从高电平到低电平产生中断。下表是其他产生中断的方式(内核代码中搜索IRQF_TRIGGER_FALLING):
中断号:
就是request_irq函数的第一个参数。我们在内核代码中搜索irqs.h,找对应的板子的:
从上面的代码看到,IRQ_EINT0_3的中断号是32.系统留出了S3C_IRQ_OFFSET=32个中断号,这是给软中断的。所以中断号就是等于硬件编号加上偏移量。可以查看内核代码的entry-macro.S
三、中断分层管理
3.1中断嵌套
所谓的中断嵌套就是,当一种中断正在执行的时候,又产生了另外中断。可以是同类型的,也可以是不同类型的。
慢速中断:是指在进行中断处理的时候,中断的总开关是不关闭的。允许其他类型中断产生。
快速中断:当中断产生的时候,控制位的IF为被置1,别的中断被禁止发生。这样就会产生我们不想看到的情况:中断丢失。
3.2中断分层
上半部:当中断发生时,它进行相应地硬件读写,并“登记”该中断。通常由中断处理程序充当上半部。
下半部:在系统空闲的时候对上半部“登记”的中断进行后续处理。
3.3工作队列
工作队列是一种将任务推后执行的形式,他把推后的任务交由一个内核线程去执行。这样下半部会在进程上下文执行,它允许重新调度甚至睡眠。 每个被推后的任务叫做“工作”,由这些工作组成的队列称为工作队列
下图为3内核的处理器队列处理图:
3.3.1工作队列描述
Linux内核使用struct work_struct来描述一个工作队列:
1 struct workqueue_struct{ 2 struct cpu_workqueue_struct *cpu_wq; 3 struct list_head list; 4 const char *name; /*workqueue name*/ 5 int singlethread; 6 int freezeable; /* Freeze threads during suspend */ 7 int rt; 8 };
3.3.2工作队列项
Linux内核使用struct work_struct来描述一个工作项:
1 struct work_struct{ 2 atomic_long_t data; 3 struct list_headentry; 4 work_func_t func; 5 }; 6 typedef void (*work_func_t)(struct work_struct *work);
3.3.3工作队列使用
step1. 创建工作队列
create_workqueue
step2. 创建工作
INIT_WORK
step3. 提交工作
queue_work
在大多数情况下, 驱动并不需要自己建立工作队列,只需定义工作, 然后将工作提交到内核已经定义好的工作队列keventd_wq中。
1. 提交工作到默认队列
schedule_work
按照工作队列修改按键程序:
1 #include <linux/module.h> 2 #include <linux/init.h> 3 #include <linux/miscdevice.h> 4 #include <linux/interrupt.h> 5 #include <linux/io.h> 6 #include <linux/fs.h> 7 #include <linux/slab.h> 8 9 #define GPNCON 0x7f008830 10 MODULE_LICENSE("GPL"); 11 struct work_struct *work1; 12 struct timer_list key_timer; 13 14 void work1_func(struct work_struct *work) 15 { 16 printk(KERN_EMERG"key down! "); 17 } 18 19 irqreturn_t key_int(int irp,void *dev_id) 20 { 21 //3.提交下半部 22 schedule_work(work1); 23 return 0; 24 } 25 26 void key_hw_init(void) //硬件初始化 27 { 28 unsigned int *gpio_config; 29 unsigned short data; 30 31 gpio_config = ioremap(GPNCON,4); 32 data = readw(gpio_config); 33 data &= ~0xfff; 34 data |= 0xaaa; 35 writew(data,gpio_config); 36 gpio_dat = ioremap(GPNDAT,4); //将数据寄存器地址转化为虚拟地址 37 } 38 39 int key_open(struct inode *node,struct file *filp) 40 { 41 return 0; 42 } 43 struct file_operations key_fops = 44 { 45 .open = key_open, 46 }; 47 48 struct miscdevice key_miscdev = 49 { 50 .minor = 200, 51 .name = "key", 52 .fops = &key_fops, 53 }; 54 55 static int button_init(void) 56 { 57 misc_register(&key_miscdev); //注册混杂设备 58 59 //按键硬件初始化 60 key_hw_init(); 61 request_irq(IRQ_EINT(0),key_int,IRQF_TRIGGER_FALLING,"key",0); //注册中断处理程序 62 //创建工作1 63 work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL); 64 INIT_WORK(work1,work1_func); 65 return 0; 66 } 67 68 static void button_exit(void) 69 { 70 misc_deregister(&key_miscdev); //注销混杂设备 71 //注销中断处理程序 72 free_irq(IRQ_EINT(0),0); 73 } 74 75 module_init(button_init); 76 module_exit(button_exit);
接下来的内容在下一个文章里包括:定时器去抖和阻塞性驱动程序设计