Linux视频驱动 学习总结之第13课(输入子系统)

《韦东山Linux视频驱动第2期》学习总结之第13课(输入子系统) (2012-02-05 16:49)
 


注:本文是对输入子系统的一个学习总结,不仅仅包括视频上的内容,还参考了两篇博客和一篇视频总结。
连接: http://blog.csdn.net/woshixingaaa/article/details/6431094
       http://blog.csdn.net/hongtao_liu/article/details/5679171
       http://www.arm9home.net/read.php?tid-16250.html

内核的输入子系统是对分散的,多种不同类别的输入设备(如键盘,鼠标,跟踪球,操纵杆,触摸屏,加速计和手写板)等字符设备进行统一处理的一层抽象,就是在字符设备驱动上抽象出的一层。
输入子系统包括两类驱动程序:事件驱动程序和设备驱动程序。事件驱动程序负责和应用程序的接口,而设备驱动程序负责和底层输入设备的通信。鼠标事件生成文件mousedev属于事件驱动程序,而PS/2鼠标驱动程序是设备驱动程序。事件驱动程序是标准的,对所有的输入类都是可用的,所以要实现的是设备驱动程序而不是事件驱动程序。设备驱动程序可以利用一个已经存在的,合适的事件驱动程序通过输入核心和用户应用程序接口。
输入子系统带来了如下好处:
1.统一了物理形态各异的相似的输入设备的处理功能
2.提供了用于分发输入报告给用户应用程序的简单的事件接口
3.抽取出了输入驱动程序的通用部分,简化了驱动,并引入了一致性
如下图,input子系统分三层,最上一层是event handler,中间是intput core,底层是input driver。input driver把event report到input core层。input core对event进行分发,传到event handler,相应的event handler层把event放到event buffer中,等待用户进程来取。
Linux视频驱动 学习总结之第13课(输入子系统)_第1张图片
它们之间的关系也如下图所表示:
Linux视频驱动 学习总结之第13课(输入子系统)_第2张图片
input_dev,input_handler,input_handle是 input子系统的3个基本的数据结构。一类handler可以和多个硬件设备相关联,一个硬件设备可以和多个handler相关联。例如:一个触摸屏设备可以作为一个event设备,作为一个鼠标设备,也可以作为一个触摸设备,所以一个设备需要与多个平台驱动进行连接。而一个平台驱动也不只为一个设备服务,一个触摸平台驱动可能要为A,B,C3个触摸设备提供上层驱动,所以需要这样一对多的连接。

总结来说,输入子系统分上下两层。最上层是核心层input.c,里面有
register_chrdev。这个注册的字符设备的fops里面只有一个open函数。该open
函数根据打开的设备节点的次设备号,找到一个input_handler。把打开的文件的里面的fop指向这个handler里面的fops,并且打开这个handler里面的open函数。以后要读写的时候就调用这个handler里面的读写函数,如下代码片段1。
    input_handler通过input_register_handler向核心层注册一个input_handler
结构体,并且在input_table数组第i个值指向这个注册的input_handler(“i”是根据handler里面的次设备号向右移5位算出的值)。这个input_table[i]就可在上诉open函数里根据打开的设备节点的次设备号找到相应input_handler。
    input_dev通过input_register_device向核心层注册一个input_dev结构体。
    当注册一个input_dev设备或者注册一个input_handler时,input_dev与
input_handler会通过调用input_attach_handler函数搜索是否有合适的input_handler或者input_dev与之匹配。如果存在inputh_handler与注册的input_dev匹配或者存在input_dev与注册的inputh_handler匹配,则会调用input_handler里的connect函数。匹配的依据就是input_handler中的id_table与input_dev中的id里的信息是否相同。
    connect函数会建立一个input_handle结构体,这个结构体里面的handler指向匹配的input_handler,dev指向input_dev。并且这个input_handle会被放到input_handler和input_dev的h_list链表里面来。以后就可以从input_dev里面的h_list找到handle,再从handle里面的handler找到input_handler。反之亦然。   
    对于输入设备,我们知道一般需要读取其数据,那么经过上述的一些列动作后又是如何读取到数据的呢?如果没有数据可读并且是NONBLOCK的话,就立刻返回。如果是没有数据可读但是BLOCK的话,就进入睡眠。既然有睡眠,那何时被唤醒呢?在evdev_read函数中唤醒read函数。evdev_event是input_handler中的成员,当有数据可读(如触摸屏被按下,按键被按下)时,event函数会被调用。而event
函数是怎么被调用到的?这就得看设备层了,设备层的驱动做了如下工作:
    <1>在中断函数中确定判断发生了什么事件,并且相应的值是多少;
    <2>通过input_event()函数上报事件;
    <3>通过input_sync()函数表明上报结束。
    分析input_event函数我们就可以知道input_handler中的event
函数是如何被调用到的了。
    在input_event中,调用了input_handle_event函数,在input_handle_event
函数中调用了input_pass_event函数。 在input_pass_event函数中,将input_handle从input_dev的h_list中一个个拿出来。如果找到一个input_handle被打开,则input_handle->input_handler即为input_dev所匹配的handler。
  1. //代码片段1
  2. static int input_open_file(struct inode*inode, struct file*file)
  3. {
  4.     struct input_handler *handler;
  5.     const struct file_operations *old_fops, *new_fops = NULL;
  6.     int err;

  7.     err = mutex_lock_interruptible(&input_mutex);
  8.     if (err)
  9.         return err;

  10.     /* No load-on-demand here?*/
  11.     //1.根据打开的设备节点的次设备号找到一个input_handler。
  12.     handler = input_table[iminor(inode)>> 5];
  13.     if (handler)
  14.         new_fops = fops_get(handler->fops);

  15.     mutex_unlock(&input_mutex);

  16.     /*
  17.      * That's _really_ odd. UsuallyNULL ->open means"nothing special",
  18.      * not"no device". Oh, well...
  19.      */
  20.     if (!new_fops|| !new_fops->open){
  21.         fops_put(new_fops);
  22.         err =-ENODEV;
  23.         goto out;
  24.     }

  25.     old_fops = file->f_op;
  26.     //2.打开的文件的fops指向这个handler里面的fops。
  27.     file->f_op= new_fops;
      
     //3.打开handler里面的open函数。
  1.     err = new_fops->open(inode, file);
  2.     if (err){
  3.         fops_put(file->f_op);
  4.         file->f_op= fops_get(old_fops);
  5.     }
  6.     fops_put(old_fops);
  7. out:
  8.     return err;
  9. }

  //没有read函数
  1. static const struct file_operations input_fops= {
  2.     .owner = THIS_MODULE,
  3.     .open = input_open_file,
  4.     .llseek = noop_llseek,
  5. };
怎么写符合输入子系统框架的驱动程序?
1. 分配一个input_dev结构体
2. 设置
3. 注册
4. 硬件相关的代码,比如在中断服务程序里上报事件

以下是基于友善之比Tiny6410开发板,Linux2.6.38内核的驱动版本。

  1. /* 参考drivers\input\keyboard\gpio_keys.c */

  2. #include <linux/module.h>
  3. #include <linux/version.h>

  4. #include <linux/init.h>
  5. #include <linux/fs.h>
  6. #include <linux/interrupt.h>
  7. #include <linux/irq.h>
  8. #include <linux/sched.h>
  9. #include <linux/pm.h>
  10. #include <linux/sysctl.h>
  11. #include <linux/proc_fs.h>
  12. #include <linux/delay.h>
  13. #include <linux/platform_device.h>
  14. #include <linux/input.h>
  15. #include <linux/irq.h>

  16. #include <asm/gpio.h>
  17. #include <asm/io.h>

  18. #include <mach/map.h>
  19. #include <mach/regs-clock.h>
  20. #include <mach/regs-gpio.h>
  21. #include <mach/gpio-bank-n.h>
  22. #include <plat/gpio-cfg.h>

  23. #define DEBUG 0

  24. struct pin_desc{
  25. int irq;
  26. char *name;
  27. unsigned int pin_num;
  28. unsigned int key_val;
  29. };

  30. struct pin_desc pins_desc[4] = {
  31. {IRQ_EINT(0),  "S1", 0,  KEY_L},
  32. {IRQ_EINT(1),  "S2", 1,  KEY_S},
  33. {IRQ_EINT(2),  "S3", 2,  KEY_ENTER},
  34. {IRQ_EINT(3),  "S4", 3,  KEY_LEFTSHIFT},
  35. };

  36. static struct input_dev *buttons_dev;
  37. static struct pin_desc *irq_pd;
  38. static struct timer_list buttons_timer;

  39. static irqreturn_t buttons_irq(int irq, void *dev_id)
  40. {
  41. /* 10ms后启动定时器 */
  42. irq_pd = (struct pin_desc *)dev_id;
  43. mod_timer(&buttons_timer, jiffies + HZ/100);

  44. #if DEBUG
  45. printk("In the irq function!\n");
  46. #endif

  47. return IRQ_RETVAL(IRQ_HANDLED);
  48. }

  49. static void buttons_timer_function(unsigned long data)
  50. {
  51. struct pin_desc * pindesc = irq_pd;
  52. unsigned int pinval;

  53. if (!pindesc)
  54. return;
  55. pinval = (~readl(S3C64XX_GPNDAT)) & (1 << pindesc->pin_num);

  56. #if DEBUG
  57. printk("In the timer function! pinval = %x\n", pinval);
  58. #endif

  59. if (pinval)
  60. {
  61. /* 松开 : 最后一个参数: 0-松开, 1-按下 */
  62. input_event(buttons_dev, EV_KEY, pindesc->key_val, 1);
  63. input_sync(buttons_dev);
  64. }
  65. else
  66. {
  67. /* 按下 */
  68. input_event(buttons_dev, EV_KEY, pindesc->key_val, 0);
  69. input_sync(buttons_dev);
  70. }
  71. }

  72. static int buttons_init(void)
  73. {
  74. int i;
  75. /* 1. 分配一个input_dev结构体 */
  76. buttons_dev = input_allocate_device();
  77. /* 2. 设置 */ /* 2.1 能产生哪类事件 */ set_bit(EV_KEY, buttons_dev->evbit);
  78. set_bit(EV_KEY, buttons_dev->evbit);
  79. set_bit(EV_REP, buttons_dev->evbit);
  80. /* 2.2 能产生这类操作里的哪些事件: L,S,ENTER,LEFTSHIT */
  81. set_bit(KEY_L, buttons_dev->keybit);
  82. set_bit(KEY_S, buttons_dev->keybit);
  83. set_bit(KEY_ENTER, buttons_dev->keybit);
  84. set_bit(KEY_LEFTSHIFT, buttons_dev->keybit);

  85. /* 3. 注册 */
  86. input_register_device(buttons_dev);
  87. /* 4. 硬件相关的操作 */
  88. init_timer(&buttons_timer);
  89. buttons_timer.function = buttons_timer_function;
  90. add_timer(&buttons_timer);
  91. for (i = 0; i < 4; i++)
  92. {
  93. request_irq(pins_desc[i].irq, buttons_irq, IRQ_TYPE_EDGE_BOTH, pins_desc[i].name, &pins_desc[i]);
  94. }
  95. return 0;
  96. }

  97. static void buttons_exit(void)
  98. {
  99. int i;
  100. for (i = 0; i < 4; i++)
  101. {
  102. free_irq(pins_desc[i].irq, &pins_desc[i]);
  103. }

  104. del_timer(&buttons_timer);
  105. input_unregister_device(buttons_dev);
  106. input_free_device(buttons_dev);
  107. }

  108. module_init(buttons_init);
  109. module_exit(buttons_exit);

  110. MODULE_LICENSE("GPL");

你可能感兴趣的:(Linux视频驱动 学习总结之第13课(输入子系统))