Linux驱动 2026年7月8日 33 分钟

 Input 子系统位图解析与 evtest 输出详解

# 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, 按键码, 事件类型, 位图解析
上一篇 Input子系统应用编程

1.查看input子系统相关信息(用户层查看设备) 参数含义解释 相关的定义文件所在位置 三个框分别表示序号、时间、具体...

下一篇 Tslib分析与移植汇总

本文章将对tslib触摸屏库进行一些分析、移植与汇总工作