转自: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

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

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

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

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

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

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

3.1 提交工作到默认队列

schedule_work

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

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

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

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

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

按键定时器去抖:

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

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

内核定时器:

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

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

function: 函数指针

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

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

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

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

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

运行效果:

阻塞型驱动设计:

阻塞的必要性:

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

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

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

key.c代码

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

key_app.c

  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. int main(void)
  8. {
  9. int fd;
  10. int key_num;
  11. int ret;
  12. //1. 打开设备
  13. fd = open("/dev/ok6410key", 0);
  14. if(fd < 0)
  15. {
  16. printf("open key_device fail!\n");
  17. }
  18. //2. 读取设备
  19. ret = read(fd, &key_num, 4);
  20. if(ret == -1)
  21. {
  22. printf("read fail\n");
  23. }
  24. printf("key is %d\n", key_num);
  25. //3. 关闭设备
  26. close(fd);
  27. return 0;
  28. }

Makefile

  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是在驱动程序中定义的次设备号.

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

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

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

  1. Linux设备驱动开发详解

    Linux设备驱动开发详解 http://download.csdn.net/detail/wuyouzi067/9581380

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

    本文转载自:http://blog.csdn.net/coding__madman/article/details/51399353 混杂设备驱动模型: 1. 混杂设备描述 在Linux系统中,存在一 ...

  3. 《Linux设备驱动开发详解(第2版)》配套视频登录51cto教育频道

    http://edu.51cto.com/course/course_id-379-page-1.html http://edu.51cto.com/course/course_id-379-page ...

  4. 《linux设备驱动开发详解》笔记——12linux设备驱动的软件架构思想

    本章重点讲解思想.思想.思想. 12.1 linux驱动的软件架构 下述三种思想,在linux的spi.iic.usb等复杂驱动里广泛使用.后面几节分别对这些思想进行详细说明. 思想1:驱动与设备分离 ...

  5. (转)FS_S5PC100平台上Linux Camera驱动开发详解(二)

    4-3 摄像头的初始化流程及v4l2子设备驱动 这个问题弄清楚了以后下面就来看获得Camera信息以后如何做后续的处理: 在fimc_init_global调用结束之后我们获得了OV9650的信息,之 ...

  6. 《linux设备驱动开发详解》笔记——15 linux i2c驱动

    结合实际代码和书中描述,可能跟书上有一定出入.本文后续芯片相关代码参考ZYNQ. 15.1 总体结构 如下图,i2c驱动分为如下几个重要模块 核心层core,完成i2c总线.设备.驱动模型,对用户提供 ...

  7. 《linux设备驱动开发详解》笔记——10中断与时钟

    10.1 中断与定时器 中断一般有如下类型: 内部中断和外部中断:内部中断来自CPU,例如软件中断指令.溢出.除0错误等:外部中断有外部设备触发 可屏蔽中断和不可屏蔽中断 向量中断和非向量中断,ARM ...

  8. 《linux设备驱动开发详解》笔记——6字符设备驱动

    6.1 字符设备驱动结构 先看看字符设备驱动的架构: 6.1.1 cdev cdev结构体是字符设备的核心数据结构,用于描述一个字符设备,cdev定义如下: #include <linux/cd ...

  9. Linux设备驱动开发详解-Note(11)--- Linux 文件系统与设备文件系统(3)

    Linux 文件系统与设备文件系统(3) 成于坚持,败于止步 sysfs 文件系统与 Linux 设备模型 1.sysfs 文件系统 Linux 2.6 内核引入了 sysfs 文件系统,sysfs ...

随机推荐

  1. iOS: 悬浮的条件筛选框使用二

    一.介绍: 在前面已经介绍了一种条件悬浮框,使用的是tableView的Plain分组样式实现的,因为这是tableView本身就具备的功能,分组悬浮效果.这次我来介绍第二种更加简单的方法,采用两个S ...

  2. opengl 3.3 tutorial

    http://www.mbsoftworks.sk/index.php?page=tutorials&series=1

  3. eclipse启动tomcat错误:A Java Exception has occurred(转)

    在tomcat bin目录下执行startup.bat可以正常启动,但在eclipse下安装了tomcat插件并且配置tomcat路径后启动且报错:A Java Exception has occur ...

  4. java实现读取文件内容(不同类型)

    在一些项目中大量的数据经常需要从文件中读取,例如xml文件,txt文件,csv文件 1.读取本地的xml文件,需要注意对应的路径 //读取xml文件,xmlFile为读取文件的路径 DocumentB ...

  5. uwsgi选择使用的python版本(转载)

    大概如下 mkdir /data/uwsgi cd /data/uwsgi wget http://projects.unbit.it/downloads/uwsgi-2.0.11.tar.gz ta ...

  6. Java遇见HTML——JSP篇之JSP指令与动作元素

    一.include指令(如:<%@include file="..."%> ) 示例: Date.jsp <%@page import="java.te ...

  7. RouterOS 软路由开启SSH服务器

    RouterOS软路由可以支持多种服务,例如SSH.FTP.Telnet.www等等 图形介面操作 命令操作 [admin@MikroTik] > ip service print        ...

  8. WPF 应用程序使用 Multilingual App Toolkit

    应用程序支持多语言,使用 Multilingual App Toolkit是一个不错的解决方案. Multilingual App Toolkit下载地址: https://visualstudiog ...

  9. IIS URL Rewrite redirect from one Domain to another

    IIS URL Rewrite enables Web administrators to create powerful rules to implement URLs that are easie ...

  10. redhat vim编辑器永久添加行号

    cd ~ vim .vimrc 第一行加入: set nu :wq 保存退出,即可 如果想取消设置,同理删除set nu即可