在前面几章我们介绍了input子系统的实现,针对input子系统而言,主要就包括input handler、input handle、input device这三种逻辑抽象,而在input子系统中input handler与input device是多对多的联系(而在设备驱动模型中,一般设备与驱动时1对多的关联)。
本章我们将通过一个input handler实例,理解input handler驱动的实现流程。我们选用evdev进行
分析。
本章的主要内容如下:
一、input handler驱动实现流程说明
二、evdev驱动分析
一、input handler驱动实现流程说明
针对input handler驱动实现主要包括如下几个流程:
- 定义一个struct input_handler类型的全局变量,struct input_handler的定义如下;
- 需要实现event/events接口,这两个函数主要对input_device分发的event进行处理;
- connect、disconnect主要实现input device与input handler的关联,类似于设备驱动模型中的probe/remove。若该input_handler提供字符设备索引节点共应用程序访问,则可以在connect接口中进行字符设备的注册操作;而在disconnect接口中完成字符设备的注销操作(这和设备驱动模型中,我们在设备驱动的probe接口中实现字符设备是类似的,如我们可以在spi device driver的probe接口中实现字符设备的创建等)。
- filter则主要用于事件过滤(若input handler需要支持事件过滤,则可以实现该接口),该接口非必须;
- match则主要用于input子系统中更精细的匹配检测,在使用input_match_device完成input_device_id的匹配后,若input handler提供match接口,则调用match函数进行精细匹配检测,该接口非必须;
- 需要定义struct input_device_id用于说明该input handler可匹配哪些input device设备。
-
调用input_register_handler完成input handler的注册。
以上便是input handler的注册流程,其实input 子系统的这种操作,可以理解为简化版的设备驱动
模型,其input handler与input device的关联与解除关联,和设备驱动模型的操作类似。下面我们以一个实例说明input handler的实现流程。
evdev驱动分析
上面我们分析了input handler的注册流程,下面我们以evdev的驱动实现为例,介绍input handler
的实现流程。evdev是一个通用的input handler,其匹配所有的input device,并且向应用层提供对应的字符设备文件,其最多支持32个字符设备节点的注册。
下面我们按照上面的顺序进行分析说明
evdev的input handler定义
如下即为evdev对应的input handler的定义,其实现了如下内容:
- 实现event、events接口,即evdev_event、evdev_events;
- 实现connect、disconnect接口,即evdev_connect、evdev_disconnect接口,在evdev_connect接口中完成字符设备的注册,字符设备名称的前缀为event(/dev/input/eventX),同时调用input_register_handle完成input_handle的注册,从而完成input device与input handler的关联;
- evdev的id_tables中未设置,即会匹配任意input device,即任何注册到系统的input device都会和evdev handler进行关联。
Evdev 注册与注销
调用input_register_handler、input_unregister_handler完成evdev的注册与注销操作。
完成以上两步,即完成了input handler的实现。下面呢我们主要分析下evdev内部的实现以及对应字
符设备操作接口的实现。
evdev相关的数据结构
evdev提供了两个数据结构,即struct evdev、struct evdev_client ,其中evdev是对evdev的抽象,其包含ev input handler、input dev、cdev,完成了evdev对应字符设备、input handler、input device的关联(借助input_handle)。而struct evdev_client 则对应一个文件描述符,可以理解为一个文件描述符对应的私有变量,当应用程序执行一次打开操作时,则为对应的字符设备文件描述符创建一个对应的struct evdev_client变量,下面是这两个数据结构的定义。
针对每一个应用程序,若成功打开evdev对应的字符设备文件(即/dev/input/eventX),则会创建一个evdev_client类型的变量,该变量中包含存储事件的缓存(buffer),并作为文件描述符的私有变量存储。同时将该变量链接至evdev的client_list链表中,当input device有事件上报时,则将该事件广播到evdev->client_list链表上所有evdev_client的buffer中,这样每一个打开的文件描述符均可以读取到该事件(这种事件分发是广播的机制)。
如下是evdev、evdev_client数据结构间的关联图,此处包含了文件描述符、进程描述符、input device、input handle、input handler的关联图:
- 文件描述符通过私有变量指针private_data可完成与evedv_client的关联(前提是该文件描述符即为打开/dev/input/eventX文件的描述符,在其open接口中,即将file->private_data指向新创建的evdev_client,且会将evdev_client链接到evdev->client_list链表上);
- evdev通过input_handle,指向对应的input_handle,从而间接关联到input_handler、input_device;
- evedv包含cdev变量,而cdev变量注册到系统的cdev_map中,而cdev中包含文件操作的操作接口指针,针对evdev而言即是evdev_fops。
而在evdev handler的connect接口中则创建input_handle,并设置input_handle与
input_handler、input_dev的关联,同时完成input_handle的注册;然后创建cdev,并完成字符设备的注册。而在evdev_fops的open接口中,则创建evdev_client,并完成evdev_client与文件描述符、evdev的关联。
Evdev connect接口分析
Evdev connect 即为evdev handler的probe接口,当input device与input handler匹配成功后,即会调用connect接口进行input device、input handler的绑定操作。下面我们分析下evdev的connect接口的实现:
- 获取一个未被使用的evdev次设备号,主要是借助input_get_new_minor(获取失败则返回失败);
- 创建input_handle,并完成注册操作(完成与input_handler、input_dev的关联);
- 创建cdev类型的变量,并将该字符设备注册至系统中。
Evdev read分析
本章不再对evdev的字符设备文件操作接口evdev_fops以及evdev handler的events/event做详细的说明,此处主要说明下evdev_fops->read接口读取流程,从而说明input device接收到event事件后,如何分发给input handler,input handler又是如何返回给应用程序的。
如下图所示,当应用程序打开文件/dev/input/eventX后,当执行read操作后,则通过sys_read接口,
最终调用evdev_read接口(关于文件系统以及字符设备文件相关的内容,请参考我之前写的文档)。
- evdev_read首先从evdev_client的buffer中读取事件,若buffer中没有事件,则sleep该读进程;
- Input device的input事件处理函数(一般是中断处理函数),接收到input事件后,则调用input_event将事件分发给该input_device关联的所有input_handler(必须执行input_sync才会执行真正的分发操作);
- 调用具体的input_handler的event/events接口,针对evdev handler而言,即为evdev_event、evdev_events接口,在evdev_events接口中,则会将event事件分发到所有的evdev_client的buffer中去,然后wakeup evdev的等待队列,唤醒evdev_read中sleep的进程或者唤醒poll函数中sleep的队列,从而上报poll事件(可配合使用epoll/select机制,支持I/O多路复用机制)
Input device与input handler的事件分发说明
在前面的分析中,我们可以发现,input_device当接收到event后,会将该事件分发给所有关联的input handler;而在evdev的event/events接口中,又会将event分发到所有关联文件描述符对应evdev_client的buffer中,这都可以理解为一种事件的广播机制(即类似于tcp/ip中的广播)。
那如果我只需要input device将event只发送到一个input handler,即实现event的单播发送呢?
这个input子系统确实实现了,在input_dev中提供了grab成员变量,完成input_dev与指定
input_handle的绑定,而在evdev的ioctl接口中,提供了EVIOCGRAB命令,其实现input_dev与指定
input_handle的绑定,同时也完成了evdev与指定evdev_client的绑定。至此以后,从input_dev接收的event,则只会发送给对应的文件描述符对应的evdev_client的buffer中,即完成事件的单播发送。
本章主要接收input handler的实现流程,并以evdev为例进行说明。下一章我们接收input device的创建,并完成一个虚拟的input device驱动进行测试验证。