Linux Input 子系统详解 文章封面

Linux Input 子系统详解

一、什么是 Input 子系统

Input 子系统是 Linux 内核专为人机交互输入设备设计的通用驱动框架,统一管理:

  • 按键 / 键盘(EV_KEY)
  • 鼠标 / 轨迹球(EV_REL 相对坐标)
  • 触摸屏 / 游戏手柄(EV_ABS 绝对坐标)
  • 加速度计(用于屏幕旋转)(EV_ABS)

与 miscdevice 方案对比

维度miscdeviceInput 子系统
用户接口/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_SYN0x00同步事件,一组事件的结束标志0
EV_KEY0x01按键 / 按钮事件1=按下,0=释放,2=长按重复
EV_REL0x02相对坐标(鼠标移动)位移量(有符号)
EV_ABS0x03绝对坐标(触摸屏)绝对位置
EV_MSC0x04杂项事件—
EV_SW0x05开关状态(耳机插拔等)1/0
EV_LED0x11LED 状态1/0

4.2 常用键码(code 字段,EV_KEY 类型)

键码值说明
KEY_0~KEY_911~19数字键
KEY_ENTER28回车
KEY_ESC1Esc
KEY_POWER116电源键
KEY_VOLUMEUP115音量加
KEY_VOLUMEDOWN114音量减
KEY_HOME102Home 键
BTN_LEFT0x110鼠标左键
BTN_TOUCH0x14a触摸屏触碰

所有键码定义在 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-&gt;idev = devm_input_allocate_device(&amp;pdev-&gt;dev);

dev-&gt;idev-&gt;name       = "my-keys";
dev-&gt;idev-&gt;dev.parent = &amp;pdev-&gt;dev;

/* 声明支持 EV_KEY 类型,以及具体的键码 */
input_set_capability(dev-&gt;idev, EV_KEY, KEY_POWER);
input_set_capability(dev-&gt;idev, EV_KEY, KEY_VOLUMEUP);

Step 2:注册设备

ret = input_register_device(dev-&gt;idev);
/* 成功后自动创建 /dev/input/eventX */

Step 3:在事件触发时上报

/* 在 IRQ 或定时器回调中调用 */
input_report_key(dev-&gt;idev, KEY_POWER, 1);  /* 按下 */
input_sync(dev-&gt;idev);                       /* 提交事件 */

input_report_key(dev-&gt;idev, KEY_POWER, 0);  /* 释放 */
input_sync(dev-&gt;idev);


六、用户空间读取方式

6.1 直接 read

int fd = open("/dev/input/event0", O_RDONLY);

struct input_event ev;
while (read(fd, &amp;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-&gt;evbit);
__set_bit(KEY_0,  idev-&gt;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 方案对照。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部