Linux Input 子系统:从驱动到 evdev
B站 嵌入式孙老师博主个人介绍博主书籍-京东购买链接Yocto项目实战教程加博主微信进技术交流群jerrydev在做 Linux BSP 开发时触摸屏、按键、鼠标、键盘这些设备最终通常都会出现在/dev/input/event0 /dev/input/event1 /dev/input/event2应用层再通过evtest、libinput、Weston、Qt 等获取输入事件。这个过程看起来很简单但真正往内核源码里追经常会碰到几个容易混淆的问题Input Driver 到底负责什么Input Core 又做了什么evdev是不是一个硬件驱动/dev/input/eventX到底是谁提供的一次触摸事件是怎么从硬件走到应用层的这篇文章把 Linux Input 子系统最核心的链路梳理一下。1. 先看懂 Linux Input 子系统整体架构先不要急着看代码。Linux Input 子系统最核心的链路其实就是Hardware │ ▼ Input Device Driver │ ▼ Input Core │ ▼ Input Handler │ ▼ evdev │ ▼ /dev/input/eventX │ ▼ Userspace对于一个触摸屏就是Touch IC ↓ Touch Driver ↓ Input Core ↓ evdev ↓ /dev/input/eventX ↓ libinput / Weston / Qt图 1Linux Input 子系统整体架构这张图其实已经把 Input 子系统最重要的几个角色分开了。Input Device Driver这一层是真正和硬件打交道的。例如Goodix Touch GPIO Keys USB Mouse USB Keyboard HID Touch常见代码目录drivers/input/touchscreen/ drivers/input/keyboard/ drivers/hid/驱动负责I2C / SPI / USB 通信 ↓ 响应 IRQ ↓ 读取硬件数据 ↓ 解析坐标 / 按键 ↓ 上报 Input Event例如触摸驱动经常能看到input_report_abs(input_dev,ABS_MT_POSITION_X,x);input_report_abs(input_dev,ABS_MT_POSITION_Y,y);input_report_key(input_dev,BTN_TOUCH,1);input_sync(input_dev);这里要注意Touch Driver 并不是直接把数据发给 Qt也不是自己直接实现一个应用层输入接口。它只负责把输入事件上报给 Linux Input 子系统。2. Input CoreInput 子系统的中间核心Input Core 主要代码位于drivers/input/input.c它处在 Device Driver 和 Input Handler 中间。可以简单理解成Input Device │ ▼ Input Core │ ▼ Input Handler设备驱动一般会创建structinput_dev例如structinput_dev*input_dev;input_devdevm_input_allocate_device(dev);然后配置设备名称input_dev-nameMy Touchscreen;再描述设备支持什么输入能力input_set_abs_params(input_dev,ABS_MT_POSITION_X,0,1920,0,0);input_set_abs_params(input_dev,ABS_MT_POSITION_Y,0,1080,0,0);最后input_register_device(input_dev);这一步非常关键。可以简单理解成Touch Driver │ ▼ struct input_dev │ ▼ input_register_device() │ ▼ Input Core也就是告诉 Input Core系统现在出现了一个新的输入设备。3. evdev 到底是什么接下来就是最容易混淆的地方。evdev的代码主要位于drivers/input/evdev.c很多人第一次看到evdev容易把它理解成event device driver然后自然把它和 Touch Driver、Mouse Driver 放在同一个层次。实际上并不是。evdev本质上是一个structinput_handler核心结构类似staticstructinput_handlerevdev_handler{.eventevdev_event,.eventsevdev_events,.connectevdev_connect,.disconnectevdev_disconnect,.nameevdev,.id_tableevdev_ids,};然后注册到 Input Coreinput_register_handler(evdev_handler);因此 Linux Input 子系统中存在两类非常重要的对象Input Device Input Handler一个来源于具体设备驱动input_register_device()一个来源于事件处理器input_register_handler()然后由 Input Core 把它们匹配起来。所以一句话记住evdev 不是 Touch、Mouse、Keyboard 这样的硬件 Driver而是 Linux Input 子系统中的通用 Event Handler。它最重要的作用就是把内核 Input Event 暴露成用户空间可以读取的 event 接口。4. /dev/input/eventX 到底是怎么来的这是理解整个 Input 子系统非常关键的一步。很多人刚开始可能会认为Touch Driver ↓ 创建 /dev/input/eventX实际上这并不准确。更加完整的关系是Driver Probe ↓ 创建 input_dev ↓ 设置 Input Capability ↓ input_register_device() ↓ Input Core ↓ 匹配 evdev_handler ↓ evdev_connect() ↓ /dev/input/eventX图 2Input 设备注册与 eventX 形成过程这一张图主要解决一个问题到底是谁把一个硬件输入设备变成了/dev/input/eventX驱动首先申请structinput_dev然后告诉 Input Core我是一个输入设备 我支持哪些 Event Type 我支持哪些 Key 我支持哪些 ABS 坐标例如input_set_capability(input_dev,EV_KEY,KEY_POWER);或者input_set_abs_params();最后input_register_device(input_dev);设备进入 Input Core 后Input Core 会尝试匹配已经注册的 Handler。例如input_dev │ ├──── evdev_handler ├──── mousedev └──── 其他 handler当匹配到evdev_handler后会进入类似evdev_connect()这样的连接流程。内部还会建立structinput_handle可以简单理解为input_dev │ │ input_handle │ evdev_handler也就是input_handle建立 Input Device 和 Input Handler 之间的连接关系。最终evdev为这个 Input Device 提供 event 字符设备接口。用户空间就能够通过/dev/input/eventX访问它。严格一点说/dev/input/eventX是内核字符设备向用户空间暴露出来的设备节点通常由devtmpfs/udev等机制呈现在/dev/input/下并不是 Qt 或普通用户应用自己创建的。5. 一次触摸事件到底是怎么走的设备节点创建出来只是第一步。第二个更重要的问题是真正发生一次触摸以后事件是怎么走到用户空间的假设用户点击一下触摸屏。首先 Touch IC 产生中断Touch IC ↓ IRQ驱动进入中断处理流程然后通过 I2C 或 SPI 读取X Y Touch State Pressure接下来驱动开始上报事件input_report_abs(input_dev,ABS_MT_POSITION_X,x);input_report_abs(input_dev,ABS_MT_POSITION_Y,y);input_report_key(input_dev,BTN_TOUCH,1);input_sync(input_dev);最终事件会经过Touch Driver ↓ Input Core ↓ evdev ↓ /dev/input/eventX ↓ Userspace图 3一次触摸事件的完整流转这张图特别适合理解三个角色各自负责什么Driver ↓ 负责产生、上报事件 Input Core ↓ 负责统一管理和分发事件 evdev ↓ 负责把事件提供给用户空间这就是 Input 子系统最核心的分层思想。6. input_report_abs() 到底上报了什么触摸驱动中非常常见input_report_abs()例如input_report_abs(dev,ABS_MT_POSITION_X,520);input_report_abs(dev,ABS_MT_POSITION_Y,980);最终表达的就是Event Type EV_ABS Event Code ABS_MT_POSITION_X Value 520Input 子系统最终把各种设备输入统一描述成type code value例如触摸屏 鼠标 键盘 GPIO Key Gamepad硬件完全不同但是进入 Input Core 后都会逐渐被抽象成统一的 Input Event。这也是 Linux Input 子系统非常重要的设计思想。7. eventX 里面的数据到底是什么应用程序从/dev/input/eventX读取出来的数据本质上就是structinput_event主要定义在include/uapi/linux/input.h结构大致如下structinput_event{structtimevaltime;__u16 type;__u16 code;__s32 value;};真正理解的时候重点看三个字段type code value例如type EV_KEY code KEY_POWER value 1表示Power Key Press如果value 0则表示Power Key Release触摸坐标也是一样type EV_ABS code ABS_MT_POSITION_X value 800就是X 8008. 常见 Input EventInput 子系统中比较常用的几个类型Event含义常见设备EV_KEY按键事件Keyboard、GPIO Key、TouchEV_REL相对坐标MouseEV_ABS绝对坐标TouchscreenEV_SYN同步事件Input Event 同步EV_SW开关状态Lid、机械开关EV_MSC其他事件特殊输入设备例如鼠标一般报告EV_REL REL_X REL_Y因为鼠标关心的是移动了多少例如X 5 Y -2而触摸屏报告EV_ABS ABS_MT_POSITION_X ABS_MT_POSITION_Y因为它关心的是现在触摸在哪里例如X 800 Y 5009. 为什么最后还要 input_sync()驱动中经常看到input_sync(input_dev);例如input_report_abs(dev,ABS_X,x);input_report_abs(dev,ABS_Y,y);input_report_key(dev,BTN_TOUCH,1);input_sync(dev);一次触摸可能包含很多信息X Y Pressure Touch State Slot Tracking ID它们其实属于同一帧输入状态。因此最后需要input_sync()告诉 Input 子系统这一组事件已经上报完成。用户空间通常就会看到EV_SYN SYN_REPORT例如EV_ABS ABS_MT_POSITION_X 520 EV_ABS ABS_MT_POSITION_Y 980 EV_KEY BTN_TOUCH 1 EV_SYN SYN_REPORT 0可以简单理解为坐标 X 坐标 Y 按下状态 ────── 这一帧结束10. evdev 和 libinput 不要混淆到了这里还要特别区分两个经常同时出现的名字evdev libinput它们不属于同一层。整体关系是Kernel ────────────────────────── Touch Driver │ ▼ Input Core │ ▼ evdev │ ▼ /dev/input/eventX ────────────────────────── Userspace │ ▼ libinput │ ▼ Weston / Wayland │ ▼ Qt / Application所以evdev 属于 Linux Kernel。而libinput 属于用户空间。libinput通常进一步处理Touch Mouse Keyboard Touchpad然后提供给Weston Wayland compositor Xorg Desktop EnvironmentQt 应用通常不需要自己直接解析/dev/input/eventX而是通过上层输入体系获得事件。11. 做 BSP 时怎么调试 Input理解前面的架构以后实际排查就很简单了。假设现在出现触摸屏完全没有反应。不要一上来就怀疑 Qt。按照 Input 链路一步一步往上查。图 4Linux Input 问题排查流程基本思路就是Driver ↓ Input Device ↓ eventX ↓ Input Event ↓ libinput / Weston / Qt第一步先看驱动有没有 Probe例如dmesg|grep-itouch或者根据具体驱动dmesg|grep-igoodix如果 Driver Probe 都失败了就优先检查DTS Power Reset I2C / SPI IRQ GPIO Driver这时候还完全轮不到 Weston 和 Qt。第二步Input Device 有没有注册查看cat/proc/bus/input/devices例如N: NameGoodix Capacitive TouchScreen P: Physinput/ts H: Handlersevent2 B: EVb B: ABS2658000这里最值得关注的是Handlersevent2说明这个 Input Device 已经对应到了/dev/input/event2第三步event 节点有没有出现执行ls-l/dev/input/例如event0 event1 event2 event3也可以结合cat/proc/bus/input/devices确定哪个eventX对应触摸设备。第四步直接使用 evtestInput 调试中非常实用evtest选择设备或者直接evtest /dev/input/event2点击屏幕。如果正常会看到类似Event: type 3 (EV_ABS), code 53 (ABS_MT_POSITION_X), value 850 Event: type 3 (EV_ABS), code 54 (ABS_MT_POSITION_Y), value 420 Event: type 0 (EV_SYN), code 0 (SYN_REPORT), value 0如果这里可以稳定看到ABS_MT_POSITION_X ABS_MT_POSITION_Y SYN_REPORT基本可以判断Touch Hardware ↓ Touch Driver ↓ Input Core ↓ evdev ↓ eventX这一整条 Kernel Input 链路已经正常。第五步eventX 正常但界面没有反应这个时候问题范围已经大幅缩小。重点继续检查libinput Weston Wayland Qt 坐标变换 屏幕旋转 Input Device Mapping而不是继续在 Touch Driver 里面盲目修改代码。这个判断在 BSP 调试中其实非常实用。12. 从源码角度重点跟两条链路就够了Linux Input 子系统代码很多但真正想理解核心原理我认为没必要一开始把整个drivers/input/都读完。先跟两条链路就够了。第一条设备是怎么注册进来的Driver Probe ↓ devm_input_allocate_device() ↓ 配置 input_dev ↓ input_register_device() ↓ Input Core ↓ 匹配 evdev_handler ↓ evdev_connect() ↓ /dev/input/eventX这一条主要解决eventX 是怎么来的第二条事件是怎么传出去的Touch IRQ ↓ Touch Driver ↓ input_report_abs() input_report_key() input_sync() ↓ Input Core ↓ evdev ↓ /dev/input/eventX ↓ Userspace这一条主要解决一次输入事件到底怎么从硬件走到应用层这两条链路搞清楚Input 子系统的大框架基本就建立起来了。13. 最后总结Linux Input 子系统如果只保留最核心的部分其实就是三层Input Device Driver │ ▼ Input Core │ ▼ Input Handler其中Input Device Driver负责硬件通信 IRQ 读取数据 解析输入 上报事件Input Core负责Input Device 管理 Input Handler 管理 设备与 Handler 匹配 Input Event 分发evdev属于Input Handler主要负责Kernel Input Event ↓ event 字符设备 ↓ /dev/input/eventX ↓ Userspace因此整篇文章最核心的一句话就是evdev 不是 Touch、Mouse、Keyboard 这样的硬件驱动而是 Linux Input 子系统中的通用 Event Handler。最终完整链路Hardware ↓ Input Device Driver ↓ Input Core ↓ evdev ↓ /dev/input/eventX ↓ libinput / Weston ↓ Qt / Application以后碰到触摸没反应、按键失效、Qt 收不到输入事件也可以直接沿着这条链路排查硬件正常吗 ↓ Driver Probe 正常吗 ↓ input_dev 注册了吗 ↓ eventX 出来了吗 ↓ evtest 有事件吗 ↓ libinput / Weston 正常吗 ↓ Application把这条线建立起来以后Linux Input 子系统其实就没有想象中那么复杂了。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →