3. 设备描述符详解:核心字段含义与配置
设备描述符(Device Descriptor)是 USB 枚举过程中主机读取的第一个描述符,它定义了设备的整体属性,包括 USB 规范版本、厂商/产品标识、设备类别以及配置数量等。该描述符位于每个 USB 设备的描述符树根节点,是枚举流程的起点。任何解析错误或字段配置不当,都会直接导致枚举失败或设备无法被正确识别。
GET_DESCRIPTOR 获取该描述符。Gadget 驱动必须在枚举开始前正确填充该结构体。
3.1 设备描述符结构总览
USB 2.0 规范定义的设备描述符包含 14 个字段,总长度为 18 字节。下表列出了所有字段及其偏移、大小和用途:
| 偏移 (字节) | 字段 | 大小 (字节) | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 描述符长度,固定为 18 |
| 1 | bDescriptorType | 1 | 描述符类型,设备描述符固定为 0x01 |
| 2 | bcdUSB | 2 | USB 规范版本号(BCD 编码),如 0x0200 表示 USB 2.0 |
| 4 | bDeviceClass | 1 | 设备类代码(由 USB-IF 分配),0x00 表示在接口描述符中定义 |
| 5 | bDeviceSubClass | 1 | 设备子类代码,与 bDeviceClass 配合使用 |
| 6 | bDeviceProtocol | 1 | 设备协议代码,0x00 表示不使用接口类定义的协议 |
| 7 | bMaxPacketSize0 | 1 | 端点 0 的最大包大小(字节),USB 2.0 通常为 64 |
| 8 | idVendor | 2 | 厂商 ID(由 USB-IF 分配),如 0x1D6B 表示 Linux 基金会 |
| 10 | idProduct | 2 | 产品 ID(厂商自行分配),用于区分同一厂商的不同产品 |
| 12 | bcdDevice | 2 | 设备版本号(BCD 编码),如 0x0100 表示 1.00 版 |
| 14 | iManufacturer | 1 | 厂商字符串描述符的索引,0 表示无字符串 |
| 15 | iProduct | 1 | 产品字符串描述符的索引 |
| 16 | iSerialNumber | 1 | 序列号字符串描述符的索引 |
| 17 | bNumConfigurations | 1 | 支持的配置描述符数量,至少为 1 |
lsusb -v 可以查看已连接设备的完整描述符树。如果设备描述符解析失败,dmesg 通常会输出类似 device descriptor read/64, error -71 的错误信息。
3.2 核心字段深度解析
3.2.1 bLength 与 bDescriptorType
这两个字段是所有 USB 描述符的公共头部。bLength 固定为 18,bDescriptorType 固定为 0x01。主机通过 bDescriptorType 区分当前读取的是设备描述符、配置描述符还是其他类型。如果 Gadget 返回的 bLength 不等于 18,主机将丢弃该描述符并重试枚举。
3.2.2 bcdUSB:USB 规范版本
bcdUSB 使用二进制编码十进制(BCD)格式表示设备遵循的 USB 规范版本。高字节表示主版本号,低字节表示次版本号。常见值如下:
- 0x0110 — USB 1.1(全速 12 Mbps)
- 0x0200 — USB 2.0(高速 480 Mbps)
- 0x0201 — USB 2.0 修订版 1.0(部分控制器使用)
- 0x0300 — USB 3.0(超高速 5 Gbps)
Gadget 驱动应根据硬件能力正确设置该字段。例如,一个仅支持全速的 Gadget 若设置 bcdUSB = 0x0200,主机可能尝试以高速模式通信,导致传输失败。
3.2.3 idVendor 与 idProduct
这两个字段共同构成设备的唯一标识。idVendor 由 USB 实施者论坛(USB-IF)统一分配,厂商需缴纳年费获得。idProduct 由厂商自行管理,用于区分同一厂商下的不同产品型号。
常见厂商 ID 示例:
- 0x1D6B — Linux 基金会(用于 Linux Gadget)
- 0x046D — Logitech
- 0x8087 — Intel
- 0x0483 — STMicroelectronics
在 Linux Gadget 驱动中,idVendor 和 idProduct 通常通过模块参数或 configfs 配置。例如:
// 内核模块参数方式
modprobe g_serial vendor=0x1D6B product=0x0104
// configfs 方式
echo "0x1D6B" > /sys/kernel/config/usb_gadget/g1/idVendor
echo "0x0104" > /sys/kernel/config/usb_gadget/g1/idProduct
3.2.4 bcdDevice:设备版本号
bcdDevice 同样采用 BCD 编码,表示设备的固件或硬件版本。该字段用于主机端驱动根据设备版本执行不同的初始化流程。例如,同一产品在固件升级后,bcdDevice 从 0x0100 变为 0x0200,主机驱动可以据此启用新功能或修复已知问题。
dmesg 会显示 reset high speed USB device number X using ehci 后跟超时错误。
3.3 设备描述符的枚举流程
下图展示了主机获取设备描述符的完整流程,以及 Gadget 端需要响应的关键步骤:
3.4 Linux 内核中的设备描述符实现
在 Linux Gadget 框架中,设备描述符由 struct usb_device_descriptor 结构体表示,定义在 include/uapi/linux/usb/ch9.h 中:
struct usb_device_descriptor {
__u8 bLength; // 固定为 18
__u8 bDescriptorType; // 固定为 USB_DT_DEVICE (0x01)
__le16 bcdUSB; // USB 规范版本号(小端)
__u8 bDeviceClass; // 设备类
__u8 bDeviceSubClass; // 设备子类
__u8 bDeviceProtocol; // 设备协议
__u8 bMaxPacketSize0; // 端点 0 最大包大小
__le16 idVendor; // 厂商 ID
__le16 idProduct; // 产品 ID
__le16 bcdDevice; // 设备版本号
__u8 iManufacturer; // 厂商字符串索引
__u8 iProduct; // 产品字符串索引
__u8 iSerialNumber; // 序列号字符串索引
__u8 bNumConfigurations; // 配置描述符数量
} __attribute__ ((packed));
Gadget 驱动通常通过 usb_assign_descriptors() 函数将描述符注册到 UDC(USB Device Controller)层。一个典型的初始化示例如下:
static struct usb_device_descriptor gadget_device_desc = {
.bLength = sizeof(struct usb_device_descriptor),
.bDescriptorType = USB_DT_DEVICE,
.bcdUSB = cpu_to_le16(0x0200), // USB 2.0
.bDeviceClass = 0x00, // 在接口中定义
.bDeviceSubClass = 0x00,
.bDeviceProtocol = 0x00,
.bMaxPacketSize0 = 64, // 全速/高速端点 0 最大包
.idVendor = cpu_to_le16(0x1D6B), // Linux 基金会
.idProduct = cpu_to_le16(0x0104), // 自定义产品
.bcdDevice = cpu_to_le16(0x0100), // 版本 1.00
.iManufacturer = 0x01, // 指向厂商字符串
.iProduct = 0x02, // 指向产品字符串
.iSerialNumber = 0x03, // 指向序列号字符串
.bNumConfigurations = 0x01, // 一个配置
};
3.5 常见枚举故障与定位
设备描述符配置错误是枚举失败的常见原因。下表总结了典型故障现象及其根因:
| 故障现象 | 可能原因 | 定位方法 |
|---|---|---|
主机反复复位,dmesg 显示 device descriptor read/64, error -71 | bMaxPacketSize0 与实际硬件能力不匹配(如全速设备设为 8 而非 64) | 检查 UDC 驱动支持的端点 0 最大包大小,确保与描述符一致 |
设备被识别为未知设备(lsusb 显示 ID 0000:0000) | idVendor 或 idProduct 为 0,或描述符校验和错误 | 使用 USB 分析仪抓取 GET_DESCRIPTOR 响应,验证原始字节 |
| 枚举成功但主机加载错误驱动 | bDeviceClass/bDeviceSubClass/bDeviceProtocol 组合与预期不符 | 确认类代码是否与接口描述符中的类代码冲突(建议设为 0x00) |
| 高速设备被降级为全速运行 | bcdUSB 设置为 0x0110 而非 0x0200,或硬件未正确实现高速握手 | 检查 bcdUSB 字段,并验证 UDC 是否支持高速模式 |
| 字符串描述符无法读取 | iManufacturer/iProduct/iSerialNumber 索引指向不存在的字符串描述符 | 确保字符串描述符已注册,且索引值在有效范围内 |
usbmon 或 wireshark 抓取 USB 总线数据包,可以精确查看主机发送的 GET_DESCRIPTOR 请求以及 Gadget 返回的原始描述符字节。对于 Gadget 端,CONFIG_USB_GADGET_DEBUG_FILES 内核选项可以导出描述符信息到 debugfs。
3.6 设备描述符字段配置检查清单
在编写或调试 Gadget 驱动时,建议逐项核对以下清单:
- bLength — 是否等于
sizeof(struct usb_device_descriptor)(18)? - bDescriptorType — 是否设置为
USB_DT_DEVICE(0x01)? - bcdUSB — 是否与硬件支持的 USB 速度匹配?全速用 0x0110,高速用 0x0200。
- bMaxPacketSize0 — 是否与 UDC 硬件实际支持的端点 0 包大小一致?
- idVendor — 是否为有效的 USB-IF 分配 ID?测试阶段可使用 Linux 基金会 ID(0x1D6B)。
- idProduct — 是否在厂商内部唯一?避免与已有产品冲突。
- bcdDevice — 是否反映了当前固件版本?建议从 0x0100 开始递增。
- 字符串索引 — iManufacturer、iProduct、iSerialNumber 是否指向已注册的字符串描述符?
- bNumConfigurations — 是否与实际注册的配置描述符数量一致?至少为 1。
- 字节序 — 所有多字节字段(bcdUSB、idVendor、idProduct、bcdDevice)是否使用了
cpu_to_le16()转换?