1. 基本概念
Linux系统支持的输入设备繁多,例如键盘、鼠标、触摸屏、手柄或者是一些输入设备像体感输入等等,Linux系统是如何管理如此之多的不同类型、不同原理、不同的输入信息的输入设备的呢?其实就是通过input输入子系统这套软件体系来完成的。
1.1 系统整体框图
1.2 输入子系统框架
从整体上来说,输入子系统由驱动层(Drivers),输入子系统核心层(Input Core)和事件处理层(Handlers)三部分组成。,如下图所示:
图中Drivers对应的就是下层设备驱动层,对应各种各样不同的输入设备,Input Core对应的就是中层核心层,Handlers对应的就是上层输入事件驱动层,最右边的代表的是用户空间。一个输入事件,如鼠标移动,键盘按键按下,通过driver->inputcore->event handler->userspace的顺序到达用户空间的应用程序。
驱动层:将底层的硬件输入转化为统一事件形式,向输入核心回报。
输入核心层:为驱动层提供输入设备注册与操作接口,如:input_register_device;通知事件处理层对事件进行处理;在/proc下产生相应的设备信息。
事件处理层:和用户空间交互,linux在用户空间将所有设备当成文件来处理,在一般的驱动程序中都有提供fops接口,以及在/dev下生成相应的设备文件nod,而在输入子系统中,这些工作都是由事件处理层完成的。
input子系统解决了不同的输入类设备的输入事件与应用层之间的数据传输,使得应用层能够获取到各种不同的输入设备的输入事件,input输入子系统能够囊括所有的不同种类的输入设备,在应用层都能够感知到所有发生的输入事件。以一次鼠标按下事件为例子来说明我们的input输入子系统的工作过程:
当我们按下鼠标左键的时候就会触发中断(中断是早就注册好的),就会去执行中断所绑定的处理函数,在函数中就会去读取硬件寄存器来判断按下的是哪个按键和状态 ---->将按键信息上报给input core层 ---> input core层处理好了之后就会上报给input event层,在这里会将我们的输入事件封装成一个input_event结构体放入一个缓冲区中 --->应用层read就会将缓冲区中的数据读取出去。
输入子系统的实现需要满足以下需求:
(1) 输入子系统要为每个输入设备在/dev/目录下生成一个设备文件,以方便应用程序读取指定输入设备产生的事件。
(2) 对于每一个输入设备,在输入子系统只需要实现其事件获取即可,至于事件如何处理、如何到达设备文件则不需要考虑。
(3) Linux输入设备可以分为事件类(如USB鼠标、USB键盘、触摸屏等)、MOUSE类(特指PS/2接口的输入设备)、游戏杆类3种,为这些输入设备实现的设备文件的接口必须有所差别。因此,输入子系统需要为不同类型的输入设备实现正确的设备文件接口。
2. 深入分析input输入子系统
在linux内核中,input设备用input_dev结构体描述,使用input子系统实现输入设备驱动的时候,驱动的核心工作是向系统报告按键,触摸屏,键盘,鼠标,等输入事件(event,通过input_event结构体描述),不再需要关心文件操作接口,因为input子系统已经完成了文件操作接口。驱动报告的事件经过inpoutcore和eventhandler最终到达用户空间。
下面我们以linux-2.6.22.6的内核来分析input子系统是如何为我们完成文件操作接口的。
2.1 分析input.c(driver/input)
input子系统是以驱动模块形式存在内核中,所以我们先来分析驱动模块(文件路径在driver/input/input.c)的入口input_init()函数。
2.1.1 入口函数input_init()
static int __init input_init(void)
{
int err;
err = class_register(&input_class); //注册类,在/sys/class目录下生成相关文件
if (err) {
printk(KERN_ERR "input: unable to register input_dev class\n");
return err;
}
err = input_proc_init(); //在/proc目录下生成相关文件
if (err)
goto fail1;
err = register_chrdev(INPUT_MAJOR, "input", &input_fops); //注册驱动设备
if (err) {
printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR);
goto fail2;
}
return 0;
fail2: input_proc_exit();
fail1: class_unregister(&input_class);
return err;
}
其中INPUT_MAJOR被宏定义为13,所以这里创建了一个主设备为13的"input"设备。接着来看该设备的操作函数结构体input_fops,代码如下:
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
};
可以看到该驱动里的file_operations只有一个操作函数.open=input_open_file,当我们加载一个新的input驱动时,内核会调用到input_open_file函数(文件路径在driver/input/input.c)。
2.1.2 input_open_file函数
static int input_open_file(struct inode *inode, struct file *file)
{
struct input_handler *handler = input_table[iminor(inode) >> 5];
const struct file_operations *old_fops, *new_fops = NULL;
int err;
if (!handler || !(new_fops = fops_get(handler->fops)))
return -ENODEV;
if (!new_fops->open) {
fops_put(new_fops);
return -ENODEV;
}
old_fops = file->f_op;
file->f_op = new_fops;
err = new_fops->open(inode, file);
if (err) {
fops_put(file->f_op);
file->f_op = fops_get(old_fops);
}
fops_put(old_fops);
return err;
}
该函数作用:
(1) 其中iminor(inode)函数调用了MINOR(inode->i_rdev);读取子设备号,然后将子设备除以32,找到新挂载的input驱动的数组号,然后根据该数组号从input_table中找到handler,input_table是input子系统中用来管理handler的一个数组,里面存放的是handler的指针。也就是通过次设备号找到本次应用层打开的输入设备对应的handler结构体。
(2) 然后执行file->f_op = new_fops,将input子系统的file_operations替换为新挂载的驱动的file_operations结构体。然后打开该新结构体里的.open函数。
2.1.3 input_register_handler函数
跟踪input_table可知该数组是在input_register_handler函数中被赋值的。input_register_handler函数如下:
int input_register_handler(struct input_handler *handler)
{
struct input_dev *dev;
INIT_LIST_HEAD(&handler->h_list);
if (handler->fops != NULL) {
if (input_table[handler->minor >> 5])
return -EBUSY;
input_table[handler->minor >> 5] = handler;
}
list_add_tail(&handler->node, &input_handler_list);
list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(dev, handler);
input_wakeup_procfs_readers();
return 0;
}
继续在内核源码中搜索input_register_handler可以看到该函数被各类输入设备所调用(input_register_handler部分内核帮我们做了,可以通过配置内核选择使用哪些input_handler)。
内核会将键盘、鼠标、ts等各种类型的输入设备封装成input_handler结构体传入input_register_handler函数的方式向内核注册这些设备驱动类型结构体,每一种类型结构体会存放到input_table里。接下来以evdev.c为例继续分析input_register_handler函数。进入evdev.c入口函数:
static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler);
}
evdev_init函数中直接调用核心层提供的handler注册函数注册event这个handler,evdev_handler这个struct input_handler类型的变量就是对这个handler的一个描述。
2.1.4 以evdev.c为例分析struct input_handler
static struct input_handler evdev_handler = {
.event = evdev_event, /* 这个函数的作用就是实现将下层的事件包进行封装,然后存放在缓冲区中待read函数读取,并唤醒阻塞等待读取数据的进程 */
.connect = evdev_connect, /* 匹配成功之后,就会调用这个函数进行连接 */
.disconnect = evdev_disconnect, /* 断开连接 */
.fops = &evdev_fops, /* 这个就是应用open/read/write...时对应的接口函数封装 file_operations */
.minor = EVDEV_MINOR_BASE, /* 这个就是本handler下的设备的次设备号的起始设备号(基设备号) */
.name = "evdev", /* handler的名字 */
.id_table = evdev_ids, /* 一个handler描述自己支持的输入设备的特征的一个结构体,一个变量就描述了该halder支持的一类设备 */
};
成员如下说明:
.event:这个函数的作用就是实现将下层的事件包进行封装,然后存放在缓冲区中待read函数读取,并唤醒阻塞等待读取数据的进程。
.connect:连接函数,将设备input_dev和某个input_handler建立连接,如下图:
.disconnect:断开连接。
.fops:这个就是应用open/read/write...时对应的接口函数封装 file_operations。
.minor:用来存放次设备号。其中EVDEV_MINOR_BASE=64, 然后调用input_register_handler(&evdev_handler)后,由于EVDEV_MINOR_BASE/32=2,所以存到input_table[2]中。
.name:handler的名字。
.id_table:表示能支持哪些输入设备,比如某个驱动设备的input_dev->id和某个input_handler的id_table相匹配,就会调用上面的.connect连接函数。
上图还提到了input_register_device这个函数,接下来就分析这个函数的作用。
2.1.5 input_register_device函数
在内核中搜索input_register_device同样可以找到有很多鼠标键盘等设备都会调用这个函数进行一一注册。input_register_device部分代码如下:
int input_register_device(struct input_dev *dev) //*dev:要注册的驱动设备
{
...
list_add_tail(&dev->node, &input_dev_list); //将input_dev->node作为节点挂接到input_dev_list链表上
...
list_for_each_entry(handler, &input_handler_list, node) //遍历input_handler_list链表上的所有handler
input_attach_handler(dev, handler); //将handler与input设备进行匹配
...
}
input_attach_handler函数做的事情有两件:调用input_match_device函数进行input_device->id与input_handler->id_table的匹配,匹配成功调用input_handler的connect连接函数进行连接。
同样我们在之前的input_register_handler函数中也能看到这个函数,所以不管新添加input_dev还是input_handler,都会进入input_attach_handler()判断两者id是否有支持, 同样匹配成功调用input_handler的connect连接函数进行连接。
PS:如果我们的上报的事件与多个handler都能够匹配成功,那么绑定之后核心层会向这多个handler都上报事件,再由handler上报给应用层。同一个input设备可以对应多个次设备号,因为对于一个输入设备来说,他在进行匹配的时候可能会与多个handler匹配成功,匹配成功就会在连接过程中创建设备文件,而不同的handler创建的设备文件的次设备号是不一样的,所以就是导致一个设备对应多个次设备号。
2.1.6 input_handler的connect连接函数
这个input_handler的connect连接函数是如何进行连接的呢?同样分析evdev.c为例:
static int evdev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id)
{
...
for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++); //查找驱动设备的子设备号
if (minor == EVDEV_MINORS) { // EVDEV_MINORS=32,所以该事件下的驱动设备最多存32个,
printk(KERN_ERR "evdev: no more free evdev devices\n");
return -ENFILE; //没找到驱动设备
}
...
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); //分配一个input_handle全局结构体(没有r)
...
evdev->handle.dev = dev; //指向参数input_dev驱动设备
evdev->handle.name = evdev->name;
evdev->handle.handler = handler; //指向参数 input_handler驱动处理结构体
evdev->handle.private = evdev;
sprintf(evdev->name, "event%d", minor); //保存驱动设备名字, event%d
...
devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor), //将主设备号和次设备号转换成dev_t类型
cdev = class_device_create(&input_class, &dev->cdev, devt,dev->cdev.dev, evdev->name); //在input类下创建驱动设备
...
error = input_register_handle(&evdev->handle); //注册这个input_handle结构体
...
}
可以看出,该连接函数主要进行了以下工作:
(1) 分配一个input_handle结构体
struct evdev *evdev;
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL)
(2) 设置各项成员,如:
input_handle.dev = input_dev; // 指向左边的输入设备input_dev
input_handle.handler = input_handler; // 指向右边的input_handler
class_device_create(&input_class, &dev->cdev, devt,dev->cdev.dev, evdev->name); //在input类下创建驱动设备
(在入口函数input_init()里没有做完的事情在这里做了)
(3) 注册这个handle
error = input_register_handle(&evdev->handle);
input_register_handle()代码如下:
int input_register_handle(struct input_handle *handle)
{
struct input_handler *handler = handle->handler; //handler= input_handler驱动处理结构体
list_add_tail(&handle->d_node, &handle->dev->h_list); //input_dev驱动设备的h_list链表就指向handle->d_node
list_add_tail(&handle->h_node, &handler->h_list); //input_handler驱动处理结构体的h_list也指向了handle->h_node
if (handler->start)
handler->start(handle);
return 0;
}
最终结果如下:
input_dev与handler是多对多的关系,从上图可以看出来,一个input_dev可以对应多个handler,一个handler也可以对应多个input_dev。因为在匹配的时候,一个input_dev会与所有的handler都进行匹配的,并不是匹配成功一次就退出。从图中可以看出来,一个handle就是用来记录系统中一对匹配成功的handler和device,我们可以从这个handle出发得到handler的信息,还可以得到device的信息。
这样,input_dev和input_handler双方的.h_list都指向了同一个handle结构体,然后通过.h_list 来找到handle的成员.dev和.handler,便能互相找到对方来建立了连接。
2.2 分析input子系统使用过程
从字符设备驱动我们知道,input子系统作为字符设备驱动,使用时应用程序应该会调用input驱动的.read函数获取输入信息,同样以evdev.c为例进行分析,evdev驱动的.read函数是evdev_read,部分代码如下:
static ssize_t evdev_read(struct file *file, char __user * buffer, size_t count, loff_t *ppos)
{
...
/*判断应用层要读取的数据是否正确*/
if (count < evdev_event_size())
return -EINVAL;
/*在非阻塞操作情况下,若client->head == client->tail|| evdev->exist时(没有数据),则return返回*/
if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))
return -EAGAIN;
/*若client->head == client->tail|| evdev->exist时(没有数据),等待中断进入睡眠状态 */
retval = wait_event_interruptible(evdev->wait,client->head != client->tail || !evdev->exist);
... //上传数据
}
可以看到,当应用程序调用read时没有输入数据时,会调用wait_event_interruptible函数进入休眠,等待数据的到来。可想而知,当数据到来时将会唤醒,然后read函数将数据传给应用程序,完成一次读取。那么驱动中是如何进行唤醒的呢?
分析evdev.c,找到是在evdev_handler里的.event成员,也就是函数evdev_event里唤醒的,当有事件发生时,就会调用到evdev_event函数,唤醒read函数进行上传数据。evdev_event函数部分代码如下:
static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
{
struct evdev *evdev = handle->private;
...
...
wake_up_interruptible(&evdev->wait); //唤醒休眠
}
继续查找发现input_handler里的.event成员又是被input_event()所调用的,input_event()部分代码如下:
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
struct input_handle *handle;
...
/* 通过input_dev ->h_list链表找到input_handle驱动处理结构体*/
list_for_each_entry(handle, &dev->h_list, d_node)
if (handle->open) //如果input_handle之前open过,那么这个就是我们的驱动处理结构体
handle->handler->event(handle, type, code, value); //调用evdev_event()的.event事件函数
}
3. 总结
3.1 input_dev注册流程(需要我们自己编写)
input_register_device //注册input设备
device_add: /sys/devices/virtual/input/input0
input_dev->node -------> input_dev_list //链表挂接
input_attach_handler //进行input_dev和input_handler之间的匹配
handler->connect //匹配成功建立连接
struct input_handle //填充handle
class_device_create //在input类下创建设备节点
input_register_handle
3.2 input_handler注册流程(在内核的evdev.c、keyboard.c、tsdev.c等模块里帮我们做了)
input_register_handler //注册input_handler
input_table[handler->minor >> 5] = handler //填充input_table数组
handler->node-------> input_handler_list //链表挂接
input_attach_handler //进行input_dev和input_handler之间的匹配
handler->connect //匹配成功后建立连接
struct input_handle //填充handle
class_device_create //在input类下创建设备节点
input_register_handle
3.3 事件如何传递到应用层
应用程序:
open
read
wait_event_interruptible //系统调用到驱动程序的.read里,没有数据则陷入休眠
驱动程序:
input_event //得到数据后调用
handle->handler->event //调用handler的事件处理函数.event
//封装数据
wake_up_interruptible //唤醒.read里陷入的休眠
.read //从休眠中唤醒并返回给应用程序事件(数据)
输入子系统介绍完毕,下一节开始编写代码使用输入子系统。