zoukankan      html  css  js  c++  java
  • 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
     
     在CODE上查看代码片派生到我的代码片
    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
     
     在CODE上查看代码片派生到我的代码片
    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! ");  
    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 ...! ");  
    36.     //第二步: 按键中断部分相应处理 注册中断 注销等等  
    37. }  
    38.   
    39. int key_open(struct inode *node, struct file *filp)  
    40. {  
    41.     printk(KERN_WARNING"open ...! ");  
    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 ", 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> ");
    }

    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
     
     在CODE上查看代码片派生到我的代码片
    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> ");  
    14. }  
    15.   
    16. void work2_func(struct work_struct *work)  
    17. {  
    18.     printk(KERN_WARNING"this is work2> ");  
    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
     
     在CODE上查看代码片派生到我的代码片
    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! ");  
    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 ...! ");  
    42.     //第二步: 按键中断部分相应处理 注册中断 注销等等  
    43. }  
    44.   
    45. int key_open(struct inode *node, struct file *filp)  
    46. {  
    47.     printk(KERN_WARNING"open ...! ");  
    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 ", 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
     
     在CODE上查看代码片派生到我的代码片
    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! ");  
    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 ...! ");  
    63. }  
    64.   
    65. int key_open(struct inode *node, struct file *filp)  
    66. {  
    67.     printk(KERN_WARNING"open ...! ");  
    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 ", 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
     
     在CODE上查看代码片派生到我的代码片
    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! ");  
    33.     }  
    34.       
    35.     key_val = readw(gpio_data)&0x20; //只读取最后一位  
    36.       
    37.     if(key_val == 0)  
    38.     {  
    39.         printk(KERN_WARNING"OK6410 key5 down! ");  
    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 ...! ");  
    70. }  
    71.   
    72. int key_open(struct inode *node, struct file *filp)  
    73. {  
    74.     printk(KERN_WARNING"open ...! ");  
    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 ", 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 ", 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
     
     在CODE上查看代码片派生到我的代码片
    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! ");  
    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! ");  
    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 ...! ");  
    78. }  
    79.   
    80. int key_open(struct inode *node, struct file *filp)  
    81. {  
    82.     printk(KERN_WARNING"open ...! ");  
    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 ",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 ", 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 ", 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
     
     在CODE上查看代码片派生到我的代码片
    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! ");  
    19.     }  
    20.       
    21.   
    22.     //2. 读取设备  
    23.     ret = read(fd, &key_num, 4);  
    24.     if(ret == -1)  
    25.     {  
    26.         printf("read fail ");  
    27.     }  
    28.     printf("key is %d ", key_num);  
    29.   
    30.     //3. 关闭设备  
    31.     close(fd);  
    32.       
    33.     return 0;  
    34. }  


    Makefile

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    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是在驱动程序中定义的次设备号.

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

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

  • 相关阅读:
    [LeetCode] Search for a Range
    [C++] extern关键字的作用
    [LeetCode] Sentence Similarity
    [LeetCode] Flood Fill
    [LeetCode] Can Place Flowers
    [LeetCode] Intersection of Two Linked Lists
    [LeetCode] My Calendar II
    [LeetCode] My Calendar I
    [LeetCode] Self Dividing Numbers
    [LeetCode] Range Sum Query
  • 原文地址:https://www.cnblogs.com/zzb-Dream-90Time/p/6253498.html
Copyright © 2011-2022 走看看