c++如何读取Linux下的HID设备原始报告描述符【深度】

大磊酱_3306

大磊酱_3306

2026-04-12

180人浏览

原创

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

c++如何读取linux下的hid设备原始报告描述符【深度】

什么是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 等属性。

Obsidian (Linux)
Obsidian (Linux)

操作 Obsidian 库(纯 Markdown 笔记),并通过 notesmd-cli 实现自动化。

下载

关键步骤:

  • 调用 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++ 的入门与实战技巧!

相关文章

c++速学教程(入门到精通)
c++速学教程(入门到精通)

c++怎么学习?c++怎么入门?c++在哪学?c++怎么学才快?不用担心,这里为大家提供了c++速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

linux c++

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.03

1798

5

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

2023.09.04

2824

5

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

2023.10.24

6164

49

字符串介绍
字符串介绍

字符串是一种数据类型,它可以是任何文本,包括字母、数字、符号等。字符串可以由不同的字符组成,例如空格、标点符号、数字等。在编程中,字符串通常用引号括起来,如单引号、双引号或反引号。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

2023.11.24

5909

6

java读取文件转成字符串的方法
java读取文件转成字符串的方法

Java8引入了新的文件I/O API,使用java.nio.file.Files类读取文件内容更加方便。对于较旧版本的Java,可以使用java.io.FileReader和java.io.BufferedReader来读取文件。在这些方法中,你需要将文件路径替换为你的实际文件路径,并且可能需要处理可能的IOException异常。想了解更多java的相关内容,可以阅读本专题下面的文章。

2024.03.22

7534

16

php中定义字符串的方式
php中定义字符串的方式

php中定义字符串的方式:单引号;双引号;heredoc语法等等。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

2024.04.29

11174

11

go语言字符串相关教程
go语言字符串相关教程

本专题整合了go语言字符串相关教程,阅读专题下面的文章了解更多详细内容。

2025.07.29

5739

17

c++字符串相关教程
c++字符串相关教程

本专题整合了c++字符串相关教程,阅读专题下面的文章了解更多详细内容。

2025.08.07

6307

13

常用字符串方法大全
常用字符串方法大全

常用字符串方法大全

2025.08.08

3148

14

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Python uv官方安装与入门文档
Python uv官方安装与入门文档

共0课时 | 0人学习

Buffalo框架快速入门指南
Buffalo框架快速入门指南

共0课时 | 0人学习

Valgrind支持平台说明
Valgrind支持平台说明

共0课时 | 0人学习