3. USB描述符体系
USB 描述符(Descriptor)是 USB 设备向主机报告自身属性、能力和资源分配的核心数据结构。主机通过标准设备请求(如 GET_DESCRIPTOR)逐层获取这些描述符,从而完成枚举过程。描述符体系采用严格的层级结构,每一层都包含指向下一层的指针或长度字段。
3.1 描述符层级结构总览
USB 描述符体系呈树形结构,从设备描述符开始,逐级向下展开。主机在枚举过程中,首先获取设备描述符(18 字节),然后根据其中的 bNumConfigurations 字段获取配置描述符及其附属的所有描述符。
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 | 接口字符串描述符索引 |
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,单位:帧/微帧) |
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 编码) |
lsusb -v 可以查看设备的完整描述符树。如果字符串描述符返回乱码或空值,通常是因为固件中的字符串索引与设备描述符中的 iManufacturer/iProduct/iSerialNumber 不匹配,或者字符串长度计算错误。
3.7 描述符获取流程与常见错误
主机在枚举过程中按固定顺序获取描述符。理解这个顺序对于定位枚举失败至关重要。
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
bMaxPacketSize0 与设备实际能力不匹配,主机将在第一次 GET_DESCRIPTOR 请求后无法正确解析返回数据,导致后续所有描述符获取失败。这是枚举失败中最常见、也最容易忽略的问题。
3.9 描述符体系总结
USB 描述符体系是枚举过程的“通信协议”,每一层描述符都承载着主机驱动所需的设备信息。在调试 URB 超时和枚举失败时,建议按以下顺序检查描述符:
- 第一步:使用
lsusb -v或usbmon确认设备描述符的bMaxPacketSize0是否正确。 - 第二步:检查配置描述符的
wTotalLength是否等于所有子描述符长度之和。 - 第三步:核对接口描述符的
bNumEndpoints是否与实际端点数量一致。 - 第四步:验证端点描述符的
wMaxPacketSize和bInterval是否在硬件规格范围内。 - 第五步:确认字符串描述符索引有效,且长度字段正确。
wMaxPacketSize 不是 2 的幂、bInterval 超出范围、描述符顺序颠倒)都会导致枚举失败或 URB 超时。掌握描述符体系的结构和调试方法,是解决 USB 驱动问题的基本功。