关闭

安卓4.1: input系统从frameworks到kernel

160人阅读 评论(0) 收藏 举报
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,在构造函数中:

  1. mInputManager = new InputManagerService(context, mInputMonitor);
  2. mInputManager.start();
    从而实现了对InputManager的调用。


2.2, 箭头2:InputManager部分

    InputManager代码比较简单,就是干了2件事,启动input收集线程和input分发线程。

  1. status_t InputManager::start() {
  2.      status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
  3.      ....
  4.      ....
  5.      result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
  6.      ....
  7.      return OK;
  8. }

2.3,箭头3:InputReader和EventHub部分

    InputReader所干的事情全部是通过input收集线程做的:
    首先建立一个死循环:

  1. bool InputReaderThread::threadLoop() {
  2.      mReader->loopOnce();
  3.      return true;
  4. }
    不停地通过EventHub的方法去获得input事件:
  1. mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
    一旦有input事件的时候,调用内部的一个方法去处理事件:
  1. if (count) {
  2.      processEventsLocked(mEventBuffer, count);
  3. }
    InputReader第一次通过EventHub获取事件时,EventHub会首先去打开所有的设备,并将每个设备信息以RawEvent的形式由mEventBuffer返给InputReader,也就是process()中处理的EventHubInterface:EVICE_ADDED类型,该过程会根据每个设备的deviceId去创建InputDevice,并根据设备的类型来创建对应的InputMapper。
  1. void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
  2.      for (const RawEvent* rawEvent = rawEvents; count;) {
  3.          int32_t type = rawEvent->type;
  4.          ....
  5.          switch (rawEvent->type) {
  6.              case EventHubInterface::DEVICE_ADDED:
  7.                  addDeviceLocked(rawEvent->when, rawEvent->deviceId);
  8.                  break;
  9.              case EventHubInterface::DEVICE_REMOVED:''
  10.                  removeDeviceLocked(rawEvent->when, rawEvent->deviceId);
  11.                  break;
  12.              case EventHubInterface::FINISHED_DEVICE_SCAN:
  13.                  handleConfigurationChangedLocked(rawEvent->when);
  14.                  break;
  15.            ....
  16.           }
  17.     }
    目前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.

  1.     getListener()->notifyMotion(&args);
  2.     getListener()->notifyKey(&args);
    EventHub做的事情比较简单,就是从一个文件设备读数据,并且封装成InputReader需要的结构体。
  1. size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
  2.      ....
  3.      Device* device = mDevices.valueAt(deviceIndex);
  4.      if (eventItem.events & EPOLLIN) {
  5.          int32_t readSize = read(device->fd, readBuffer,
  6.                                  sizeof(struct input_event) * capacity);
  7.      ....
    device->fd即是由kernel层通过input core和Event handler写入对应的节点的(比如/dev/input/event*).


2.4,箭头4:InputReader通知InputDispatcher
    InputReader和InputDispatcher属于两个独立的线程,他们之间的通讯方式主要是通过InputListener来实现。在InputManager中,InputReader是这么被构造的:  

  1. mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
    mDispatcher作为了一个inner listener被传送到InputReader。
    当InputReader收集到input事件并进行判断处理后,封装到一个NotifyArgs的事件对应的子结构体中;这套结构体定义在InputListener.h中。
    通知的过程,如上节所示,其实就是调用一个getListener()->notifyMotion(&args)来实现,看下代码实现(以Motion事件为例):

  1. class QueuedInputListener : public InputListenerInterface {
  2.      ....
  3.      Vector mArgsQueue;
  4. };

  1. void QueuedInputListener::notifyMotion(const NotifyKeyArgs* args) {
  2.      mArgsQueue.push(new NotifyMotionArgs(*args));
  3. }
    在InputReader线程每一轮收集input事件(InputReader::loopOnce函数)的结束,通过

  1.     mQueuedListener->flush();
    将queue里面的所有事件推送到listener,也就是InputDispatcher,具体实现步骤:
    1, QueuedInputListener里面的flush代码如下:

  1. void QueuedInputListener::flush() {
  2.      size_t count = mArgsQueue.size();
  3.      for (size_t i = 0; i < count; i ) {
  4.          NotifyArgs* args = mArgsQueue[i];
  5.          args->notify(mInnerListener);
  6.          ....
  7.          ....
  8. }
    2, args->notify(mInnerListener)里面做的事情很简单,就是调用InputDispatcher里面对应的函数将事件推送到InputDispatcher的队列中等候处理。
  1. void NotifyMotionArgs::notify(const sp& listener) const {
  2.      listener->notifyMotion(this);
  3. }
    3, InputDispatcher中:
  1. void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
  2.      ....
  3.      ....
  4.      // Just enqueue a new motion event.
  5.      MotionEntry* newEntry = new MotionEntry(args->eventTime,
  6.      args->deviceId, args->source, policyFlags,
  7.      args->action, args->flags, args->metaState, args->buttonState,
  8.      args->edgeFlags, args->xPrecision, args->yPrecision, args->downTime,
  9.      args->pointerCount, args->pointerProperties, args->pointerCoords);
  10.      needWake = enqueueInboundEventLocked(newEntry);
  11. }
    4, enqueueInboundEventLocked会将事件实例推送到mInboundQueue;在这里,InputDispatcher的线程会从mInboundQueue采集事件并进行处理和分发。
至此,InputReader将所有的input事件通知InputDispatcher完毕。

2.5,箭头5:InputDispatcher部分
    跟InputReader类似,InputDispatcher也是通过一个死循环来无限读取mInboundQueue中的事件。

  1. bool InputDispatcherThread::threadLoop() {
  2.      mDispatcher->dispatchOnce();
  3.      return true;
  4. }


  1. void InputDispatcher::dispatchOnce() {
  2.      ....
  3.      dispatchOnceInnerLocked(&nextWakeupTime);
  4.      mLooper->pollOnce(timeoutMillis);
  5.      ....
  6. }
    mInboundQueue的事件来源如上节所示。
    其中,在加入mInboundQueue之前,InputDispatcher已经做了一些一些预处理,这其实是InputDispatcher最重要的一部分。以Key事件为例:

  1. void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
  2.      ....
  3.      mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags);
  4.      if (mInputFilterEnabled) {
  5.          ....
  6.          if (!mPolicy->filterInputEvent(&event, policyFlags)) {
  7.              return; // event was consumed by the filter
  8.          }
  9.      }
  10.      KeyEntry* newEntry = new KeyEntry(args->eventTime,
  11.                                        args->deviceId, args->source, policyFlags,
  12.                                        args->action, flags, args->keyCode, args->scanCode,
  13.                                        metaState, repeatCount, args->downTime);
  14.      needWake = enqueueInboundEventLocked(newEntry);
  15.      ....
  16. }
它会首先通过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事件为例:

  1. void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
  2.      ....
  3.      mPendingEvent = synthesizeKeyRepeatLocked(currentTime);
  4.      ....
  5.      DropReason dropReason = DROP_REASON_NOT_DROPPED;
  6.      if (!(mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER)) {
  7.          dropReason = DROP_REASON_POLICY;
  8.      } else if (!mDispatchEnabled) {
  9.          dropReason = DROP_REASON_DISABLED;
  10.      }
  11.      switch (mPendingEvent->type) {
  12.          ....
  13.          case EventEntry::TYPE_KEY: {
  14.              KeyEntry* typedEntry = static_cast(mPendingEvent);
  15.              if (isAppSwitchDue) {
  16.                  if (isAppSwitchKeyEventLocked(typedEntry)) {
  17.                      resetPendingAppSwitchLocked(true);
  18.                      isAppSwitchDue = false;
  19.                  } else if (dropReason == DROP_REASON_NOT_DROPPED) {
  20.                      dropReason = DROP_REASON_APP_SWITCH;
  21.                  }
  22.              }

  23.              if (dropReason == DROP_REASON_NOT_DROPPED && isStaleEventLocked(currentTime, typedEntry)) {
  24.                 dropReason = DROP_REASON_STALE;
  25.              }

  26.              if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {
  27.                  dropReason = DROP_REASON_BLOCKED;
  28.              }
  29.              done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
  30.              ....
先是从mInboundQueue里面取一个事件,然后判断是它的policyFalg如果不包含POLICY_FLAG_PASS_TO_USER,则丢弃,下面还有一大堆丢弃的理由。最后,通过dispatchKeyLocked调用startDispatchCycleLocked将事件由connect pulish出去。具体步骤简述为:
找到taget窗口==> 

与该窗口建立Connection==> 

将input事件加入Connection的outBoundQueue==> 

处理Connection的outBoundQueue,写入共享内存并通过管道告诉input消费者==> 

mLooper调用handleReceiveCallback继续处理。
channel的注册是在WindowManagerService里面实现的,没新增加一个窗口,经过一些列条件判断:

  1.     mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
registerInputChannel会增加个轮询looper:
  1.     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中实现如下:

  1. static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
  2. {
  3.      ....
  4.      while (retval input_event_size() <= count &&
  5.      evdev_fetch_next_event(client, &event)) {
  6.          if (input_event_to_user(buffer retval, &event))
  7.          return -EFAULT;
  8.      }
  9.      ....
  10. }
input_event_to_user在input-compat.c里面实现,其实就是一个copy_to_user的过程。而event的内容来自evdev_fetch_next_event函数。evdev_fetch_next_event实现的是从结构体evdev_client中取buffer。
  1. static int evdev_fetch_next_event(struct evdev_client *client, struct input_event *event)
  2. {
  3.      int have_event;
  4.      ....
  5.      have_event = client->packet_head != client->tail;
  6.      if (have_event) {
  7.          *event = client->buffer[client->tail ];
  8.          ....
  9.      }
  10. }


  1. struct evdev_client {
  2.      unsigned int head;
  3.      unsigned int tail;
  4.      ....
  5.      struct fasync_struct *fasync;
  6.      struct evdev *evdev;
  7.      struct list_head node;
  8.      ....
  9.      struct input_event buffer[];
  10. };
    具体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:

  1. void input_event(struct input_dev *dev,
  2.                  unsigned int type, unsigned int code, int value)
  3. {
  4.      unsigned long flags;
  5.      if (is_event_supported(type, dev->evbit, EV_MAX)) {
  6.          spin_lock_irqsave(&dev->event_lock, flags);
  7.          add_input_randomness(type, code, value);
  8.          input_handle_event(dev, type, code, value);
  9.          spin_unlock_irqrestore(&dev->event_lock, flags);
  10.      }
  11. }
    type也定义在这个头文件中,分为:
  1.     EV_SYN       0x00 同步事件
  2.     EV_KEY       0x01 按键事件
  3.     EV_REL       0x02 相对坐标(如:鼠标移动,报告相对最后一次位置的偏移)
  4.     EV_ABS       0x03 绝对坐标(如:触摸屏或操作杆,报告绝对的坐标位置)
  5.     EV_MSC       0x04 其它
  6.     EV_SW        0x05 开关
  7.     EV_LED       0x11 按键/设备灯
  8.     EV_SND       0x12 声音/警报
  9.     EV_REP       0x14 重复
  10.     EV_FF        0x15 力反馈
  11.     EV_PWR       0x16 电源
  12.     EV_FF_STATUS 0x17 力反馈状态
  13.     EV_MAX       0x1f 事件类型最大个数和提供位掩码支持
    input_event其实就是通过input_handle_event实现的。input_handle_event通过input_pass_event最终通过调用input_handler的event方法实现。对于evdev来说,event的方法实现如下:
  1. static void evdev_event(struct input_handle *handle,
  2.                         unsigned int type, unsigned int code, int value)
  3. {
  4.      struct evdev *evdev = handle->private;
  5.      struct evdev_client *client;
  6.      struct input_event event;
  7.      ....
  8.      if (client)
  9.          evdev_pass_event(client, &event);
  10.      else
  11.          list_for_each_entry_rcu(client, &evdev->client_list, node)
  12.              evdev_pass_event(client, &event);
  13.      ....
  14. }


  1. static void evdev_pass_event(struct evdev_client *client,
  2.                              struct input_event *event)
  3. {
  4.      ....
  5.      client->buffer[client->head++] = *event;
  6.      ....
  7. }
    过程就很清楚了,最终通过evdev_pass_event,从而实现了往evdev_client结构体的buffer写事件。


3.3,箭头9 10:driver部分:如何通过i2c模块读取事件发送到input子系统
    这一块其实属于BSP的大头。如何给对应的输入设备硬件写一个driver。简单分析下touchscreen的驱动吧。
    首先是入口函数init。touchscreen根据接口类型,他们的init函数可能也很有差异:

  1. // usb接口的触摸屏
  2. static struct usb_driver usbtouch_driver {
  3.      ....
  4.      .probe = usbtouch_probe,
  5.      ....
  6. };

  7. static int __init usbtouch_init(void)
  8. {
  9.      ....
  10.      usb_register(&usbtouch_driver);
  11.      ....
  12. }
然后在设备重载的usbtouch_probe函数里面初始化input_dev以及注册到input子系统中。


  1. // serio接口的触摸屏
  2. static struct serio_driver seriotouch_driver {
  3.      ....
  4.      .connect = seriotouch_connect,
  5.      ....
  6. };

  7. static int __init seriotouch_init(void)
  8. {
  9.      ....
  10.      serio_register_driver(&seriotouch_driver);
  11.      ....
  12. }
    然后在设备重载的seriotouch_connect函数里面初始化input_dev以及注册到input子系统中。

  1. // 板载的i2c接口的触摸屏
  2. static struct i2c_driver i2c_ts_driver = {
  3.      ....
  4.      .probe = i2c_ts_probe,
  5.      .remove = __devexit_p(i2c_ts_remove),
  6.      ....
  7. };

  8. static int __init i2c_ts_init(void)
  9. {
  10.      ....
  11.      i2c_add_driver(&i2c_ts_driver);
  12.      ....
  13. }
    然后在设备重载的i2c_ts_probe函数里面初始化input_dev以及注册到input子系统中。
    其他平台相关的可以调用自己平台的register函数来实现init。

    input_dev以及注册到input子系统中的过程基本类似。差异主要体现在对input事件的下半部分的处理上有差异。
    相同的部分

  1.     // allocate一个input设备
  2.      struct input_dev* input_dev;
  3.      input_dev = input_allocate_device();
  4.      // 初始化硬件的各种参数和可接受事件类型
  5.      input_dev->name = "blablabla";
  6.      input_dev->id.vendor = blublublu;
  7.      input_set_abs_params(input_dev, ABS_X, 0, MAX_X, 0, 0);
  8.      input_set_abs_params(input_dev, ABS_Y, 0, MAX_Y, 0, 0);
  9.      set_bit(ABS_MT_TOUCH_MAJOR, input_dev->absbit);
  10.      ....
  11.      // 将设备注册到input子系统,也就是加入到input_handler链表
  12.      input_register_device(input_dev);
不同部分,interrupt的响应:
  1. // usb, 通过interupt的urb来实现,然后在irq函数里面响应
  2. usb_fill_int_urb(usbtouch->irq, usbtouch->udev,
  3.                  usb_rcvintpipe(usbtouch->udev, endpoint->bEndpointAddress),
  4.                  usbtouch->data, type->rept_size,
  5.                  usbtouch_irq, usbtouch, endpoint->bInterval);

  1. // serio,serio_driver封装了一个interrup函数,重载即可
  2. static struct serio_driver seriotouch_driver = {
  3.      ....
  4.      .interrupt = seriotouch_interrupt,
  5.      .connect = seriotouch_connect,
  6.      .disconnect = seriotouch_disconnect,
  7.      ....
  8. };

  1. // i2c,自己调用workqueque处理
  2. static int i2c_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)
  3. {
  4.      ....
  5.      struct work_struct pen_event_work;
  6.      struct workqueue_struct *ts_workqueue;
  7.      INIT_WORK(&ft5x_ts->pen_event_work, i2c_ts_pen_irq_work);
  8.      ts_workqueue = create_singlethread_workqueue(dev_name(&client->dev));
  9.      request_irq(SW_INT_IRQNO_PIO, i2c_ts_interrupt, IRQF_TRIGGER_FALLING | IRQF_SHARED,
  10.                  "i2c_ts", i2c_ts);
  11.      ....
  12. }

  13. static irqreturn_t i2c_ts_interrupt(int irq, void *dev_id)
  14. {
  15.      ....
  16.      queue_work(ts_workqueue, &pen_event_work);
  17.      ....
  18.      return IRQ_HANDLED;
  19. }
    最后在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/目录下

参考文档
Linux input子系统分析
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:34882次
    • 积分:523
    • 等级:
    • 排名:千里之外
    • 原创:2篇
    • 转载:87篇
    • 译文:0篇
    • 评论:2条
    最新评论