Linux 输入子系统

时间:2022-04-22 02:12:00
Technorati 标签: Kernel 输入子系统 Input

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

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

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

Linux 输入子系统

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

核心数据结构体

    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个。

Linux 输入子系统

 

 

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. 分配一个输入设备

    Linux 输入子系统

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

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

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

   2. 注册一个输入设备

     Linux 输入子系统

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

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

Linux 输入子系统

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

具体代码如下:

Linux 输入子系统 

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

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

Linux 输入子系统

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

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

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

 Linux 输入子系统

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

  Linux 输入子系统

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)

Linux 输入子系统 

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

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

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

Linux 输入子系统

 

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

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

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

Linux 输入子系统

6. 输入事件处理层注册

     Linux 输入子系统

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

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

Linux 输入子系统

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

Linux 输入子系统

Linux 输入子系统

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

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

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

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

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

        Linux 输入子系统

   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&period;c事件处理过程

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

  2. Linux输入子系统&lpar;转&rpar;

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

  3. Linux输入子系统&lpar;Input Subsystem&rpar;

    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&period;Linux 输入子系统分析

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

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

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

随机推荐

  1. OA办公自动化系统源码

    最新extjs6富客户端,.net平台开发,sql server数据库,基础权限人员基础平台,可方便二次开发,使用EF为orm,autofac为ioc,Castle为基础的aop,实现常用OA系统功能 ...

  2. MP3播放器团队项目

    一.设计思路 程序要求能播放MP3文件,因此需调用库中的播放方法:右键工具箱选择项,添加com组件,选择window media player后工具箱就会多一个控件,然后拖到窗体中就OK了.另在窗体中 ...

  3. css背景图片拉伸 以及100&percnt; 满屏显示

    如何用css背景图片拉伸 以及100% 满屏显示呢?这个问题听起来似乎很简单.但是很遗憾的告诉大家.不是我们想的那么简单. 比如一个容器(body,div,span)中设定一个背景.这个背景的长宽值在 ...

  4. js 字符串日期 转成 Date

    只支持 2015/09/23 反斜杠这样类型 2015-09-23 单横的这种无法识别 var dateStr='${endDate}'; dateStr=dateStr.replace(/-/g,' ...

  5. HTB Linux queuing discipline manual - user guide笔记

    1. Introduction HTB is meant as a more understandable, intuitive and faster replacement for the CBQ ...

  6. IOException parsing XML document from class path resource &lbrack;WebRoot&sol;WEB-INF&sol;applicationContext&period;xml&rsqb;&semi;

    parsing XML document from class path resource [applicationContext.xml]; nested exception is java.io. ...

  7. 统计学习方法c&plus;&plus;实现之二 k近邻法

    统计学习方法c++实现之二 k近邻算法 前言 k近邻算法可以说概念上很简单,即:"给定一个训练数据集,对新的输入实例,在训练数据集中找到与这个实例最邻近的k个实例,这k个实例的多数属于某个类 ...

  8. 解决 Faster R-CNN 图片中框不在一张图片上显示的问题

    目录 解决 Faster R-CNN 图片中框不在一张图片上显示的问题 发现问题 如何解决这个问题? 参考issues 解决 Faster R-CNN 图片中框不在一张图片上显示的问题 发现问题 在使 ...

  9. C&num;的Winform中OpenFileDialog对话框Filter属性设置包含特定字符,使用正则表达式

    OpenFileDialog对话框的Filter属性说明: 首先观察Filter属性的组成部分:“Word文件|*.doc ”,前面的“Word文件”成为标签,是一个可读的字符串,可以自定定义,“|* ...

  10. &OpenCurlyDoubleQuote;子查询返回的值不止一个。当子查询跟随在 &equals;、&excl;&equals;、&lt&semi;、&lt&semi;&equals;、&gt&semi;、&gt&semi;&equals; 之后,或子查询用作表达式时,这种情况是不允许的。”SQL查询错误解析

    为了实现下述代码,首先得有数据库和相应的表格,本文用的是https://blog.csdn.net/qaz13177_58_/article/details/5575711/中的案例,即先用链接中那些 ...