3. USB 描述符体系:结构与关系

USB 描述符是设备向 Host 报告自身能力与配置的标准化数据结构。Host 在枚举过程中通过标准请求(GET_DESCRIPTOR)逐层获取这些描述符,从而构建出完整的设备逻辑模型。理解描述符的层级关系与字段含义,是移植适配不同 Host 驱动的基础。

核心概念:USB 描述符采用“树形层级”组织,从设备描述符(根)向下依次为配置描述符、接口描述符、端点描述符。字符串描述符作为可选补充,提供可读的名称信息。

3.1 描述符层级总览

每个 USB 设备有且仅有一个设备描述符,至少包含一个配置描述符;每个配置描述符下可包含多个接口描述符;每个接口描述符下可包含多个端点描述符。字符串描述符通过索引被其他描述符引用。

USB 描述符层级结构 设备描述符 配置描述符 1 配置描述符 2 接口描述符 0 接口描述符 1 接口描述符 0 端点 0 (控制) 端点 1 (批量) 端点 0 (控制) 字符串描述符 (索引引用)
移植要点:不同 Host 驱动对描述符的解析顺序和容错策略不同。例如,某些 Host 在获取配置描述符时只请求前 9 字节(配置描述符头部),再根据 wTotalLength 字段请求完整描述符;而另一些 Host 则一次性请求全部。适配时需确保设备端能正确处理这两种模式。

3.2 设备描述符 (Device Descriptor)

设备描述符是 USB 设备的“身份证”,位于描述符树的根节点。Host 通过默认控制端点(端点 0)发送 GET_DESCRIPTOR 请求,索引为 0x01,获取该描述符。其长度为固定的 18 字节。

偏移 字段 大小 说明
0 bLength 1 描述符长度,固定为 18 (0x12)
1 bDescriptorType 1 描述符类型,设备描述符为 0x01
2-3 bcdUSB 2 USB 规范版本号 (BCD 码),如 0x0200 表示 USB 2.0
4 bDeviceClass 1 设备类代码 (由 USB-IF 分配),0x00 表示在接口描述符中定义
5 bDeviceSubClass 1 设备子类代码
6 bDeviceProtocol 1 设备协议代码
7 bMaxPacketSize0 1 端点 0 的最大包大小 (字节)
8-9 idVendor 2 厂商 ID (VID),由 USB-IF 分配
10-11 idProduct 2 产品 ID (PID),由厂商自行分配
12-13 bcdDevice 2 设备版本号 (BCD 码)
14 iManufacturer 1 厂商字符串描述符索引,0 表示无
15 iProduct 1 产品字符串描述符索引
16 iSerialNumber 1 序列号字符串描述符索引
17 bNumConfigurations 1 支持的配置描述符数量
常见适配问题:某些 Host 驱动(如旧版 Linux 内核)对 bMaxPacketSize0 字段有严格校验。如果设备实际端点 0 最大包大小与描述符中声明的不一致,Host 可能拒绝继续枚举。移植时务必确保该字段与硬件配置匹配。

3.3 配置描述符 (Configuration Descriptor)

配置描述符描述设备的电源管理属性和接口总数。其头部为 9 字节固定长度,但 wTotalLength 字段指示了包括其自身、所有接口描述符和端点描述符在内的总长度。Host 通常分两步获取:先读 9 字节头部,再根据 wTotalLength 读取完整配置描述符集合。

偏移 字段 大小 说明
0 bLength 1 描述符长度,固定为 9 (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,如 50 表示 100mA
移植对比:Windows 和 Linux 在解析配置描述符时,对 bmAttributes 的 D5 位(远程唤醒)处理不同。Windows 需要设备在描述符中声明该位,并在后续通过 SET_FEATURE 启用;Linux 则可能忽略该位,直接通过驱动控制。适配时需根据目标 Host 调整描述符声明。

3.4 接口描述符 (Interface Descriptor)

接口描述符代表设备的一个功能单元。一个配置下可以有多个接口(复合设备),每个接口可以包含多个端点(除端点 0 外)。接口描述符长度为 9 字节。

偏移 字段 大小 说明
0 bLength 1 描述符长度,固定为 9 (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 接口字符串描述符索引
备用设置 (Alternate Setting):接口描述符支持通过 bAlternateSetting 定义多个变体。Host 使用 SET_INTERFACE 请求切换。例如,视频设备可能通过备用设置切换帧率或分辨率。移植时需注意:某些 Host 驱动在枚举时只读取 bAlternateSetting=0 的接口描述符,忽略其他备用设置,直到应用层显式请求。

3.5 端点描述符 (Endpoint Descriptor)

端点描述符定义数据传输的端点属性。除默认控制端点(端点 0)外,每个端点都需要一个描述符。其长度为 7 字节。

偏移 字段 大小 说明
0 bLength 1 描述符长度,固定为 7 (0x07)
1 bDescriptorType 1 描述符类型,端点描述符为 0x05
2 bEndpointAddress 1 端点地址:D7=方向 (0 OUT, 1 IN),D3-D0=端点号
3 bmAttributes 1 传输类型:0=控制,1=等时,2=批量,3=中断
4-5 wMaxPacketSize 2 最大包大小 (字节),等时传输时包含附加信息
6 bInterval 1 轮询间隔 (毫秒或帧数,取决于传输类型)
端点间隔适配:bInterval 字段在不同 USB 速度下含义不同。全速中断端点单位为 1ms,高速中断端点单位为 125µs (即 2^(bInterval-1) * 125µs)。某些 Host 驱动(如 macOS)对间隔值有最小限制,如果设备设置的间隔过小,Host 可能拒绝加载驱动。移植时建议参考目标 Host 的驱动文档设置合理的间隔值。

3.6 字符串描述符 (String Descriptor)

字符串描述符提供可读的 Unicode 文本,如厂商名、产品名、序列号。其他描述符通过索引字段(如 iManufacturer)引用字符串描述符。字符串描述符的索引 0 是特殊的语言 ID 描述符,用于获取设备支持的语言列表。

偏移 字段 大小 说明
0 bLength 1 描述符长度,可变
1 bDescriptorType 1 描述符类型,字符串描述符为 0x03
2..N bString 可变 Unicode 字符串 (UTF-16LE 编码)
语言 ID 描述符:当 Host 请求索引为 0 的字符串描述符时,设备应返回一个包含 2 字节语言 ID 数组的描述符。例如,0x0409 表示美式英语。某些 Host 驱动(如 Android 的 USB 框架)只请求一次字符串描述符,并缓存结果;如果设备在枚举过程中返回不同的字符串内容,可能导致驱动加载失败。

3.7 描述符关系总结

下表总结了五种标准描述符的关键关系与枚举顺序:

描述符类型 获取顺序 长度 关键字段 引用关系
设备描述符 第 1 步 18 字节 (固定) idVendor, idProduct, bNumConfigurations 引用字符串描述符 (iManufacturer, iProduct, iSerialNumber)
配置描述符 第 2 步 9 字节头部 + 可变 wTotalLength, bNumInterfaces, bConfigurationValue 包含接口描述符集合
接口描述符 第 3 步 9 字节 (固定) bInterfaceNumber, bInterfaceClass, bNumEndpoints 包含端点描述符集合
端点描述符 第 4 步 7 字节 (固定) bEndpointAddress, bmAttributes, wMaxPacketSize 无子描述符
字符串描述符 按需获取 可变 bString (Unicode) 被其他描述符通过索引引用
Host 枚举时描述符获取流程 1. 获取设备描述符 2. 获取配置描述符 3. 解析接口/端点 按需获取字符串描述符 选择配置 (SET_CONFIGURATION) 适配要点 不同 Host 驱动在步骤 2 中可能分两次请求配置描述符(先 9 字节头部,再完整集合) 设备端必须支持这种分段请求,否则枚举可能失败

3.8 描述符代码示例

以下是一个典型的 USB 设备描述符和配置描述符的 C 语言结构体定义与初始化示例:

// 设备描述符结构体 (18 字节)
typedef struct {
    uint8_t  bLength;            // 0x12
    uint8_t  bDescriptorType;    // 0x01
    uint16_t bcdUSB;             // 0x0200 (USB 2.0)
    uint8_t  bDeviceClass;       // 0x00
    uint8_t  bDeviceSubClass;    // 0x00
    uint8_t  bDeviceProtocol;    // 0x00
    uint8_t  bMaxPacketSize0;    // 0x40 (64 字节)
    uint16_t idVendor;           // 0x1234
    uint16_t idProduct;          // 0x5678
    uint16_t bcdDevice;          // 0x0100
    uint8_t  iManufacturer;      // 1 (字符串索引)
    uint8_t  iProduct;           // 2
    uint8_t  iSerialNumber;      // 3
    uint8_t  bNumConfigurations; // 1
} __attribute__((packed)) usb_device_descriptor_t;

// 配置描述符头部结构体 (9 字节)
typedef struct {
    uint8_t  bLength;            // 0x09
    uint8_t  bDescriptorType;    // 0x02
    uint16_t wTotalLength;       // 总长度
    uint8_t  bNumInterfaces;     // 1
    uint8_t  bConfigurationValue;// 1
    uint8_t  iConfiguration;     // 0 (无字符串)
    uint8_t  bmAttributes;       // 0x80 (总线供电)
    uint8_t  bMaxPower;          // 50 (100mA)
} __attribute__((packed)) usb_config_descriptor_t;

// 初始化示例
const usb_device_descriptor_t device_desc = {
    .bLength = 0x12,
    .bDescriptorType = 0x01,
    .bcdUSB = 0x0200,
    .bDeviceClass = 0x00,
    .bDeviceSubClass = 0x00,
    .bDeviceProtocol = 0x00,
    .bMaxPacketSize0 = 0x40,
    .idVendor = 0x1234,
    .idProduct = 0x5678,
    .bcdDevice = 0x0100,
    .iManufacturer = 1,
    .iProduct = 2,
    .iSerialNumber = 3,
    .bNumConfigurations = 1
};
移植建议:在编写描述符时,务必使用 __attribute__((packed)) 或等效的编译器指令,确保结构体在内存中紧密排列,没有填充字节。不同编译器对结构体对齐的处理不同,错误的对齐会导致 Host 解析出乱码,进而枚举失败。