1,概述
废话少说,直接上图,input从kernel到android frameworks一锅端式结构框架图。
该图kernel部分是以用i2c接口的input设备为例。
2,Frameworks层
2.1,箭头1:WindowManagerService部分
WindowManagerService是android系统一个非常重要的服务,实现的功能的包括窗口管理、绘制,Activity切换动画,Acitivity窗口显示的先后顺序,输入法管理,收集input事件和分发input事件等等。只关注input相关的部分。
main函数首先起了一个WMThread,MTTread里面new了一个WindowManagerService,在构造函数中:- mInputManager = new InputManagerService(context, mInputMonitor);
- mInputManager.start();
从而实现了对InputManager的调用。
2.2, 箭头2:InputManager部分
InputManager代码比较简单,就是干了2件事,启动input收集线程和input分发线程。- status_t InputManager::start() {
- status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
- ....
- ....
- result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
- ....
- return OK;
- }
2.3,箭头3:InputReader和EventHub部分
InputReader所干的事情全部是通过input收集线程做的:
首先建立一个死循环:- bool InputReaderThread::threadLoop() {
- mReader->loopOnce();
- return true;
- }
不停地通过EventHub的方法去获得input事件:- mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
一旦有input事件的时候,调用内部的一个方法去处理事件:- if (count) {
- processEventsLocked(mEventBuffer, count);
- }
InputReader第一次通过EventHub获取事件时,EventHub会首先去打开所有的设备,并将每个设备信息以RawEvent的形式由mEventBuffer返给InputReader,也就是process()中处理的EventHubInterface:EVICE_ADDED类型,该过程会根据每个设备的deviceId去创建InputDevice,并根据设备的类型来创建对应的InputMapper。- void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
- for (const RawEvent* rawEvent = rawEvents; count;) {
- int32_t type = rawEvent->type;
- ....
- switch (rawEvent->type) {
- case EventHubInterface::DEVICE_ADDED:
- addDeviceLocked(rawEvent->when, rawEvent->deviceId);
- break;
- case EventHubInterface::DEVICE_REMOVED:''
- removeDeviceLocked(rawEvent->when, rawEvent->deviceId);
- break;
- case EventHubInterface::FINISHED_DEVICE_SCAN:
- handleConfigurationChangedLocked(rawEvent->when);
- break;
- ....
- }
- }
目前android实现了五种类型的InputMapper,分别是滑盖/翻盖SwitchInputMapper、键盘KeyboardInputMapper、轨迹球TrackballInputMapper、多点触屏MultiTouchInputMapper以及单点触屏SingleTouchInputMapper。
非device add,remove以及scan事件(也就是正常的input事件)处理过程比较复杂和漫长,有兴趣的可以看代码。简单说起来就是根据input device的类型将事件交给不同的InputMapper处理,通常是塞进一个结构体mCurrentRawPointerData,通过对比mLastRawPointerData以及一些列的位操作来判断事件的类型——key事件或者motion事件等等,以及motion事件对应的action——up,down,hover,scroll等等从而通过QueuedInputListener告诉InputDispatcher.- getListener()->notifyMotion(&args);
- getListener()->notifyKey(&args);
EventHub做的事情比较简单,就是从一个文件设备读数据,并且封装成InputReader需要的结构体。- size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
- ....
- Device* device = mDevices.valueAt(deviceIndex);
- if (eventItem.events & EPOLLIN) {
- int32_t readSize = read(device->fd, readBuffer,
- sizeof(struct input_event) * capacity);
- ....
device->fd即是由kernel层通过input core和Event handler写入对应的节点的(比如/dev/input/event*).
2.4,箭头4:InputReader通知InputDispatcher
InputReader和InputDispatcher属于两个独立的线程,他们之间的通讯方式主要是通过InputListener来实现。在InputManager中,InputReader是这么被构造的: - mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
mDispatcher作为了一个inner listener被传送到InputReader。
当InputReader收集到input事件并进行判断处理后,封装到一个NotifyArgs的事件对应的子结构体中;这套结构体定义在InputListener.h中。
通知的过程,如上节所示,其实就是调用一个getListener()->notifyMotion(&args)来实现,看下代码实现(以Motion事件为例):- class QueuedInputListener : public InputListenerInterface {
- ....
- Vector mArgsQueue;
- };
- void QueuedInputListener::notifyMotion(const NotifyKeyArgs* args) {
- mArgsQueue.push(new NotifyMotionArgs(*args));
- }
在InputReader线程每一轮收集input事件(InputReader::loopOnce函数)的结束,通过
- mQueuedListener->flush();
将queue里面的所有事件推送到listener,也就是InputDispatcher,具体实现步骤:
1, QueuedInputListener里面的flush代码如下:- void QueuedInputListener::flush() {
- size_t count = mArgsQueue.size();
- for (size_t i = 0; i < count; i ) {
- NotifyArgs* args = mArgsQueue[i];
- args->notify(mInnerListener);
- ....
- ....
- }
2, args->notify(mInnerListener)里面做的事情很简单,就是调用InputDispatcher里面对应的函数将事件推送到InputDispatcher的队列中等候处理。- void NotifyMotionArgs::notify(const sp& listener) const {
- listener->notifyMotion(this);
- }
3, InputDispatcher中:- void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
- ....
- ....
- // Just enqueue a new motion event.
- MotionEntry* newEntry = new MotionEntry(args->eventTime,
- args->deviceId, args->source, policyFlags,
- args->action, args->flags, args->metaState, args->buttonState,
- args->edgeFlags, args->xPrecision, args->yPrecision, args->downTime,
- args->pointerCount, args->pointerProperties, args->pointerCoords);
- needWake = enqueueInboundEventLocked(newEntry);
- }
4, enqueueInboundEventLocked会将事件实例推送到mInboundQueue;在这里,InputDispatcher的线程会从mInboundQueue采集事件并进行处理和分发。
至此,InputReader将所有的input事件通知InputDispatcher完毕。
2.5,箭头5:InputDispatcher部分
跟InputReader类似,InputDispatcher也是通过一个死循环来无限读取mInboundQueue中的事件。- bool InputDispatcherThread::threadLoop() {
- mDispatcher->dispatchOnce();
- return true;
- }
- void InputDispatcher::dispatchOnce() {
- ....
- dispatchOnceInnerLocked(&nextWakeupTime);
- mLooper->pollOnce(timeoutMillis);
- ....
- }
mInboundQueue的事件来源如上节所示。
其中,在加入mInboundQueue之前,InputDispatcher已经做了一些一些预处理,这其实是InputDispatcher最重要的一部分。以Key事件为例:- void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
- ....
- mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags);
- if (mInputFilterEnabled) {
- ....
- if (!mPolicy->filterInputEvent(&event, policyFlags)) {
- return; // event was consumed by the filter
- }
- }
- KeyEntry* newEntry = new KeyEntry(args->eventTime,
- args->deviceId, args->source, policyFlags,
- args->action, flags, args->keyCode, args->scanCode,
- metaState, repeatCount, args->downTime);
- needWake = enqueueInboundEventLocked(newEntry);
- ....
- }
它会首先通过mPolicy的方法interceptKeyBeforeQueueing去特殊处理下这个事件,比如说HOME键,ENDCALL键,音量键以及wake键(取消黑屏或者锁屏的键),一旦这个键被判断不需要分发给当前application了,传入的参数policyFlags将会做一些处理使之在后面处理分发的时候不被分发(policyFlags &= ~POLICY_FLAG_PASS_TO_USER);详细情况可以往下看。
特殊按键的处理是在PhoneWindowManager(frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java)里面处理的,可以按照自己需要添加或者修改。一些特殊的事件,比如说switch(滑盖翻盖)事件,显然不用分发到具体appication,所以在notifySwitch里面直接交给mPolicy处理了而不用加入到mInboundQueue去继续分发。
InputDispatcher的分发代码如下,继续以Key事件为例:- void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
- ....
- mPendingEvent = synthesizeKeyRepeatLocked(currentTime);
- ....
- DropReason dropReason = DROP_REASON_NOT_DROPPED;
- if (!(mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER)) {
- dropReason = DROP_REASON_POLICY;
- } else if (!mDispatchEnabled) {
- dropReason = DROP_REASON_DISABLED;
- }
- switch (mPendingEvent->type) {
- ....
- case EventEntry::TYPE_KEY: {
- KeyEntry* typedEntry = static_cast(mPendingEvent);
- if (isAppSwitchDue) {
- if (isAppSwitchKeyEventLocked(typedEntry)) {
- resetPendingAppSwitchLocked(true);
- isAppSwitchDue = false;
- } else if (dropReason == DROP_REASON_NOT_DROPPED) {
- dropReason = DROP_REASON_APP_SWITCH;
- }
- }
- if (dropReason == DROP_REASON_NOT_DROPPED && isStaleEventLocked(currentTime, typedEntry)) {
- dropReason = DROP_REASON_STALE;
- }
- if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {
- dropReason = DROP_REASON_BLOCKED;
- }
- done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
- ....
先是从mInboundQueue里面取一个事件,然后判断是它的policyFalg如果不包含POLICY_FLAG_PASS_TO_USER,则丢弃,下面还有一大堆丢弃的理由。最后,通过dispatchKeyLocked调用startDispatchCycleLocked将事件由connect pulish出去。具体步骤简述为:
找到taget窗口==>
与该窗口建立Connection==>
将input事件加入Connection的outBoundQueue==>
处理Connection的outBoundQueue,写入共享内存并通过管道告诉input消费者==>
mLooper调用handleReceiveCallback继续处理。
channel的注册是在WindowManagerService里面实现的,没新增加一个窗口,经过一些列条件判断:
- mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
registerInputChannel会增加个轮询looper:- mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
大致逻辑如此。这一块比较复杂,网上资料也挺齐全,就不详述了。
至此,Frameworks层彻底结束。至于Input事件具体怎么被分配和消费,就属于另外一个话题了。
3, Kernel层
kernel层将着重描述kernel driver的分析。其余的部分由于错误基本没有,文档也非常多,就不详细讲解了。
3.1 箭头6 7:evdev部分: 用户读input事件
设备的节点在初始化设备的时候通过mknod的方法挂载的,详细可以参看android代码下的system/core/init/devices.c,它通过ueventd进程来调用。
EventHub读取input事件其实最终调用的就是file_operations的read函数,在evdev中实现如下:- static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
- {
- ....
- while (retval input_event_size() <= count &&
- evdev_fetch_next_event(client, &event)) {
- if (input_event_to_user(buffer retval, &event))
- return -EFAULT;
- }
- ....
- }
input_event_to_user在input-compat.c里面实现,其实就是一个copy_to_user的过程。而event的内容来自evdev_fetch_next_event函数。evdev_fetch_next_event实现的是从结构体evdev_client中取buffer。- static int evdev_fetch_next_event(struct evdev_client *client, struct input_event *event)
- {
- int have_event;
- ....
- have_event = client->packet_head != client->tail;
- if (have_event) {
- *event = client->buffer[client->tail ];
- ....
- }
- }
- struct evdev_client {
- unsigned int head;
- unsigned int tail;
- ....
- struct fasync_struct *fasync;
- struct evdev *evdev;
- struct list_head node;
- ....
- struct input_event buffer[];
- };
具体buffer的内容怎么填充进去的,我们在下一节继续研究。
3.2,箭头8:input core部分:往event handler写event
主要由input.c函数实现。input.c定义了input子系统最核心的3个结构体:input_dev, input_handle和input_handler,他们的功能和调用关系就不详述了,可以参看/include/linux/input.h或者网上找资料。 我们着重关注下input子系统是如何往evdev_client结构体写入input事件的。
常用的input driver往input子系统发送事件的时候,通常是通过调用input子系统提供的一些函数,类似:input_report_abs(绝对坐标类事件),input_report_key(按键事件),input_sync(同步事件),input_report_rel(相对坐标事件)等等等等。他们在input.h都是以内联的方式调用input_event:- void input_event(struct input_dev *dev,
- unsigned int type, unsigned int code, int value)
- {
- unsigned long flags;
- if (is_event_supported(type, dev->evbit, EV_MAX)) {
- spin_lock_irqsave(&dev->event_lock, flags);
- add_input_randomness(type, code, value);
- input_handle_event(dev, type, code, value);
- spin_unlock_irqrestore(&dev->event_lock, flags);
- }
- }
type也定义在这个头文件中,分为:- EV_SYN 0x00 同步事件
- EV_KEY 0x01 按键事件
- EV_REL 0x02 相对坐标(如:鼠标移动,报告相对最后一次位置的偏移)
- EV_ABS 0x03 绝对坐标(如:触摸屏或操作杆,报告绝对的坐标位置)
- EV_MSC 0x04 其它
- EV_SW 0x05 开关
- EV_LED 0x11 按键/设备灯
- EV_SND 0x12 声音/警报
- EV_REP 0x14 重复
- EV_FF 0x15 力反馈
- EV_PWR 0x16 电源
- EV_FF_STATUS 0x17 力反馈状态
- EV_MAX 0x1f 事件类型最大个数和提供位掩码支持
input_event其实就是通过input_handle_event实现的。input_handle_event通过input_pass_event最终通过调用input_handler的event方法实现。对于evdev来说,event的方法实现如下:- static void evdev_event(struct input_handle *handle,
- unsigned int type, unsigned int code, int value)
- {
- struct evdev *evdev = handle->private;
- struct evdev_client *client;
- struct input_event event;
- ....
- if (client)
- evdev_pass_event(client, &event);
- else
- list_for_each_entry_rcu(client, &evdev->client_list, node)
- evdev_pass_event(client, &event);
- ....
- }
- static void evdev_pass_event(struct evdev_client *client,
- struct input_event *event)
- {
- ....
- client->buffer[client->head++] = *event;
- ....
- }
过程就很清楚了,最终通过evdev_pass_event,从而实现了往evdev_client结构体的buffer写事件。
3.3,箭头9 10:driver部分:如何通过i2c模块读取事件发送到input子系统
这一块其实属于BSP的大头。如何给对应的输入设备硬件写一个driver。简单分析下touchscreen的驱动吧。
首先是入口函数init。touchscreen根据接口类型,他们的init函数可能也很有差异:- // usb接口的触摸屏
- static struct usb_driver usbtouch_driver {
- ....
- .probe = usbtouch_probe,
- ....
- };
- static int __init usbtouch_init(void)
- {
- ....
- usb_register(&usbtouch_driver);
- ....
- }
然后在设备重载的usbtouch_probe函数里面初始化input_dev以及注册到input子系统中。
- // serio接口的触摸屏
- static struct serio_driver seriotouch_driver {
- ....
- .connect = seriotouch_connect,
- ....
- };
- static int __init seriotouch_init(void)
- {
- ....
- serio_register_driver(&seriotouch_driver);
- ....
- }
然后在设备重载的seriotouch_connect函数里面初始化input_dev以及注册到input子系统中。
- // 板载的i2c接口的触摸屏
- static struct i2c_driver i2c_ts_driver = {
- ....
- .probe = i2c_ts_probe,
- .remove = __devexit_p(i2c_ts_remove),
- ....
- };
- static int __init i2c_ts_init(void)
- {
- ....
- i2c_add_driver(&i2c_ts_driver);
- ....
- }
然后在设备重载的i2c_ts_probe函数里面初始化input_dev以及注册到input子系统中。
其他平台相关的可以调用自己平台的register函数来实现init。
input_dev以及注册到input子系统中的过程基本类似。差异主要体现在对input事件的下半部分的处理上有差异。
相同的部分- // allocate一个input设备
- struct input_dev* input_dev;
- input_dev = input_allocate_device();
- // 初始化硬件的各种参数和可接受事件类型
- input_dev->name = "blablabla";
- input_dev->id.vendor = blublublu;
- input_set_abs_params(input_dev, ABS_X, 0, MAX_X, 0, 0);
- input_set_abs_params(input_dev, ABS_Y, 0, MAX_Y, 0, 0);
- set_bit(ABS_MT_TOUCH_MAJOR, input_dev->absbit);
- ....
- // 将设备注册到input子系统,也就是加入到input_handler链表
- input_register_device(input_dev);
不同部分,interrupt的响应:- // usb, 通过interupt的urb来实现,然后在irq函数里面响应
- usb_fill_int_urb(usbtouch->irq, usbtouch->udev,
- usb_rcvintpipe(usbtouch->udev, endpoint->bEndpointAddress),
- usbtouch->data, type->rept_size,
- usbtouch_irq, usbtouch, endpoint->bInterval);
- // serio,serio_driver封装了一个interrup函数,重载即可
- static struct serio_driver seriotouch_driver = {
- ....
- .interrupt = seriotouch_interrupt,
- .connect = seriotouch_connect,
- .disconnect = seriotouch_disconnect,
- ....
- };
- // i2c,自己调用workqueque处理
- static int i2c_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)
- {
- ....
- struct work_struct pen_event_work;
- struct workqueue_struct *ts_workqueue;
- INIT_WORK(&ft5x_ts->pen_event_work, i2c_ts_pen_irq_work);
- ts_workqueue = create_singlethread_workqueue(dev_name(&client->dev));
- request_irq(SW_INT_IRQNO_PIO, i2c_ts_interrupt, IRQF_TRIGGER_FALLING | IRQF_SHARED,
- "i2c_ts", i2c_ts);
- ....
- }
- static irqreturn_t i2c_ts_interrupt(int irq, void *dev_id)
- {
- ....
- queue_work(ts_workqueue, &pen_event_work);
- ....
- return IRQ_HANDLED;
- }
最后在i2c_ts_pen_irq_work函数中通过i2c模块读取数据,然组织,再通过input提供的函数将之发送给input子系统。
事件数据提供部分无非是从相应模块读取数据,然后根据协议将数据通过input_report_key之类的函数将组织好的数据一系列的发送给input子系统。
4, BSP相关
> 首先确保设备供电正常,设备无损坏
> 用adb连接手机或者开发板,用getevent命令查看输入设备是否存在,不存在,在上一步没问题的情况下:
>> lsmod,看对应的driver的ko文件是否被insmod,如果没有,*检查android系统的init.rc或者手动insmod
>> 或者检查driver部分的input初始化和注册是否成功;这个目前知道的方式就是通过抓kernel部分的log
> 启动输入(比如按键,或者点击屏幕),并且用getevent抓一系列事件,看是否有输出的事件集满足android响应的协议,如果不是,请修改driver部分interupt的响应代码:
> 如果事件正确,请检查farmework的代码。具体就不细述了
> 对于keybord来说,有个重要的keylayout文件和keymap文件,如果不用默认的话,需要编辑kl文件并且在你的device.mk中导入到/system/usr/keylayout/目录下
> 对于touchscreen,有些设备需要从一个idc文件读取初始化内容,需要编辑你的device.mk中导入到/system/usr/idc/目录下
参考文档
阅读(1319) | 评论(0) | 转发(0) |