上一篇博文 「ATMEL maXTouch IC驱动代码分析」 我们讲到了 Touch 驱动代码如何读取IC内部获取到的触摸事件信息并通过input_report_abs()
和input_sync()
函数上报给 Linux 的 Input 子系统的过程。今天这篇文章我们就走进 Input 子系统内部来看一下事件是如何被传递到 Android 的用户空间的。
内容如下。
目录
- Input 子系统框架
- 注册 Input 设备
- 数据上报过程
- 总结
首先我们从 Input 子系统介绍开始。Input 子系统由驱动层、输入子系统核心层(Input Core)和事件处理层(Event Handler)3部分组成。一个输入事件,如鼠标移动,触摸事件等通过驱动层->系统核心层->事件处理层->用户空间的顺序到达用户空间并传给应用程序使用。其中Input Core即输入子系统核心层由 driver/input/input.c
及相关头文件实现。其对下提供了设备驱动的接口,对上提供了事件处理层的编程接口。输入子系统主要设计input_dev
、input_handler
、input_handle
等数据结构,它们的用途和功能如下图所示。
我们在之前介绍驱动代码的时候讲到过,输入设备在初始化的时候都需要调用input_allocate_device()
和input_register_device()
进行注册。其中input_allocate_device()
函数在内存中为输入设备结构体分配一个空间,并对其主要的成员进行了初始化。它的代码如下。
该函数返回一个指向input_dev
类型的指针,该结构体是一个输入设备结构体,包含了输入设备的一些相关信息,如设备支持的按键码、设备的名称、设备支持的事件等。
接下来调用的input_register_device()
函数很重要,我们看一下它的具体实现。
input_register_device()
函数是输入子系统核心(input core)提供的函数。该函数将input_dev
结构体注册到输入子系统核心中,input_dev
结构体必须由前面讲的 input_allocate_device()
函数来分配。input_register_device()
函数如果注册失败,必须调用 input_free_device()
函数释放分配的空间。如果该函数注册成功,在卸载函数中应该调用 input_unregister_device()
函数来注销输入设备结构体。
input_register_device()
函数主要完成了如下的工作:
- 函数中调用
__set_bit()
函数设置input_dev
所支持的事件类型。事件类型由input_dev
的evbit
成员来表示,在这里将其EV_SYN
置位,表示设备支持所有的事件。注意,一个设备可以支持一种或者多种事件类型。常用的事件类型如下:
- 调用
dev_set_name()
设置input_dev
中的device的名字,名字以input0
、input1
、input2
、input3
、input4
等的形式出现在sysfs文件系统中。
- 使用
device_add()
函数将input_dev
包含的device结构注册到Linux设备模型中,并可以在sysfs文件系统中表现出来。
- 调用
list_add_tail()
函数将input_dev
加入input_dev_list
链表中,input_dev_list
链表中包含了系统中所有的input_dev
设备。
- 调用了
input_attach_handler()
函数,
input_attach_handler()
函数用来匹配input_dev
和input_handler
,只有匹配成功,才能进行下一步的关联操作。
input_attach_handler()
函数的代码如下:
input_attach_handler()
主要完成的工作有:
- 首先判断
handle
的blacklist
是否被赋值,如果被赋值,则匹配blacklist
中的数据跟dev->id
的数据是否匹配。blacklist
是一个input_device_id*
的类型,其指向input_device_ids
的一个表,这个表中存放了驱动程序应该忽略的设备。即使在id_table
中找到支持的项,也应该忽略这种设备。
- 调用
input_match_device()
函数匹配handle->id_table
和dev->id
中的数据。如果不成功则返回。handle->id_table
也是一个input_device_id
类型的指针,其表示驱动支持的设备列表。
- 如果匹配成功,则调用
handler->connect()
函数将handler
与input_dev
连接起来。
input_match_device()
函数
input_match_device()
函数用来与input_dev
和handler
进行匹配。handler
的id_table
表中定义了其支持的input_dev
设备。
该函数的代码如下:
input_match_device()
主要完成的工作有:
- 匹配设备的产品总线类型/vendor/版本信息。
- 如果
id->flags
定义的类型匹配成功,或者id->flags
没有定义,才会进入到MATCH_BIT
的匹配项。
MATCH_BIT
宏的定义如下:
从MATCH_BIT
宏的定义可以看出。只有当iput device
和input handler
的ID成员在evbit、keybit、… swbit项相同才会匹配成功。
3. 数据上报过程
Input 子系统各层之间通信的基本单位就是事件,任何一个输入设备的动作都可以抽像成一种事件,如键盘的按下,触摸屏的按下,鼠标的移动等。事件有三种属性:类型(type),编码(code),值(value),Input子系统支持的所有事件都定义在input.h
中,包括所有支持的类型,所属类型支持的编码等。事件传送的方向是硬件驱动层–>子系统核心–>事件处理层–>用户空间。
在驱动代码的介绍中,我们讲到驱动最终调用到input_report_abs()
将touchevent打包发送给Input子系统。
input_report_abs()
函数代码如下:
可以看到其为内联函数, 为input_event(,EV_ABS, ...)
的二次封装。
input_event()
的代码如下,略过无关的部分:
input_event()
调用了input_handle_event()
函数。
所以我们可以简单看下input_handle_event()
–> input_get_disposition()
EV_SYN事件和EV_ABS的返回值。
让我们回到input_handle_event()
–> input_pass_values()
其重点函数为input_to_handler()
这里面input_handle
结构体代表一个成功配对的input_dev
和input_handler
。
关于input_handle
,input_dev
和input_handler
结构体的含义如下:
struct input_dev
: 物理输入设备的基本数据结构,包含设备相关的一些信息。
struct input_handler
: 事件处理结构体,定义怎么处理事件的逻辑。
struct input_handle
: 用来创建input_dev和input_handler之间关系的结构体。
input_handler
结构体的定义如下:
该结构体主要是
- 定义了一个
event()
处理函数,这个函数将被输入子系统调用去处理发送给设备的事件。例如将发送一个事件命令LED灯点亮,实际控制硬件的点亮操作就可以放在event()
函数中实现。
- 定义了一个
connect()
函数,该函数用来连接handler
和input_dev
。
- 定义了一个
disconnect()
函数,这个函数用来断开handler
和input_dev
之间的联系。
- 定义了一个name,表示handler的名字,显示在
/proc/bus/input/handlers
目录中。
- 定义了一个
id_table
表,表示驱动能够处理的表。
- 指向一个
input_device_id
表,这个表包含handler应该忽略的设备。
- 定义了一个链表h_list,表示与这个
input_handler
相联系的下一个handler。
- 定义了一个链表node,将其连接到全局的
input_handler_list
链表中,所有的input_handler
都连接在其上。
input_register_handler()
函数注册一个新的input handler
处理器。这个handler将为输入设备使用,一个handler可以添加到多个支持它的设备中,也就是一个handler可以处理多个输入设备的事件。函数的参数传入简要注册的input_handler
指针,该函数的代码如下:
完成的主要工作:
- 调用
list_add_tail()
函数,将handler加入全局的input_handler_list
链表中,该链表包含了系统中所有的input_handler
。
- 调用了
input_attach_handler()
函数。input_attach_handler()
函数的作用是匹配 input_dev_list
链表中的input_dev
与handler。如果成功会将input_dev
与handler联系起来。
Input_Handle
结构体
input_register_handle()
函数用来注册一个新的handle到输入子系统中。input_handle
的主要功能是用来连接input_dev
和input_handler
。
input_handle
是用来连接input_dev
和input_handler
的一个中间结构体。事件通过input_handle
从 input_dev
发送到input_handler
,或者从input_handler
发送到input_dev
进行处理。在使用input_handle
之前,需要对其进行注册,注册函数是input_register_handle()
。
input_register_handle()
函数用来注册一个新的handle到输入子系统中。该函数接收一个input_handle
类型的指针,该变量要在注册前对其成员初始化。
input_register_handle()
函数的代码如下:
- 调用
list_add_tail_rcu()
函数将handle加入输入设备的dev->h_list
链表中。
- 调用
list_add_tail()
函数将handle加入input_handler的handler->h_list
链表中。
input_dev、input_handler和handle三者之间的关系如下:
- input_dev 是硬件驱动层,代表一个input设备;
- input_handler 是事件处理层,代表一个事件处理器;
- input_handle 个人认为属于核心层,代表一个配对的input设备与input事件处理器;
- 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。
我们看到上面的代码调用到
这里handler->events则是Evdev.c(drivers\input)
里定义的。
events 函数是当事件处理器接收到了来自input设备传来的事件时调用的处理函数,负责处理事件。
我们看一下函数原型。
事件处理层(eventhandler)负责将事件上报,将键值、坐标等数据上报的对应的设备节点.
3.6 由事件处理层 (eventhandler) 到用户空间(user space)
__pass_event()
将event放到client->buffer[]里,由buffer 传入用户空间。
__pass_event()
函数最终将事件传递给了用户端的client 结构中的input_event
数组中,只需将这个input_event
数组复制给用户空间,进程就能收到触摸屏按下的信息了。
input_event
结构体:
3.7 用户空间读取事件
我们从上面分析,看到数据已经放到了client->buffer[], 那读取也肯定也是从这里读。实际上,在文件evdev.c 中Evdev_read()
函数将这个input_event
数组复制给用户空间。
调用了input_event_to_user()
函数
事件读取函数调用流程
4. 总结
最后总结一下整个数据的走向和传送的流程。
- 按照linux设备架构,驱动模型实现touchscreen driver。
- 模块初始化函数中将触摸屏注册到了输入子系统中,于此同时,注册函数在事件处理层链表中寻找事件处理器,这里找到的是evdev,并且将驱动与事件处理器挂载。并且在
/dev/input
中生成设备文件event0,以后我们访问这个文件就会访问到设备数据。
- 当点击触屏后, 进到中断处理,然后读取数据,再report,并存到client的buffer[]里。
- 上层用户空调read时, 只要有数据,不断从client->buffer[]读取并通过
copy_to_user()
拷到用户空间, 所以上层就拿到数据了。