input子系统整体框架

来源:互联网 发布:solr6 java实战 编辑:程序博客网 时间:2024/05/04 21:36

2.模块结构

下图是input输入子系统框架,输入子系统linux由输入子系统核心层( Core层 ),驱动层和事件处理层(Event Handler)三部份组成。Android层操作input子系统由Native层、Java框架层、应用程序三部分组成。

1: input输入子系统层次图

    一个输入事件,如手指触摸,键盘按键按下,横竖屏转动等等通过 input driver -> Input core -> Event handler -> userspace 到达用户空间传给应用程序

    驱动层负责读取原始数据,如触摸屏的驱动,g_sensor的驱动等。

    输入子系统核心层(input core),其对下提供了设备驱动的接口,对上提供了事件处理层的编程接口。

    事件处理层(event handler)负责将事件上报,将键值、坐标等数据上报的对应的设备节点。

3.关键的结构体和函数接口介绍

3.1.关键结构体(共三个)

struct input_dev {

const char *name;

const char *phys;

const char *uniq;

struct input_id id;

unsigned long evbit[BITS_TO_LONGS(EV_CNT)];

unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];

unsigned long relbit[BITS_TO_LONGS(REL_CNT)];

unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];

unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];

unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];

unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];

unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];

unsigned long swbit[BITS_TO_LONGS(SW_CNT)];

struct list_head h_list;

struct list_head node;

};

关于input_dev结构体,一个input_dev结构体对象代表着一个输入设备,以上只列出部分的成员,其中的name表示设备的名字,evbit表示设备支持的事件类型,设备驱动程序需要设置这些值。

struct input_handler {

void *private;

void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);

bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);

bool (*match)(struct input_handler *handler, struct input_dev *dev);

int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);

void (*disconnect)(struct input_handle *handle);

void (*start)(struct input_handle *handle);

const struct file_operations *fops;

int minor;

const char *name;

const struct input_device_id *id_table;

struct list_head h_list;

struct list_head node;

};

input_handler为子系统的处理层。一旦devhandler匹配上了就会调用connect函数。其中这里有一个很关键的地方,就是struct input_device_id *id_table;这里是匹配规则,只要dev注册时满足id_table的条件就会匹配上

struct input_handle {

void *private;

int open;

const char *name;

struct input_dev *dev;

struct input_handler *handler;

struct list_head d_node;

struct list_head h_node;

};

input_handle可以说就是中间层,专门负责联系devhandler

3.2.分析三个结构体之间的关系

1)、在内核中,input_dev 表示一个 input设备;input_handler 表示input设备的 interface。所有的input_dev用双向链表input_dev_list 连起来。在调用 input_register_device(struct input_dev *dev) 的时候,会将新的 input_dev 加入到这个链表中。

2)、所有的input_handler用双向链表 input_handler_list 连起来。在调用 input_register_handler(struct input_handler *handler) 的时候,会将新的 input_handler 加入到这个链表中。

3)、每 个input_dev 和 input_handler 是要关联上才能工作的,在注册 input_dev 或者 input_handler的时候,就遍历上面的列表,找到相匹配的,然后调用 input_handler 的 connect函数来将它们联系到一起。

4)、通常在input_handler 的 connect函数中,就会创建 input_handle, input_handle就是负责将 input_dev input_handler 联系在一起的.

3.3.函数接口

1input_register_handler(struct input_handler *handler)注册一个新的handler处理层。例如linux里面自带的evdev.c就是一个handler处理层代码。

2input_register_handle(struct input_handle *handle)注册一个中间层handle,用于关联devhandler。一般在devhandler匹配后connection函数里面注册。

3input_register_device(struct input_dev *dev)注册一个设备,这个就是设备驱动程序需要调用的接口。用于向input子系统注册一个input设备。

4input_match_device(struct input_handler *handler,struct input_dev *dev)匹配函数,每当注册一个设备或者一个handler处理层,都会调用该接口进行devhandler的匹配。

5input_open_device(struct input_handle *handle)打开设备。

6input_event(struct input_dev *dev,unsigned int type, unsigned int code, int value)事件上报接口,主要是调用handler层的event函数进行上报,将上报的工作交给handler处理层。

4.注册驱动到Input子系统和数据的传递过程

4.1. 驱动层部分

调用Input子系统的注册函数input_register_device(input_dev),从函数名字可以看出,这个函数是将设备注册到子系统中来,input_devinput_dev结构体,代表着输入设备的全部信息,比如说:一个CTP设备就用input_dev结构体表示。在调用这个注册函数之前,先要对input_dev进行申请和初始化,如下:

struct input_dev *input_dev;

input_dev = input_allocate_device();

完整的流程是:

struct input_dev *input_dev;

input_dev = input_allocate_device();

input_register_device(input_dev);

这样,我们就已经注册好设备了。

注册好设备,接下来就要匹配了,到底哪一个input_handler要处理这个设备,要匹配过后才能关联上。现在,基本有个概念就是:input_dev是输入设备,必须向子系统注册,input_handler是事件处理模块,要与input_dev匹配。

Input驱动编写步骤:

1)分配一个输入设备;

2)注册一个输入设备;

3)驱动支持什么事件;

Set_bit告诉input子系统它支持哪些事件

Set_bit(EV_KEY,button_dev.evbit)

Struct input_dev中有两个成员,一个是evbit;一个是keybit.分别用来表示设备所支持的事件类型和按键类型。

4)驱动事件报告;

5)释放和注销设备;

4.2. 处理层部分

这部分linux已经写好了,开发人员只需要了解和理解这个过程,以edev.c为例。

调用int input_register_handler(struct input_handler *handler)函数向子系统注册一个input_hander对象。接下来,进入函数input_register_handler里面,看看做了哪些的处理。

1)、input_table[handler->minor >> 5] = handler;handler对象保存到input_table全局数组里面,这样做的作用是以后根据次设备号可以找到对应的handler对象,只有找到对象handler才能使用它的回调函数处理上报的数据。

2)、list_add_tail(&handler->node, &input_handler_list);handler对象添加到队列中去;

3)、input_attach_handler(dev, handler);匹配函数,将devhandler匹配起来。

4)、深入匹配函数,了解devhandler匹配过程:

input_attach_handler():

const struct input_device_id *id=input_match_device(handler, dev);

调用input_match_device(handler, dev)函数进行匹配,成功返回id号,匹配失败返回NULL。后面会结合例子详细说明匹配规则。

error = handler->connect(handler, dev, id);调用handler的回调函数connect函数,连接handlerdev

5)、进入函数handler->connect(handler, dev, id)

这里主要做了两件事情:第一件就是声明struct evdev *evdev变量,并且填充好他的对象值,例如:

 evdev->handle.dev = input_get_device(dev);

 evdev->handle.name = dev_name(&evdev->dev);

 evdev->handle.handler = handler;

 evdev->handle.private = evdev;

第二件事就是:申请注册handle。终于到了子系统桥梁handle结构体了。之前讨论的主要是两个对象devhandler,他们代表着什么之前也讨论了,那现在这个handle有什么作用呢?他的作用就是连接devhandler这两个对象的。那下面来分析一下handle的注册过程。

int input_register_handle(struct input_handle *handle)

handle结构体里面有两个变量,

struct list_head d_node;

struct list_head h_node;

注册函数里面就是填充这两个变量,

list_add_rcu(&handle->d_node, &dev->h_list);

list_add_tail_rcu(&handle->h_node, &handler->h_list);

这样,将handle对象关联到devhandler对象。关联的细节如下:

在驱动端利用dev对象采集数据,通过devh_list列表找到对应的handle对象,再通过handle对象找到handler对象,这个过程就是从驱动端到处理端的全过程。

4.3. 数据传输过程

注册部分已经分析结束了,接下来就是要分析数据流向,驱动程序得到的数据如何上报到上层应用,或者简单来说,上层应用如何得到数据。

2:内核层Event流向

input子系统是驱动上报数据,暂存在内存中,应用程序需要时去读这片内存就可以取到数据,至于为什么采用这个模式,因为知道应用程序需要数据的时机和硬件采集到的数据是很难同步的,采用异步的方式更为合适。

第一步:驱动程序调用input_event()函数上报数据

input.h头文件中定义了:

static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)

{

input_event(dev, EV_KEY, code, !!value);

}

static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value)

{

input_event(dev, EV_REL, code, value);

}

static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)

{

input_event(dev, EV_ABS, code, value);

}

所以在驱动程序里面一般是调用input_report_abs()来上报的(以触摸屏为例)。

进入input_event()函数:

主要是调用了input_handle_event(dev, type, code, value);

input_handle_event里面主要调用了input_pass_event(dev, type, code, value);

最后input_pass_event里面主要做了两个事情:第一就是拿到handle对象,第二就是调用handle->handler->event(handle, type, code, value);现在又要回到handler里面去,前面说过,处理数据的对象是handler,进入handler->event里面看看。

1)、声明变量struct input_event eventevent代表数据包数据;

event.type = type;

event.code = code;

event.value = value;

2)、遍历evdev对象中的client_list列表,得到client对象;

3)、调用evdev_pass_event(client, &event, time_mono, time_real);

4)、关键的一步来了,在evdev_pass_event函数里面:

client->buffer[client->head++] = *event;这一步就是将event,即数据包赋值到client->buffer里面保存起来。client->buffer是一个数组,client->headclient->tail指示着操作数据的范围,从而形成一个环形数组。上报的数据一个一个的保存到client->buffer数组里面,当接收到上报数据的结束符(EV_SYN)时,子系统会调用函数wake_up_interruptible(&evdev->wait)来唤醒等待队列,因为读函数里面会阻塞等待唤醒。

以上,数据流向的第一部分已经结束了,现在client->buffer里面已经保存着驱动上报的数据了。

数据流向的第二部分就是上层应用如何获得数据,数据保存在client->buffer内存里,通过handler处理层的节点操作来将内核数据传输到应用空间。节点操作:openread函数等等。节点是:/dev/input/eventX。后面结合Android框架层深入研究数据流向的第二部分。

5.自定义handler处理层(系统优化)

在linux内核源码中已经有了一个标准的handler处理层evdev.c代码。它匹配所有的输入设备,并进行相应的处理,但是,它只负责处理上报数据工作,如果需要进行另外的处理,只能自定义handler进行特定处理。例如,休眠唤醒后调高CPU的频率,从而更快地做出响应。

handler层可以对应多个dev,同样的dev可以对应多个handler,所以,在处理自定义handler时不会影响原本evdev.c的上报数据的操作。

5.1. handler层与dev层的匹配规则

前面说到,驱动端向子系统加入设备时会进行一些初始化操作,例如:设置devnameid信息(支持的总线,协议等等),支持的事件类型等等。可以把这些信息作为设备的特性,标识不同的设备。而handler层也有类似的设置,只要设备dev满足这些规则就可以进行匹配。

下面分析匹配函数,从而了解匹配规则的细节:

static const struct input_device_id *input_match_device(struct input_handler *handler,

struct input_dev *dev)

{

const struct input_device_id *id;

int i;

for (id = handler->id_table; id->flags || id->driver_info; id++) {

if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)

if (id->bustype != dev->id.bustype)

continue;

if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)

if (id->vendor != dev->id.vendor)

continue;

if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)

if (id->product != dev->id.product)

continue;

if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)

if (id->version != dev->id.version)

continue;

MATCH_BIT(evbit,  EV_MAX);

MATCH_BIT(keybit, KEY_MAX);

MATCH_BIT(relbit, REL_MAX);

MATCH_BIT(absbit, ABS_MAX);

MATCH_BIT(mscbit, MSC_MAX);

MATCH_BIT(ledbit, LED_MAX);

MATCH_BIT(sndbit, SND_MAX);

MATCH_BIT(ffbit,  FF_MAX);

MATCH_BIT(swbit,  SW_MAX);

if (!handler->match || handler->match(handler, dev))

return id;

}

return NULL;

}

从源码中可以看出,一个for循环遍历了传递过来的handler对象的id_table,判断每一个id设备是否与dev相应的配置匹配。

handler类型里面有input_device_id结构体,input_device_id结构体如下:

Data Fields

kernel_ulong_t 

flags

__u16 

bustype

__u16 

vendor

__u16 

product

__u16 

version

kernel_ulong_t 

evbit[INPUT_DEVICE_ID_EV_MAX/BITS_PER_LONG+1]

kernel_ulong_t 

keybit[INPUT_DEVICE_ID_KEY_MAX/BITS_PER_LONG+1]

kernel_ulong_t 

relbit[INPUT_DEVICE_ID_REL_MAX/BITS_PER_LONG+1]

kernel_ulong_t 

absbit[INPUT_DEVICE_ID_ABS_MAX/BITS_PER_LONG+1]

kernel_ulong_t 

mscbit[INPUT_DEVICE_ID_MSC_MAX/BITS_PER_LONG+1]

kernel_ulong_t 

ledbit[INPUT_DEVICE_ID_LED_MAX/BITS_PER_LONG+1]

kernel_ulong_t 

sndbit[INPUT_DEVICE_ID_SND_MAX/BITS_PER_LONG+1]

kernel_ulong_t 

ffbit[INPUT_DEVICE_ID_FF_MAX/BITS_PER_LONG+1]

kernel_ulong_t 

swbit[INPUT_DEVICE_ID_SW_MAX/BITS_PER_LONG+1]

kernel_ulong_t 

driver_info

匹配的过程主要做了两件事:

一、根据id->flag检查id是否匹配。id->flag记录需要匹配哪些域。

二、检查支持的事件种类是否一致。

第二种检查用到的宏MATCH_BIT。其中最重要的一句是“if ((id->bit[i] & dev->bit[i]) != id->bit[i])”,这句话意味着id支持的事件种类是dev支持的事件的子集就算匹配了。如果某个handlerid除了id->driver_info之外的域都为0,那么此handler可以和任意dev匹配。实际上<内核>/driver/input/evdev.c中就是这么初始化id的。

比如说:dev对象注册时:

set_bit(EV_ABS, input_dev->evbit);

set_bit(EV_KEY, input_dev->evbit);

那么input_device_id 只要满足.evbit = { BIT_MASK(EV_ABS) } 或者.evbit = { BIT_MASK(KEY_ABS) } 或者.evbit = { BIT_MASK(EV_ABS) | BIT_MASK(KEY_ABS)} ,只要是子集就可以匹配通过。

for (id = handler->id_table; id->flags || id->driver_info; id++) 从源码可以看出,flagsdriver_info不能同时为空,否则无法遍历整个id列表。

5.2.自定义handler层(例子)

需求:系统休眠后,CPU一般会工作在一个很低的频率,唤醒后,如果频率很低会影响反应速度,当检测到输入设备有信息输入时,相应的提高CPU的频率,从而达到快速响应,要求是:只匹配CTP输入设备,不匹配g-sensor设备。

1)、装载和卸载函数:注册handler和销毁handler

static int __init match_ts_init(void)

{

return input_register_handler(&match_ts_handler);

}

static void __exit match_ts_exit(void)

{

input_unregister_handler(&match_ts_handler);

}

module_init(match_ts_init);

module_exit(match_ts_exit);

2)、定义handler对象和匹配规则:

static const struct input_device_id match_ts_ids[] = {

{

        .driver_info = 1,

        .evbit = { BIT_MASK(EV_ABS)| BIT_MASK(EV_KEY) },/*only match ctp*/

     },

{ },

};

static struct input_handler match_ts_handler = {

.event = match_ts_event,

.connect = match_ts_connect,

.disconnect = match_ts_disconnect,

.name = "match_ts",

.id_table = match_ts_ids,

};

3)、connection函数:初始化handle和注册handle并打开设备。

static int match_ts_connect(struct input_handler *handler, struct input_dev *dev,

 const struct input_device_id *id)

{

struct input_handle *handle;

int error;

printk("%s: match_ts handler connect to %s\n", __func__, dev->name);

handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL);

if (!handle)

return -ENOMEM;

handle->dev = dev;

handle->handler = handler;

handle->name = "match_ts";

error = input_register_handle(handle);

if (error)

goto err2;

error = input_open_device(handle);

if (error)

goto err1;

return 0;

err1:

input_unregister_handle(handle);

err2:

kfree(handle);

return error;

}

4)、上报函数

static void match_ts_event(struct input_handle *handle,unsigned int type, unsigned int code, int value)

{

printk("%s:type=%d,code=%d,value=%d\n",__func__,type,code,value);

//这里可以进行相应的操作,例如,提高CPU的频率等等。

}

6.Input EventAndroid层实现

Input子系统的Android层包括HAL层和框架类。

6.1.模块功能

1)、EventHub

它是系统中所有事件的中央处理站。它管理所有系统中可以识别的输入设备的输入事件,此外,当设备增加或删除时,EventHub将产生相应的输入事件给系统。

EventHub通过getEvents函数,给系统提供一个输入事件流。它也支持查询输入设备当前的状态(如哪些键当前被按下)。而且EventHub还跟踪每个输入调入的能力,比如输入设备的类别,输入设备支持哪些按键。 

2)、InputReader

InputReaderEventHub中读取原始事件数据(RawEvent),并由各个InputMapper处理之后输入对应的input listener

InputReader拥有一个InputMapper集合。它做的大部分工作在InputReader线程中完成,但是InputReader可以接受任意线程的查询。为了可管理性,InputReader使用一个简单的Mutex来保护它的状态。

InputReader拥有一个EventHub对象,但这个对象不是它创建的,而是在创建InputReader时作为参数传入的。

3)、InputDispatcher

InputDispatcher负责把事件分发给输入目标,其中的一些功能(如识别输入目标)由独立的policy对象控制。

4)、InputManager

InputManager是系统事件处理的核心,它虽然不做具体的事,但管理工作还是要做的,比如接受我们客户的投诉和索赔要求,或者老板的出所筒。

InputManager使用两个线程:

1)、InputReaderThread

"InputReader"线程,它负责读取并预处理RawEventapplies policy并且把消息送入DispatcherThead管理的队列中。

2)、InputDispatcherThread

"InputDispatcher"线程,它在队列上等待新的输入事件,并且异步地把这些事件分发给应用程序。

InputReaderThread类与InputDispatcherThread类不共享内部状态,所有的通信都是单向的,从InputReaderThreadInputDispatcherThread。两个类可以通过InputDispatchPolicy进行交互。

InputManager类从不与Java交互,而InputDispatchPolicy负责执行所有与系统的外部交互,包括调用DVM业务。

6.2.操作流程

文件结构: frameworks/base/services/input

  frameworks/base/services/java/com/android/server

  frameworks/base/services/jni

框图:

3AndroidEvent流向

1)、SystemServer。它是android init进程启动的,它的任务就是启动和管理android服务。SystemServer里面:

创建InputManagerService对象:

inputManager = new InputManagerService(context, wmHandler);

启用服务:ServiceManager.addService(Context.INPUT_SERVICE, inputManager);

启动线程:inputManager.start();

2)、InputManagerService。主要是调用Native层的接口,NativeInit()NativeStart()

3)、Native接口:创建EventHub对象和InputManager对象。

4)、InputManager:创建InputDispatcher对象和InputReader对象。

5)、读线程:调用EventHub对象中的getEvent()接口来读取底层的数据。

6)、分发线程:将读线程得到的数据按需分发到具体的接收者。

7.总结

Input子系统处理输入事务,任何输入设备的驱动程序都可以通过Input输入子系统提供的接口注册到内核,利用子系统提供的功能来与用户空间交互。输入设备一般包括键盘,鼠标,触摸屏等,在内核中都是以输入设备出现的。

Input子系统是分层结构的,总共分为三层:硬件驱动层,子系统核心层,事件处理层。

1)其中硬件驱动层负责操作具体的硬件设备,这层的代码是针对具体的驱动程序的,需要驱动程序的作者来编写。
2)子系统核心层是链接其他两个层之间的纽带与桥梁,向下提供驱动层的接口,向上提供事件处理层的接口。
3)事件处理层负责与用户程序打交道,将硬件驱动层传来的事件报告给用户程序。

Input子系统的三个重要结构体:

input_dev 是硬件驱动层,代表一个input设备。

input_handler 是事件处理层,代表一个事件处理器。

input_handle 个人认为属于核心层,代表一个配对的input设备与input事件处理器。

总结了几点问题:

1、多点触控如何上报数据问题:所有的触控点数据组成一个数据集合,每个触控点数据由input_mt_sync()标识分开,每个数据集合由input_sync()分开。Input子系统会根据不同的标记信号来区分上报的数据信息。

2、Input子系统是否支持standby问题:input子系统不涉及standby的操作,由具体的驱动程序支持。

3、触摸体验的响应时间问题:设置“辅助功能”里的“触摸和按住延迟”,可以影响触摸的响应时间,实现的是延迟效果,在Android层根据设置的值不一样,delay一段时间。所以没有缩短响应时间的效果,只能在原本的响应时间上延迟一段时间才交给应用程序处理。影响响应时间主要包括:硬件上报率、input子系统上报速度和Android层处理时间。

4、是否支持多次打开同一个设备结点问题:支持,在事件处理层的open函数里面支持多次打开,每次打开都会创建一个client对象。

1 0
原创粉丝点击