news 2026/7/5 13:10:48

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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_ENTERBTN_LEFT
  • value:事件值,对于按键来说 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_ENTER,x86 上的用户空间程序能正确识别。这就是 Input 子系统的好处——统一的按键代码映射。

实际驱动中的事件报告

让我们看一下实际驱动中的消抖工作函数:

staticvoiddebounce_work_handler(structwork_struct*work){structdelayed_work*dwork=to_delayed_work(work);structinput_key_dev*dev=container_of(dwork,structinput_key_dev,debounce_work);intcurrent_state;unsignedlongflags;/* 读取 GPIO 逻辑状态 */current_state=key_hw_get_raw_state(dev->gpio);spin_lock_irqsave(&dev->lock,flags);/* 只有状态变化才报告事件 */if(current_state!=dev->last_state){dev->last_state=current_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_state=1表示按键被按下,current_state=0表示按键被松开。我们直接把这个值传给input_report_key(),不需要再反转。

其次是用自旋锁保护last_state。中断处理函数和工作队列可能同时访问这个变量,不加锁会有竞态条件。虽然对于单个按键这种竞态可能不会造成致命问题,但正确做法还是加锁。

补充:为什么current_state可以直接传给input_report_key()?因为gpiod_get_value()已经应用了 GPIO_ACTIVE_LOW 标志。如果设备树中指定了GPIO_ACTIVE_LOW,按下按键时物理电平是 0,但gpiod_get_value()返回 1(逻辑值"按下")。

事件的传递路径

报告事件后,事件是如何到达用户空间的?让我们追踪一下路径:

  1. 驱动调用input_report_key()→ 调用input_event()
  2. Input Core 处理input_handle_event()把事件放入缓冲区
  3. 唤醒 Handler→ evdev Handler 被唤醒
  4. 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(),这样用户空间能区分不同的按键事件。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/5 13:09:19

2:IDEA中git的使用--基础操作

1. 创建项目 首先在git bash 中clone项目到本地,然后使用IDEA打开: git clone gitgithub.com:xxxx.git2. Git 界面 2.1 当前分支 和 Commit 左上角可以看到当前项目名称和当前分支。Changes:表示有改动的文件,包括修改、新增、删除…

作者头像 李华
网站建设 2026/7/5 13:02:04

2026年分布式GEO代理流量调度:源码级状态机防重挂实战

一、引言与生产环境痛点随着 2026 年企业级 GEO 优化需求的持续深化,多级代理的流量分发与结算体系正面临前所未有的高并发挑战。在分布式环境下,一次 GEO 代理任务的创建可能触发多个下游服务的状态变更,而网络抖动或服务超时极易导致“任务…

作者头像 李华
网站建设 2026/7/5 13:00:56

掌握数据库连接池与事务管理优化

掌握数据库连接池与事务管理优化:构建高性能应用的关键支柱在现代企业级应用开发中,数据库作为数据存储与管理的核心组件,其性能表现直接关系到整个系统的响应速度与稳定性。而数据库连接池与事务管理作为数据库访问层的两大关键技术&#xf…

作者头像 李华
网站建设 2026/7/5 12:59:30

Altium Designer 26自动布线规则配置与优化实践

1. Altium Designer 26自动布线基础认知PCB自动布线是电子设计自动化(EDA)领域的重要功能模块,Altium Designer 26作为行业领先的EDA工具,其自动布线系统经过多年迭代已形成完整的规则驱动型布线架构。与传统手动布线相比,自动布线能显著提升…

作者头像 李华
网站建设 2026/7/5 12:58:36

嵌入式6DoF运动追踪:IIM-42652 IMU与PIC18微控制器的实践指南

1. 项目背景与核心组件解析 在嵌入式系统开发领域,运动追踪技术正经历着从基础3D感知到完整6自由度(6DoF)测量的演进。这个转变的核心在于如何通过硬件组合实现空间姿态的精确捕捉。IIM-42652作为TDK InvenSense推出的6轴惯性测量单元(IMU),集成了3轴陀螺…

作者头像 李华