Linux 输入子系统

Technorati 标签:

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

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

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

技术分享

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

核心数据结构体

    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\n", button_irq);
                 return -EBUSY;
         }

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

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

         if (!button_dev)

        {
              printk(KERN_ERR "button.c: Not enough memory\n");
              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\n");
                 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. 分配一个输入设备

    技术分享

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

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

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

   2. 注册一个输入设备

     技术分享

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

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

技术分享

然后将底层输入设备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的相互寻找类型。

具体代码如下:

技术分享 

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

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

技术分享

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

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

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

 技术分享

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

  技术分享

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)

技术分享 

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

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

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

技术分享

 

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

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

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

技术分享

6. 输入事件处理层注册

     技术分享

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

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

技术分享

在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来处理。

技术分享

技术分享

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

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

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

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

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

        技术分享

   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

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。