3. Linux HID子系统架构:HID核心层、HID总线驱动、HID设备驱动、HID事件处理
Linux HID(Human Interface Device)子系统是内核中处理人机交互设备的标准化框架。对于蓝牙HID设备(如蓝牙鼠标、键盘、游戏手柄),理解HID子系统的分层架构是驱动开发的核心前提。整个子系统从上到下分为四个层次:HID核心层、HID总线驱动、HID设备驱动、HID事件处理。每一层各司其职,通过标准接口协作,完成从硬件数据到用户空间事件的完整链路。
3.1 四层架构总览
下图展示了Linux HID子系统的完整分层结构,以及数据从蓝牙控制器到用户空间应用程序的流动路径。
3.2 HID核心层(HID Core Layer)
HID核心层是整个子系统的中枢,位于内核的 drivers/hid/hid-core.c 中。它不直接与硬件交互,而是提供以下关键服务:
- 报告描述符解析:将设备上报的HID报告描述符(Report Descriptor)解析为内核可理解的字段结构,包括用途页(Usage Page)、用途ID(Usage ID)、报告大小、报告计数等。
- 设备注册与管理:维护HID设备的生命周期,包括设备的添加、移除、电源状态切换。
- 传输调度:协调底层传输驱动(如USB、蓝牙、I2C)与上层驱动之间的数据传递。
- hiddev 和 hidraw 接口:提供用户空间直接访问HID设备的字符设备接口。
| 数据结构 | 定义位置 | 作用 |
|---|---|---|
struct hid_device | include/linux/hid.h | 代表一个HID设备实例,包含报告描述符、设备状态、传输回调等 |
struct hid_report | include/linux/hid.h | 描述一个HID报告(输入/输出/特性)的字段布局 |
struct hid_field | include/linux/hid.h | 报告中的单个字段,包含用途、物理最小值/最大值等 |
struct hid_ll_driver | include/linux/hid.h | 底层传输驱动接口,定义open/close/start/stop/raw_request等回调 |
struct hid_ll_driver 是连接蓝牙传输层与HID核心层的桥梁。你需要实现 start() 和 stop() 回调来管理蓝牙连接,实现 raw_request() 来发送HID输出报告。
3.3 HID总线驱动层(HID Bus Driver Layer)
HID总线驱动层负责将具体的物理传输协议(USB、蓝牙、I2C、SPI)抽象为HID核心层可调用的统一接口。每个传输类型对应一个总线驱动:
- USB HID总线驱动(usbhid):位于
drivers/hid/usbhid/,处理USB中断传输和控制传输。 - 蓝牙HID总线驱动(hidp):位于
net/bluetooth/hidp/,通过蓝牙L2CAP和HIDP协议传输HID报告。 - I2C HID总线驱动(i2c-hid):位于
drivers/hid/i2c-hid/,用于触摸板、触摸屏等I2C接口设备。 - SPI HID总线驱动(spi-hid):位于
drivers/hid/spi-hid/,用于特定嵌入式平台。
总线驱动的核心职责是:
- 注册HID设备到HID核心层,填充
struct hid_ll_driver回调。 - 处理底层中断/事件,将原始字节流传递给HID核心层。
- 实现HID输出报告的底层发送。
3.4 HID设备驱动层(HID Device Driver Layer)
HID设备驱动层是开发者最常介入的层次。它针对特定设备或设备类别,实现设备初始化、报告解析、特殊功能控制等逻辑。设备驱动通过HID核心层提供的API注册到HID总线。
典型的HID设备驱动开发流程:
- 定义
struct hid_driver结构体,填充.name、.id_table(设备匹配表)、.probe、.remove、.raw_event等回调。 - 在
.probe中初始化设备私有数据,设置报告回调。 - 在
.raw_event或.report回调中解析HID报告,提取按键、坐标等数据。 - 调用
hid_register_driver()注册驱动。
.raw_event 回调运行在中断上下文或工作队列中,不能执行睡眠操作。如果需要复杂的报告解析,应使用工作队列延迟处理。
以下是一个简化的蓝牙HID设备驱动框架代码:
/* 蓝牙HID鼠标驱动示例框架 */
#include <linux/hid.h>
#include <linux/module.h>
static int bt_mouse_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
int ret;
/* 解析报告描述符 */
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
return ret;
}
/* 启动硬件 */
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) {
hid_err(hdev, "hw start failed\n");
return ret;
}
return 0;
}
static void bt_mouse_remove(struct hid_device *hdev)
{
hid_hw_stop(hdev);
}
static int bt_mouse_raw_event(struct hid_device *hdev,
struct hid_report *report,
u8 *data, int size)
{
/* 处理原始HID报告数据 */
/* data[0] 通常包含按键状态,data[1] data[2] 为X/Y位移 */
return 0;
}
static const struct hid_device_id bt_mouse_table[] = {
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_EXAMPLE,
USB_PRODUCT_ID_BT_MOUSE) },
{ }
};
MODULE_DEVICE_TABLE(hid, bt_mouse_table);
static struct hid_driver bt_mouse_driver = {
.name = "bt_mouse",
.id_table = bt_mouse_table,
.probe = bt_mouse_probe,
.remove = bt_mouse_remove,
.raw_event = bt_mouse_raw_event,
};
module_hid_driver(bt_mouse_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("HID Developer");
3.5 HID事件处理层(HID Event Handler Layer)
HID事件处理层位于设备驱动之上,负责将HID报告转换为标准输入事件或提供原始数据访问。Linux内核提供了多种事件处理器:
| 处理器名称 | 内核模块 | 输出接口 | 适用场景 |
|---|---|---|---|
| hid-input | hid-input.c | /dev/input/event* | 标准HID设备(鼠标、键盘、游戏手柄),自动映射HID用途到输入事件 |
| hid-generic | hid-generic.c | /dev/input/event* | 通用HID设备,作为hid-input的补充 |
| hidraw | hidraw.c | /dev/hidraw* | 用户空间直接读写HID报告,适合自定义协议设备 |
| hiddev | hiddev.c | /dev/usb/hiddev* | 旧式HID设备接口,主要用于USB HID |
| 自定义handler | 开发者实现 | 自定义 | 需要特殊事件映射或非标准报告格式的设备 |
hid-input 是最常用的事件处理器。它通过HID核心层解析后的报告字段,自动将用途页和用途ID映射为Linux输入子系统的 EV_KEY、EV_REL、EV_ABS 等事件类型。例如,HID用途页 0x09(Button)用途ID 0x01(Button 1)会被映射为 BTN_LEFT。
3.6 各层之间的协作关系
理解四层之间的协作关系,有助于在开发蓝牙HID驱动时快速定位问题。以下是一个典型的数据收发流程:
- 接收路径:蓝牙控制器收到HID报告 → 蓝牙HIDP传输层 → HID核心层(报告解析) → HID总线驱动(hidp) → HID设备驱动(raw_event回调) → HID事件处理层(hid-input映射) → 输入子系统 → 用户空间应用程序。
- 发送路径:用户空间写入(如LED设置) → HID事件处理层 → HID设备驱动(output_report回调) → HID核心层 → HID总线驱动(raw_request) → 蓝牙HIDP传输层 → 蓝牙控制器。
evtest 工具可以查看输入事件是否正确生成;使用 hid-replay 或 cat /dev/hidraw* 可以验证原始HID报告是否到达。如果事件层无输出但hidraw有数据,问题出在hid-input映射;如果hidraw也无数据,问题出在设备驱动或传输层。
下表总结了各层在蓝牙HID驱动开发中的关注点:
| 层次 | 开发者关注点 | 常见调试方法 |
|---|---|---|
| HID核心层 | 报告描述符解析是否正确;设备注册是否成功 | 查看 /sys/kernel/debug/hid/* 调试文件;启用 hid_debug=1 内核参数 |
| HID总线驱动 | 蓝牙HIDP连接管理;hid_ll_driver 回调实现 | 蓝牙HCI日志(btmon);检查 /sys/class/bluetooth/ 设备状态 |
| HID设备驱动 | 设备初始化;报告解析逻辑;私有数据处理 | 在 raw_event 中添加 print_hex_dump 打印报告数据 |
| HID事件处理 | 用途映射是否正确;事件类型和编码是否匹配 | 使用 evtest 观察事件;使用 hexdump /dev/hidraw* 验证原始数据 |