linux字符驱动之中断按键

来源:互联网 发布:上交所网络投票系统 编辑:程序博客网 时间:2024/06/08 07:17

问:内核的中断体系是怎么样的?

答:ARM架构Linux内核中,有5种常见的异常,其中中断异常是其一,Linux内核将所有中断统一编号,使用一个irq_desc结构体来描述这些中断,里面记录了中断名称、中断状态、中断标记、并提供了中断的底层硬件访问函数(如:清除、屏蔽、使能中断),提供了这个中断的处理函数入口,通过它还可以调用用户注册的的中断处理函数。

问:irq_desc结构体有哪些重要的成员?

[cpp] view plain copy
 print?
  1. /** 
  2.  * struct irq_desc - interrupt descriptor 
  3.  * @irq:        interrupt number for this descriptor 
  4.  * @handle_irq:     highlevel irq-events handler [if NULL, __do_IRQ()] 
  5.  * @chip:       low level interrupt hardware access 
  6.  * @action:     the irq action chain 
  7.  * @status:     status information 
  8.  * @irq_count:      stats field to detect stalled irqs 
  9.  * @name:       flow handler name for /proc/interrupts output 
  10.  */  
  11. struct irq_desc {  
  12.     unsigned int        irq;  
  13.     ......  
  14.     irq_flow_handler_t  handle_irq;  
  15.     struct irq_chip     *chip;  
  16.     ......  
  17.     struct irqaction    *action;    /* IRQ action list */  
  18.     unsigned int        status;     /* IRQ status */  
  19.     ......  
  20.     unsigned int        irq_count;  /* For detecting broken IRQs */  
  21.     ......  
  22.     const char      *name;  
  23. } ____cacheline_internodealigned_in_smp;  

关于irq_desc结构体的具体描述,请参考《嵌入式Linux应用完全开发手册》的第20章的20.2.1节,即416页开始的描述。关于linux中断体系的详细讲解,我就不多说了,大家参考韦老师的视频教程:

"第12课第4.1节.字符设备驱动程序之中断方式的按键驱动_Linux异常处理结构"

"第12课第4.2节.字符设备驱动程序之中断方式的按键驱动_Linux中断处理结构"

这二个视频,已经讲解的非常到位,如果还不理解的,大家可以参考韦老师的书的第20章内容。建议大家结合linux源码和书一起看,这样对你深入linux中断体系就不远了。

问:既然linux内核的中断体系已经 那么完善了,那么驱动工程师还需要做什么?

答:驱动工程师需要调用request_irq向内核注册中断,其原型为:

[cpp] view plain copy
 print?
  1. request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char * devname, void * dev_id)  
第一个参数irq:中断号,在irqs.h中定义,与架构相关;

第二个参数handler:  用户中断处理函数;

第三个参数flags:中断标记;

第四个参数devname:中断名字;可以通过cat proc/interrupts查看;

第五个参数dev_id:  在free_irq中有用,也用作区分中断处理函数;

问:request_irq函数有什么作用?

答:

1)、irq_desc[irq]结构体中的action链表中已经链入了用户注册的中断处理函数。

2)、中断触发方式已经被设置好。

3)、中断已经被使能。

问:如何卸载中断处理函数?

答:中断是一种很稀缺的资源,当不再使用一个设备时,应该释放它占据的中断。驱动工程师通过free_irq函数来实现,其原型为:

[cpp] view plain copy
 print?
  1. void free_irq(unsigned int irq, void *dev_id)  

第一个参数irq:中断号,与request_irq中的irq一致,用于定位action链表;

第二个参数dev_id:用于在action链表中找到要卸载的表项;同一个中断的不同中断处理函数必须使用不同的dev_id来区分,这就要求在注册共享中断时参数dev_id必须唯一。

问:free_irq有什么作用?

答:
1)、根据中断号irq、dev_id从action链表中找到表项,将它移除。

2)、如果它是唯一的表项,还要调用IRQ_DESC[IRQ].CHIP->SHUTDOWN或者IRQ_DESC[IRQ].CHIP->DISABLE来关闭中断。

前面说了那么多中断,暂且放下中断,来谈谈linux是如何睡觉的吧,睡觉?linux也会睡觉?没错,只不过在术语上不叫睡觉,人家那叫休眠。

问:linux内核如何进行休眠?

答:使用wait_event函数,其扩展型常用的函数为wait_event_interruptible(wq, condition),即可被中断打断的休眠。

wq是一个等待队列,condition是条件,如果condition = 0,则将会进行休眠,直到condition = 1,并且有唤醒函数唤醒它。

问:linux内核如果唤醒进程?

答:使用wait_up函数,其扩展型常用的函数为wake_up_interruptible(wq),wq与wait_event_interruptible的wq是一致的。

问:能直接使用wait_event_interruptible和wake_up_interruptible函数吗?

答:不能,必须先事先使用static DECLARE_WAIT_QUEUE_HEAD(wq)定义并初始化一个等待队列头,并设置condition条件变量。


详细请参考驱动源码:

[cpp] view plain copy
 print?
  1. #include <linux/delay.h>  
  2. #include <linux/irq.h>  
  3. #include <asm/uaccess.h>  
  4. #include <asm/irq.h>  
  5. #include <asm/io.h>  
  6. #include <linux/module.h>  
  7. #include <linux/device.h>         //class_create  
  8. #include <mach/regs-gpio.h>       //S3C2410_GPF1  
  9. //#include <asm/arch/regs-gpio.h>    
  10. #include <mach/hardware.h>  
  11. //#include <asm/hardware.h>  
  12. #include <linux/interrupt.h>  //wait_event_interruptible  
  13.   
  14.   
  15. /* 定义并初始化等待队列头 */  
  16. static DECLARE_WAIT_QUEUE_HEAD(button_waitq);  
  17.   
  18.   
  19. static struct class *thirddrv_class;  
  20. static struct device *thirddrv_device;  
  21.   
  22. static struct pin_desc{  
  23.     unsigned int pin;  
  24.     unsigned int key_val;  
  25. };  
  26.   
  27. static struct pin_desc pins_desc[4] = {  
  28.         {S3C2410_GPF1,0x01},  
  29.         {S3C2410_GPF4,0x02},  
  30.         {S3C2410_GPF2,0x03},  
  31.         {S3C2410_GPF0,0x04},  
  32. };   
  33.   
  34. static int ev_press = 0;  
  35.   
  36. /* 键值: 按下时, 0x01, 0x02, 0x03, 0x04 */  
  37. /* 键值: 松开时, 0x81, 0x82, 0x83, 0x84 */  
  38. static unsigned char key_val;  
  39. int major;  
  40.   
  41. /* 用户中断处理函数 */  
  42. static irqreturn_t buttons_irq(int irq, void *dev_id)  
  43. {  
  44.     struct pin_desc *pindesc = (struct pin_desc *)dev_id;  
  45.     unsigned int pinval;  
  46.     pinval = s3c2410_gpio_getpin(pindesc->pin);  
  47.   
  48.     if(pinval)  
  49.     {  
  50.         /* 松开 */  
  51.         key_val = 0x80 | (pindesc->key_val);  
  52.     }  
  53.     else  
  54.     {  
  55.         /* 按下 */  
  56.         key_val = pindesc->key_val;  
  57.     }  
  58.   
  59.     ev_press = 1;                            /* 表示中断已经发生 */  
  60.      wake_up_interruptible(&button_waitq);   /* 唤醒休眠的进程 */  
  61.     return IRQ_HANDLED;  
  62. }  
  63. static int third_drv_open(struct inode * inode, struct file * filp)  
  64. {  
  65.     /*  K1 ---- EINT1,K2 ---- EINT4,K3 ---- EINT2,K4 ---- EINT0 
  66.      *  配置GPF1、GPF4、GPF2、GPF0为相应的外部中断引脚 
  67.      *  IRQT_BOTHEDGE应该改为IRQ_TYPE_EDGE_BOTH 
  68.      */  
  69.     request_irq(IRQ_EINT1, buttons_irq, IRQ_TYPE_EDGE_BOTH, "K1",&pins_desc[0]);  
  70.     request_irq(IRQ_EINT4, buttons_irq, IRQ_TYPE_EDGE_BOTH, "K2",&pins_desc[1]);  
  71.     request_irq(IRQ_EINT2, buttons_irq, IRQ_TYPE_EDGE_BOTH, "K3",&pins_desc[2]);  
  72.     request_irq(IRQ_EINT0, buttons_irq, IRQ_TYPE_EDGE_BOTH, "K4",&pins_desc[3]);  
  73.     return 0;  
  74. }  
  75.   
  76. static ssize_t third_drv_read(struct file *file, char __user *user, size_t size,loff_t *ppos)  
  77. {  
  78.     if (size != 1)  
  79.             return -EINVAL;  
  80.       
  81.     /* 当没有按键按下时,休眠。 
  82.      * 即ev_press = 0; 
  83.      * 当有按键按下时,发生中断,在中断处理函数会唤醒 
  84.      * 即ev_press = 1;  
  85.      * 唤醒后,接着继续将数据通过copy_to_user函数传递给应用程序 
  86.      */  
  87.     wait_event_interruptible(button_waitq, ev_press);  
  88.     copy_to_user(user, &key_val, 1);  
  89.       
  90.     /* 将ev_press清零 */  
  91.     ev_press = 0;  
  92.     return 1;     
  93. }  
  94.   
  95. static int third_drv_close(struct inode *inode, struct file *file)  
  96. {  
  97.     free_irq(IRQ_EINT1,&pins_desc[0]);  
  98.     free_irq(IRQ_EINT4,&pins_desc[1]);  
  99.     free_irq(IRQ_EINT2,&pins_desc[2]);  
  100.     free_irq(IRQ_EINT0,&pins_desc[3]);  
  101.     return 0;  
  102. }  
  103.   
  104. /* File operations struct for character device */  
  105. static const struct file_operations third_drv_fops = {  
  106.     .owner      = THIS_MODULE,  
  107.     .open       = third_drv_open,  
  108.     .read       = third_drv_read,  
  109.     .release    = third_drv_close,  
  110. };  
  111.   
  112.   
  113. /* 驱动入口函数 */  
  114. static int third_drv_init(void)  
  115. {  
  116.     /* 主设备号设置为0表示由系统自动分配主设备号 */  
  117.     major = register_chrdev(0, "third_drv", &third_drv_fops);  
  118.   
  119.     /* 创建thirddrv类 */  
  120.     thirddrv_class = class_create(THIS_MODULE, "thirddrv");  
  121.   
  122.     /* 在thirddrv类下创建buttons设备,供应用程序打开设备*/  
  123.     thirddrv_device = device_create(thirddrv_class, NULL, MKDEV(major, 0), NULL, "buttons");  
  124.   
  125.     return 0;  
  126. }  
  127.   
  128. /* 驱动出口函数 */  
  129. static void third_drv_exit(void)  
  130. {  
  131.     unregister_chrdev(major, "third_drv");  
  132.     device_unregister(thirddrv_device);  //卸载类下的设备  
  133.     class_destroy(thirddrv_class);      //卸载类  
  134. }  
  135.   
  136. module_init(third_drv_init);  //用于修饰入口函数  
  137. module_exit(third_drv_exit);  //用于修饰出口函数      
  138.   
  139. MODULE_AUTHOR("LWJ");  
  140. MODULE_DESCRIPTION("Just for Demon");  
  141. MODULE_LICENSE("GPL");  //遵循GPL协议  

应用测试程序源码:

[cpp] view plain copy
 print?
  1. #include <stdio.h>  
  2. #include <sys/types.h>  
  3. #include <sys/stat.h>  
  4. #include <fcntl.h>  
  5. #include <unistd.h>  
  6.   
  7.   
  8. /* third_test 
  9.  */   
  10. int main(int argc ,char *argv[])  
  11.   
  12. {  
  13.     int fd;  
  14.     unsigned char key_val;  
  15.       
  16.     fd = open("/dev/buttons",O_RDWR);  
  17.     if (fd < 0)  
  18.     {  
  19.         printf("open error\n");  
  20.     }  
  21.   
  22.     while(1)  
  23.     {  
  24.         read(fd,&key_val,1);  
  25.         printf("key_val = 0x%x\n",key_val);  
  26.           
  27.     }  
  28.     return 0;  
  29. }  


测试步骤1:

[cpp] view plain copy
 print?
  1. [WJ2440]# ls  
  2. Qt             etc            mnt            second_drv.ko  udisk  
  3. TQLedtest      first_drv.ko   opt            second_test    usr  
  4. app_test       first_test     proc           sys            var  
  5. bin            home           root           third_drv.ko   web  
  6. dev            lib            sbin           third_test  
  7. driver_test    linuxrc        sddisk         tmp  
  8. [WJ2440]# ls /dev/buttons -l  
  9. ls: /dev/buttons: No such file or directory  
  10. [WJ2440]# insmod third_drv.ko   
  11. [WJ2440]# lsmod   
  12. third_drv 3016 0 - Live 0xbf003000  
  13. [WJ2440]# ls /dev/buttons -l  
  14. crw-rw----    1 root     root      252,   0 Jan  2 02:12 /dev/buttons  
  15. [WJ2440]# ./third_test   
  16. key_val = 0x1  
  17. key_val = 0x81  
  18. key_val = 0x2  
  19. key_val = 0x82  
  20. key_val = 0x3  
  21. key_val = 0x83  
  22. key_val = 0x4  
  23. key_val = 0x84  
  24. key_val = 0x2  
  25. key_val = 0x2  
  26. key_val = 0x82  
  27. key_val = 0x1  
  28. key_val = 0x81  
  29. key_val = 0x2  
  30. key_val = 0x82  
  31. key_val = 0x2  
  32. key_val = 0x82  
  33. key_val = 0x4  
  34. key_val = 0x4  
  35. key_val = 0x4  
  36. key_val = 0x84  


/* 键值: 按下时, 0x01, 0x02, 0x03, 0x04 */
/* 键值: 松开时, 0x81, 0x82, 0x83, 0x84 */


测试步骤2:

[cpp] view plain copy
 print?
  1. [WJ2440]# ./third_test &  
  2. [WJ2440]# top  
  3. Mem: 10912K used, 49252K free, 0K shrd, 0K buff, 8104K cached  
  4. CPU:  0.0% usr  0.7% sys  0.0% nic 99.0% idle  0.0% io  0.1% irq  0.0% sirq  
  5. Load average: 0.00 0.05 0.03 1/23 627  
  6.   PID  PPID USER     STAT   VSZ %MEM CPU %CPU COMMAND  
  7.   627   589 root     R     2092  3.4   0  0.7 top  
  8.   589     1 root     S     2092  3.4   0  0.0 -/bin/sh  
  9.     1     0 root     S     2088  3.4   0  0.0 init  
  10.   590     1 root     S     2088  3.4   0  0.0 /usr/sbin/telnetd -l /bin/login  
  11.   587     1 root     S     1508  2.5   0  0.0 EmbedSky_wdg  
  12.   626   589 root     S     1428  2.3   0  0.0 ./third_test  
  13.   573     2 root     SW<      0  0.0   0  0.0 [rpciod/0]  
  14.     5     2 root     SW<      0  0.0   0  0.0 [khelper]  
  15.   329     2 root     SW<      0  0.0   0  0.0 [nfsiod]  
  16.     2     0 root     SW<      0  0.0   0  0.0 [kthreadd]  
  17.     3     2 root     SW<      0  0.0   0  0.0 [ksoftirqd/0]  
  18.     4     2 root     SW<      0  0.0   0  0.0 [events/0]  
  19.    11     2 root     SW<      0  0.0   0  0.0 [async/mgr]  
  20.   237     2 root     SW<      0  0.0   0  0.0 [kblockd/0]  
  21.   247     2 root     SW<      0  0.0   0  0.0 [khubd]  
  22.   254     2 root     SW<      0  0.0   0  0.0 [kmmcd]  
  23.   278     2 root     SW       0  0.0   0  0.0 [pdflush]  
  24.   279     2 root     SW       0  0.0   0  0.0 [pdflush]  
  25.   280     2 root     SW<      0  0.0   0  0.0 [kswapd0]  
  26.   325     2 root     SW<      0  0.0   0  0.0 [aio/0]  

可发现,按键没有被按下时,third_test进程是处于睡眠状态的,并且几乎不占用CPU的利用率。


测试步骤3(如何卸载驱动):

[cpp] view plain copy
 print?
  1. [WJ2440]# lsmod  
  2. third_drv 3016 2 - Live 0xbf003000  
  3. [WJ2440]# rmmod third_drv      
  4. rmmod: remove 'third_drv': Resource temporarily unavailable  
  5. [WJ2440]# kill -9 626  
  6. [1]+  Killed                     ./third_test  
  7. [WJ2440]# rmmod  third_drv      
  8. rmmod: module 'third_drv' not found  
  9. [WJ2440]# lsmod   
  10. [WJ2440]#   


注意事项:

1.楼主在编译驱动的时候,发现linux的头文件经常因版本不一致,导致路径不一致,很是苦恼。

2.有些中断共享标记,如:IRQ_TYPE_EDGE_BOTH,在韦老师那里是IRQT_BOTHEDGE,又是版本害的。

3.楼主使用的linux版本是2.6.30.4

4.楼主在测试驱动时,发现居然没有任何信息打印出来,调试了20分钟,最后发现原来是printf没有加'\n'

原创粉丝点击