Linux驱动 2026年7月7日 37 分钟

Input 子系统 Handler 匹配机制详解

# Input 子系统 Handler 匹配机制详解

## 一、概述

在 Linux Input 子系统中,`input_device`(输入设备)和 `input_handler`(输入处理器)通过 **匹配机制** 建立连接。匹配成功后,handler 会处理设备产生的事件,并向用户空间提供访问接口(如 `/dev/input/eventX`)。

核心匹配函数:`input_match_device()`(位于 `drivers/input/input.c:934`)

---

## 二、匹配函数源码解析

### 函数签名

```c
static const struct input_device_id *input_match_device(
    struct input_handler *handler,
    struct input_dev *dev)
```

**返回值:**
- 匹配成功:返回匹配的 `input_device_id` 指针
- 匹配失败:返回 `NULL`

### 匹配流程(按顺序执行)

#### 1. 遍历 handler 的 id_table

```c
for (id = handler->id_table; id->flags || id->driver_info; id++) {
```

**循环条件:** `id->flags` 或 `id->driver_info` 非零时继续。

这意味着:
- 如果 `flags=0` 且 `driver_info=0`,则视为终止符,停止遍历
- 可以通过设置 `driver_info=1` 来创建"万能匹配器"

#### 2. 硬件标识匹配(可选)

```c
if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
    if (id->bustype != dev->id.bustype)
        continue;

if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
    if (id->vendor != dev->id.vendor)
        continue;

if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
    if (id->product != dev->id.product)
        continue;

if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
    if (id->version != dev->id.version)
        continue;
```

**特点:**
- 只有当 `id->flags` 中设置了对应的 `MATCH_*` 位时,才会检查该字段
- 如果 `flags=0`,则跳过所有硬件标识检查

#### 3. 能力位图匹配(必须检查)

```c
if (!bitmap_subset(id->evbit, dev->evbit, EV_MAX))
    continue;  // 事件类型能力

if (!bitmap_subset(id->keybit, dev->keybit, KEY_MAX))
    continue;  // 按键码能力

if (!bitmap_subset(id->relbit, dev->relbit, REL_MAX))
    continue;  // 相对坐标能力(鼠标移动)

if (!bitmap_subset(id->absbit, dev->absbit, ABS_MAX))
    continue;  // 绝对坐标能力(触摸屏、游戏杆)

if (!bitmap_subset(id->mscbit, dev->mscbit, MSC_MAX))
    continue;  // 杂项事件能力

if (!bitmap_subset(id->ledbit, dev->ledbit, LED_MAX))
    continue;  // LED 控制能力

if (!bitmap_subset(id->sndbit, dev->sndbit, SND_MAX))
    continue;  // 声音能力(蜂鸣器)

if (!bitmap_subset(id->ffbit, dev->ffbit, FF_MAX))
    continue;  // 力反馈能力

if (!bitmap_subset(id->swbit, dev->swbit, SW_MAX))
    continue;  // 开关能力
```

**核心逻辑:** `bitmap_subset(A, B, size)` 检查 A 是否是 B 的子集。

**数学含义:** `id 中的位图 ⊆ dev 中的位图`

**实际含义:** 设备必须**至少**支持 handler 要求的所有能力。

**特殊情况:** 如果 `id` 中的位图为空(全零),则 `bitmap_subset(空, 任意) = true`,永远通过检查。

#### 4. 自定义匹配函数(可选)

```c
if (!handler->match || handler->match(handler, dev))
    return id;  // 匹配成功
```

**逻辑:**
- 如果 `handler->match` 为 `NULL`,直接返回匹配成功
- 如果 `handler->match` 存在,调用它进行自定义判断
- 自定义函数返回非零值(true)表示匹配成功

---

## 三、典型 Handler 匹配策略示例

### 示例 1:evdev(万能匹配器)

**位置:** `drivers/input/evdev.c:1223`

```c
static const struct input_device_id evdev_ids[] = {
    { .driver_info = 1 },    /* Matches all devices */
    { },                     /* Terminating zero entry */
};

static struct input_handler evdev_handler = {
    .event      = evdev_event,
    .connect    = evdev_connect,
    .disconnect = evdev_disconnect,
    .name       = "evdev",
    .id_table   = evdev_ids,
    .match      = NULL,      // 无自定义匹配函数
};
```

**匹配策略分析:**

| 匹配阶段 | evdev 的值 | 匹配结果 |
|---------|-----------|---------|
| 循环条件 | `flags=0, driver_info=1` | ✅ 进入循环 |
| 硬件标识检查 | `flags=0` | ✅ 全部跳过 |
| 能力位图检查 | 所有位图为 `{0}`(空集) | ✅ 空集是任意集合的子集 |
| 自定义匹配 | `match=NULL` | ✅ 直接返回成功 |

**结论:** evdev 匹配**所有** input 设备,无论设备有什么能力。

**用途:** 为所有输入设备创建 `/dev/input/eventX` 节点,提供原始事件流接口。

---

### 示例 2:mousedev(鼠标设备处理器)

**位置:** `drivers/input/mousedev.c:1005`

```c
static const struct input_device_id mousedev_ids[] = {
    {
        .flags = INPUT_DEVICE_ID_MATCH_EVBIT |
                 INPUT_DEVICE_ID_MATCH_KEYBIT |
                 INPUT_DEVICE_ID_MATCH_RELBIT,
        .evbit = { BIT_MASK(EV_KEY) | BIT_MASK(EV_REL) },
        .keybit = { [BIT_WORD(BTN_LEFT)] = BIT_MASK(BTN_LEFT) },
        .relbit = { BIT_MASK(REL_X) | BIT_MASK(REL_Y) },
    },  /* 鼠标设备:至少一个按钮,两个相对坐标轴 */
    {
        .flags = INPUT_DEVICE_ID_MATCH_EVBIT |
                 INPUT_DEVICE_ID_MATCH_RELBIT,
        .evbit = { BIT_MASK(EV_KEY) | BIT_MASK(EV_REL) },
        .relbit = { BIT_MASK(REL_WHEEL) },
    },  /* 单独的滚轮设备 */
    {
        .flags = INPUT_DEVICE_ID_MATCH_EVBIT |
                 INPUT_DEVICE_ID_MATCH_KEYBIT |
                 INPUT_DEVICE_ID_MATCH_ABSBIT,
        .evbit = { BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS) },
        .keybit = { [BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH) },
        .absbit = { BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) },
    },  /* 平板设备:至少触摸检测,两个绝对坐标轴 */
    { }  /* Terminating entry */
};
```

**匹配策略分析(第一个条目):**

| 检查项 | 要求 | 含义 |
|-------|------|------|
| 事件类型 | `EV_KEY` + `EV_REL` | 必须支持按键事件和相对坐标事件 |
| 按键能力 | `BTN_LEFT` | 必须有左键 |
| 相对坐标能力 | `REL_X` + `REL_Y` | 必须支持 X/Y 相对移动 |

**匹配设备:** 标准鼠标(有按钮 + 相对移动)

**不匹配设备:**
- 纯按键设备(缺少 `REL_X`/`REL_Y`)
- 触摸屏(使用 `ABS_X`/`ABS_Y` 而非 `REL_X`/`REL_Y`)

**用途:** 为鼠标类设备创建 `/dev/input/mouseX` 节点,提供兼容传统鼠标协议的接口。

---

### 示例 3:joydev(游戏杆/手柄处理器)

**位置:** `drivers/input/joydev.c:953`

```c
static const struct input_device_id joydev_ids[] = {
    {
        .flags = INPUT_DEVICE_ID_MATCH_EVBIT |
                 INPUT_DEVICE_ID_MATCH_ABSBIT,
        .evbit = { BIT_MASK(EV_ABS) },
        .absbit = { BIT_MASK(ABS_X) },
    },  /* 有 X 轴的绝对坐标设备 */
    {
        .flags = INPUT_DEVICE_ID_MATCH_EVBIT |
                 INPUT_DEVICE_ID_MATCH_ABSBIT,
        .evbit = { BIT_MASK(EV_ABS) },
        .absbit = { BIT_MASK(ABS_WHEEL) },
    },  /* 方向盘设备 */
    {
        .flags = INPUT_DEVICE_ID_MATCH_EVBIT |
                 INPUT_DEVICE_ID_MATCH_ABSBIT,
        .evbit = { BIT_MASK(EV_ABS) },
        .absbit = { BIT_MASK(ABS_THROTTLE) },
    },  /* 油门设备 */
    {
        .flags = INPUT_DEVICE_ID_MATCH_EVBIT |
                 INPUT_DEVICE_ID_MATCH_KEYBIT,
        .evbit = { BIT_MASK(EV_KEY) },
        .keybit = { [BIT_WORD(BTN_JOYSTICK)] = BIT_MASK(BTN_JOYSTICK) },
    },  /* 游戏杆按钮 */
    {
        .flags = INPUT_DEVICE_ID_MATCH_EVBIT |
                 INPUT_DEVICE_ID_MATCH_KEYBIT,
        .evbit = { BIT_MASK(EV_KEY) },
        .keybit = { [BIT_WORD(BTN_GAMEPAD)] = BIT_MASK(BTN_GAMEPAD) },
    },  /* 游戏手柄按钮 */
    {
        .flags = INPUT_DEVICE_ID_MATCH_EVBIT |
                 INPUT_DEVICE_ID_MATCH_KEYBIT,
        .evbit = { BIT_MASK(EV_KEY) },
        .keybit = { [BIT_WORD(BTN_TRIGGER_HAPPY)] = BIT_MASK(BTN_TRIGGER_HAPPY) },
    },  /* 扳机按钮 */
    { }  /* Terminating entry */
};

static struct input_handler joydev_handler = {
    .event      = joydev_event,
    .match      = joydev_match,  // 有自定义匹配函数!
    .connect    = joydev_connect,
    .disconnect = joydev_disconnect,
    .name       = "joydev",
    .id_table   = joydev_ids,
};
```

**自定义匹配函数(位置:`joydev.c:808`):**

```c
static bool joydev_match(struct input_handler *handler, struct input_dev *dev)
{
    /* 排除触摸板和触摸屏 */
    if (test_bit(EV_KEY, dev->evbit) && test_bit(BTN_TOUCH, dev->keybit))
        return false;

    /* 排除手写板、数位板等设备 */
    if (test_bit(EV_KEY, dev->evbit) && test_bit(BTN_DIGI, dev->keybit))
        return false;

    /* 排除绝对坐标鼠标 */
    if (joydev_dev_is_absolute_mouse(dev))
        return false;

    return true;
}
```

**匹配策略分析:**

1. **通过 id_table 初筛:** 设备必须有绝对坐标轴(`ABS_X`/`ABS_WHEEL` 等)或游戏按钮(`BTN_JOYSTICK` 等)
2. **通过自定义函数排除:** 即使有绝对坐标,也要排除触摸屏、数位板、绝对坐标鼠标

**匹配设备:** 游戏杆、游戏手柄、方向盘

**不匹配设备:**
- 触摸屏(有 `BTN_TOUCH`)
- 数位板(有 `BTN_DIGI`)
- 纯按键设备(缺少绝对坐标或游戏按钮)

**用途:** 为游戏控制器创建 `/dev/input/jsX` 节点。

---

### 示例 4:evbug(调试工具)

**位置:** `drivers/input/evbug.c:93`

```c
static const struct input_device_id evbug_ids[] = {
    { .driver_info = 1 },    /* Matches all devices */
    { },                     /* Terminating zero entry */
};

static struct input_handler evbug_handler = {
    .event      = evbug_event,
    .connect    = evbug_connect,
    .disconnect = evbug_disconnect,
    .name       = "evbug",
    .id_table   = evbug_ids,
};
```

**匹配策略:** 与 evdev 相同,匹配所有设备。

**用途:** 调试工具,将所有输入事件打印到内核日志(`dmesg`)。

---

## 四、实际应用:按键设备的匹配过程

### 假设场景

你编写了一个按键驱动,注册了以下设备:

```c
struct input_dev *button_dev = input_allocate_device();

// 设置设备能力
button_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_SYN);
button_dev->keybit[BIT_WORD(KEY_ENTER)] = BIT_MASK(KEY_ENTER);
button_dev->keybit[BIT_WORD(KEY_ESC)] = BIT_MASK(KEY_ESC);

// 设置设备信息
button_dev->id.bustype = BUS_HOST;
button_dev->id.vendor  = 0x1234;
button_dev->id.product = 0x5678;
button_dev->id.version = 0x0100;

input_register_device(button_dev);
```

**设备能力:**
- 事件类型:`EV_KEY`(按键事件)、`EV_SYN`(同步事件)
- 按键码:`KEY_ENTER`、`KEY_ESC`

### 匹配结果表

| Handler | 匹配结果 | 原因 | 创建的设备节点 |
|---------|---------|------|--------------|
| **evdev** | ✅ 匹配 | 万能匹配器,匹配所有设备 | `/dev/input/event0` |
| **mousedev** | ❌ 不匹配 | 缺少 `EV_REL` 和 `REL_X`/`REL_Y` | 无 |
| **joydev** | ❌ 不匹配 | 缺少 `ABS_X` 等绝对坐标或 `BTN_JOYSTICK` 等游戏按钮 | 无 |
| **evbug** | ✅ 匹配 | 万能匹配器(如果加载该模块) | 无设备节点,仅打印日志 |

### 用户空间访问

匹配成功后,用户空间可以通过 `/dev/input/event0` 读取按键事件:

```c
#include <linux/input.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main(void)
{
    int fd = open("/dev/input/event0", O_RDONLY);
    struct input_event ev;

    while (read(fd, &ev, sizeof(ev)) > 0) {
        if (ev.type == EV_KEY) {
            printf("Key code: %d, Value: %d (%s)\n", 
                   ev.code, ev.value,
                   ev.value ? "pressed" : "released");
        }
    }

    close(fd);
    return 0;
}
```

---

## 五、总结

### 匹配机制核心要点

1. **id_table 遍历:** handler 可以定义多个匹配条目,只要有一个匹配成功即可
2. **flags 控制检查项:** 硬件标识检查是可选的,由 `flags` 控制
3. **能力位图是子集关系:** handler 要求的能力必须是设备提供的能力的子集
4. **空集永远匹配:** 如果 handler 的位图为空,则匹配任意设备
5. **自定义函数提供灵活性:** 可以实现复杂的排除逻辑

### Handler 分类

| 类型 | 代表 | 匹配策略 |
|-----|------|---------|
| **万能匹配型** | evdev, evbug | `driver_info=1`,所有位图为空 |
| **精确匹配型** | mousedev | 明确要求特定事件类型和能力位 |
| **多条件或型** | joydev | 提供多个 id_table 条目,满足任一即可 |
| **排除型** | joydev | 通过自定义 `match` 函数排除不需要的设备 |

### 设计驱动时的建议

1. **设置正确的事件类型:** 根据设备功能设置 `evbit`(`EV_KEY`、`EV_REL`、`EV_ABS` 等)
2. **设置具体的能力位:** 设置 `keybit`、`relbit`、`absbit` 等,明确设备支持的具体按键/坐标
3. **填写设备信息:** 设置 `id.bustype`、`id.vendor`、`id.product`,便于某些 handler 精确匹配
4. **测试匹配结果:** 注册设备后,检查 `/dev/input/` 下创建了哪些节点

### 常见设备节点

| 节点模式 | 对应 Handler | 适用设备 |
|---------|-------------|---------|
| `/dev/input/eventX` | evdev | 所有输入设备 |
| `/dev/input/mouseX` | mousedev | 鼠标、触摸板 |
| `/dev/input/jsX` | joydev | 游戏杆、手柄 |

---

## 六、参考代码位置

| 文件 | 关键函数/结构 | 说明 |
|-----|-------------|------|
| `drivers/input/input.c:934` | `input_match_device()` | 核心匹配函数 |
| `drivers/input/input.c:991` | `input_attach_handler()` | 匹配成功后调用 connect |
| `drivers/input/evdev.c:1223` | `evdev_ids[]` | evdev 的匹配表 |
| `drivers/input/mousedev.c:1005` | `mousedev_ids[]` | mousedev 的匹配表 |
| `drivers/input/joydev.c:953` | `joydev_ids[]` | joydev 的匹配表 |
| `drivers/input/joydev.c:808` | `joydev_match()` | joydev 的自定义匹配函数 |

---

**文档版本:** v1.0  
**适用内核:** Linux 4.1.15 (i.MX6ULL)  
**创建日期:** 2026-07-07
上一篇 Input子系统

两者匹配之后会做的事情,创建一个inputhandle结构体,里面记录handler和inputdev 可以相互找到,从...

下一篇 GPIO子系统代码练习

犹豫了一下,最终还是决定一边看一边跟着写代码,这样子可能慢一点,但是基础打扎实了,后面越学越容易,这次就从GPIO子系统...