# Input 子系统位图解析与 evtest 输出详解
## 一、概述
在 Linux Input 子系统中,设备的能力通过**位图(bitmap)**来表示。每个 bit 对应一种能力(事件类型、按键码、坐标轴等),bit 为 1 表示支持该能力。
`evtest` 工具以十六进制格式显示这些位图,格式为 `B: XXX=YYYYYYYY`。
---
## 二、位图格式详解
### 基本规则
```
B: KEY=10000000
```
- **`B:`** 表示这是一个位图(Bitmap)
- **`KEY`** 表示位图类型(按键码、事件类型等)
- **`10000000`** 是十六进制数,每个十六进制位对应 4 个二进制位
### 位图类型
| 位图类型 | 含义 | 对应设备能力 |
|---------|------|------------|
| `B: EV` | 事件类型位图 | 支持的事件类型(EV_KEY, EV_REL, EV_ABS 等) |
| `B: KEY` | 按键码位图 | 支持的按键码(KEY_ENTER, KEY_ESC 等) |
| `B: REL` | 相对坐标位图 | 支持的相对坐标轴(REL_X, REL_Y, REL_WHEEL 等) |
| `B: ABS` | 绝对坐标位图 | 支持的绝对坐标轴(ABS_X, ABS_Y, ABS_PRESSURE 等) |
| `B: MSC` | 杂项事件位图 | 支持的杂项事件 |
| `B: LED` | LED 位图 | 可控制的 LED(NUM_LOCK, CAPS_LOCK 等) |
| `B: SND` | 声音位图 | 可发出的声音(蜂鸣器) |
| `B: FF` | 力反馈位图 | 支持的力反馈效果 |
| `B: SW` | 开关位图 | 支持的开关事件 |
| `B: PROP` | 设备属性位图 | 设备特殊属性 |
---
## 三、位图解析方法
### Step 1:十六进制转二进制
```
B: KEY=10000000
十六进制每位对应 4 个二进制位:
1 → 0001
0 → 0000
完整转换:
十六进制: 1 0 0 0 0 0 0 0
二进制: 0001 0000 0000 0000 0000 0000 0000 0000
```
### Step 2:确定位编号(从右往左,从 0 开始)
```
二进制: 0001 0000 0000 0000 0000 0000 0000 0000
位编号: 31~28 27~24 23~20 19~16 15~12 11~8 7~4 3~0
```
### Step 3:找出值为 1 的位
```
二进制位: 31 30 29 28 27 26 ... 3 2 1 0
二进制值: 0 0 0 1 0 0 ... 0 0 0 0
↑
bit 28 = 1
```
### Step 4:查找对应的定义
```c
// include/uapi/linux/input.h
#define KEY_ENTER 28
```
**结论:** `B: KEY=10000000` 表示设备支持 `KEY_ENTER` 按键。
---
## 四、实际案例解析
### 案例 1:GPIO 按键设备
```
I: Bus=0019 Vendor=0001 Product=0001 Version=0100
N: Name="gpio-keys"
P: Phys=gpio-keys/input0
S: Sysfs=/devices/platform/gpio-keys/input/input2
U: Uniq=
H: Handlers=kbd event2
B: PROP=0
B: EV=100003
B: KEY=10000000
```
#### 逐行解析
| 字段 | 值 | 含义 |
|-----|---|------|
| `I: Bus` | `0019` | 总线类型 = `BUS_HOST`(0x19,主机内部总线,GPIO) |
| `Vendor` | `0001` | 厂商 ID |
| `Product` | `0001` | 产品 ID |
| `Version` | `0100` | 版本号 |
| `N: Name` | `"gpio-keys"` | 设备名称 |
| `P: Phys` | `gpio-keys/input0` | 物理路径 |
| `S: Sysfs` | `/devices/platform/...` | sysfs 路径 |
| `H: Handlers` | `kbd event2` | 匹配的 handler:`kbd`(键盘)和 `event2`(evdev) |
| `B: PROP` | `0` | 无特殊设备属性 |
| `B: EV` | `100003` | 事件类型位图(见下文详解) |
| `B: KEY` | `10000000` | 按键码位图(见下文详解) |
---
### `B: EV=100003` 详解(事件类型位图)
#### Step 1:十六进制转二进制
```
十六进制: 1 0 0 0 0 3
二进制: 0001 0000 0000 0000 0000 0011
位编号: 23~20 19~16 15~12 11~8 7~4 3~0
```
#### Step 2:找出值为 1 的位
```
位: 23 22 21 20 19 18 ... 3 2 1 0
值: 0 0 0 1 0 0 ... 0 0 1 1
↑ ↑ ↑
bit 20 bit 1 bit 0
```
**哪些位是 1?**
- bit 0 = 1
- bit 1 = 1
- bit 20 = 1
#### Step 3:查找事件类型定义
```c
// include/uapi/linux/input.h
#define EV_SYN 0x00 // 0
#define EV_KEY 0x01 // 1
#define EV_REL 0x02 // 2
#define EV_ABS 0x03 // 3
#define EV_MSC 0x04 // 4
#define EV_SW 0x05 // 5
#define EV_LED 0x11 // 17
#define EV_SND 0x12 // 18
#define EV_REP 0x14 // 20
#define EV_FF 0x15 // 21
```
#### 结论
| bit | 事件类型 | 含义 |
|-----|---------|------|
| 0 | `EV_SYN` | 同步事件(用于标记事件组边界) |
| 1 | `EV_KEY` | 按键事件 |
| 20 | `EV_REP` | 重复事件(按键长按时自动重复上报) |
**总结:** 该设备支持**按键事件**、**同步事件**和**自动重复事件**。
---
### `B: KEY=10000000` 详解(按键码位图)
#### Step 1:十六进制转二进制
```
十六进制: 1 0 0 0 0 0 0 0
二进制: 0001 0000 0000 0000 0000 0000 0000 0000
位编号: 31~28 27~24 23~20 19~16 15~12 11~8 7~4 3~0
```
#### Step 2:找出值为 1 的位
```
位: 31 30 29 28 27 26 ... 3 2 1 0
值: 0 0 0 1 0 0 ... 0 0 0 0
↑
bit 28 = 1
```
**只有 bit 28 = 1。**
#### Step 3:查找按键码定义
```c
// include/uapi/linux/input.h:243
#define KEY_ENTER 28
```
#### 结论
**该设备只支持一个按键:`KEY_ENTER`(回车键)。**
---
## 五、常见按键码参考
| 按键码值 | 宏定义 | 含义 |
|---------|-------|------|
| 0 | `KEY_RESERVED` | 保留 |
| 1 | `KEY_ESC` | ESC 键 |
| 2 | `KEY_1` | 数字键 1 |
| 3 | `KEY_2` | 数字键 2 |
| ... | ... | ... |
| 28 | `KEY_ENTER` | 回车键 |
| 30 | `KEY_A` | 字母 A |
| 31 | `KEY_S` | 字母 S |
| ... | ... | ... |
| 103 | `KEY_UP` | 上箭头 |
| 105 | `KEY_LEFT` | 左箭头 |
| 106 | `KEY_RIGHT` | 右箭头 |
| 108 | `KEY_DOWN` | 下箭头 |
完整定义请查看 `include/uapi/linux/input.h`。
---
## 六、多按键设备示例
### 示例:标准键盘
```
B: KEY=1 0000 0000 0007 ff9f fbff ffff fffe
```
这表示支持大量按键。我们只解析部分:
```
最右侧 8 位十六进制: fffe
二进制: 1111 1111 1111 1110
```
转换为按键:
- bit 1 = 1 → `KEY_ESC`
- bit 2 = 1 → `KEY_1`
- bit 3 = 1 → `KEY_2`
- bit 4 = 1 → `KEY_3`
- ...
- bit 0 = 0 → 不支持 `KEY_RESERVED`
---
## 七、位图计算技巧
### 快速计算公式
**已知按键码,计算位图:**
```c
按键码 keycode = 28
// 计算在哪个 32 位字(word)中
word_index = keycode / 32 = 28 / 32 = 0
// 计算在该字中的位偏移
bit_offset = keycode % 32 = 28 % 32 = 28
// 设置该位
bitmap[word_index] |= (1 << bit_offset)
// bitmap[0] |= (1 << 28)
// 结果:0x10000000
```
**已知位图,找出支持的按键:**
```c
for (int word = 0; word < bitmap_size; word++) {
for (int bit = 0; bit < 32; bit++) {
if (bitmap[word] & (1 << bit)) {
int keycode = word * 32 + bit;
printf("支持按键: %d\n", keycode);
}
}
}
```
---
## 八、设备树对应关系
### GPIO 按键设备树示例
根据 `evtest` 输出,推测设备树:
```dts
gpio-keys {
compatible = "gpio-keys";
#address-cells = <1>;
#size-cells = <0>;
key_enter {
label = "User Key";
linux,code = <28>; /* KEY_ENTER */
gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;
};
};
```
**关键点:**
- `linux,code = <28>` 对应 `KEY_ENTER`
- 驱动会自动设置 `keybit[0] |= (1 << 28)`
- 最终在 `evtest` 中显示为 `B: KEY=10000000`
---
## 九、用户空间读取示例
### 读取按键事件
```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;
printf("等待按键事件...\n");
while (read(fd, &ev, sizeof(ev)) == sizeof(ev)) {
// 过滤出按键事件
if (ev.type == EV_KEY) {
printf("时间: %ld.%06ld, 按键码: %d, 值: %d (%s)\n",
ev.time.tv_sec,
ev.time.tv_usec,
ev.code,
ev.value,
ev.value ? "按下" : "释放");
// 判断是否为 ENTER 键
if (ev.code == KEY_ENTER) {
printf(" → 检测到 ENTER 键\n");
}
}
}
close(fd);
return 0;
}
```
### 编译和运行
```bash
arm-linux-gnueabihf-gcc -o test_key test_key.c
# 在目标板上运行
./test_key
```
**预期输出:**
```
等待按键事件...
时间: 1234567.890123, 按键码: 28, 值: 1 (按下)
→ 检测到 ENTER 键
时间: 1234567.990456, 按键码: 28, 值: 0 (释放)
→ 检测到 ENTER 键
```
---
## 十、驱动中的位图设置
### 按键驱动示例
```c
static int gpio_keys_probe(struct platform_device *pdev)
{
struct input_dev *input;
int error;
input = devm_input_allocate_device(&pdev->dev);
if (!input)
return -ENOMEM;
input->name = "gpio-keys";
input->phys = "gpio-keys/input0";
input->id.bustype = BUS_HOST;
input->id.vendor = 0x0001;
input->id.product = 0x0001;
input->id.version = 0x0100;
/* 设置事件类型位图 */
input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_SYN);
/* 如果支持自动重复 */
__set_bit(EV_REP, input->evbit);
/* 设置按键码位图 */
__set_bit(KEY_ENTER, input->keybit);
/* 注册设备 */
error = input_register_device(input);
if (error)
return error;
return 0;
}
```
**位图设置宏:**
```c
#define BIT_MASK(nr) (1UL << ((nr) % BITS_PER_LONG))
#define BIT_WORD(nr) ((nr) / BITS_PER_LONG)
// 设置某个 bit
__set_bit(KEY_ENTER, input->keybit);
// 等价于:
input->keybit[BIT_WORD(KEY_ENTER)] |= BIT_MASK(KEY_ENTER);
// 展开:
input->keybit[28/32] |= (1 << (28%32));
// 结果:
input->keybit[0] |= (1 << 28);
```
---
## 十一、位图对比示例
### 单按键设备
```
B: KEY=10000000
```
- 只支持 `KEY_ENTER` (bit 28)
### 双按键设备
```
B: KEY=10000002
```
- 支持 `KEY_ESC` (bit 1) 和 `KEY_ENTER` (bit 28)
### 简单键盘(0~31 按键)
```
B: KEY=fffffffe
```
- 支持 bit 1~31 的所有按键
- 不支持 bit 0 (`KEY_RESERVED`)
### 完整键盘
```
B: KEY=1 0000 0000 0007 ff9f fbff ffff fffe
```
- 支持大量按键(几乎所有标准键盘按键)
---
## 十二、调试技巧
### 1. 使用 evtest 查看设备
```bash
# 列出所有输入设备
evtest
# 选择设备编号(例如 2)
# 然后按键测试
```
### 2. 查看 sysfs 信息
```bash
# 查看设备能力
cat /sys/class/input/event2/device/capabilities/ev
cat /sys/class/input/event2/device/capabilities/key
# 结果也是十六进制位图
```
### 3. 手动计算位图
假设你想支持 `KEY_A` (30), `KEY_B` (48), `KEY_C` (46):
```
KEY_A (30): 在 word 0 (30/32=0), bit 30 (30%32=30)
→ bitmap[0] |= (1 << 30) = 0x40000000
KEY_B (48): 在 word 1 (48/32=1), bit 16 (48%32=16)
→ bitmap[1] |= (1 << 16) = 0x00010000
KEY_C (46): 在 word 1 (46/32=1), bit 14 (46%32=14)
→ bitmap[1] |= (1 << 14) = 0x00004000
最终位图(从 word 1 到 word 0):
B: KEY=14000 40000000
```
### 4. 内核日志调试
```c
// 在驱动中打印位图
printk("keybit[0] = 0x%08lx\n", input->keybit[0]);
printk("keybit[1] = 0x%08lx\n", input->keybit[1]);
```
---
## 十三、总结
### 核心要点
1. **位图是十六进制表示**,每个十六进制位对应 4 个二进制位
2. **从右往左数**,bit 0 在最右边
3. **bit 为 1 表示支持该能力**,对应的按键码/事件类型等
4. **evtest 输出格式**:`B: TYPE=HEXADECIMAL`
5. **计算公式**:
- `word_index = keycode / 32`
- `bit_offset = keycode % 32`
- `bitmap[word_index] |= (1 << bit_offset)`
### 快速查表
| 字段 | 含义 | 示例 |
|-----|------|------|
| `B: EV=100003` | 事件类型 | `EV_SYN`, `EV_KEY`, `EV_REP` |
| `B: KEY=10000000` | 按键码 | `KEY_ENTER` (28) |
| `B: REL=103` | 相对坐标 | `REL_X`, `REL_Y`, `REL_WHEEL` |
| `B: ABS=260800000000003` | 绝对坐标 | 多点触控坐标轴 |
### 常见应用
- **按键设备**:只需设置 `EV_KEY` 和对应的 `keybit`
- **鼠标**:需要 `EV_REL` 和 `REL_X`/`REL_Y`/`REL_WHEEL`
- **触摸屏**:需要 `EV_ABS` 和 `ABS_X`/`ABS_Y` 等
- **游戏手柄**:需要 `EV_KEY`、`EV_ABS` 和对应的按键/轴
---
**文档版本:** v1.0
**适用内核:** Linux 4.1.15 (i.MX6ULL)
**创建日期:** 2026-07-08
**关键词:** Input 子系统, bitmap, evtest, 按键码, 事件类型, 位图解析