Technorati 标签: Kernel 输入子系统 Input

     在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_list和node,分配后,我们需要在默认配置的基础上,添加自己的配置信息。

   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 device的h_list链表上.还将handle挂到对应的handler的hlist链表上,因此,可以把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:  }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

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

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

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

 

参考文献:

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

http://blog.chinaunix.net/uid-27717694-id-3758334.html

Linux 输入子系统的更多相关文章

  1. linux输入子系统(input subsystem)之evdev.c事件处理过程

    1.代码 input_subsys.drv.c 在linux输入子系统(input subsystem)之按键输入和LED控制的基础上有小改动,input_subsys_test.c不变. input ...

  2. Linux输入子系统(转)

    Linux输入子系统(Input Subsystem) 1.1.input子系统概述 输入设备(如按键,键盘,触摸屏,鼠标等)是典型的字符设备,其一般的工作机制是低层在按键,触摸等动作发生时产生一个中 ...

  3. Linux输入子系统(Input Subsystem)

    Linux输入子系统(Input Subsystem) http://blog.csdn.net/lbmygf/article/details/7360084 input子系统分析  http://b ...

  4. Linux输入子系统框架分析(1)

    在Linux下的输入设备键盘.触摸屏.鼠标等都能够用输入子系统来实现驱动.输入子系统分为三层,核心层和设备驱动层.事件层.核心层和事件层由Linux输入子系统本身实现,设备驱动层由我们实现.我们在设备 ...

  5. Linux输入子系统详解

    input输入子系统框架  linux输入子系统(linux input subsystem)从上到下由三层实现,分别为:输入子系统事件处理层(EventHandler).输入子系统核心层(Input ...

  6. linux输入子系统

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

  7. linux输入子系统概念介绍

    在此文章之前,我们讲解的都是简单的字符驱动,涉及的内容有字符驱动的框架.自动创建设备节点.linux中断.poll机制.异步通知.同步互斥.非阻塞.定时器去抖动. 上一节文章链接:http://blo ...

  8. linux输入子系统简述【转】

    本文转载自:http://blog.csdn.net/xubin341719/article/details/7678035 1,linux输入子系统简述 其实驱动这部分大多还是转载别人的,linux ...

  9. 7.Linux 输入子系统分析

    为什么要引入输入子系统? 在前面我们写了一些简单的字符设备的驱动程序,我们是怎么样打开一个设备并操作的呢? 一般都是在执行应用程序时,open一个特定的设备文件,如:/dev/buttons .... ...

  10. Android底层开发之Linux输入子系统要不要推断系统休眠状态上报键值

    Android底层开发之Linux输入子系统要不要推断系统休眠状态上报键值 题外话:一个问题研究到最后,那边记录文档的前半部分基本上都是没用的,甚至是错误的. 重点在最后,前边不过一些假想猜測. ht ...

随机推荐

  1. 使用openssl工具生成证书

    第一步. 生成rsa私钥文件 :\> openssl genrsa -out bexio.pem 1024 : 若要加密生成的rsa私钥文件(des3加密) :\> openssl gen ...

  2. 20 Free Open Source Web Media Player Apps

    free Media Players (Free MP3, Video, and Music Player ...) are cool because they let web developers ...

  3. 剑指 offer set 10 栈的压入、弹出序列

    总结 1. 通过按位对比来判断, 没有更优的方法了

  4. AlloyTouch实现下拉刷新

    原文地址:https://github.com/AlloyTeam/AlloyTouch/wiki/Pull-to-refresh 效果展示 扫码体验 你也可以点击这里访问Demo 可以点击这里查看代 ...

  5. LMAX Disruptor – High Performance, Low Latency and Simple Too 转载

    原文地址:http://www.symphonious.net/2011/07/11/lmax-disruptor-high-performance-low-latency-and-simple-to ...

  6. Google搜索技巧-从入门到精通(从此学习进步、工作顺心)

    转载:http://www.blogbus.com/koudaizhi-logs/55687286.html 一  GOOGLE简介 Google (www.google.com)是一个搜寻引擎,由某 ...

  7. 关于调试程序接收通过adb发送带有参数的广播问题

    一句话,如果你检查完格式没有错:关于通过adb启动Activity.activity.service以及发送broadcast的命令 am broadcast -a myAction --es cit ...

  8. 网络传输速度bps与下载文件所需时间的换算

    相信很多同志都非常关注自己家的计算机上网的宽带是多少.关心单位上网的宽带是多少! 但是很多同志都经常误解网络传输速度,以至于责备网络接入商(电信.网通.铁通等单位)欺骗用户,限制上网的速度! 本文,就 ...

  9. WPF/Silverlight Layout 系统概述——Measure(转)

    前言 在WPF/Silverlight当中,如果已经存在的Element无法满足你特殊的需求,你可能想自定义Element,那么就有可能会面临重写MeasureOverride和ArrangeOver ...

  10. iOS - UI - UITableView

    1.UITableView 表格视图 服从数据源 - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSIn ...