3. Linux输入子系统
在Linux中,触摸屏、按键、鼠标、键盘、游戏手柄等设备并不是各自为政地暴露接口,而是统一纳入输入子系统(Input Subsystem)管理。对触摸驱动开发者而言,理解input子系统是打通“硬件中断→内核事件→用户空间”链路的关键。本节从架构、设备节点、事件类型与事件码、evdev接口以及用户空间读取方法五个层面展开。
一、input子系统整体架构
input子系统采用典型的分层设计,自上而下可分为三层:
- 输入事件处理层(Event Handler Layer):面向用户空间,提供evdev、joydev、mousedev等接口,其中evdev是触摸屏最常用的接口,对应
/dev/input/eventX。 - 输入核心层(Input Core Layer):由
drivers/input/input.c实现,负责注册input设备、分配设备号、维护事件队列,并在设备与handler之间做匹配和事件分发。 - 输入设备驱动层(Input Device Driver Layer):具体硬件驱动,如
goodix_ts、ft5x06、atmel_mxt_ts等,负责从I2C/SPI读取原始坐标,再通过input_report_abs()等API上报事件。
三层之间通过input_dev结构体与input_handler结构体解耦。驱动只需关心“我有什么能力、我上报什么事件”,而无需关心用户空间如何读取。
二、设备节点与设备识别
当触摸驱动成功注册后,内核会在/dev/input/下生成字符设备节点,常见有:
/dev/input/event0、event1……:evdev接口,原始事件流,触摸屏首选。/dev/input/mouse0:mousedev接口,模拟鼠标相对坐标。/dev/input/js0:joydev接口,用于游戏手柄。
由于eventX编号不固定,实际开发中应通过/sys/class/input/eventX/device/name或evtest工具确认设备。例如:
# 查看所有输入设备名称
cat /proc/bus/input/devices
# 查看某个event对应的设备名
cat /sys/class/input/event2/device/name
# 输出示例:Goodix Capacitive TouchScreen
# 使用 evtest 交互式查看事件
evtest /dev/input/event2
libinput或udev规则按设备名、VID/PID绑定固定符号链接,避免eventX漂移导致应用读错设备。三、事件类型与事件码
input子系统将所有输入抽象为“类型(type)+ 码(code)+ 值(value)”三元组。触摸屏最常用的事件类型如下表:
| 事件类型 | 宏定义 | 典型事件码 | 说明 |
|---|---|---|---|
| 同步事件 | EV_SYN |
SYN_REPORT |
一次完整上报的结束标志 |
| 按键事件 | EV_KEY |
BTN_TOUCH、BTN_TOOL_FINGER |
触摸按下/抬起、工具类型 |
| 绝对坐标 | EV_ABS |
ABS_MT_POSITION_X/Y、ABS_MT_SLOT、ABS_MT_TRACKING_ID |
多点触摸坐标与轨迹 |
| 相对坐标 | EV_REL |
REL_X、REL_Y |
鼠标类相对位移 |
| 杂项事件 | EV_MSC |
MSC_SCAN |
扫描码等辅助信息 |
多点触摸协议分为Type A与Type B。现代触摸屏普遍采用Type B:通过ABS_MT_SLOT指定触点槽位,用ABS_MT_TRACKING_ID标识触点生命周期,最后以SYN_REPORT提交一帧。驱动中典型上报序列如下:
input_mt_slot(ts->input_dev, slot);
input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, true);
input_report_abs(ts->input_dev, ABS_MT_POSITION_X, x);
input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, y);
input_sync(ts->input_dev); // 等价于上报 EV_SYN / SYN_REPORT
四、evdev接口与用户空间读取方法
evdev是用户空间访问输入设备的标准接口。每个eventX节点支持read()、poll()、epoll(),读取到的数据是struct input_event数组:
struct input_event {
struct timeval time; // 事件时间戳
__u16 type; // 事件类型,如 EV_ABS
__u16 code; // 事件码,如 ABS_MT_POSITION_X
__s32 value; // 事件值
};
用户空间读取的典型流程为:打开设备→查询能力→循环读取→解析事件→处理坐标。下面是一个最小可用的C示例:
#include <linux/input.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main(void) {
int fd = open("/dev/input/event2", O_RDONLY);
if (fd < 0) { perror("open"); return -1; }
struct input_event ev;
while (read(fd, &ev, sizeof(ev)) == sizeof(ev)) {
if (ev.type == EV_ABS && ev.code == ABS_MT_POSITION_X)
printf("X = %d\n", ev.value);
if (ev.type == EV_SYN && ev.code == SYN_REPORT)
printf("--- frame end ---\n");
}
close(fd);
return 0;
}
若使用Python,可借助evdev库快速验证:
from evdev import InputDevice, categorize, ecodes
dev = InputDevice('/dev/input/event2')
for event in dev.read_loop():
if event.type == ecodes.EV_ABS:
print(categorize(event))
五、常见误区与排障复盘
误区一:直接读/dev/input/eventX而不判断设备能力。不同触摸屏支持的事件码不同,应先通过EVIOCGBIT ioctl查询EV_ABS能力位,确认是否支持ABS_MT_POSITION_X,否则可能读到空事件或解析错误。
误区二:忽略SYN_REPORT。一帧多点数据由多个input_event组成,必须以SYN_REPORT为边界提交,否则会出现坐标撕裂或触点错位。
误区三:权限不足。普通用户默认无权读取/dev/input/eventX,需通过udev规则或input用户组授权,否则open()返回EACCES。
排障时可按以下顺序定位:先用evtest确认内核是否上报事件;再用hexdump /dev/input/event2观察原始字节流;最后检查应用层解析逻辑与权限配置。掌握这条链路,就能把触摸硬件、内核驱动与上层图形栈真正串起来。