Linux 输入子系统
在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: }
首先,它判断缓存区大小是否足够.在读取数据的情况下,可能当前缓存区内没有数据可读.在这里先睡眠等待缓存
区中有数据.如果在睡眠的时候,.条件满足.是不会进行睡眠状态而直接返回的. 然后根据read()提够的缓存区大小.将
client中的数据写入到用户空间的缓存区中.
参考文献:
http://blog.csdn.net/lbmygf/article/details/7360084
http://blog.chinaunix.net/uid-27717694-id-3758334.html