• driver: Linux设备模型之input子系统具体解释


    本节从总体上解说了输入子系统的框架结构。有助于读者从总体上认识linux的输入子系统。在陷入代码分析的过程中,通过本节的知识可以找准方向,明确原理。

    本节重点:


    •          输入子系统的框架结构
    •          各层相应内核中的文件位置
    •          输入子系统的事件处理机制
    •          输入子系统的驱动层基本操作流程
    •          输入子系统的驱动层经常使用函数


    本节难点:

    •          输入子系统的事件处理机制
    •          输入子系统的驱动工作流程


    1    初识linux输入子系统

    linux输入子系统(linux input subsystem)从上到下由三层实现,分别为:输入子系统事件处理层(EventHandler)、输入子系统核心层(InputCore)和输入子系统设备驱动层。

    对于输入子系统设备驱动层而言,主要实现对硬件设备的读写訪问,中断设置,并把硬件产生的事件转换为核心层定义的规范提交给事件处理层。

    对于核心层而言,为设备驱动层提供了规范和接口。设备驱动层仅仅要关心怎样驱动硬件并获得硬件数据(比如按下的按键数据),然后调用核心层提供的接口,核心层会自己主动把数据提交给事件处理层。

    对于事件处理层而言,则是用户编程的接口(设备节点),并处理驱动层提交的数据处理。

    对于linux输入子系统的框架结构例如以下图1所看到的:


    图1  linux输入子系统框架结构

     

    由上图所展现的内容就是linux输入子系统的分层结构。

    /dev/input文件夹下显示的是已经注冊在内核中的设备编程接口。用户通过open这些设备文件来打开不同的输入设备进行硬件操作。

    事件处理层为不同硬件类型提供了用户訪问及处理接口。

    比如当我们打开设备/dev/input/mice时。会调用到事件处理层的Mouse Handler来处理输入事件。这也使得设备驱动层无需关心设备文件的操作,由于Mouse Handler已经有了相应事件处理的方法。

    输入子系统由内核代码drivers/input/input.c构成。它的存在屏蔽了用户到设备驱动的交互细节,为设备驱动层和事件处理层提供了相互通信的统一界面。

    下图2简单描写叙述了linux输入子系统的事件处理机制:


    图2  linux输入子系统事件处理机制

     

    由上图可知输入子系统核心层提供的支持以及怎样上报事件到input event drivers。

    作为输入设备的驱动开发人员,须要做下面几步:

    ?           在驱动载入模块中,设置你的input设备支持的事件类型。类型參见表1设置

    ?

               注冊中断处理函数。比如键盘设备须要编写按键的抬起、放下,触摸屏设备须要编写按下、抬起、绝对移动。鼠标设备须要编写单击、抬起、相对移动。而且须要在必要的时候提交硬件数据(键值/坐标/状态等等)

    ?           将输入设备注冊到输入子系统中

     

    表1  Linux输入子系统支持的数据类型

    EV_SYN     0x00    同步事件

    EV_KEY     0x01    按键事件

    EV_REL     0x02    相对坐标(如:鼠标移动。报告相对最后一次位置的偏移)

    EV_ABS     0x03    绝对坐标(如:触摸屏或操作杆。报告绝对的坐标位置)

    EV_MSC     0x04    其他

    EV_SW      0x05    开关

    EV_LED     0x11    按键/设备灯

    EV_SND     0x12    声音/警报

    EV_REP     0x14    反复

    EV_FF      0x15    力反馈

    EV_PWR    0x16    电源

    EV_FF_STATUS    0x17   力反馈状态

    EV_MAX    0x1f    事件类型最大个数和提供位掩码支持

    由表1可知,设备所能表示的事件种类,一个设备可以选择一个或多个事件类型上报给输入子系统。

    Linux输入子系统提供了设备驱动层上报输入事件的函数,在include/linux/input.h中:

    voidinput_report_key(struct input_dev *dev, unsigned int code, int value);      //上报按键事件

    voidinput_report_rel(struct input_dev *dev, unsigned int code, int value);       //上报相对坐标事件

    voidinput_report_abs(struct input_dev *dev, unsigned int code, int value);              //上报绝对坐标事件

    ……

    当提交输入设备产生的输入事件之后。须要调用下面的函数来通知输入子系统,以处理设备产生的完整事件:


    1. void input_sync(struct input_dev *dev);  


    2    输入设备驱动的简单案例

    在Linux内核文档的documentation/input下,有一个input-programming.txt文件,解说了编写输入设备驱动程序的核心步骤。

    提供的案例代码描写叙述了一个button设备,产生的事件通过BUTTON_PORT引脚获取。当有按下/释放发生时,BUTTON_IRQ被触发,下面是驱动的源码:


    1. #include                                                                                                           
    2.  #include   
    3.  #include   
    4.   
    5.  #include   
    6.  #include   
    7.   
    8.  static struct input_dev *button_dev;  
    9.   
    10.  static void button_interrupt(int irq, void*dummy, struct pt_regs *fp)  
    11.  {  
    12.         input_report_key(button_dev, BTN_1, inb(BUTTON_PORT) & 1);  
    13.         input_sync(button_dev);  
    14.  }        
    15.   
    16.  static int __init button_init(void)  
    17.  {  
    18.         int error;  
    19.           
    20.         if (request_irq(BUTTON_IRQ, button_interrupt, 0, "button",NULL)) {  
    21.                  printk(KERN_ERR"button.c: Can't allocate irq %d ", button_irq);  
    22.                  return -EBUSY;  
    23.         }        
    24.           
    25.          button_dev = input_allocate_device();  
    26.         if (!button_dev) {  
    27.                  printk(KERN_ERR"button.c: Not enough memory ");  
    28.                  error = -ENOMEM;  
    29.                  goto err_free_irq;  
    30.         }  
    31.   
    32.         button_dev->evbit[0] = BIT(EV_KEY);  
    33.         button_dev->keybit[LONG(BTN_0)] = BIT(BTN_0);  
    34.   
    35.         error = input_register_device(button_dev);  
    36.         if (error) {  
    37.                  printk(KERN_ERR"button.c: Failed to register device ");  
    38.                  goto err_free_dev;  
    39.         }  
    40.   
    41.         return 0;  
    42.   
    43.  err_free_dev:  
    44.         input_free_device(button_dev);  
    45.  err_free_irq:  
    46.         free_irq(BUTTON_IRQ, button_interrupt);  
    47.         return error;  
    48.  }  
    49.   
    50.  static void __exit button_exit(void)  
    51.  {  
    52.        input_unregister_device(button_dev);  
    53.         free_irq(BUTTON_IRQ, button_interrupt);  
    54. }  
    55.   
    56. module_init(button_init);  
    57. module_exit(button_exit);  

    编写基于输入子系统的设备驱动程序须要包括,由于它包括了输入子系统的接口和全部的宏定义,这些内容在编写输入设备驱动程序时须要用到。

    button_init函数说明:

    当模块载入(insmod)或内核引导过程中,button_init函数会被调用。

    首先做的工作是获取可以正确控制硬件设备的硬件资源(比如内存、IO内存、中断和DMA),在代码中BUTTON_IRQ作为BUTTON设备的中断资源。通过request_irq()函数被申请注冊。

    当有按键按下/释放时,调用button_interrupt()中断处理函数获取按键值BUTTON_PORT(BUTTON设备的I/O资源)。

    那么输入子系统怎么可以知道这个设备为输入设备呢?通过第8行为设备定义一个用于描写叙述一个输入设备对象。


    1. static struct input_dev *button_dev;  


    定义了button_dev之后,怎样通知输入子系统有新的输入设备了呢?或者说怎样把一个新的输入设备增加到输入子系统中呢?可以通过输入子系统核心层input.c中提供的函数分配一个输入设备,在代码的第25行。


    1. button_dev= input_allocate_device();  


    有了输入设备的描写叙述,当事件产生时,输入子系统怎么可以知道设备产生的事件类型呢?通过32和33行的代码。


    1. button_dev->evbit[0]= BIT(EV_KEY);  
    2. button_dev->keybit[LONG(BTN_0)]= BIT(BTN_0);  


    当中evbit和keybit成员分别代表设备产生的事件类型和上报的按键值。

    当中输入子系统的一些位操作NBITS、BIT、LONG经常被用到:


    1. #defineNBITS(x) (((x)/BITS_PER_LONG)+1)                 //通过位x获取数组的长度  
    2. #defineBIT(x)       (1UL<<((x)%BITS_PER_LONG))       //返回位x在数组中的位域  
    3. #defineLONG(x) ((x)/BITS_PER_LONG)                        //返回位x的索引  


    以上的工作做完之后。就可以注冊为输入设备了,代码的35行。


    1. input_register_device(button_dev);  


    这个函数把button_dev输入设备挂入输入设备链表中,而且通知事件处理层调用connect函数完毕设备和事件处理的绑定,当用户打开设备时,便可以调用到相应的事件处理接口获得硬件上报的数据了。

    input_register_device()函数是会睡眠的函数。因此不可以在中断上下文和持有自旋锁的代码中调用。

    当我们把上面的工作做完之后。设备驱动中唯一值得关注的就是button_interrupt()中断处理函数了。当按键动作发生,button_interrupt()函数被调用。完毕事件的上报由当中的两条语句完毕。


    1. input_report_key(button_dev, BTN_1, inb(BUTTON_PORT) & 1);  
    2. input_sync(button_dev);  


    当中input_report_key上报了这是一个按键事件,且它的值为inb(BUTTON_PORT) & 1。由于案例代码仅仅产生一个按键的值,因此input_sync()在这里不起关键作用。但假设是一个触摸屏。即有x坐标和y坐标,则须要通过input_sync()函数把x和y坐标完整地传递给输入子系统。


  • 相关阅读:
    MongoDB 基础命令 (MongoDB Shell)
    MongoDB 在 Mac OSX 平台安装
    数组根据index拆分和查询下标
    简单介绍递归算法以及应用场景
    android studio ndk开发环境搭建
    基于vue开发的多功能的时间选择器组件,开箱即用
    简单了解JS中的几种遍历
    零基础学习webpack打包管理
    让你高效的理解JavaScript中的同步、异步和事件循环
    学习flex布局(弹性布局)
  • 原文地址:https://www.cnblogs.com/yfceshi/p/7270437.html
Copyright © 2020-2023  润新知