4. USB 描述符体系:结构与字段解析
USB 描述符(Descriptor)是设备向主机报告自身能力与配置信息的标准数据结构。主机在枚举过程中通过一系列标准请求(Get Descriptor)读取这些描述符,从而构建完整的设备模型。描述符采用层级嵌套的树形结构,从最顶层的设备描述符逐级向下,直至最底层的端点描述符。
核心原则:描述符是主机与设备之间的“契约”。任何字段错误或顺序异常都会导致枚举失败,轻则设备无法识别,重则系统挂起。
4.1 设备描述符(Device Descriptor)
设备描述符是 USB 描述符体系的根节点,位于描述符链表的头部。主机通过 Get_Descriptor(DEVICE) 请求获取,长度为 18 字节,固定偏移。
| 偏移 | 字段 | 大小 | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 描述符长度,固定 0x12 (18) |
| 1 | bDescriptorType | 1 | 类型码,设备描述符为 0x01 |
| 2 | bcdUSB | 2 | USB 规范版本号(BCD 码),如 0x0200 表示 USB 2.0 |
| 4 | bDeviceClass | 1 | 设备类代码(由 USB-IF 分配),0x00 表示在接口描述符中定义 |
| 5 | bDeviceSubClass | 1 | 设备子类代码 |
| 6 | bDeviceProtocol | 1 | 设备协议代码 |
| 7 | bMaxPacketSize0 | 1 | 端点 0 的最大包大小(字节),USB 2.0 通常为 64 |
| 8 | idVendor | 2 | 厂商 ID(VID),由 USB-IF 分配 |
| 10 | idProduct | 2 | 产品 ID(PID),厂商自行分配 |
| 12 | bcdDevice | 2 | 设备版本号(BCD 码) |
| 14 | iManufacturer | 1 | 厂商字符串描述符索引,0 表示无 |
| 15 | iProduct | 1 | 产品字符串描述符索引 |
| 16 | iSerialNumber | 1 | 序列号字符串描述符索引 |
| 17 | bNumConfigurations | 1 | 支持的配置数量 |
移植适配要点:在嵌入式 Host 驱动中,设备描述符的
bMaxPacketSize0 字段决定了控制传输的初始包大小。如果设备上报的值与 Host 预期不符,会导致 SETUP 阶段数据溢出或接收不完整。建议在 Host 驱动中增加对该字段的校验与动态调整逻辑。
4.2 配置描述符(Configuration Descriptor)
配置描述符描述设备的供电模式、最大电流消耗以及包含的接口数量。一个设备可以有多个配置,但同一时刻只能激活一个。配置描述符长度为 9 字节。
| 偏移 | 字段 | 大小 | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 描述符长度,固定 0x09 (9) |
| 1 | bDescriptorType | 1 | 类型码,配置描述符为 0x02 |
| 2 | wTotalLength | 2 | 整个配置描述符集合的总长度(包括配置、接口、端点等所有子描述符) |
| 4 | bNumInterfaces | 1 | 该配置支持的接口数量 |
| 5 | bConfigurationValue | 1 | 配置值,用于 Set_Configuration 请求选择该配置 |
| 6 | iConfiguration | 1 | 配置字符串描述符索引 |
| 7 | bmAttributes | 1 | 配置特性:D7=总线供电,D6=自供电,D5=远程唤醒 |
| 8 | bMaxPower | 1 | 最大电流消耗,单位 2mA,值 50 表示 100mA |
常见错误:
wTotalLength 字段必须精确等于所有子描述符长度之和。如果 Host 驱动读取的总长度与设备实际返回的数据量不一致,会导致解析偏移错乱,后续接口和端点描述符全部解析失败。移植时建议在 Host 端增加长度校验。
4.3 接口描述符(Interface Descriptor)
接口描述符定义设备的一个功能接口,每个接口可以包含一个或多个端点(端点 0 除外)。接口描述符长度为 9 字节。复合设备(如 USB 摄像头 + 麦克风)会包含多个接口描述符。
| 偏移 | 字段 | 大小 | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 描述符长度,固定 0x09 (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 | 接口字符串描述符索引 |
类驱动匹配机制:Host 端的类驱动(如 HID、MSC、CDC)通常通过
bInterfaceClass 字段来匹配对应的接口。如果设备描述符中 bDeviceClass 为 0x00,则 Host 必须遍历所有接口描述符,根据每个接口的类代码加载相应的驱动。
4.4 端点描述符(Endpoint Descriptor)
端点描述符定义数据传输的端点属性,包括传输方向、传输类型和最大包大小。除端点 0 外,每个非控制端点都必须有对应的端点描述符。端点描述符长度为 7 字节。
| 偏移 | 字段 | 大小 | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 描述符长度,固定 0x07 (7) |
| 1 | bDescriptorType | 1 | 类型码,端点描述符为 0x05 |
| 2 | bEndpointAddress | 1 | 端点地址:Bit 7 方向(0=OUT, 1=IN),Bit 3-0 端点号 |
| 3 | bmAttributes | 1 | 传输类型:0=控制,1=等时,2=批量,3=中断 |
| 4 | wMaxPacketSize | 2 | 最大包大小(字节),等时传输还包含附加信息 |
| 6 | bInterval | 1 | 轮询间隔(毫秒或微帧),中断/等时传输使用 |
移植适配要点:
wMaxPacketSize 在 USB 2.0 高速模式下,批量端点的最大包大小固定为 512 字节,中断端点最大为 1024 字节。Host 驱动在解析该字段后,需要根据传输类型和速度模式分配对应的 FIFO 或 DMA 缓冲区大小。如果缓冲区分配不足,会导致数据溢出或传输错误。
4.5 字符串描述符(String Descriptor)
字符串描述符提供可读的文本信息,如厂商名称、产品名称和序列号。字符串描述符使用 Unicode(UTF-16LE)编码,长度可变。字符串描述符的索引 0 是特殊的语言 ID 描述符,用于声明设备支持的语言。
| 偏移 | 字段 | 大小 | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 描述符总长度(包括 bLength 和 bDescriptorType) |
| 1 | bDescriptorType | 1 | 类型码,字符串描述符为 0x03 |
| 2 | bString / wLANGID | 可变 | 索引 0 时为语言 ID 数组(2 字节/项);其他索引时为 Unicode 字符串 |
字符串描述符的获取流程:
- 主机先请求索引 0 的字符串描述符,获取设备支持的语言 ID 列表。
- 主机选择一个语言 ID(通常为 0x0409,英语-美国),然后请求对应索引的字符串描述符。
- 设备返回的字符串描述符中,
bLength字段指示了实际返回的字节数,主机据此分配接收缓冲区。
4.6 描述符间的依赖关系与校验
在 USB 枚举过程中,描述符的解析顺序和依赖关系至关重要。以下表格总结了各描述符之间的关键关联:
| 描述符 | 依赖关系 | 关键校验点 |
|---|---|---|
| 设备描述符 | 无(根节点) | bLength=18, bDescriptorType=0x01, bMaxPacketSize0 与速度匹配 |
| 配置描述符 | 设备描述符中的 bNumConfigurations | wTotalLength 必须等于所有子描述符长度之和 |
| 接口描述符 | 配置描述符中的 bNumInterfaces | bInterfaceNumber 连续递增,bNumEndpoints 与实际端点数量一致 |
| 端点描述符 | 接口描述符中的 bNumEndpoints | bEndpointAddress 不重复,wMaxPacketSize 不超过速度限制 |
| 字符串描述符 | 设备/配置/接口描述符中的 i* 索引字段 | 索引 0 必须存在且包含有效的语言 ID |
4.7 描述符解析的代码示例
以下是一个简化的 Host 端描述符解析函数,展示如何从原始数据流中提取设备描述符字段:
/* 设备描述符解析示例 */
typedef struct {
uint8_t bLength;
uint8_t bDescriptorType;
uint16_t bcdUSB;
uint8_t bDeviceClass;
uint8_t bDeviceSubClass;
uint8_t bDeviceProtocol;
uint8_t bMaxPacketSize0;
uint16_t idVendor;
uint16_t idProduct;
uint16_t bcdDevice;
uint8_t iManufacturer;
uint8_t iProduct;
uint8_t iSerialNumber;
uint8_t bNumConfigurations;
} usb_device_descriptor_t;
int usb_parse_device_descriptor(const uint8_t *buf, uint16_t len,
usb_device_descriptor_t *desc) {
if (len < 18) {
return -1; /* 数据长度不足 */
}
if (buf[0] != 18 || buf[1] != 0x01) {
return -2; /* 长度或类型错误 */
}
desc->bLength = buf[0];
desc->bDescriptorType = buf[1];
desc->bcdUSB = (buf[3] << 8) | buf[2];
desc->bDeviceClass = buf[4];
desc->bDeviceSubClass = buf[5];
desc->bDeviceProtocol = buf[6];
desc->bMaxPacketSize0 = buf[7];
desc->idVendor = (buf[9] << 8) | buf[8];
desc->idProduct = (buf[11] << 8) | buf[10];
desc->bcdDevice = (buf[13] << 8) | buf[12];
desc->iManufacturer = buf[14];
desc->iProduct = buf[15];
desc->iSerialNumber = buf[16];
desc->bNumConfigurations = buf[17];
return 0; /* 解析成功 */
}
移植适配警告:上述代码假设数据流是小端字节序(USB 标准)。在解析多字节字段(如 idVendor、wTotalLength)时,必须注意 Host 平台的字节序。如果 Host 是大端架构(如某些 PowerPC 或网络处理器),需要在读取后执行字节序转换。
4.8 描述符体系总结
USB 描述符体系是枚举过程的核心数据模型,理解其层级结构和字段含义是进行 Host 驱动移植的基础。以下要点需要重点掌握:
- 层级关系:设备 → 配置 → 接口 → 端点,逐级嵌套,不可跨越。
- 长度固定:设备描述符 18 字节、配置描述符 9 字节、接口描述符 9 字节、端点描述符 7 字节,字符串描述符长度可变。
- 类型码:每个描述符的第二个字节(bDescriptorType)标识类型,Host 驱动据此区分描述符种类。
- 索引字段:iManufacturer、iProduct、iSerialNumber 等索引字段指向字符串描述符,0 表示该字符串不存在。
- 校验优先:在解析每个描述符之前,必须先校验 bLength 和 bDescriptorType,防止解析越界或类型不匹配。
进阶提示:在 USB 3.x 中,描述符体系增加了 B