4. USB 描述符体系:结构与字段解析

USB 描述符(Descriptor)是设备向主机报告自身能力与配置信息的标准数据结构。主机在枚举过程中通过一系列标准请求(Get Descriptor)读取这些描述符,从而构建完整的设备模型。描述符采用层级嵌套的树形结构,从最顶层的设备描述符逐级向下,直至最底层的端点描述符。

核心原则:描述符是主机与设备之间的“契约”。任何字段错误或顺序异常都会导致枚举失败,轻则设备无法识别,重则系统挂起。
USB 描述符层级结构 设备描述符 配置描述符 接口描述符 端点描述符 端点描述符 端点描述符 字符串描述符 每个USB设备有且仅有 1个设备描述符 至少1个配置描述符 可包含多个配置 每个配置至少1个接口 复合设备可含多个接口 每个接口至少1个端点 控制端点0默认存在

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 轮询间隔(毫秒或微帧),中断/等时传输使用
bEndpointAddress 字段位结构 Bit 7 Bit 6-4 Bit 3-0 方向 0=OUT, 1=IN 保留 必须为 0 端点号 0x01 ~ 0x0F 示例:0x82 Bit 7 = 1 → IN 方向 Bit 3-0 = 0x02 → 端点 2 → 端点 2 IN
移植适配要点: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 字符串
字符串描述符的获取流程:
  1. 主机先请求索引 0 的字符串描述符,获取设备支持的语言 ID 列表。
  2. 主机选择一个语言 ID(通常为 0x0409,英语-美国),然后请求对应索引的字符串描述符。
  3. 设备返回的字符串描述符中,bLength 字段指示了实际返回的字节数,主机据此分配接收缓冲区。

4.6 描述符间的依赖关系与校验

在 USB 枚举过程中,描述符的解析顺序和依赖关系至关重要。以下表格总结了各描述符之间的关键关联:

描述符 依赖关系 关键校验点
设备描述符 无(根节点) bLength=18, bDescriptorType=0x01, bMaxPacketSize0 与速度匹配
配置描述符 设备描述符中的 bNumConfigurations wTotalLength 必须等于所有子描述符长度之和
接口描述符 配置描述符中的 bNumInterfaces bInterfaceNumber 连续递增,bNumEndpoints 与实际端点数量一致
端点描述符 接口描述符中的 bNumEndpoints bEndpointAddress 不重复,wMaxPacketSize 不超过速度限制
字符串描述符 设备/配置/接口描述符中的 i* 索引字段 索引 0 必须存在且包含有效的语言 ID
Host 端描述符解析流程 1. 获取设备描述符 2. 获取配置描述符 3. 解析接口描述符 4. 解析端点描述符 校验 bLength、bDescriptorType、bMaxPacketSize0 设置端点 0 的最大包大小 读取 wTotalLength,分配完整描述符缓冲区 校验 bNumInterfaces 根据 bInterfaceClass 匹配类驱动 记录 bInterfaceNumber 和 bNumEndpoints 解析 bEndpointAddress、bmAttributes、wMaxPacketSize 配置端点 FIFO 和传输类型

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