2. USB描述符体系:设备描述符、配置描述符、接口描述符、端点描述符、字符串描述符
USB描述符是USB设备向主机报告自身能力与属性的结构化数据集合。主机通过标准设备请求(如GET_DESCRIPTOR)逐层获取这些描述符,从而完成设备枚举与驱动匹配。描述符体系采用严格的层级树状结构,每一层描述符都包含指向下一层描述符的字段。
核心要点:描述符是USB协议中“自描述”机制的基石。驱动开发者在调试端点配置与URB传输错误时,必须能够准确解析描述符中的、、等关键字段。
2.1 描述符层级结构总览
USB描述符从顶层到底层依次为:设备描述符 → 配置描述符 → 接口描述符 → 端点描述符。字符串描述符作为可选补充,为上述描述符中的索引字段提供可读文本。
2.2 设备描述符(Device Descriptor)
设备描述符是USB设备的第一级描述符,位于描述符树的根节点。主机在枚举阶段首先通过GET_DESCRIPTOR(DEVICE)获取该描述符,其长度为固定的18字节。
| 偏移 | 字段 | 大小 | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 描述符长度(固定0x12) |
| 1 | bDescriptorType | 1 | 描述符类型(设备=0x01) |
| 2-3 | bcdUSB | 2 | USB规范版本号(BCD码,如0x0200表示USB 2.0) |
| 4 | bDeviceClass | 1 | 设备类代码(0x00表示在接口描述符中定义) |
| 5 | bDeviceSubClass | 1 | 设备子类代码 |
| 6 | bDeviceProtocol | 1 | 设备协议代码 |
| 7 | bMaxPacketSize0 | 1 | 端点0的最大包大小(8/16/32/64) |
| 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 | 支持的配置描述符数量 |
调试提示:在Linux内核中,设备描述符通过
struct usb_device_descriptor结构体表示,定义在<linux/usb/ch9.h>中。使用lsusb -v命令可以查看实际设备的描述符内容。
2.3 配置描述符(Configuration Descriptor)
配置描述符描述设备的电源管理属性和接口数量。一个设备可以有多个配置(如低功耗模式和高性能模式),但同一时刻只能激活一个配置。配置描述符的长度为9字节,其后紧跟接口描述符和端点描述符。
| 偏移 | 字段 | 大小 | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 描述符长度(固定0x09) |
| 1 | bDescriptorType | 1 | 描述符类型(配置=0x02) |
| 2-3 | wTotalLength | 2 | 配置描述符及其子描述符的总长度 |
| 4 | bNumInterfaces | 1 | 该配置下的接口数量 |
| 5 | bConfigurationValue | 1 | 用于SET_CONFIGURATION请求的配置值 |
| 6 | iConfiguration | 1 | 配置字符串描述符索引 |
| 7 | bmAttributes | 1 | 配置特性(D7:总线供电, D6:自供电, D5:远程唤醒) |
| 8 | bMaxPower | 1 | 最大功耗(单位2mA,如0x32表示100mA) |
2.4 接口描述符(Interface Descriptor)
接口描述符描述一个功能单元(如键盘、音频、存储),一个配置可以包含多个接口。接口描述符长度为9字节,其bInterfaceClass字段决定了设备的功能类型,是驱动匹配的关键依据。
| 偏移 | 字段 | 大小 | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 描述符长度(固定0x09) |
| 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 | 接口字符串描述符索引 |
常见错误:接口描述符中的
bAlternateSetting字段容易被忽略。当驱动未正确处理备用设置时,可能导致端点配置错误,进而引发URB传输超时或STALL错误。
2.5 端点描述符(Endpoint Descriptor)
端点描述符是描述符体系中最底层的描述符,直接定义数据传输的通道属性。每个非0端点都必须有对应的端点描述符,其长度为7字节(音频设备为9字节)。
| 偏移 | 字段 | 大小 | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 描述符长度(固定0x07) |
| 1 | bDescriptorType | 1 | 描述符类型(端点=0x05) |
| 2 | bEndpointAddress | 1 | 端点地址(D7:方向, D3-D0:端点号) |
| 3 | bmAttributes | 1 | 传输类型(D1-D0: 0=控制, 1=等时, 2=批量, 3=中断) |
| 4-5 | wMaxPacketSize | 2 | 最大包大小(D10-D0: 大小, D12-D11: 附加事务数) |
| 6 | bInterval | 1 | 轮询间隔(中断/等时端点,单位ms或125μs) |
端点地址与传输类型详解
bEndpointAddress字段的位定义:
- Bit 7:方向(0=OUT/主机到设备,1=IN/设备到主机)
- Bit 6-4:保留(必须为0)
- Bit 3-0:端点号(0x00-0x0F)
bmAttributes字段的传输类型编码:
- 00:控制传输(Control)——用于枚举和命令
- 01:等时传输(Isochronous)——实时数据,无重传
- 10:批量传输(Bulk)——大数据量,保证交付
- 11:中断传输(Interrupt)——低延迟,轮询机制
2.6 字符串描述符(String Descriptor)
字符串描述符提供可读的Unicode文本信息,如厂商名称、产品名称和序列号。字符串描述符通过索引号被其他描述符引用,索引0为特殊语言ID描述符。字符串描述符的长度可变,前2字节为长度和类型,后续为Unicode字符串数据。
| 偏移 | 字段 | 大小 | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 描述符长度(可变,包括长度和类型字节) |
| 1 | bDescriptorType | 1 | 描述符类型(字符串=0x03) |
| 2..n | bString | 可变 | Unicode字符串(UTF-16LE编码) |
调试技巧:在Linux中,使用
lsusb -v -s <bus>:<device> | grep -A 2 "iManufacturer\|iProduct\|iSerial"可以快速查看字符串描述符内容。如果字符串描述符读取失败(如设备返回STALL),通常表示设备未实现该字符串索引。
2.7 描述符解析实战:从原始字节到驱动配置
以下是一个典型的USB HID设备描述符原始数据示例(通过usbmon或逻辑分析仪捕获):
// 设备描述符(18字节)
12 01 00 02 00 00 00 40 0A 12 34 56 00 01 01 02 03 01
// 解析结果:
// bLength: 0x12 (18)
// bDescriptorType: 0x01 (DEVICE)
// bcdUSB: 0x0200 (USB 2.0)
// bDeviceClass: 0x00 (在接口中定义)
// bMaxPacketSize0: 0x40 (64字节)
// idVendor: 0x120A (示例厂商)
// idProduct: 0x5634 (示例产品)
// bNumConfigurations: 0x01 (1个配置)
// 配置描述符 + 接口描述符 + 端点描述符(组合数据)
09 02 22 00 01 01 00 A0 32
09 04 00 00 01 03 01 02 00
07 05 81 03 40 00 0A
// 解析结果:
// 配置描述符: wTotalLength=0x0022(34), bNumInterfaces=1, bMaxPower=0x32(100mA)
// 接口描述符: bInterfaceNumber=0, bNumEndpoints=1, bInterfaceClass=0x03(HID)
// 端点描述符: bEndpointAddress=0x81(IN,端点1), bmAttributes=0x03(中断传输)
// wMaxPacketSize=0x0040(64字节), bInterval=10ms
驱动开发要点:在编写USB驱动时,通常不需要手动解析描述符原始字节。Linux内核提供了
usb_get_descriptor()系列函数和struct usb_device中的缓存描述符。但理解描述符的二进制布局对于调试URB传输错误至关重要——例如,wMaxPacketSize解析错误会导致批量传输的分包边界错乱。
2.8 描述符错误排查清单
| 错误现象 | 可能原因 | 排查方法 |
|---|---|---|
| 枚举失败,设备不被识别 | 设备描述符bMaxPacketSize0与实际端点0能力不匹配 |
检查设备硬件配置,确认端点0 FIFO大小 |
| 驱动绑定失败 | 接口描述符bInterfaceClass/bInterfaceProtocol与驱动匹配表不一致 |
使用lsusb -v核对描述符,检查驱动id_table |
| URB传输返回-EPROTO | 端点描述符wMaxPacketSize与实际硬件能力不符 |
验证端点最大包大小,检查是否超过硬件限制 |
| 中断传输无响应 | 端点描述符bInterval值过小导致总线拥塞,或过大导致延迟过高 |
调整bInterval值,参考USB规范建议范围 |
| 字符串描述符读取失败 | 设备未实现该索引的字符串,或语言ID不匹配 | 先读取索引0获取支持的语言ID列表 |