HID原始报告描述符是设备元信息,需通过sysfs的report_descriptor文件读取,而非/dev/hidrawX;因其由内核枚举时解析并缓存,read()只能获取输入报告,且访问需root权限、注意内核版本兼容性。

什么是HID原始报告描述符,为什么不能直接用 read() 读
Linux下HID设备的原始报告描述符(Raw Report Descriptor)不是设备文件(如 /dev/hidraw0)里可随数据流读出的内容——它属于设备元信息,由内核在枚举阶段解析并缓存,用户空间无法通过常规 read() 从 /dev/hidrawX 获取。试图对 /dev/hidraw0 执行 read() 只会返回实际输入报告(Input Reports),而非描述符本身。
真正能拿到原始二进制描述符的路径只有一条:通过 sysfs 接口访问内核暴露的只读属性文件。
从 /sys/class/hidraw/ 定位设备并读取 report_descriptor
每个 hidraw 设备在 sysfs 中有对应目录,路径形如 /sys/class/hidraw/hidraw0/device/report_descriptor。该文件以二进制形式存放原始报告描述符(未解析、未压缩、字节序为小端),长度即为描述符实际字节数。
实操要点:
- 必须以 root 或
hidraw组权限访问(否则open()返回-EACCES) - 该文件不可写、不可
mmap,只能open()+read() - 读取前建议先用
stat()获取st_size,避免盲目分配缓冲区;但注意某些旧内核版本(≤ 4.15)可能返回st_size == 0,此时需循环读直到read()返回0 - 设备热拔插后该路径可能失效,需监听
uevents或重枚举/sys/class/hidraw/
示例片段(C++17,带错误检查):
#include <fcntl.h>
#include <sys>
#include <unistd.h>
#include <vector>
std::vector<uint8_t> read_report_descriptor(const std::string& hidraw_dev) {
std::string path = "/sys/class/hidraw/" + hidraw_dev + "/device/report_descriptor";
int fd = open(path.c_str(), O_RDONLY);
if (fd == -1) return {};
struct stat st;
if (fstat(fd, &st) == 0 && st.st_size > 0) {
std::vector<uint8_t> buf(st.st_size);
ssize_t n = read(fd, buf.data(), buf.size());
close(fd);
if (n > 0) buf.resize(n);
return buf;
}
// fallback: size unknown
std::vector<uint8_t> buf(4096);
ssize_t n = read(fd, buf.data(), buf.size());
close(fd);
if (n > 0) {
buf.resize(n);
return buf;
}
return {};
}
</uint8_t></uint8_t></uint8_t></vector></unistd.h></sys></fcntl.h>
用 libudev 动态发现 hidraw 设备并关联到物理路径
硬编码 hidraw0 不可靠。真实场景中需根据 VID/PID、制造商或产品字符串定位目标设备。核心是利用 libudev 遍历 hidraw 子系统,并向上查找 parent(通常是 usb 或 bluetooth)提取 ID_VENDOR_ID、ID_MODEL_ID 等属性。
关键步骤:
- 调用
udev_device_get_parent_with_subsystem_devtype(dev, "usb", "usb_device")向上找 USB 设备节点 - 从该 parent 节点获取
idVendor和idProduct属性(十六进制字符串,需strtol(..., nullptr, 16)) - 同时读取
devnode(如/dev/hidraw2)和syspath(用于拼接report_descriptor路径) - 注意:部分 HID 设备(如蓝牙 HOGP)无 USB parent,需 fallback 到
hid子系统的HID_ID属性(格式为0003:00001234:00005678,其中后两段是 VID/PID)
常见坑:内核版本差异与描述符截断问题
Linux 内核对 report_descriptor 的暴露逻辑在不同版本中不一致:
-
≥ 5.3:完整导出,长度准确 -
4.15–5.2:部分设备出现截断(尤其 > 1024 字节的描述符),read()可能只返回前 1024 字节,且st_size为 0 :某些配置下该文件根本不存在(返回 <code>ENOENT),需改用ioctl(HIDIOCGRAWINFO)+HIDIOCGRDESCSIZE/HIDIOCGRDESC(但仅适用于已打开的/dev/hidrawX,且要求内核启用HID_RAW支持)
如果你遇到读出来只有几十字节、明显不对,优先检查内核版本和 cat /sys/class/hidraw/hidraw0/device/report_descriptor | hexdump -C 输出长度;若确认被截断,唯一健壮解法是升级内核或换用 ioctl 方式(需额外权限且更复杂)。
描述符本身是 HID 协议定义的紧凑二进制流,没有固定长度字段,解析时必须严格按 HID 规范逐项 consume,任何越界或误判 item tag 都会导致后续解析雪崩——这也是为什么别轻易手写 parser,推荐用 libhidapi 的 hid_get_manufacturer_string() 等辅助接口做交叉验证。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











