一、什么是 Input 子系统
内容
隐藏
Input 子系统是 Linux 内核专为人机交互输入设备设计的通用驱动框架,统一管理:
- 按键 / 键盘(EV_KEY)
- 鼠标 / 轨迹球(EV_REL 相对坐标)
- 触摸屏 / 游戏手柄(EV_ABS 绝对坐标)
- 加速度计(用于屏幕旋转)(EV_ABS)
与 miscdevice 方案对比
| 维度 | miscdevice | Input 子系统 |
|---|---|---|
| 用户接口 | /dev/xxx,自定义协议 | /dev/input/eventX,标准 input_event 结构 |
| 数据获取 | 用户主动轮询(sleep+read) | 内核主动上报,用户 read() 阻塞等待 |
| 工具生态 | 无 | evtest、libinput、Android InputReader 直接可用 |
| 电源效率 | 轮询消耗 CPU | 事件驱动,无事件时零 CPU 占用 |
| 驱动代码量 | 需实现 file_operations | 只需 input_report_* + input_sync |
| 适用设备 | 任意字符设备 | 人机交互输入设备 |
二、架构概览
┌────────────────────────────────────────────┐
│ 用户空间 │
│ read(/dev/input/eventX) → struct input_event│
│ evtest / libinput / Android InputReader │
└────────────────────┬───────────────────────┘
│ evdev 字符设备
┌────────────────────▼───────────────────────┐
│ Input 核心层(input.c) │
│ input_register_device │
│ input_handle_event → 分发给所有 handler │
└──────────┬─────────────────┬───────────────┘
│ evdev handler │ 其他 handler
┌──────▼──────┐ ┌──────▼──────┐
│ evdev.c │ │ kbd.c │
│ /dev/input/ │ │ 虚拟终端键盘 │
│ eventX │ └─────────────┘
└─────────────┘
┌────────────────────────────────────────────┐
│ 具体输入驱动 │
│ key_input.c touchscreen.c mouse.c ... │
└────────────────────┬───────────────────────┘
│ GPIO / I²C / USB
┌────────────────────▼───────────────────────┐
│ 硬件 │
│ 按键 / 触摸屏 / 鼠标 ... │
└────────────────────────────────────────────┘
三、核心数据结构
3.1 struct input_dev — 输入设备描述符
struct input_dev {
const char *name; /* 设备名,出现在 /sys/.../name */
const char *phys; /* 物理路径(可选)*/
struct device dev; /* 内嵌设备,连接 sysfs */
unsigned long evbit[...]; /* 支持的事件类型位图 */
unsigned long keybit[...]; /* 支持的键码位图(EV_KEY)*/
unsigned long relbit[...]; /* 支持的相对轴位图(EV_REL)*/
unsigned long absbit[...]; /* 支持的绝对轴位图(EV_ABS)*/
/* ... */
};
关键 API:
/* 分配(devm 版,设备移除时自动释放+注销)*/
dev->idev = devm_input_allocate_device(&pdev->dev);
/* 声明设备支持的事件能力 */
input_set_capability(idev, EV_KEY, KEY_0);
/* 等价于手动设置位图:
set_bit(EV_KEY, idev->evbit);
set_bit(KEY_0, idev->keybit); */
/* 注册,创建 /dev/input/eventX */
ret = input_register_device(idev);
/* 注销(devm 版自动调用)*/
input_unregister_device(idev);
3.2 struct input_event — 用户空间事件结构
用户空间通过 read() 从 /dev/input/eventX 获取的数据格式:
struct input_event {
struct timeval time; /* 事件时间戳 */
__u16 type; /* 事件类型:EV_KEY / EV_REL / EV_ABS / EV_SYN */
__u16 code; /* 事件代码:KEY_0 / REL_X / ABS_X ... */
__s32 value; /* 事件值:1=按下 0=释放 / 相对位移 / 绝对坐标 */
};
3.3 数据结构全景类图
classDiagram
class input_dev {
+char *name
+unsigned long evbit[]
+unsigned long keybit[]
+unsigned long absbit[]
+struct device dev
}
class input_handler {
+connect()
+disconnect()
+event()
+char *name
}
class input_handle {
+input_dev *dev
+input_handler *handler
+list_head d_node
+list_head h_node
}
class evdev {
<<input_handler>>
+创建 /dev/input/eventX
}
class DriverDev {
<<具体驱动私有数据>>
+input_dev *idev
+gpio_desc *gpiod
+timer_list timer
}
class UserSpace {
<<用户空间>>
+struct input_event
+read() 阻塞
}
input_dev "1" --> "N" input_handle : 连接多个 handler
input_handler <|-- evdev : 实现
input_handle --> input_dev : .dev
input_handle --> input_handler : .handler
DriverDev --> input_dev : .idev 指针
evdev ..> UserSpace : /dev/input/eventX四、事件类型与键码
4.1 常用事件类型(type 字段)
| 类型 | 值 | 说明 | value 含义 |
|---|---|---|---|
EV_SYN | 0x00 | 同步事件,一组事件的结束标志 | 0 |
EV_KEY | 0x01 | 按键 / 按钮事件 | 1=按下,0=释放,2=长按重复 |
EV_REL | 0x02 | 相对坐标(鼠标移动) | 位移量(有符号) |
EV_ABS | 0x03 | 绝对坐标(触摸屏) | 绝对位置 |
EV_MSC | 0x04 | 杂项事件 | — |
EV_SW | 0x05 | 开关状态(耳机插拔等) | 1/0 |
EV_LED | 0x11 | LED 状态 | 1/0 |
4.2 常用键码(code 字段,EV_KEY 类型)
| 键码 | 值 | 说明 |
|---|---|---|
KEY_0~KEY_9 | 11~19 | 数字键 |
KEY_ENTER | 28 | 回车 |
KEY_ESC | 1 | Esc |
KEY_POWER | 116 | 电源键 |
KEY_VOLUMEUP | 115 | 音量加 |
KEY_VOLUMEDOWN | 114 | 音量减 |
KEY_HOME | 102 | Home 键 |
BTN_LEFT | 0x110 | 鼠标左键 |
BTN_TOUCH | 0x14a | 触摸屏触碰 |
所有键码定义在
include/uapi/linux/input-event-codes.h
4.3 EV_SYN 同步机制
每次 input_sync() 会向事件队列追加一个 EV_SYN/SYN_REPORT 事件,
告诉用户空间”本轮事件上报完毕”,可以处理了。
事件队列(用户 read 得到的顺序):
EV_KEY KEY_0 1 ← 按下
EV_SYN SYN_REPORT 0 ← 同步(input_sync 产生)
EV_KEY KEY_0 0 ← 释放
EV_SYN SYN_REPORT 0 ← 同步
五、驱动开发三步走
Step 1:分配并配置 input_dev
dev->idev = devm_input_allocate_device(&pdev->dev);
dev->idev->name = "my-keys";
dev->idev->dev.parent = &pdev->dev;
/* 声明支持 EV_KEY 类型,以及具体的键码 */
input_set_capability(dev->idev, EV_KEY, KEY_POWER);
input_set_capability(dev->idev, EV_KEY, KEY_VOLUMEUP);
Step 2:注册设备
ret = input_register_device(dev->idev);
/* 成功后自动创建 /dev/input/eventX */
Step 3:在事件触发时上报
/* 在 IRQ 或定时器回调中调用 */
input_report_key(dev->idev, KEY_POWER, 1); /* 按下 */
input_sync(dev->idev); /* 提交事件 */
input_report_key(dev->idev, KEY_POWER, 0); /* 释放 */
input_sync(dev->idev);
六、用户空间读取方式
6.1 直接 read
int fd = open("/dev/input/event0", O_RDONLY);
struct input_event ev;
while (read(fd, &ev, sizeof(ev)) == sizeof(ev)) {
if (ev.type == EV_KEY)
printf("key %d %sn", ev.code,
ev.value ? "pressed" : "released");
}
6.2 使用 evtest 工具
evtest /dev/input/event0
# 输出示例:
# Event: time 1234.567, type 1 (EV_KEY), code 11 (KEY_0), value 1
# Event: time 1234.567, type 0 (EV_SYN), code 0 (SYN_REPORT), value 0
6.3 查找设备节点
# 通过名称找对应的 eventX
cat /sys/class/input/event*/device/name
grep -l "my-keys" /sys/class/input/event*/device/name
七、input_set_capability vs 手动设置位图
两者等价,推荐使用 input_set_capability:
/* 方式 A:推荐 */
input_set_capability(idev, EV_KEY, KEY_0);
/* 方式 B:手动(底层实现)*/
__set_bit(EV_KEY, idev->evbit);
__set_bit(KEY_0, idev->keybit);
对于触摸屏,需要设置绝对轴范围:
input_set_capability(idev, EV_ABS, ABS_X);
input_set_abs_params(idev, ABS_X, 0, 1023, 0, 0);
/* 参数:轴, 最小值, 最大值, fuzz(滤波), flat(死区) */
八、常见问题
Q1:/dev/input/eventX 没有出现
input_register_device返回非零,检查evbit/keybit是否设置input_set_capability在input_register_device之前调用
Q2:用户空间 read 立即返回空数据
- 忘记调用
input_sync(),事件没有提交 - 事件类型/键码未通过
input_set_capability声明,被核心过滤
Q3:evtest 报告 “Permission denied”
chmod a+r /dev/input/eventX
# 或加入 input 用户组
Q4:如何区分多个 Input 设备
通过 sysfs 的 name 属性:
cat /sys/class/input/event0/device/name # 输出设备名
九、参考资料
Documentation/input/— 内核官方 Input 文档drivers/input/keyboard/gpio_keys.c— 标准 GPIO 按键驱动参考include/uapi/linux/input-event-codes.h— 完整事件类型和键码表- 20_Key_Input子系统驱动开发 — Input 驱动实战
延伸阅读
Input 子系统的抽象可通过新版按键驱动验证,并与传统 miscdevice 方案对照。
- 返回嵌入式 Linux 学习路线,确认本文在完整知识结构中的位置。
- Key Input 子系统驱动开发
- Key Platform 驱动开发(中断与定时器消抖)




