3. USB描述符体系

USB 描述符(Descriptor)是 USB 设备向主机报告自身属性、能力和资源分配的核心数据结构。主机通过标准设备请求(如 GET_DESCRIPTOR)逐层获取这些描述符,从而完成枚举过程。描述符体系采用严格的层级结构,每一层都包含指向下一层的指针或长度字段。

核心要点:描述符是 USB 枚举的“身份证”和“资源清单”。任何描述符内容错误、长度不匹配或顺序颠倒,都会直接导致枚举失败或 URB 超时。

3.1 描述符层级结构总览

USB 描述符体系呈树形结构,从设备描述符开始,逐级向下展开。主机在枚举过程中,首先获取设备描述符(18 字节),然后根据其中的 bNumConfigurations 字段获取配置描述符及其附属的所有描述符。

USB 描述符层级结构 设备描述符 配置描述符 接口描述符 端点描述符 字符串描述符 层级关系说明 • 1 个设备 → N 个配置 • 1 个配置 → N 个接口 • 1 个接口 → N 个端点

3.2 设备描述符(Device Descriptor)

设备描述符是 USB 设备的第一份“身份证”,长度为固定的 18 字节。主机在枚举的第一步(地址分配后)即发送 GET_DESCRIPTOR 请求获取设备描述符。

偏移 字段 大小 说明
0 bLength 1 描述符长度,固定为 18
1 bDescriptorType 1 描述符类型,设备描述符为 0x01
2-3 bcdUSB 2 USB 规范版本号(BCD 码),如 0x0200 表示 USB 2.0
4 bDeviceClass 1 设备类代码(0 表示在接口描述符中定义)
5 bDeviceSubClass 1 设备子类代码
6 bDeviceProtocol 1 设备协议代码
7 bMaxPacketSize0 1 端点 0 的最大包大小(关键参数)
8-9 idVendor 2 厂商 ID(VID)
10-11 idProduct 2 产品 ID(PID)
12-13 bcdDevice 2 设备版本号(BCD 码)
14 iManufacturer 1 厂商字符串描述符索引
15 iProduct 1 产品字符串描述符索引
16 iSerialNumber 1 序列号字符串描述符索引
17 bNumConfigurations 1 支持的配置数量
调试要点:bMaxPacketSize0 是枚举初期最关键的参数。对于 USB 2.0 高速设备,该值必须为 64;全速设备可为 8/16/32/64。如果该值设置错误,主机将无法正确解析后续描述符,导致枚举立即失败。

3.3 配置描述符(Configuration Descriptor)

配置描述符描述设备的电源管理属性和接口总数。一个设备可以有多个配置(如低功耗模式和高性能模式),但同一时刻只能有一个配置被激活。配置描述符长度为 9 字节。

偏移 字段 大小 说明
0 bLength 1 描述符长度,固定为 9
1 bDescriptorType 1 配置描述符类型,0x02
2-3 wTotalLength 2 配置描述符及其所有附属描述符的总长度
4 bNumInterfaces 1 该配置下的接口数量
5 bConfigurationValue 1 用于 SET_CONFIGURATION 请求的配置值
6 iConfiguration 1 配置字符串描述符索引
7 bmAttributes 1 电源属性(位 7:总线供电,位 6:自供电,位 5:远程唤醒)
8 bMaxPower 1 最大电流需求(单位:2mA)
常见枚举失败原因:wTotalLength 字段必须等于该配置下所有描述符(配置 + 接口 + 端点 + 类特殊描述符)的字节总和。如果该值偏小,主机会截断后续描述符;如果偏大,主机会等待更多数据导致 URB 超时。

3.4 接口描述符(Interface Descriptor)

接口描述符描述一个功能单元(如 HID 键盘、UVC 摄像头、音频麦克风)。一个配置可以包含多个接口,接口可以包含备用设置(Alternate Setting)。接口描述符长度为 9 字节。

偏移 字段 大小 说明
0 bLength 1 描述符长度,固定为 9
1 bDescriptorType 1 接口描述符类型,0x04
2 bInterfaceNumber 1 接口编号(从 0 开始)
3 bAlternateSetting 1 备用设置编号
4 bNumEndpoints 1 该接口使用的端点数量(不包括端点 0)
5 bInterfaceClass 1 接口类代码(如 HID=0x03, Mass Storage=0x08)
6 bInterfaceSubClass 1 接口子类代码
7 bInterfaceProtocol 1 接口协议代码
8 iInterface 1 接口字符串描述符索引
接口与备用设置(Alternate Setting)示例 接口 0 (Alt 0) bNumEndpoints = 1 接口 0 (Alt 1) bNumEndpoints = 2 接口 1 (Alt 0) bNumEndpoints = 0 典型场景:USB 音频设备 Alt 0 = 无数据端点(静音),Alt 1 = 启用同步端点(播放)

3.5 端点描述符(Endpoint Descriptor)

端点描述符是描述符体系中最底层的传输实体,定义数据传输的通道属性。每个非零端点都必须有对应的端点描述符。端点描述符长度为 7 字节(USB 2.0 标准),USB 3.0 扩展为 9 字节。

偏移 字段 大小 说明
0 bLength 1 描述符长度,USB 2.0 固定为 7
1 bDescriptorType 1 端点描述符类型,0x05
2 bEndpointAddress 1 端点地址(位 3-0:端点号,位 7:方向 0=OUT, 1=IN)
3 bmAttributes 1 传输类型(位 1-0:0=控制, 1=等时, 2=批量, 3=中断)
4-5 wMaxPacketSize 2 最大包大小(USB 2.0 高速批量端点固定为 512)
6 bInterval 1 轮询间隔(中断/等时端点:1-255,单位:帧/微帧)
URB 超时关联分析:端点描述符中的 wMaxPacketSize 和 bInterval 直接影响 URB 的传输行为。例如,高速批量端点 wMaxPacketSize 必须为 512,如果固件错误地设置为 64,主机将无法正确拆分数据包,导致 URB 提交失败或超时。中断端点的 bInterval 如果设置过小(如 1),会频繁触发中断传输,增加 CPU 负载;如果设置过大,则可能导致实时性要求高的数据(如鼠标)出现明显延迟。

3.6 字符串描述符(String Descriptor)

字符串描述符提供人类可读的文本信息(如厂商名、产品名、序列号),是可选描述符。字符串描述符使用 Unicode UTF-16LE 编码。索引 0 是特殊的语言 ID 描述符,用于声明设备支持的语言。

偏移 字段 大小 说明
0 bLength 1 描述符长度(2 + 字符串字节数)
1 bDescriptorType 1 字符串描述符类型,0x03
2..N bString N-2 Unicode 字符串(UTF-16LE 编码)
调试技巧:在 Linux 下使用 lsusb -v 可以查看设备的完整描述符树。如果字符串描述符返回乱码或空值,通常是因为固件中的字符串索引与设备描述符中的 iManufacturer/iProduct/iSerialNumber 不匹配,或者字符串长度计算错误。

3.7 描述符获取流程与常见错误

主机在枚举过程中按固定顺序获取描述符。理解这个顺序对于定位枚举失败至关重要。

枚举过程中描述符获取顺序 1. 获取设备描述符 (18B) 2. 设置地址 (SET_ADDRESS) 3. 再次获取设备描述符 4. 获取配置描述符 (9B) 5. 获取完整配置描述符树 6. 获取字符串描述符 常见枚举失败场景 • 场景 A:设备描述符 bMaxPacketSize0 错误 → 主机无法正确接收后续数据,枚举卡死 • 场景 B:配置描述符 wTotalLength 偏小 → 主机截断接口/端点描述符,驱动 probe 失败 • 场景 C:端点描述符 wMaxPacketSize 与硬件 FIFO 不匹配 → URB 提交后无响应,触发超时 • 场景 D:字符串描述符索引越界或长度错误 → lsusb 显示乱码,但通常不影响功能

3.8 描述符调试实战:使用 usbmon 捕获原始描述符

在 Linux 环境下,usbmon 是分析描述符传输最直接的工具。以下示例展示如何捕获并解析设备描述符的原始数据。

# 加载 usbmon 模块
sudo modprobe usbmon

# 查看可用的 USB 总线
ls /sys/kernel/debug/usb/usbmon/

# 捕获总线 2 上的所有 URB(需要 root 权限)
sudo cat /sys/kernel/debug/usb/usbmon/2u > usb_trace.log

# 在另一个终端插入 USB 设备,然后 Ctrl+C 停止捕获
# 查看捕获到的 GET_DESCRIPTOR 请求
grep "GET_DESCRIPTOR" usb_trace.log | head -5

# 典型输出示例(设备描述符请求):
# ffff8880a1b3c000 0 2:1:0 1280 S Ci:2:0000:0 0 18 = 12010002 00000040 12340987 01020102 01
# 解析:
# 12 = bLength (18)
# 01 = bDescriptorType (DEVICE)
# 0002 = bcdUSB (USB 2.0)
# 00 = bDeviceClass
# 00 = bDeviceSubClass
# 00 = bDeviceProtocol
# 40 = bMaxPacketSize0 (64)
# 1234 = idVendor
# 0987 = idProduct
# 0102 = bcdDevice
# 01 = iManufacturer
# 02 = iProduct
# 01 = bNumConfigurations
重要提示:在调试枚举失败时,优先检查设备描述符的前 8 个字节。如果 bMaxPacketSize0 与设备实际能力不匹配,主机将在第一次 GET_DESCRIPTOR 请求后无法正确解析返回数据,导致后续所有描述符获取失败。这是枚举失败中最常见、也最容易忽略的问题。

3.9 描述符体系总结

USB 描述符体系是枚举过程的“通信协议”,每一层描述符都承载着主机驱动所需的设备信息。在调试 URB 超时和枚举失败时,建议按以下顺序检查描述符:

  • 第一步:使用 lsusb -v 或 usbmon 确认设备描述符的 bMaxPacketSize0 是否正确。
  • 第二步:检查配置描述符的 wTotalLength 是否等于所有子描述符长度之和。
  • 第三步:核对接口描述符的 bNumEndpoints 是否与实际端点数量一致。
  • 第四步:验证端点描述符的 wMaxPacketSize 和 bInterval 是否在硬件规格范围内。
  • 第五步:确认字符串描述符索引有效,且长度字段正确。
核心结论:描述符是 USB 设备的“宪法”,任何违反 USB 规范(如 wMaxPacketSize 不是 2 的幂、bInterval 超出范围、描述符顺序颠倒)都会导致枚举失败或 URB 超时。掌握描述符体系的结构和调试方法,是解决 USB 驱动问题的基本功。