# 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