3. USB 描述符体系:结构与关系
USB 描述符是设备向 Host 报告自身能力与配置的标准化数据结构。Host 在枚举过程中通过标准请求(GET_DESCRIPTOR)逐层获取这些描述符,从而构建出完整的设备逻辑模型。理解描述符的层级关系与字段含义,是移植适配不同 Host 驱动的基础。
3.1 描述符层级总览
每个 USB 设备有且仅有一个设备描述符,至少包含一个配置描述符;每个配置描述符下可包含多个接口描述符;每个接口描述符下可包含多个端点描述符。字符串描述符通过索引被其他描述符引用。
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 | 支持的配置描述符数量 |
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 |
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 | 接口字符串描述符索引 |
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 编码) |
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) | 被其他描述符通过索引引用 |
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 解析出乱码,进而枚举失败。