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结构体解耦。驱动只需关心“我有什么能力、我上报什么事件”,而无需关心用户空间如何读取。

Linux input 子系统分层架构与事件流向 用户空间 User Space read() / poll() / epoll() libinput / tslib / Qt /dev/input/eventX 输入事件处理层 Event Handler Layer evdev joydev mousedev sysfs 属性 输入核心层 Input Core input_register_device() 事件队列与分发 input_dev / handler 匹配 输入设备驱动层 Input Device Driver I2C/SPI 触摸驱动 GPIO 按键驱动 USB HID 驱动 input_report_abs() input_event 分发 evdev 写入缓冲区 open / read handler 注册 driver probe

二、设备节点与设备识别

当触摸驱动成功注册后,内核会在/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观察原始字节流;最后检查应用层解析逻辑与权限配置。掌握这条链路,就能把触摸硬件、内核驱动与上层图形栈真正串起来。