zoukankan      html  css  js  c++  java
  • Linux 输入子系统

    Technorati 标签:

         在Linux中,输入设备(如按键、键盘、触摸屏、鼠标等)是典型的字符设备,其一般的工作机理,是底层在按键、触摸时,触发一个中断,或者驱动通过定时器定时查询,通过这两种方式通知CPU,CPU然后通过SPI、I2C或I/O接口读取键值、坐标等数据,放入缓冲区,字符设备驱动管理该缓冲区,向上提供read接口供应用程序使用。

         在上述的工作流程中,只有终端、读取数值是根具体硬件设备相关,而输入事件的缓冲区管理以及字符设备驱动的接口函数,都是通用的,因此,有必要统一这些不同的输入设备,提炼出通用部分。

        Linux的Input子系统整体框架如下:

    image

        先介绍核心数据结构体,再介绍一个简单的例子,然后引入基本功能函数。

    核心数据结构体

        Input子系统有三层,比较核心的结构体有四个,分别为 输入事件input_event,输入设备input_dev,核心处理input_handle,事件处理input_handler,分属于Input的不同层级,在上面这些结构体中,input_handle处于核心地位。如上图所示:

        整个Input子系统有 两个全局链表,一个是input_dev_list 链表,里面有当前系统下,所有的底层输入设备,一个是input_handler_list链表,里面有当前系统下,所有的事件处理函数。

    输入设备

    struct input_dev {

    …..

    struct input_id id;//与input_handler匹配用的id,包括 总线类型、生产厂商、产品类型、版本

    unsigned long evbit[BITS_TO_LONGS(EV_CNT)];     //设备所支持的事件类型 ,如按键事件 EV_KEY

    unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];  //设备所支持的子事件类型,如 按键值

    int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);

    unsigned long key[BITS_TO_LONGS(KEY_CNT)];//反应设备当前的按键状态

    struct input_handle *grab;//当前占有该设备的input_handle

    struct list_head h_list;//该链表头用于链接此设备所关联的input_handle

    struct list_head node; //用于将此设备链接到input_dev_list

    }

    事件处理器

    struct input_handler{

    …..

    int minor;  //表示设备的次设备号

    /*event用于处理事件*/

    void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);

    /*connect用于建立handler和device的联系*/

    int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);

    const struct file_operations *fops;//handler的一些处理函数

    const struct input_device_id *id_table;//用于和device匹配 ,这个是事件处理器所支持的input设备

    const struct input_device_id *blacklist;//匹配黑名单,这个是事件处理器应该忽略的input设备

    struct list_head h_list;//这个链表用来链接他所支持的input_handle结构,input_dev与input_handler配对之后就会生成一个input_handle结构

    struct list_head node; //链接到input_handler_list,这个链表链接了所有注册到内核的事件处理器

    }

     

    连接结构体

    每一个 input_handle 结构体代表一个成功配对的 input_dev 和 input_handler。

    struct input_handle {

    void *private; //每个配对的事件处理器都会分配一个对应的设备结构,如evdev事件处理器的evdev结构,注意这个结构与设备驱动层的input_dev不同,初始化handle时,保存到这里。

    int open; //打开标志,每个input_handle 打开后才能操作,这个一般通过事件处理器的open方法间接设置

    const char *name;

    struct input_dev *dev; //关联的input_dev结构

    struct input_handler *handler; //关联的input_handler结构

    struct list_head d_node; //input_handle通过d_node连接到了input_dev上的h_list链表上

    struct list_head h_node; //input_handle通过h_node连接到了input_handler的h_list链表上

    };

     

    数据结构之间的关系

    struct input_dev物理输入设备的基本数据结构,包含设备相关的一些信息

    struct input_handler 事件处理结构体,定义怎么处理事件的逻辑

    struct input_handle用来创建 input_dev 和 input_handler 之间关系的结构体

    input_dev 通过全局的input_dev_list链接在一起。设备注册的时候实现这个操作。

    input_handler 通过全局的input_handler_list链接在一起。事件处理器注册的时候实现这个操作(事件处理器一般内核自带,一般不需要我们来写)

    input_hande 没有一个全局的链表,它注册的时候将自己分别挂在了input_dev 和 input_handler 的h_list上了。通过input_dev 和input_handler就可以找到input_handle在设备注册和事件处理器,注册的时候都要进行配对工作,配对后就会实现链接。通过input_handle也可以找到input_dev和input_handler。

     

    那么为什么一个input_device和input_handler中拥有的是h_list而不是一个handle呢?因为一个device可能对应多个handler,而一个handler也不能只处理一个device,比如说一个鼠标,它可以对应even handler,也可以对应mouse handler,因此当其注册时与系统中的handler进行匹配,就有可能产生两个实例,一个是evdev,另一个是mousedev,而任何一个实例中都只有一个handle。至于以何种方式来传递事件,就由用户程序打开哪个实例来决定。后面一个情况很容易理解,一个事件驱动不能只为一个甚至一种设备服务,系统中可能有多种设备都能使用这类handler,比如event handler就可以匹配所有的设备。在input子系统中,有8种事件驱动,每种事件驱动最多可以对应32个设备,因此dev实例总数最多可以达到256个。

     

     

    Input Driver例子

    下面以一个简单驱动为例子来介绍

    #include <asm/irq.h>

    #include <asm/io.h>

    static struct input_dev *button_dev;   /*输入设备结构体*/
    static irqreturn_t button_interrupt(int irq, void *dummy)     /*中断处理函数*/
    {
              input_report_key(button_dev, BTN_0, inb(BUTTON_PORT) & 1);  /*向输入子系统报告产生按键事件*/
            input_sync(button_dev);       /*通知接收者,一个报告发送完毕*/
            return IRQ_HANDLED;

    }

    static int __init button_init(void)      /*加载函数*/
    {
            int error;
            if (request_irq(BUTTON_IRQ, button_interrupt, 0, "button", NULL))  /*申请中断,绑定中断处理函数*/
            {
                     printk(KERN_ERR "button.c: Can't allocate irq %d ", button_irq);
                     return -EBUSY;
             }

            button_dev = input_allocate_device(); /*分配一个设备结构体*/

            //input_allocate_device()函数在内存中为输入设备结构体分配一个空间,并对其主要的成员进行了初始化.

             if (!button_dev)

            {
                  printk(KERN_ERR "button.c: Not enough memory ");
                  error = -ENOMEM;
                  goto err_free_irq;

             }

             button_dev->evbit[0] = BIT_MASK(EV_KEY);   /*设置按键信息*/

             button_dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0);

           //分别用来设置设备所产生的事件以及上报的按键值。Struct iput_dev中有两个成员,一个是evbit.一个是keybit.分别用

           //表示设备所支持的动作和键值。

             error = input_register_device(button_dev);      /*注册一个输入设备*/
             if (error)
             {
                     printk(KERN_ERR "button.c: Failed to register device ");
                     goto err_free_dev;
             }

            return 0;

    err_free_dev:

             input_free_device(button_dev);

    err_free_irq:
              free_irq(BUTTON_IRQ, button_interrupt);
              return error;                  

    }

    static void __exit button_exit(void)      /*卸载函数*/
    {
            input_unregister_device(button_dev); /*注销按键设备*/
            free_irq(BUTTON_IRQ, button_interrupt);        /*释放按键占用的中断线*/
    }
    module_init(button_init);
    module_exit(button_exit);

         这个demo代码,在button_init()中,首先注册了中断处理函数,然后调用input_allocate_device()函数分配一个input_dev结构体,并调用input_register_device函数对其进行注册。在中断处理函数中,demo将接受到的按键信息上报给Input子系统,Input子系统向用户态程序提供按键输入信息。

        在上述这个简单的驱动里面,涉及到几个问题

        1. 输入设备如何传递事件到核心层

        2. 核心层如何找到对应事件的事件处理函数

        3. 底层输入设备驱动是如何和事件处理层联系上的

     

    基本功能函数

    下面从使用流程入手,简要介绍以下input的整体流程,这里只会标注出主要代码流程。

        1. 分配一个输入设备

        image

         从注释中可知,释放一个还未注册的输入设备,使用input_free_device,释放一个已经注册的设备,使用input_unregister_device。

         由于没有输入参数,因此,可以猜测出这个分配出来的input_dev的一些配置都是默认配置。

         这里面,比较重要的有两个链表h_listnode,分配后,我们需要在默认配置的基础上,添加自己的配置信息。

       2. 注册一个输入设备

         image

         从注释上,可以知道,传入参数必须为input_allocate_device的返回值。

         这个函数里面会设置input_dev所支持的基本事件类型,注意,一个设备可以支持一种或者多种事件类型。Input子系统需要在sysfs文件系统中体现出现,因此,input在sysfs中的device名称会在这里面设置。

    image

    然后将底层输入设备input_dev添加到全局设备链表input_dev_list中,对全局链表input_handler_list中的每一个handler函数,调用

    input_attach_handler()。

    每一次input_dev的注册,都会遍历事件处理链表input_handler_list,寻找输入设备对应的事件处理程序。

    每一次input_hanlder的注册,都会遍历设备链表input_dev_list,寻找事件处理程序对应的输入设备。

    上面这两个操作几乎是对称的,机制同platform中的device和device driver的相互寻找类型。

    具体代码如下:

    image 

    platform机制中的寻找,是根据设备名和设备驱动名称来匹配的,这里也不例外,匹配的过程,通过对比input_dev和input_handler的id成员,具体为id成员的总线类型、设备厂商、设备号、设备版本是否一致来判断是否匹配成功。

        3. 输入设备找到事件处理程序

    image

        正常情况下,使用 事件处理程序中的 id_table和输入设备input_dev.id成员进行匹配。id_table指向该事件处理程序支持的设备列表。

        匹配成功后,调用handler->connect,将handler和input_dev连接起来。

       4. 向Input核心层报告输入事件

     image

      这里面的核心函数为 input_handle_event(dev,type,code,value),里面是一个大的switch,一级为事件类型type,二级switch为event code,这里只分析按键相关:

      image

    disposition 的取值有如下几种,它表示使用什么样的方式处理该输入事件。
    #define INPUT_IGNORE_EVENT                0         // 表示忽略事件,不对其进行处理
    #define INPUT_PASS_TO_HANDLERS         1        // 表示将事件交给 handler 处理
    #define INPUT_PASS_TO_DEVICE             2        // 表示将事件交给 input_dev 处理
    #define INPUT_PASS_TO_ALL                 (INPUT_PASS_TO_HANDLERS | INPUT_PASS_TO_DEVICE)

    image 

    如果该事件是传递给设备自身,则调用设备驱动自身的event函数来处理事件。

    如果该事件时传递给上层事件处理函数,则调用input_pass_event来传递事件,将调用输入设备对于的handler的event()函数来处理输入事件。

    注意:只有在handle被打开的情况下,才会接收到事件。

    image

     

    5. Input子系统输入事件处理层

          输入事件处理层是在系统初始化时,注册进系统的。

         输入子系统的事件处理层核心数据结构为 input_handler,所有输入子系统的事件处理程序都挂在input_handler_list中。在

    image

    6. 输入事件处理层注册

         image

         系统定义了8个输入事件处理层, 这些事件处理层通过handler->h_list连接起来,同时,也存储在全局input_table数组(他们在数组中的索引为设备号右移5位的值)和全局input_handler_list链表中。

          同输入设备注册一样,输入事件处理注册时,需要寻找对应的输入设备。代码如下:

    image

    在input_attach_handler中,最后会调用error = handler->connect(handler, dev, id);也就是evdev_handler->connect,也就是

    evdev_connect函数,在这里面初始化input handle,并且注册到系统。

    在这里面,会将handle挂到所对应input deviceh_list链表上.还将handle挂到对应的handlerhlist链表上,因此,可以把handle看成是 handler和 input device的信息集合体 .在这个结构里集合了匹配成功的 handler和 input device。就这样,handler和input dev匹配到一起。

    7. 事件层处理来自核心层的事件

          这里,会调用事件处理层的event函数,也就是evdev_event。每当input dev上报一个事件时,会将其交给和它匹配的handler的event函数来处理,在这里,又会通过遍历链表来调用evdev_pass_event来处理。

    image

    image

    这里的操作,就是将event上传的数据保存到client->buffer中。client->head是当前的数据位置,这里是一个环形缓冲区,

    写数据是从client->head写.而读数据则是从client->tail中读.

    写完之后,通过向上层发起SIGIO信号来通知有事件发生,可以从缓冲区中读取数据了。

    7. 输入事件处理函数的文件访问接口

            输入设备在上层表现为主设备号为INPUT_MAJOR的设备文件,对他的读写会通过VFS,最后传递到evdev_fops的文件操作结构体中去。

            image

       1:  static ssize_t evdev_read(struct file *file, char __user *buffer,
       2:                size_t count, loff_t *ppos)
       3:  {
       4:      struct evdev_client *client = file->private_data;
       5:      struct evdev *evdev = client->evdev;
       6:      struct input_event event;
       7:      int retval;
       8:   
       9:      if (count < input_event_size())
      10:          return -EINVAL;
      11:   
      12:      if (client->head == client->tail && evdev->exist &&
      13:          (file->f_flags & O_NONBLOCK))
      14:          return -EAGAIN;
      15:   
      16:      retval = wait_event_interruptible(evdev->wait,
      17:          client->head != client->tail || !evdev->exist);
      18:      if (retval)
      19:          return retval;
      20:   
      21:      if (!evdev->exist)
      22:          return -ENODEV;
      23:   
      24:      while (retval + input_event_size() <= count &&
      25:             evdev_fetch_next_event(client, &event)) {
      26:   
      27:          if (input_event_to_user(buffer + retval, &event))
      28:              return -EFAULT;
      29:   
      30:          retval += input_event_size();
      31:      }
      32:   
      33:      return retval;
      34:  }

         首先,它判断缓存区大小是否足够.在读取数据的情况下,可能当前缓存区内没有数据可读.在这里先睡眠等待缓存

    区中有数据.如果在睡眠的时候,.条件满足.是不会进行睡眠状态而直接返回的. 然后根据read()提够的缓存区大小.将

    client中的数据写入到用户空间的缓存区中.

     

    参考文献:

    http://blog.csdn.net/lbmygf/article/details/7360084

    http://blog.chinaunix.net/uid-27717694-id-3758334.html
  • 相关阅读:
    web.xml配置文件详解
    spring MVC配置文件详解
    路由导航刷新后导致当前选中的导航样式不见的解决办法
    vue input 使用v-model想要改变父属性的写法
    JS 编写一个指定范围内的随机数返回方法
    vue-router 3.1.5报错:vue-router.esm.js?8c4f:2089 Uncaught (in promise)
    Failed to mount component: template or render function not defined. vue
    vscode 操作debugger for chrome的配置文件写法
    JS操作DOM方法总结
    npm 代理配置的方法
  • 原文地址:https://www.cnblogs.com/cherishui/p/4248959.html
Copyright © 2011-2022 走看看