Linux按键驱动程序设计详解---从简单到不简单【转】

 

转自:http://blog.csdn.net/coding__madman/article/details/51399353

版权声明:本文为博主原创文章,未经博主允许不得转载。

混杂设备驱动模型:

1. 混杂设备描述

        在Linux系统中,存在一类字符设备,它们拥有相同的主设备号(10),单次设备号不同,我们称这类设备为混            杂设备(miscdevice).所有的混杂设备形成一个链表,对设备访问时内核根据次设备号查到相应的混杂设备。

         混杂设备也是字符设备!

     linux中使用struct miscdevice来描述一个混杂设备。

     

2. 混杂驱动注册

    Linux中使用misc_register函数来注册一个混杂设备驱动。

    int  misc_register(struct miscdev *misc)

3. 范例驱动分析

     3.1 初始化miscdevice(minor、name、fops)

     3.2 注册miscdevice (通过misc_register函数实现)

这里安照上面的分析,先来搭建一个最简单只有一个open操作的混杂按键设备驱动模型,后边逐步深入分析逐步完善代码。

key.c

 

[cpp] view plain copy

 
 

  1. #include<linux/module.h>  
  2. #include<linux/init.h>  
  3. #inlcude<linux/miscdevice.h> /* for struct miscdevice*/  
  4.   
  5. int key_open(struct inode *node, struct file *filp)  
  6. {  
  7.       
  8.       
  9.     return 0;  
  10. }  
  11.   
  12. struct file_operations key_fops =   
  13. {  
  14.     .open = key_open,  
  15. };  
  16.   
  17. struct miscdevice key_miscdev  //定义一个misdevice结构  
  18. {  
  19.     .minor = 200;  
  20.     .name = "key";  
  21.     .fops = &key_fops;//这里key_fops是一个struct file_operations结构  
  22. };  
  23.   
  24. static int key_init()  
  25. {  
  26.     misc_register(&key_miscdev);//注册一个混杂设备驱动设备  
  27.       
  28.     return 0;  
  29. }  
  30.   
  31. static void key_exit()  
  32. {  
  33.     misc_deregister(&key_miscdev);//注销一个混杂设备驱动  
  34. }  
  35.   
  36.   
  37. module_init(key_init);  
  38. module_exit(key_exit);  

2. Linux 中断处理流程分析

下面先来分析写好按键驱动的一些准备工作!按键一般用中断的模式来处理,这里先分析linux中断处理程序:

1. 裸机中断处理流程分析

    1.1 中断有一个统一的入口 irq:

    ......

    第一步: 保护现场(中断部分执行完毕后要恢复之前的状态继续执行)

    第二步: 跳转到hand_ini处执行中断程序

                先事先注册中断程序,然后根据相应的中断找到对应的中断处理程序

    第三步:恢复现场,

在Linux操作系统中,irq中断的统一入口其实也是这样的(entry-armv.S文件中)

这里的irq_hander其实是一个宏定义:

而arch_irq_hander_default这个宏是在entry-macro-multi.S这个文件中

拿到中断号,然后设置相关寄存器并且调到asm_do_IRQ处理中断

看看generic_handle_irq(irq)这个函数:

然后函数又跳到这里了:

最后调到了handle_irq这个结构中。

这里总结一下上面函数跳转的分析过程:

第一步:根据中断产生的统一入口进入中断处理程序,拿到产生中断源的中断号

第二步:根据这个中断号irq找到irq_desc结构, 在这个irq结构中就会有一个action选项,在这个action结构中就是用户事先填写的中断处理程序handler,这里用一张图来说明:

上面分析了那么多,其实就是为了说明在驱动中如果要用中断,驱动程序该干嘛?

第一点:实现中断处理程序

第二点:当我们的中断产生了,能够被linux操作系统调用到用户事先定义好的中断处理程序,还需要把中断处理程序               注册到Linux操作系统中来,简单的来说就是注册中断

 

3. Linux 中断处理程序设计

    3.1 注册中断

参数说明:

unsigned int irq :中断号

void(*handler)(int , void *):中断处理函数

unsigned long flags:与中断管理有关的各种选项

const char *devname:设备名

void *dev_id:共享中断时使用

在flags参数中, 可以选择一些与中断管理有关的选项,如:

. IRQF_DISABLED(SA_INTERRUPT) 快速中断

如果设置该位,表示是一个“快速”中断处理程序;如果没有设置该位,那么就是一个“慢速”中断处理程序。

. IRQF_SHARED(SA_SHIRQ)  共享中断该位表明该中断号是多个设备共享的。

快/慢速中断的主要区别在于:快速中断保证中断处理的原子性(不被打断),而慢速中断则不保证。换句话说,也就是“开启中断”标志位(处理器IF)在运行快速中断处理程序时是关闭的,因此在服务该中断时,不会被其他类型的中断打断;而调用慢速中断处理时,其他类型的中断仍可以得到服务。

 

    3.2 中断处理

中断处理程序的特别之处是在中断上下文中运行的,它的行为为受到某些限制:

1. 不能使用可能引起阻塞的函数

2. 不能使用可能引起调度的函数

处理流程:

    3.3 注销处理
当设备不再需要使用中断时(通常在驱动卸载时),应当把它们注销,使用函数:

void free_irq(unsigned int irq, void *dev_id)  // 参数dev_id 可以结和上面那张图来看,就是共享中断中的那个中断

结和上面的分析在之前的代码基础上加入下面的部分:

中断处理函数部分:

下面来分析按键硬件部分的相关知识!硬件原理图以及相关GPIO设置

这里先贴上OK6410开发板上的按键硬件原理图部分:

这里KEYINT1是和GPN0相连,

对应的CPU引脚是GPN组,下面查看下GPN引脚datasheet的相关部分:

由下面的图这里可以看到将GPNCON寄存器的最后两位设置为0b10(外部中断模式)

GPN0对应的外部中断号查芯片手册可以看到为:XEINT0

这里看看OK6410内核源码部分关于中断号的宏定义:

这个在Irqs.h文件中:要与自己使用的硬件平台对应,我这里是OK6410

这里对应的设备中断号为S3C_EINT(0)或者写出IRQ_EINT(0)都是一样的

这个文件源码中还有一句#define S3C_IRQ_OFFSET(32)

中断号偏移 其中前面的32个中断号是留给用户程序作为软中断来使用, 

这里贴出在前面的基础上加的key.c的代码:

 

[cpp] view plain copy

 

 

  1. #include <linux/module.h>  
  2. #include <linux/init.h>  
  3. #include <linux/miscdevice.h> /* for struct miscdevice*/  
  4. #include <linux/interrupt.h>  
  5. #include <linux/fs.h> /* for iormap */  
  6. #include <linux/io.h>  
  7.   
  8. #define GPNCON 0x7F008830  
  9.   
  10. irqreturn_t key_int(int irq, void *dev_id)  
  11. {  
  12.     //1. 检测是否发生了按键中断 这里可以暂时不做,因为这里没有使用共享中断  
  13.       
  14.     //2. 清除已经发生的按键中断 这个是指硬件内部处理,按键CPU内部不需要做处理  
  15.          //比如如果是网卡驱动 就要处理  
  16.       
  17.     //3. 打印按键值  
  18.       
  19.     printk(KERN_WARNING"key down!\n");  
  20.       
  21.     return 0;  
  22. }  
  23.   
  24. void key_hw_init(void) //按键硬件初始化部分  
  25. {  
  26.     unsigned int *gpio_config;  
  27.     unsigned short data;  
  28.       
  29.     //第一步:设置GPNCON寄存器设置GPIO为输入  
  30.     gpio_config = ioremap(GPNCON, 4);//将物理地址转化为虚拟地址  
  31.     data = readw(gpio_config);  
  32.     data &= ~0b11; //先清零  
  33.     data |= 0b10;  //后两位设置成0b10  
  34.     writew(data, gpio_config);  
  35.     printk(KERN_WARNING"init ...!\n");  
  36.     //第二步: 按键中断部分相应处理 注册中断 注销等等  
  37. }  
  38.   
  39. int key_open(struct inode *node, struct file *filp)  
  40. {  
  41.     printk(KERN_WARNING"open ...!\n");  
  42.       
  43.     return 0;  
  44. }  
  45.   
  46. struct file_operations key_fops =   
  47. {  
  48.     .open = key_open,  
  49. };  
  50.   
  51. struct miscdevice key_miscdev = //定义一个misdevice结构  
  52. {  
  53.     .minor = 200,  
  54.     .name = "key",  
  55.     .fops = &key_fops,//这里key_fops是一个struct file_operations结构  
  56. };  
  57.   
  58. static int key_init(void)  
  59. {  
  60.     int err;  
  61.       
  62.     misc_register(&key_miscdev);//注册一个混杂设备驱动设备  
  63.       
  64.     //按键初始化 硬件初始化部分一般可一放在模块初始化部分或者open函数中 这里放在模块初始化部分  
  65.     key_hw_init();  
  66.       
  67.     //由高电平变为低电平产生中断 IRQF_TRIGGER_FALLING  
  68.       
  69.       
  70.     if( (err = request_irq(S3C_EINT(0),key_int, IRQF_TRIGGER_FALLING, "key", 0)) < 0 )//注册中断处理程序 5个参数  
  71.     {  
  72.          printk(KERN_WARNING"err = %d\n", err);  
  73.          goto irq_err;  
  74.     }  
  75.       
  76.     return 0;  
  77.       
  78. irq_err:  
  79.         misc_deregister(&key_miscdev);    
  80.     return -1;  
  81. }  
  82.   
  83. static void key_exit(void)  
  84. {  
  85.     free_irq(S3C_EINT(0), 0);//注销中断 这里irqnumber参数暂时用一个变量来表示(中断号)  
  86.       
  87.     misc_deregister(&key_miscdev);//注销一个混杂设备驱动  
  88.       
  89.     printk(KERN_WARNING"key up!");  
  90. }  
  91.   
  92.   
  93. module_init(key_init);  
  94. module_exit(key_exit);  
  95. MODULE_LICENSE("GPL");  
  96. MODULE_DESCRIPTION("key driver");  

这里贴一个代码编译后在开发板上运行,按下按键的效果截图:

 

 

中断分层设计:

1. 中断嵌套

2. 中断分层方式

    2.1 软中断

    2.2 tasklet

    2.3 工作队列(使用更广泛)

工作队列是一种将任务推后执行的形式,他把推后的任务交由一个内核线程去执行。这样下半部会在进程上下文执行,它允许重新调度甚至睡眠。每个被推后的任务叫做“工作”,由这些工作组成的队列称为工作队列

这里应该是用struct  workqueue_struct:

2.1. 从内核源码查看create_workqueue函数的用法:

这是内核源码里面找到的这个函数用法示例,这里可以看到create_workqueue函数只有一个参数,参数为工作队列的名字,返回的为创建好的一个工作队列指针,下面第三个箭头所指向的部分就是这个指针的类型!

用法示例:

struct workqueue_struct *my_wq;//定义一个工作队列指针

my_wq = create_workqueue("my_queue");

 

2.2. 下面去内核源码中查找一下init_work这个函数的用法:

两个参数:

work :要初始化的工作work指针

func  :工作要执行的函数

用法示例:

struct work_struct *work1;//定义一项工作

void work1_func(struct work_struct *work)
{
printk(KERN_WARNING"this is work1>\n");
}

work1 = kmalloc(sizeof(struct work_struct), GFP_KERNEL);
INIT_WORK(work1 , work1_func );

 

2.3. queue_work函数用法示例:

也是两个参数:

一个是工作队列指针 struct workqueue_struct *wq

一个是工作指针

用法示例:

queue_work(my_wq, work1);

 

下面根据上面的分析这里贴出一个示例小程序:

 

[cpp] view plain copy

 

 

  1. #include<linux/module.h>  
  2. #include<linux/init.h>  
  3. #include <linux/slab.h> /* for kmalloc */  
  4.   
  5. struct workqueue_struct *my_wq; //定义一个工作队列指针  
  6. struct work_struct *work1; //定义一项工作  
  7. struct work_struct *work2; //定义一项工作  
  8.   
  9. MODULE_LICENSE("GPL");  
  10.   
  11. void work1_func(struct work_struct *work)  
  12. {  
  13.     printk(KERN_WARNING"this is work1>\n");  
  14. }  
  15.   
  16. void work2_func(struct work_struct *work)  
  17. {  
  18.     printk(KERN_WARNING"this is work2>\n");  
  19. }  
  20.   
  21. int init_que(void)  
  22. {  
  23.     //1. 创建工作队列  
  24.     my_wq = create_workqueue("my_queue");  
  25.       
  26.     //2. 创建工作  
  27.     //work1 = kmalloc(sizeof(struct work_struct), GFP_KERNEL);  
  28.       work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);  
  29.     INIT_WORK(work1 , work1_func );  
  30.       
  31.     //3. 挂载(提交)提交工作  
  32.     queue_work(my_wq, work1);  
  33.       
  34.     //2. 创建工作  
  35.     work2 = kmalloc(sizeof(struct work_struct), GFP_KERNEL);  
  36.     INIT_WORK(work2 , work2_func );  
  37.       
  38.     //3. 挂载(提交)提交工作  
  39.     queue_work(my_wq, work2);  
  40.       
  41.       
  42.     return 0;  
  43. }  
  44.   
  45. void clean_que(void)  
  46. {  
  47.       
  48. }  
  49.   
  50. module_init(init_que);  
  51. module_exit(clean_que);  

 

 

3. 使用工作队列实现分层

在大多数情况下,驱动并不需要自己建立工作队列,只需定义工作,然后将工作提交到内核已经定义好的工作队列keventd_wq中。

3.1 提交工作到默认队列

schedule_work

在上面的代码这样修改也是同样的效果:

有了上面的基础,然后对之前的按键驱动进行改进!通过中断分层来实现按键驱动

按键中断处理程序 硬件处理部分比较简单,中断上半部 硬件中断处理基本可以不做
下半部 和硬件没有什么关系的部分,就是下面打印按键值部分 可以放到按键中断以外来处理,为系统节省更多的时间出来,避免应为中断程序处理部分耗时过长造成中断丢失!

 

[cpp] view plain copy

 

 

  1. #include <linux/module.h>  
  2. #include <linux/init.h>  
  3. #include <linux/miscdevice.h> /* for struct miscdevice*/  
  4. #include <linux/interrupt.h>  
  5. #include <linux/fs.h> /* for iormap */  
  6. #include <linux/io.h>  
  7. #include <linux/slab.h> /* for kmalloc */  
  8.   
  9. #define GPNCON 0x7F008830  
  10.   
  11. struct work_struct *work1;//定义一项工作  
  12.   
  13. void work1_func(struct work_struct *work)  
  14. {  
  15.     printk(KERN_WARNING"key down!\n");  
  16. }  
  17.   
  18. irqreturn_t key_int(int irq, void *dev_id)  
  19. {  
  20.     //1. 检测是否发生了按键中断 这里可以暂时不做,因为这里没有使用共享中断  
  21.       
  22.     //2. 清除已经发生的按键中断 这个是指硬件内部处理,按键CPU内部不需要做处理  
  23.            
  24.     //3. 提交下半部  
  25.     schedule_work(work1);  
  26.       
  27.     return 0;  
  28. }  
  29.   
  30. void key_hw_init(void) //按键硬件初始化部分  
  31. {  
  32.     unsigned int *gpio_config;  
  33.     unsigned short data;  
  34.       
  35.     //第一步:设置GPNCON寄存器设置GPIO为输入  
  36.     gpio_config = ioremap(GPNCON, 4);//将物理地址转化为虚拟地址  
  37.     data = readw(gpio_config);  
  38.     data &= ~0b11; //先清零  
  39.     data |= 0b10;  //后两位设置成0b10  
  40.     writew(data, gpio_config);  
  41.     printk(KERN_WARNING"init ...!\n");  
  42.     //第二步: 按键中断部分相应处理 注册中断 注销等等  
  43. }  
  44.   
  45. int key_open(struct inode *node, struct file *filp)  
  46. {  
  47.     printk(KERN_WARNING"open ...!\n");  
  48.       
  49.     return 0;  
  50. }  
  51.   
  52. struct file_operations key_fops =   
  53. {  
  54.     .open = key_open,  
  55. };  
  56.   
  57. struct miscdevice key_miscdev = //定义一个misdevice结构  
  58. {  
  59.     .minor = 200,  
  60.     .name = "key",  
  61.     .fops = &key_fops,//这里key_fops是一个struct file_operations结构  
  62. };  
  63.   
  64. static int key_init(void)  
  65. {  
  66.     int err;  
  67.       
  68.     misc_register(&key_miscdev);//注册一个混杂设备驱动设备  
  69.       
  70.     //按键初始化 硬件初始化部分一般可一放在模块初始化部分或者open函数中 这里放在模块初始化部分  
  71.     key_hw_init();  
  72.       
  73.     work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);  
  74.     INIT_WORK(work1 , work1_func );  
  75.       
  76.     //由高电平变为低电平产生中断 IRQF_TRIGGER_FALLING  
  77.     if( (err = request_irq(S3C_EINT(0),key_int, IRQF_TRIGGER_FALLING, "key", 0)) < 0 )//注册中断处理程序 5个参数  
  78.     {  
  79.          printk(KERN_WARNING"err = %d\n", err);  
  80.          goto irq_err;  
  81.     }  
  82.       
  83.     return 0;  
  84.       
  85. irq_err:  
  86.         misc_deregister(&key_miscdev);    
  87.     return -1;  
  88. }  
  89.   
  90. static void key_exit(void)  
  91. {  
  92.     free_irq(S3C_EINT(0), 0);//注销中断 这里irqnumber参数暂时用一个变量来表示(中断号)  
  93.       
  94.     misc_deregister(&key_miscdev);//注销一个混杂设备驱动  
  95.       
  96.     printk(KERN_WARNING"key up!");  
  97. }  
  98.   
  99.   
  100. module_init(key_init);  
  101. module_exit(key_exit);  
  102. MODULE_LICENSE("GPL");  
  103. MODULE_DESCRIPTION("key driver");  

编译并且insmod安装这个驱动模块,同样可以看到按键打印的效果!不过本质上驱动处理效率上提高了!当然这里只是一个很简单的例程!

 

 

按键定时器去抖:

按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,开关不会马上稳定接通或断开。因而在闭合及断开的瞬间总是伴随有一连串的抖动。

按键去抖动的方法主要有两种,一种是硬件电路去抖动;另一种就是软件延时去抖。而延时一般由分为两种,一种是for循环等待,另一种是定时器延时,在操作系统中,由于效率方面的原因,一般不允许使用for循环来等待,只能使用定时器。

内核定时器:

上面两个重要的成员(红色部分)

expires: 超时也就是定时多长时间

function: 函数指针

 

这之间的函数就不细说了,还是同样的方法不会就查看内核代码!上面的按键驱动实际上是不完善的,按一下会打印好几个按键按下的信息,这里利用上面介绍到的内核定时器知识优化上面的按键程序:

 

[cpp] view plain copy

 

 

  1. #include <linux/module.h>  
  2. #include <linux/init.h>  
  3. #include <linux/miscdevice.h> /* for struct miscdevice*/  
  4. #include <linux/interrupt.h>  
  5. #include <linux/fs.h> /* for iormap */  
  6. #include <linux/io.h>  
  7. #include <linux/slab.h> /* for kmalloc */  
  8.   
  9. #define GPNCON  0x7F008830  
  10. #define GPNDAT  0x7F008834  
  11.   
  12. unsigned int *gpio_data;  
  13.   
  14. struct work_struct *work1;//定义一项工作  
  15.   
  16. struct timer_list key_timer; //定义一个定时器key_timer  
  17.   
  18. void work1_func(struct work_struct *work)  
  19. {  
  20.     //启动定时器 jiffies是全局变量,用来表示当前系统时间 1S=1000个滴答数  
  21.     mod_timer(&key_timer,jiffies + HZ/10); //设置100ms超时 1HZ=1S  
  22. }  
  23.   
  24. void key_timer_func(unsigned long data)  
  25. {  
  26.     unsigned int key_val;  
  27.       
  28.     key_val = readw(gpio_data)&0x01; //只读取最后一位  
  29.       
  30.     if(key_val == 0)  
  31.     {  
  32.         printk(KERN_WARNING"OK6410 key0 down!\n");  
  33.     }  
  34.   
  35. }  
  36.   
  37. irqreturn_t key_int(int irq, void *dev_id)  
  38. {  
  39.     //1. 检测是否发生了按键中断 这里可以暂时不做,因为这里没有使用共享中断  
  40.       
  41.     //2. 清除已经发生的按键中断 这个是指硬件内部处理,按键CPU内部不需要做处理  
  42.            
  43.     //3. 提交下半部  
  44.     schedule_work(work1);  
  45.       
  46.     return 0;  
  47. }  
  48.   
  49. void key_hw_init(void) //按键硬件初始化部分  
  50. {  
  51.     unsigned int *gpio_config;  
  52.     unsigned short data;  
  53.       
  54.     gpio_config = ioremap(GPNCON, 4);//将物理地址转化为虚拟地址  
  55.     data = readw(gpio_config);  
  56.     data &= ~0b11; //先清零  
  57.     data |= 0b10;  //后两位设置成0b10  
  58.     writew(data, gpio_config);  
  59.       
  60.     gpio_data = ioremap(GPNDAT, 4);//将物理地址转化为虚拟地址  
  61.       
  62.     printk(KERN_WARNING"init ...!\n");  
  63. }  
  64.   
  65. int key_open(struct inode *node, struct file *filp)  
  66. {  
  67.     printk(KERN_WARNING"open ...!\n");  
  68.       
  69.     return 0;  
  70. }  
  71.   
  72. struct file_operations key_fops =   
  73. {  
  74.     .open = key_open,  
  75. };  
  76.   
  77. struct miscdevice key_miscdev = //定义一个misdevice结构  
  78. {  
  79.     .minor = 200,  
  80.     .name = "key",  
  81.     .fops = &key_fops,//这里key_fops是一个struct file_operations结构  
  82. };  
  83.   
  84. static int key_init(void)  
  85. {  
  86.     int err;  
  87.       
  88.     misc_register(&key_miscdev);//注册一个混杂设备驱动设备  
  89.       
  90.     key_hw_init();  
  91.       
  92.     work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);  
  93.     INIT_WORK(work1 , work1_func );  
  94.       
  95.     //初始化定时器  
  96.     init_timer(&key_timer);  
  97.     key_timer.function = key_timer_func; //将定义的函数赋值给函数指针  
  98.       
  99.     //注册定时器  
  100.     add_timer(&key_timer);  
  101.       
  102.     if( (err = request_irq(S3C_EINT(0),key_int, IRQF_TRIGGER_FALLING, "key", 0)) < 0 )  
  103.     {  
  104.          printk(KERN_WARNING"err = %d\n", err);  
  105.          goto irq_err;  
  106.     }  
  107.       
  108.     return 0;  
  109.       
  110. irq_err:  
  111.         misc_deregister(&key_miscdev);    
  112.     return -1;  
  113. }  
  114.   
  115. static void key_exit(void)  
  116. {  
  117.     free_irq(S3C_EINT(0), 0);//注销中断 这里irqnumber参数暂时用一个变量来表示(中断号)  
  118.       
  119.     misc_deregister(&key_miscdev);//注销一个混杂设备驱动  
  120.       
  121.     printk(KERN_WARNING"key up!");  
  122. }  
  123.   
  124. module_init(key_init);  
  125. module_exit(key_exit);  
  126. MODULE_LICENSE("GPL");  
  127. MODULE_DESCRIPTION("key driver");  

编译运行可以看到:按一下按键 只打印一个OK6410 key0 down!

 

在上面的基础上继续优化,实现多按键驱动这里增加key5按键!(结合上边的原理图部分)

 

[cpp] view plain copy

 

 

  1. #include <linux/module.h>  
  2. #include <linux/init.h>  
  3. #include <linux/miscdevice.h> /* for struct miscdevice*/  
  4. #include <linux/interrupt.h>  
  5. #include <linux/fs.h> /* for iormap */  
  6. #include <linux/io.h>  
  7. #include <linux/slab.h> /* for kmalloc */  
  8.   
  9. #define GPNCON  0x7F008830  
  10. #define GPNDAT  0x7F008834  
  11.   
  12. unsigned int *gpio_data;  
  13.   
  14. struct work_struct *work1;//定义一项工作  
  15.   
  16. struct timer_list key_timer; //定义一个定时器key_timer  
  17.   
  18. void work1_func(struct work_struct *work)  
  19. {  
  20.     //启动定时器 jiffies是全局变量,用来表示当前系统时间 1S=1000个滴答数  
  21.     mod_timer(&key_timer,jiffies + HZ/10); //设置100ms超时 1HZ=1S  
  22. }  
  23.   
  24. void key_timer_func(unsigned long data)  
  25. {  
  26.     unsigned int key_val;  
  27.       
  28.     key_val = readw(gpio_data)&0x01; //只读取最后一位  
  29.       
  30.     if(key_val == 0)  
  31.     {  
  32.         printk(KERN_WARNING"OK6410 key0 down!\n");  
  33.     }  
  34.       
  35.     key_val = readw(gpio_data)&0x20; //只读取最后一位  
  36.       
  37.     if(key_val == 0)  
  38.     {  
  39.         printk(KERN_WARNING"OK6410 key5 down!\n");  
  40.     }  
  41.   
  42. }  
  43.   
  44. irqreturn_t key_int(int irq, void *dev_id)  
  45. {  
  46.     //1. 检测是否发生了按键中断 这里可以暂时不做,因为这里没有使用共享中断  
  47.       
  48.     //2. 清除已经发生的按键中断 这个是指硬件内部处理,按键CPU内部不需要做处理  
  49.            
  50.     //3. 提交下半部  
  51.     schedule_work(work1);  
  52.       
  53.     return 0;  
  54. }  
  55.   
  56. void key_hw_init(void) //按键硬件初始化部分  
  57. {  
  58.     unsigned int *gpio_config;  
  59.     unsigned short data;  
  60.       
  61.     gpio_config = ioremap(GPNCON, 4);//将物理地址转化为虚拟地址  
  62.     data = readw(gpio_config);  
  63.     data &= ~0b110000000011; //先清零  
  64.     data |= 0b100000000010;  //后两位设置成0b10  
  65.     writew(data, gpio_config);  
  66.       
  67.     gpio_data = ioremap(GPNDAT, 4);//将物理地址转化为虚拟地址  
  68.       
  69.     printk(KERN_WARNING"init ...!\n");  
  70. }  
  71.   
  72. int key_open(struct inode *node, struct file *filp)  
  73. {  
  74.     printk(KERN_WARNING"open ...!\n");  
  75.       
  76.     return 0;  
  77. }  
  78.   
  79. struct file_operations key_fops =   
  80. {  
  81.     .open = key_open,  
  82. };  
  83.   
  84. struct miscdevice key_miscdev = //定义一个misdevice结构  
  85. {  
  86.     .minor = 200,  
  87.     .name = "key",  
  88.     .fops = &key_fops,//这里key_fops是一个struct file_operations结构  
  89. };  
  90.   
  91. static int key_init(void)  
  92. {  
  93.     int err;  
  94.       
  95.     misc_register(&key_miscdev);//注册一个混杂设备驱动设备  
  96.       
  97.     key_hw_init();  
  98.       
  99.     work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);  
  100.     INIT_WORK(work1 , work1_func );  
  101.       
  102.     //初始化定时器  
  103.     init_timer(&key_timer);  
  104.     key_timer.function = key_timer_func; //将定义的函数赋值给函数指针  
  105.       
  106.     //注册定时器  
  107.     add_timer(&key_timer);  
  108.       
  109.     if( (err = request_irq(S3C_EINT(0),key_int, IRQF_TRIGGER_FALLING, "key", 0)) < 0 )  
  110.     {  
  111.          printk(KERN_WARNING"err = %d\n", err);  
  112.          goto irq_err;  
  113.     }  
  114.     if( (err = request_irq(S3C_EINT(5),key_int, IRQF_TRIGGER_FALLING, "key", 0)) < 0 )  
  115.     {  
  116.          printk(KERN_WARNING"err = %d\n", err);  
  117.          goto irq_err;  
  118.     }  
  119.   
  120.       
  121.     return 0;  
  122.       
  123. irq_err:  
  124.         misc_deregister(&key_miscdev);    
  125.     return -1;  
  126. }  
  127.   
  128. static void key_exit(void)  
  129. {  
  130.     free_irq(S3C_EINT(0), 0);//注销中断 这里irqnumber参数暂时用一个变量来表示(中断号)  
  131.       
  132.     misc_deregister(&key_miscdev);//注销一个混杂设备驱动  
  133.       
  134.     printk(KERN_WARNING"key up!");  
  135. }  
  136.   
  137. module_init(key_init);  
  138. module_exit(key_exit);  
  139. MODULE_LICENSE("GPL");  
  140. MODULE_DESCRIPTION("key driver");  

运行效果:

 

 

阻塞型驱动设计:

阻塞的必要性:

1. 当一个设备无法立即满足用户的读写请求时应当如何处理?例如: 调用read时,设备没有数据提供,但以后可能会有:或者一个进程试图向设备写入数据,但是设备暂时没有准备好接受数据。当上述情况发生的时候,驱动程序应当阻塞进程,当它进入等待(睡眠)状态,直到请求可以得到满足。

2. 在实现阻塞型驱动的过程中,也需要有一个“候车室”来安排被阻塞的进程“休息”,当唤醒它们的条件成熟时,则可以从“候车室”中将这些进程唤醒。而这个“候车室”就是等待队列。

 

这里结合阻塞型驱动的知识点继续优化程序代码!这里顺便写个应用测试程序来测试按键驱动!

key.c代码

 

[cpp] view plain copy

 

 

  1. #include <linux/module.h>  
  2. #include <linux/init.h>  
  3. #include <linux/miscdevice.h> /* for struct miscdevice*/  
  4. #include <linux/interrupt.h>  
  5. #include <linux/fs.h> /* for iormap */  
  6. #include <linux/io.h>  
  7. #include <linux/slab.h> /* for kmalloc */  
  8. #include<linux/uaccess.h> /* for copy_to_usr */  
  9. #include <linux/sched.h>  
  10.   
  11. #define GPNCON  0x7F008830  
  12. #define GPNDAT  0x7F008834  
  13.   
  14. unsigned int *gpio_data;  
  15.   
  16. struct work_struct *work1;//定义一项工作  
  17.   
  18. struct timer_list key_timer; //定义一个定时器key_timer  
  19.   
  20. unsigned int key_num = 0;  
  21.   
  22. wait_queue_head_t key_q; //定义一个等待队列  
  23.   
  24. void work1_func(struct work_struct *work)  
  25. {  
  26.     //启动定时器 jiffies是全局变量,用来表示当前系统时间 1S=1000个滴答数  
  27.     mod_timer(&key_timer,jiffies + HZ/10); //设置100ms超时 1HZ=1S  
  28. }  
  29.   
  30. void key_timer_func(unsigned long data)  
  31. {  
  32.     unsigned int key_val;  
  33.       
  34.     key_val = readw(gpio_data)&0x01; //只读取最后一位  
  35.     if(key_val == 0)  
  36.     {  
  37.         //printk(KERN_WARNING"OK6410 key0 down!\n");  
  38.         key_num = 1;  
  39.     }  
  40.       
  41.     key_val = readw(gpio_data)&0x20; //只读取最后一位  
  42.     if(key_val == 0)  
  43.     {  
  44.         //printk(KERN_WARNING"OK6410 key5 down!\n");  
  45.         key_num = 6;  
  46.     }  
  47.       
  48.     wake_up(&key_q);  
  49. }  
  50.   
  51. irqreturn_t key_int(int irq, void *dev_id)  
  52. {  
  53.     //1. 检测是否发生了按键中断 这里可以暂时不做,因为这里没有使用共享中断  
  54.       
  55.     //2. 清除已经发生的按键中断 这个是指硬件内部处理,按键CPU内部不需要做处理  
  56.            
  57.     //3. 提交下半部  
  58.     schedule_work(work1);  
  59.       
  60.     //return 0;  
  61.     return IRQ_HANDLED;  
  62. }  
  63.   
  64. void key_hw_init(void) //按键硬件初始化部分  
  65. {  
  66.     unsigned int *gpio_config;  
  67.     unsigned short data;  
  68.       
  69.     gpio_config = ioremap(GPNCON, 4);//将物理地址转化为虚拟地址  
  70.     data = readw(gpio_config);  
  71.     data &= ~0b110000000011; //先清零  
  72.     data |= 0b100000000010;  //后两位设置成0b10  
  73.     writew(data, gpio_config);  
  74.       
  75.     gpio_data = ioremap(GPNDAT, 4);//将物理地址转化为虚拟地址  
  76.       
  77.     printk(KERN_WARNING"init ...!\n");  
  78. }  
  79.   
  80. int key_open(struct inode *node, struct file *filp)  
  81. {  
  82.     printk(KERN_WARNING"open ...!\n");  
  83.       
  84.     return 0;  
  85. }  
  86.   
  87. ssize_t key_read(struct file *filp, char __user *buf, size_t size, loff_t *pos)  
  88. {  
  89.     wait_event(key_q,key_num);//休眠 没有按下为0  
  90.       
  91.     //将key_value值返回给用户空间  
  92.     printk(KERN_WARNING"in kernel :key num is %d\n",key_num);  
  93.     copy_to_user(buf, &key_num, 4); //buf为用户空间传过来的地址  
  94.       
  95.     key_num = 0;  
  96.       
  97.     return 4;  
  98. }  
  99.   
  100. struct file_operations key_fops =   
  101. {  
  102.     .open = key_open,  
  103.     .read = key_read,  
  104. };  
  105.   
  106. struct miscdevice key_miscdev = //定义一个misdevice结构  
  107. {  
  108.     .minor = 200,  
  109.     .name = "6410key",  
  110.     .fops = &key_fops,//这里key_fops是一个struct file_operations结构  
  111. };  
  112.   
  113. static int key_init11(void)  
  114. {  
  115.     int err;  
  116.       
  117.     misc_register(&key_miscdev);//注册一个混杂设备驱动设备  
  118.       
  119.     if( (err = request_irq(S3C_EINT(0),key_int, IRQF_TRIGGER_FALLING, "6410key", 0)) < 0 )  
  120.     {  
  121.          printk(KERN_WARNING"err = %d\n", err);  
  122.          goto irq_err;  
  123.     }  
  124.     if( (err = request_irq(S3C_EINT(5),key_int, IRQF_TRIGGER_FALLING, "6410key", 0)) < 0 )  
  125.     {  
  126.          printk(KERN_WARNING"err = %d\n", err);  
  127.          goto irq_err;  
  128.     }  
  129.   
  130.     key_hw_init();  
  131.       
  132.     work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);  
  133.     INIT_WORK(work1 , work1_func );  
  134.       
  135.     //初始化定时器  
  136.     init_timer(&key_timer);  
  137.     key_timer.function = key_timer_func; //将定义的函数赋值给函数指针  
  138.       
  139.     //注册定时器  
  140.     add_timer(&key_timer);  
  141.       
  142.     //初始化一个等待队列  
  143.     init_waitqueue_head(&key_q);  
  144.       
  145.     return 0;  
  146.       
  147. irq_err:  
  148.         misc_deregister(&key_miscdev);    
  149.     return -1;  
  150. }  
  151.   
  152. static void key_exit(void)  
  153. {  
  154.     free_irq(S3C_EINT(0), 0);//注销中断 这里irqnumber参数暂时用一个变量来表示(中断号)  
  155.     free_irq(S3C_EINT(5), 0);//注销中断 这里irqnumber参数暂时用一个变量来表示(中断号)  
  156.       
  157.     misc_deregister(&key_miscdev);//注销一个混杂设备驱动  
  158.       
  159.     printk(KERN_WARNING"key up!");  
  160. }  
  161.   
  162. module_init(key_init11);  
  163. module_exit(key_exit);  
  164. MODULE_LICENSE("GPL");  
  165. MODULE_DESCRIPTION("key driver");  

key_app.c

 

 

[cpp] view plain copy

 

 

  1. #include<stdio.h>  
  2. #include<stdlib.h>  
  3. #include<unistd.h>  
  4. #include<sys/types.h>  
  5. #include<sys/stat.h>  
  6. #include<fcntl.h>  
  7.   
  8. int main(void)  
  9. {  
  10.     int fd;  
  11.     int key_num;  
  12.     int ret;  
  13.       
  14.     //1. 打开设备  
  15.     fd = open("/dev/ok6410key", 0);  
  16.     if(fd < 0)  
  17.     {  
  18.         printf("open key_device fail!\n");  
  19.     }  
  20.       
  21.   
  22.     //2. 读取设备  
  23.     ret = read(fd, &key_num, 4);  
  24.     if(ret == -1)  
  25.     {  
  26.         printf("read fail\n");  
  27.     }  
  28.     printf("key is %d\n", key_num);  
  29.   
  30.     //3. 关闭设备  
  31.     close(fd);  
  32.       
  33.     return 0;  
  34. }  

Makefile

 

 

[cpp] view plain copy

 

 

  1. obj-m := key.o  
  2. KDIR := /home/kernel/linux-ok6410  
  3. all:  
  4.     make -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm  
  5. clean:  
  6.     rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.bak *.order  

编译:

 

同步到开发上,安装驱动模块 insmod key.ko

然后mknod /dev/ok6410key  c   10  200 

这一行的命令作用是产生设备结点供应用程序访问 ,ok6410key为设备名字 c表示这个是字符设备 混杂设备也是字符设备 10 是混杂字符设备的统一设备号 200是在驱动程序中定义的次设备号.

运行应用程序按下按键效果截图:

终于搞定了!(历时两天半)

时间: 2024-12-01 15:01:09

Linux按键驱动程序设计详解---从简单到不简单【转】的相关文章

《Linux 设备驱动开发详解(第2版)》——1.2 无操作系统时的设备驱动

1.2 无操作系统时的设备驱动 Linux 设备驱动开发详解(第2版)并不是任何一个计算机系统都一定要运行操作系统,在许多情况下,操作系统都不必存在.对于功能比较单一.控制并不复杂的系统,譬如ASIC内部.公交车的刷卡机.电冰箱.微波炉.简单的手机和小灵通等,并不需要多任务调度.文件系统.内存管理等复杂功能,用单任务架构完全可以良好地支持它们的工作.一个无限循环中夹杂对设备中断的检测或者对设备的轮询是这种系统中软件的典型架构,如代码清单1.1. 代码清单1.1 单任务软件典型架构 1 int m

《Linux 设备驱动开发详解(第2版)》——1.4 Linux设备驱动

1.4 Linux设备驱动 Linux 设备驱动开发详解(第2版)1.4.1 设备的分类及特点 计算机系统的硬件主要由CPU.存储器和外设组成.随着IC制作工艺的发展,目前,芯片的集成度越来越高,往往在CPU内部就集成了存储器和外设适配器.譬如,相当多的ARM.PowerPC.MIPS等处理器都集成了UART.I2C控制器.USB控制器.SDRAM控制器等,有的处理器还集成了片内RAM和Flash. 驱动针对的对象是存储器和外设(包括CPU内部集成的存储器和外设),而不是针对CPU核.Linux

《Linux 设备驱动开发详解(第2版)》——1.6 设备驱动Hello World:LED驱动

1.6 设备驱动Hello World:LED驱动 Linux 设备驱动开发详解(第2版)1.6.1 无操作系统时的LED驱动 在嵌入式系统的设计中,LED一般直接由CPU的GPIO(通用可编程I/O口)控制.GPIO一般由两组寄存器控制,即一组控制寄存器和一组数据寄存器.控制寄存器可设置GPIO口的工作方式为输入或是输出.当引脚被设置为输出时,向数据寄存器的对应位写入1和0会分别在引脚上产生高电平和低电平:当引脚设置为输入时,读取数据寄存器的对应位可获得引脚上的电平为高或低. 在本例子中,我们

《Linux 设备驱动开发详解(第2版)》——1.3 有操作系统时的设备驱动

1.3 有操作系统时的设备驱动 Linux 设备驱动开发详解(第2版)1.2节中我们看到一个干净利落的设备驱动,它直接运行在硬件之上,不与任何操作系统关联.当系统中包含操作系统后,设备驱动会变得怎样? 首先,无操作系统时设备驱动的硬件操作工作仍然是必不可少的,没有这一部分,驱动不可能与硬件打交道. 其次,我们还需要将驱动融入内核.为了实现这种融合,必须在所有设备的驱动中设计面向操作系统内核的接口,这样的接口由操作系统规定,对一类设备而言结构一致,独立于具体的设备. 由此可见,当系统中存在操作系统

《Linux 设备驱动开发详解(第2版)》——导读

前言 本书第1版在2008年初出版以后,受到广大读者的支持和厚爱,累计销售1.6万册,从几年的市场和读者反馈看,在第1版中还存在一些不足,主要是以下几方面. 没有现成的开发环境,读者需要从头到尾构建,而构建需要花费很长的时间,许多时候会不成功,加之配套光盘中的实例没有Makefile,更加大了操作的难度. 没有配套的开发板,大量的基于S3C2410的实例读者身边如果没有可以直接运行的平台,就无法亲身体验这些驱动. 个别内容实用性不强或过于陈旧,也有个别知识点的讲解语言晦涩,读者不易理解,如pla

《Linux设备驱动开发详解 A》一一3.1 Linux内核的发展与演变

3.1 Linux内核的发展与演变 Linux操作系统是UNIX操作系统的一种克隆系统,是一种类UNIX操作系统,诞生于1991年10月5日(第一次正式向外公布的时间),起初的作者是Linus Torvalds.Linux操作系统的诞生.发展和成长过程依赖着5个重要支柱:UNIX操作系统.Minix操作系统.GNU计划.POSIX标准和Internet. 1.?UNIX操作系统 UNIX 操作系统是美国贝尔实验室的Ken. Thompson和Dennis Ritchie于1969年夏在DEC P

《Linux设备驱动开发详解 A》一一1.5 Linux设备驱动的开发环境构建

1.5 Linux设备驱动的开发环境构建 1.5.1 PC上的Linux环境 本书配套资源提供了一个Ubuntu的VirtualBox虚拟机映像,该虚拟机上安装了本书涉及的所有源代码.工具链和各种开发工具,读者无须再安装和配置任何环境.该虚拟机可运行于Windows.Ubuntu等操作系统中,运行方法如下. 1)安装VirtualBox. 如果主机为Windows系统,请安装VirtualBox WIN版本: VirtualBox-4.3.20-96997-Win.exe 如果主机为Ubuntu

《Linux设备驱动开发详解 A》一一第1章 Linux设备驱动概述及开发环境构建

第1章 Linux设备驱动概述及开发环境构建本章导读本章将介绍Linux设备驱动开发的基本概念,并对本书所基于的平台和开发环境进行讲解.1.1节阐明设备驱动的概念和作用.1.2节和1.3节分别讲解在无操作系统情况下和有操作系统情况下设备驱动的设计,通过对设计差异的分析,讲解设备驱动与硬件和操作系统的关系.1.4节对Linux操作系统的设备驱动进行了概要性的介绍,给出设备驱动与整个软硬件系统的关系,分析Linux设备驱动的重点.难点和学习方法.1.5节对本书所基于的QEMU模拟的vexpress

《Linux设备驱动开发详解 A》一一3.4 Linux内核的编译及加载

3.4 Linux内核的编译及加载 3.4.1 Linux内核的编译 Linux驱动开发者需要牢固地掌握Linux内核的编译方法以为嵌入式系统构建可运行的Linux操作系统映像.在编译内核时,需要配置内核,可以使用下面命令中的一个: make conf?ig(基于文本的最为传统的配置界面,不推荐使用) make menuconf?ig(基于文本菜单的配置界面) make xconf?ig(要求QT被安装) make gconf?ig(要求GTK+被安装) 在配置Linux内核所使用的make c