input_report_key + input_sync:按键事件的正确报告姿势

📅 2026/7/5 13:10:56
input_report_key + input_sync:按键事件的正确报告姿势
input_report_key input_sync按键事件的正确报告姿势这个仓库已经开源所有教程主线内核移植跑新版本imx-linux/uboot都在这里或者一起来尝试跑7.1的Linux欢迎各位大佬观摩喜欢的话点个⭐昨天刚更新仓库地址https://github.com/Awesome-Embedded-Learning-Studio/imx-forge静态网页https://awesome-embedded-learning-studio.github.io/imx-forge/上一章我们讲了 Input 子系统的架构知道驱动需要通过input_event()报告事件。这一章我们深入看一下事件报告的具体实现。说实话这部分代码虽然不多但细节还挺多的用错了可能导致用户空间收不到事件或者收到错误的事件。input_event底层事件报告函数input_event()是事件报告的核心函数内核定义如下voidinput_event(structinput_dev*dev,unsignedinttype,unsignedintcode,intvalue);三个参数的含义type事件类型如EV_KEY按键、EV_REL相对坐标code事件代码如KEY_ENTER、BTN_LEFTvalue事件值对于按键来说 1按下0松开补充按键的 value 只有 0 和 1但其他事件类型可能有更多值。比如EV_REL的 value 是移动量可以是正负EV_ABS的 value 是绝对坐标值。调用input_event()前要确保设备支持该事件类型否则会被忽略/* 确保设备支持 EV_KEY */set_bit(EV_KEY,dev-input_dev-evbit);set_bit(KEY_ENTER,dev-input_dev-keybit);/* 现在可以安全报告事件 */input_event(dev-input_dev,EV_KEY,KEY_ENTER,1);如果你报告了一个设备不支持的事件Input Core 会默默忽略。这个设计有点坑——你不会收到任何错误提示只是事件不生效。调试的时候如果发现事件没有传递到用户空间第一件事就是检查设备能力是否正确设置。input_report_key便利宏对于按键事件Input 子系统提供了便利宏input_report_key()voidinput_report_key(structinput_dev*dev,unsignedintcode,intvalue){input_event(dev,EV_KEY,code,value);}这个宏其实就是input_event()的封装省去了写EV_KEY的麻烦。实际使用/* 报告 Enter 键按下 */input_report_key(dev-input_dev,KEY_ENTER,1);/* 报告 Enter 键松开 */input_report_key(dev-input_dev,KEY_ENTER,0);提示类似的便利宏还有input_report_rel()相对坐标、input_report_abs()绝对坐标等对应不同的事件类型。input_sync事件同步点报告事件后必须调用input_sync()标记同步点voidinput_sync(structinput_dev*dev){input_event(dev,EV_SYN,SYN_REPORT,0);}EV_SYN是同步事件类型SYN_REPORT是一批事件结束的标记。为什么需要这个因为一次硬件动作可能产生多个事件比如移动鼠标会同时报告 X 和 Y 的相对移动量。input_sync()告诉子系统这一批事件结束了确保用户空间原子地收到所有相关事件。⚠️ 注意忘记调用input_sync()是常见的错误。没有同步点事件可能会堆积在缓冲区里用户空间 read() 可能阻塞或者收到延迟的事件。完整的按键事件报告流程/* 按键按下 */input_report_key(dev-input_dev,KEY_ENTER,1);input_sync(dev-input_dev);/* 按键松开 */input_report_key(dev-input_dev,KEY_ENTER,0);input_sync(dev-input_dev);按键代码KEY_ENTER 从哪来的我们一直在用KEY_ENTER这个常量定义在哪里它在内核头文件include/uapi/linux/input-event-codes.h#defineKEY_RESERVED0#defineKEY_ESC1#defineKEY_12#defineKEY_23/* ... 数百个按键定义 ... */#defineKEY_ENTER28#defineKEY_LEFTCTRL29/* ... */这个文件定义了所有标准的按键代码包括键盘键、鼠标按钮、游戏手柄按钮等。常用的有KEY_ENTER/* 回车键代码 28 */KEY_ESC/* ESC 键代码 1 */KEY_1~KEY_9/* 数字键 1-9 */KEY_LEFTSHIFT/* 左 Shift代码 42 */BTN_LEFT/* 鼠标左键代码 272 */BTN_RIGHT/* 鼠标右键代码 273 */提示按键代码是跨平台的标准。你在 ARM 平台上报告KEY_ENTERx86 上的用户空间程序能正确识别。这就是 Input 子系统的好处——统一的按键代码映射。实际驱动中的事件报告让我们看一下实际驱动中的消抖工作函数staticvoiddebounce_work_handler(structwork_struct*work){structdelayed_work*dworkto_delayed_work(work);structinput_key_dev*devcontainer_of(dwork,structinput_key_dev,debounce_work);intcurrent_state;unsignedlongflags;/* 读取 GPIO 逻辑状态 */current_statekey_hw_get_raw_state(dev-gpio);spin_lock_irqsave(dev-lock,flags);/* 只有状态变化才报告事件 */if(current_state!dev-last_state){dev-last_statecurrent_state;/* input_report_key 会自动处理 GPIO_ACTIVE_LOW */input_report_key(dev-input_dev,KEY_ENTER,current_state);input_sync(dev-input_dev);}spin_unlock_irqrestore(dev-lock,flags);}这里有几个重要的细节。首先是key_hw_get_raw_state()返回的是逻辑值考虑了 GPIO_ACTIVE_LOW所以current_state1表示按键被按下current_state0表示按键被松开。我们直接把这个值传给input_report_key()不需要再反转。其次是用自旋锁保护last_state。中断处理函数和工作队列可能同时访问这个变量不加锁会有竞态条件。虽然对于单个按键这种竞态可能不会造成致命问题但正确做法还是加锁。补充为什么current_state可以直接传给input_report_key()因为gpiod_get_value()已经应用了 GPIO_ACTIVE_LOW 标志。如果设备树中指定了GPIO_ACTIVE_LOW按下按键时物理电平是 0但gpiod_get_value()返回 1逻辑值按下。事件的传递路径报告事件后事件是如何到达用户空间的让我们追踪一下路径驱动调用input_report_key()→ 调用input_event()Input Core 处理→input_handle_event()把事件放入缓冲区唤醒 Handler→ evdev Handler 被唤醒Handler 唤醒用户空间→ 等待在 read() 的进程被唤醒整个过程是同步的input_report_key()返回时事件已经在内核缓冲区中了。但用户空间 read() 不一定会立即返回这取决于 read() 的调用方式阻塞还是非阻塞。常见错误忘记设置 keybit我们说过如果设备不支持某个事件类型报告事件会被忽略。一个常见的错误是/* 只设置了 EV_KEY */set_bit(EV_KEY,dev-input_dev-evbit);/* 但忘记设置 KEY_ENTER*//* set_bit(KEY_ENTER, dev-input_dev-keybit); *//* 报告事件 */input_report_key(dev-input_dev,KEY_ENTER,1);input_sync(dev-input_dev);/* 这个事件会被默默忽略*/⚠️ 注意evbit声明支持的事件类型keybit声明支持的按键代码。两个都要设置事件才能正确传递。正确做法/* 设置支持按键事件 */set_bit(EV_KEY,dev-input_dev-evbit);set_bit(KEY_ENTER,dev-input_dev-keybit);/* 现在可以报告事件 */input_report_key(dev-input_dev,KEY_ENTER,1);input_sync(dev-input_dev);报告多个按键如果你的设备有多个按键可以这样报告/* 设置设备能力 */set_bit(EV_KEY,dev-input_dev-evbit);set_bit(KEY_ENTER,dev-input_dev-keybit);set_bit(KEY_ESC,dev-input_dev-keybit);set_bit(KEY_1,dev-input_dev-keybit);/* 报告不同的按键 */input_report_key(dev-input_dev,KEY_ENTER,1);input_sync(dev-input_dev);input_report_key(dev-input_dev,KEY_ESC,1);input_sync(dev-input_dev);input_report_key(dev-input_dev,KEY_1,1);input_sync(dev-input_dev);每次报告一个按键后调用一次input_sync()这样用户空间能区分不同的按键事件。