Android Input子系统浅谈
来源:互联网 发布:protobuf java v3 编辑:程序博客网 时间:2024/06/05 10:27
Android Input子系统浅谈
本文主要讲解[Android Input 子系统][6],我会从一下几个方面讲解:
- linux kernel的input子系统框架
- 以触摸屏驱动为例讲解内核input子系统
- Android framework层Input子系统的框架
- Input子系统的应用程序接口
linux kernel里面input子系统框架
- **主要作用是维护两个重要的链表input_dev_list和input_handler_list
- 下面这段代码便是内核里面Input子系统的框架层部分代码
代码位置:/kernel/driver/input/input.c
可以看到input的内核框架层也是以类似于driver的方式注册的
这段代码便是Input子系统在内核中的核心框架;大家可能会疑惑怎么这
么简单,看着什么也没有做,其实却是这样,这这段code里面主要建立
了一些用于debug的节点,主要有如下节点:
class节点/sys/class/input :调用class_register生成
proc节点/proc/bus/input/devices和handles:调用input_proc_init生成
dev节点:/dev/input:调用register_chrdev_region
以上三种节点在后面会讲解分别用作什么
static int __init input_init(void){ int err; err = class_register(&input_class); if (err) { pr_err("unable to register input_dev class\n"); return err; } err = input_proc_init(); if (err) goto fail1; err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0), INPUT_MAX_CHAR_DEVICES, "input"); if (err) { pr_err("unable to register char major %d", INPUT_MAJOR); goto fail2; } return 0; fail2: input_proc_exit(); fail1: class_unregister(&input_class); return err;}static void __exit input_exit(void){ input_proc_exit(); unregister_chrdev_region(MKDEV(INPUT_MAJOR, 0), INPUT_MAX_CHAR_DEVICES); class_unregister(&input_class);}subsys_initcall(input_init);
两个重要的链表
- 两个重要的链表
- input_register_device():
- 向input_dev_list里面添加input device
- 调用input_attach_handler去匹配input_handler
input_register_handler(): - 向input_handler_list天剑input handler
- 调用input_attach_handler去匹配input_handler
注意上面这两个函数都提到了input_attach_handler()这意味着我们在注册我们的input device的时候会去匹配我们的input handler同时当我们去注册input handler的时候会去匹配input device
通常这两个函数是在我们的driver里面调用的
以触摸屏驱动为例讲解内核input子系统
input_dev = input_allocate_device(); if (!input_dev) { err = -ENOMEM; dev_err(&client->dev, "[Focal][Touch] %s: failed to allocate input device\n", __func__); goto exit_input_dev_alloc_failed; } ftxxxx_ts->input_dev = input_dev; set_bit(KEY_BACK, input_dev->keybit); set_bit(KEY_HOME, input_dev->keybit); set_bit(KEY_APPSELECT, input_dev->keybit); //set_bit(KEY_POWER, input_dev->keybit); __set_bit(EV_ABS, input_dev->evbit); __set_bit(EV_KEY, input_dev->evbit); __set_bit(BTN_TOUCH, input_dev->keybit); __set_bit(INPUT_PROP_DIRECT, input_dev->propbit);// printk("maxx=%d,maxy=%d\n",ftxxxx_ts->x_max,ftxxxx_ts->y_max); input_mt_init_slots(input_dev, CFG_MAX_TOUCH_POINTS, 0);// input_set_abs_params(input_dev, ABS_MT_TRACKING_ID, 0, CFG_MAX_TOUCH_POINTS, 0, 0); input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, 31, 0, 0); input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, ftxxxx_ts->x_max, 0, 0); input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, ftxxxx_ts->y_max, 0, 0); input_set_abs_params(input_dev, ABS_MT_PRESSURE, 0, PRESS_MAX, 0, 0); input_dev->name = Focal_input_dev_name; err = input_register_device(input_dev);
上面这段代码便是我摘抄的触摸屏驱动的部分代码,主要是input device的注册过程,重点函数是input_register_device()正如我们上面所说的他会去input_handler_list里面去寻找匹配input handler,对于向触摸屏和鼠标等我们的内核已经为我们注册好了input handler代码位于:/kernel/driver/input/evdev.c:
这段代码主要注册了一个input handler,我们的触摸屏驱动匹配到的就是这个handler
/kernel/driver/input/evdev.c:static int __init evdev_init(void){ return input_register_handler(&evdev_handler);}static void __exit evdev_exit(void){ input_unregister_handler(&evdev_handler);}module_init(evdev_init);module_exit(evdev_exit);
在touch driver里面调用input_register_device()会有如下关键的一段代码:
将input device添加进input_dev_list,并且对于每一个注册好的input handler调用input_attach_handler()去匹配device(在这里使我们的触摸屏)
list_add_tail(&dev->node, &input_dev_list); list_for_each_entry(handler, &input_handler_list, node) input_attach_handler(dev, handler);。。。
下面便是匹配的代码:对于我们的touch 这里匹配到的就是我们在evdev.c里面注册好的handler,紧接着调用handler->connect(handler, dev, id);
会到evdev.c里面会知道这里的connect是evdev_connect()
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler){ const struct input_device_id *id; int error; id = input_match_device(handler, dev); if (!id) return -ENODEV; error = handler->connect(handler, dev, id); if (error && error != -ENODEV) pr_err("failed to attach handler %s to device %s, error: %d\n", handler->name, kobject_name(&dev->dev.kobj), error); return error;}
这个函数里面用我们的设备号作为标号作为设备名:
dev_set_name(&evdev->dev, “event%d”, dev_no);
紧接着得到我们的input device在这里就是我们的touch:
evdev->handle.dev = input_get_device(dev);
还有之前在input.c里面注册的input_class:
evdev->dev.class = &input_class;
还记得我们之前在input.c里面register_chrdev_region吗:
evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);这里的INPUT_MAJOR就是我们在那里注册的主设备号
下面紧接着注册字符设备:此时注册的字符设备在/dev/input/event%d:
/dev/input:这个是我们在我们的input.c里面注册的,这里的event%d是以次设备号作为标号命名的,
static int evdev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id){ struct evdev *evdev; 。。。。。 。。。。。 dev_set_name(&evdev->dev, "event%d", dev_no); evdev->handle.dev = input_get_device(dev); evdev->handle.name = dev_name(&evdev->dev); evdev->handle.handler = handler; evdev->handle.private = evdev; evdev->dev.devt = MKDEV(INPUT_MAJOR, minor); evdev->dev.class = &input_class; evdev->dev.parent = &dev->dev; evdev->dev.release = evdev_free; device_initialize(&evdev->dev); error = input_register_handle(&evdev->handle); if (error) goto err_free_evdev; cdev_init(&evdev->cdev, &evdev_fops); evdev->cdev.kobj.parent = &evdev->dev.kobj; error = cdev_add(&evdev->cdev, evdev->dev.devt, 1); if (error) goto err_unregister_handle; error = device_add(&evdev->dev); if (error) goto err_cleanup_evdev; return 0; err_cleanup_evdev: evdev_cleanup(evdev); err_unregister_handle: input_unregister_handle(&evdev->handle); err_free_evdev: put_device(&evdev->dev); err_free_minor: input_free_minor(minor); return error;}
到此我们的touch有关的内核注册函数讲解完毕,下面讲解,当我们的手指触摸touch的时候内核里面的数据是如何上报的
当我们的手指触摸touch的时候会触发中断:下面是我贴出来的focal的touchdriver里面的中断函数:首先在中断里面会调用ftxxxx_read_Touchdata()读取手指数据(一般为i2c读取),这里不是我们关注的重点;紧接着调用ftxxxx_report_value():上报读到的数据
static irqreturn_t ftxxxx_ts_interrupt(int irq, void *dev_id){ ret = ftxxxx_read_Touchdata(ftxxxx_ts); 。。。。。 ftxxxx_report_value(ftxxxx_ts); 。。。。。 return IRQ_HANDLED;}
这个函数里面会调用一系列的内核input子系统提供的API如:input_report_abs(),input_mt_slot(),input_report_key()等这些函数主要是按照一定的格式将我们读到的touch手指触摸信息写入我们在evdev.c注册好的/dev/input/event0:这里我们假设我们的touch对应的次设备号是0
然后我们的用户空间就可以读取这个节点的数据得到touch的触摸事件。这个在Android 的framwork层里面会讲解,其实不只是Android其他的只要是使用linux内核的只要使用input子系统都应该是类似的在用户空间读取这个节点
static void ftxxxx_report_value(struct ftxxxx_ts_data *data){ ..... /*protocol B*/ filter_touch_point = event->touch_point; for (i = 0; i < event->touch_point; i++) { report_point=true; if(!report_point) continue; input_mt_slot(data->input_dev,event->au8_finger_id[i]); if (event->au8_touch_event[i]== 0 || event->au8_touch_event[i] == 2) { input_mt_report_slot_state(data->input_dev,MT_TOOL_FINGER,true); input_report_abs(data->input_dev, ABS_MT_PRESSURE, event->pressure[i]); input_report_abs(data->input_dev, ABS_MT_TOUCH_MAJOR, event->area[i]); input_report_abs(data->input_dev, ABS_MT_POSITION_X, event->au16_x[i]); input_report_abs(data->input_dev, ABS_MT_POSITION_Y, event->au16_y[i]); } else { uppoint++; input_mt_report_slot_state(data->input_dev,MT_TOOL_FINGER,false); } } if((last_touchpoint>0)&&(event->Cur_touchpoint==0)) { for(i=0;i<CFG_MAX_TOUCH_POINTS;i++) { input_mt_slot(data->input_dev,i); input_mt_report_slot_state(data->input_dev,MT_TOOL_FINGER,false); } last_touchpoint=0; } if(filter_touch_point == uppoint) { input_report_key(data->input_dev, BTN_TOUCH, 0); } else { input_report_key(data->input_dev, BTN_TOUCH, event->touch_point > 0); } input_sync(data->input_dev); last_touchpoint=event->Cur_touchpoint;}
我们以input_report_key()为例看看我们的数据是如何写入event0的。
这里我省略了很多判断,只留下关键部分
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value){ input_event(dev, EV_KEY, code, !!value);}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); input_handle_event(dev, type, code, value); spin_unlock_irqrestore(&dev->event_lock, flags); }}static void input_handle_event(struct input_dev *dev, unsigned int type, unsigned int code, int value){ int disposition; 。。。。。 if (disposition & INPUT_FLUSH) { if (dev->num_vals >= 2) input_pass_values(dev, dev->vals, dev->num_vals); dev->num_vals = 0; } else if (dev->num_vals >= dev->max_vals - 2) { dev->vals[dev->num_vals++] = input_value_sync; input_pass_values(dev, dev->vals, dev->num_vals); dev->num_vals = 0; }}static void input_pass_values(struct input_dev *dev, struct input_value *vals, unsigned int count){ struct input_handle *handle; 。。。。。 handle = rcu_dereference(dev->grab); if (handle) { count = input_to_handler(handle, vals, count); } else { list_for_each_entry_rcu(handle, &dev->h_list, d_node) if (handle->open) count = input_to_handler(handle, vals, count); } 。。。。。}static unsigned int input_to_handler(struct input_handle *handle, struct input_value *vals, unsigned int count){ struct input_handler *handler = handle->handler; 。。。。。 if (handler->events) handler->events(handle, vals, count); else if (handler->event) for (v = vals; v != end; v++) handler->event(handle, v->type, v->code, v->value); return count;}
到这里调用到了input_to_handler();这个函数里面调用handler->events;
对于touch这里的handler就是我们在evdev.c里面注册的因策这里的events函数就是:evdev.c里面的evdev_events():
evdev_events函数紧接着就会调用evdev_pass_values()
static void evdev_events(struct input_handle *handle, const struct input_value *vals, unsigned int count){ struct evdev *evdev = handle->private; struct evdev_client *client; ktime_t time_mono, time_real; time_mono = ktime_get(); time_real = ktime_sub(time_mono, ktime_get_monotonic_offset()); rcu_read_lock(); client = rcu_dereference(evdev->grab); if (client) evdev_pass_values(client, vals, count, time_mono, time_real); else list_for_each_entry_rcu(client, &evdev->client_list, node) evdev_pass_values(client, vals, count, time_mono, time_real); rcu_read_unlock();}static void evdev_pass_values(struct evdev_client *client, const struct input_value *vals, unsigned int count, ktime_t mono, ktime_t real){ struct evdev *evdev = client->evdev; const struct input_value *v; struct input_event event; bool wakeup = false; event.time = ktime_to_timeval(client->clkid == CLOCK_MONOTONIC ? mono : real); /* Interrupts are disabled, just acquire the lock. */ spin_lock(&client->buffer_lock); for (v = vals; v != vals + count; v++) { event.type = v->type; event.code = v->code; event.value = v->value; __pass_event(client, &event); if (v->type == EV_SYN && v->code == SYN_REPORT) wakeup = true; } spin_unlock(&client->buffer_lock); if (wakeup) wake_up_interruptible(&evdev->wait);}
evdev_pass_value()会调用__pass_event()将我们的touch数据写入event0的buffer,这里我们就不继续往下看了,最后调用wake_up_interruptible()唤醒用户空间读取这里数据的进程对于Android就是我们framwork层的input子系统后面会讲
Android framework层Input子系统的框架
现在请大家跟着我的思绪,想象假设我们的kernel已经跑起来然后跑进了第一个init进程,init进程会启动zygote进程,而zygote进程会启动systemserver进程,这里的细节之后有时间我会在其他的博文里面讲解,在这里我们重点关注和input子系统相关的部分。现在我们假设系统跑到了zygote启动systemserver,之所以讲这里是由于我们的input子系统是systemserver里面的一个service:代码如下:
在systemserver进程启动的过程中会调用startOtherService(),这个函数里面会new InputManagerService(context);
ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
inputManager.start();这三个函数分别创建inputmanager,将inputmanager注册进binder机制(android 的进程间通信机制)
最后调用inputmanager.start();启动inputmanager;这里我们重点分析inputmanager的创建以及启动
位置:/framwork/base/services/java/com/android/server/systemserver.java:private void startOtherServices() { 。。。。。 inputManager = new InputManagerService(context); Slog.i(TAG, "Window Manager"); wm = WindowManagerService.main(context, inputManager, mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL, !mFirstBoot, mOnlyCore); ServiceManager.addService(Context.WINDOW_SERVICE, wm); ServiceManager.addService(Context.INPUT_SERVICE, inputManager); mActivityManagerService.setWindowManager(wm); inputManager.setWindowManagerCallbacks(wm.getInputMonitor()); inputManager.start(); 。。。。。}public InputManagerService(Context context) { this.mContext = context; this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper()); mUseDevInputEventForAudioJack = context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack); Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack=" + mUseDevInputEventForAudioJack); mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue()); LocalServices.addService(InputManagerInternal.class, new LocalService());}这里有一个jni调用nativeInit()会调用到/framwork/base/services/core/jni/com_android_server_input_InputManagerService.cpp里面的nativeInit()代码如下static jlong nativeInit(JNIEnv* env, jclass clazz, jobject serviceObj, jobject contextObj, jobject messageQueueObj) { sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj); if (messageQueue == NULL) { jniThrowRuntimeException(env, "MessageQueue is not initialized."); return 0; } NativeInputManager* im = new NativeInputManager(contextObj, serviceObj, messageQueue->getLooper()); im->incStrong(0); return reinterpret_cast<jlong>(im);}调用到了NativeInputManager::NativeInputManager(jobject contextObj, jobject serviceObj, const sp<Looper>& looper) : mLooper(looper), mInteractive(true) { JNIEnv* env = jniEnv(); mContextObj = env->NewGlobalRef(contextObj); mServiceObj = env->NewGlobalRef(serviceObj); { AutoMutex _l(mLock); mLocked.systemUiVisibility = ASYSTEM_UI_VISIBILITY_STATUS_BAR_VISIBLE; mLocked.pointerSpeed = 0; mLocked.pointerGesturesEnabled = true; mLocked.showTouches = false; } sp<EventHub> eventHub = new EventHub(); mInputManager = new InputManager(eventHub, this, this);}两个重点函数 EventHub()和InputManager()第一个参数是eventhub主要创建InputReader和InputDispatcher以及在initialize里面创建InputReaderThread和InputDispatcherThread两个线程,不过此时线程并没有启动代码位于:/framwork/native/services/inputflinger/InputManager.cppInputManager::InputManager( const sp<EventHubInterface>& eventHub, const sp<InputReaderPolicyInterface>& readerPolicy, const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) { mDispatcher = new InputDispatcher(dispatcherPolicy); mReader = new InputReader(eventHub, readerPolicy, mDispatcher); initialize();}void InputManager::initialize() { mReaderThread = new InputReaderThread(mReader); mDispatcherThread = new InputDispatcherThread(mDispatcher);}
下面我们返回到syetemserver.java里面inputManager.start();
这个函数里面又是一个jni调用这里调用到/framwork/base/services/core/jni/com_android_server_input_InputManagerService.cpp里面的nativeStart()
public void start() { Slog.i(TAG, "Starting input manager"); nativeStart(mPtr); 。。。。。}这里的im->getInputManager()->start()调用的是/framwork/native/services/inputflinger/InputManager.cpp的startstatic void nativeStart(JNIEnv* env, jclass clazz, jlong ptr) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); status_t result = im->getInputManager()->start(); if (result) { jniThrowRuntimeException(env, "Input manager could not be started."); }}在这里会启动刚才常见的两个线程status_t InputManager::start() { status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY); if (result) { ALOGE("Could not start InputDispatcher thread due to error %d.", result); return result; } result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY); if (result) { ALOGE("Could not start InputReader thread due to error %d.", result); mDispatcherThread->requestExit(); return result; } return OK;}
下面我们重点分析:
mReaderThread->run(“InputReader”, PRIORITY_URGENT_DISPLAY);
这里会调用到/framwork/native/services/inputflinger/InputReader.cpp里面的thread_loop();
bool InputReaderThread::threadLoop() { mReader->loopOnce(); return true;}下面函数中的mEventHub->getEvents会依次打开/dev/input/目录底下的节点阻塞等待,其中就包括我们的touch,加入这是我们的手指触摸touch则会触发中断上报数据,会唤醒eventhub读取数据void InputReader::loopOnce() { int32_t oldGeneration; int32_t timeoutMillis; bool inputDevicesChanged = false; Vector<InputDeviceInfo> inputDevices; { // acquire lock AutoMutex _l(mLock); oldGeneration = mGeneration; timeoutMillis = -1; uint32_t changes = mConfigurationChangesToRefresh; if (changes) { mConfigurationChangesToRefresh = 0; timeoutMillis = 0; refreshConfigurationLocked(changes); } else if (mNextTimeout != LLONG_MAX) { nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout); } } // release lock size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE); { // acquire lock AutoMutex _l(mLock); mReaderIsAliveCondition.broadcast(); if (count) { processEventsLocked(mEventBuffer, count); } if (mNextTimeout != LLONG_MAX) { nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); if (now >= mNextTimeout) {#if DEBUG_RAW_EVENTS ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f);#endif mNextTimeout = LLONG_MAX; timeoutExpiredLocked(now); } } if (oldGeneration != mGeneration) { inputDevicesChanged = true; getInputDevicesLocked(inputDevices); } } // release lock if (inputDevicesChanged) { mPolicy->notifyInputDevicesChanged(inputDevices); } mQueuedListener->flush();}
这里我裁剪贴出了部分getevent代码
scanDevicesLocked()函数里面会依次打开我们的/dev/input底下的节点
并且将文件描述符fd添加进mEpollFd监控(这里类似于linux的select系统调用)
最后getevent会阻塞在epoll_wait()要么超时返回要么在/dev/input下的节点有事件要读取。关于linux的阻塞IO机制我觉得很重要后面有时间我会专门有一节讲解
size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) { 。。。。。 for (;;) { 。。。。。 if (mNeedToScanDevices) { mNeedToScanDevices = false; scanDevicesLocked(); mNeedToSendFinishedDeviceScan = true; } ..... ...... // Grab the next input event. bool deviceChanged = false; while (mPendingEventIndex < mPendingEventCount) { const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++]; if (eventItem.data.u32 == EPOLL_ID_INOTIFY) { if (eventItem.events & EPOLLIN) { mPendingINotify = true; } else { ALOGW("Received unexpected epoll event 0x%08x for INotify.", eventItem.events); } continue; } if (eventItem.data.u32 == EPOLL_ID_WAKE) { if (eventItem.events & EPOLLIN) { ALOGV("awoken after wake()"); awoken = true; char buffer[16]; ssize_t nRead; do { nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer)); } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer)); } else { ALOGW("Received unexpected epoll event 0x%08x for wake read pipe.", eventItem.events); } continue; } Device* device = mDevices.valueAt(deviceIndex); if (eventItem.events & EPOLLIN) { int32_t readSize = read(device->fd, readBuffer, sizeof(struct input_event) * capacity); ..... } ..... } ..... int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis); ..... return event - buffer;}
写到这里我们已经进入framwork层的input子系统,不过我不打算向下写了,因为这里有一篇写的不错的文章,这里有别人的一篇写的很好我这里给出连接:
android5.0 Lollipop input子系统
- Android Input子系统浅谈
- android 4.2 input 子系统
- android Input子系统分析
- android input子系统详解
- Android Framework------之Input子系统
- Android Framework------之Input子系统
- Android Framework------之Input子系统
- Android Framework------之Input子系统
- Android Framework------之Input子系统
- Android Input输入子系统分析
- Android Input子系统驱动部分
- Android Framework------之Input子系统
- Android 的Input Event 子系统的来龙去脉
- Android 的Input Event 子系统的来龙去脉
- Android之Input子系统按键repeat
- Android之Input子系统事件分发流程
- Android之Input子系统与输入法
- android input子系统之-常用命令及技巧
- CFileDialog用法
- YTU 2918: Shape系列-5
- Java CookBook Learning Day5th--HTTP (by Tim O'Brien)
- 学习git笔记(一)
- 开发者,去搭建自己的博客系统吧
- Android Input子系统浅谈
- 多线程使用unixODBC时,必须的配置(threadintg)
- 浅谈回调函数——以sort中的cmp为例
- 行车记录仪 方案 主控芯片
- 检测mysql中sql语句的效率的方法
- 利用getLayoutParams()方法和setLayoutParams()方法
- SDN网络中的路由规则(四)
- Unable to detect application ABI's
- 内部类的分类和使用内部类的好处