2. USB 描述符体系:设备描述符、配置描述符、接口描述符、端点描述符、字符串描述符的结构与关系
USB 描述符体系是 USB 设备枚举过程中的核心数据结构,它定义了设备的身份、能力、功能接口以及数据传输方式。主机通过读取这些描述符来识别设备并加载相应的驱动程序。本章将详细解析五种标准描述符的结构、字段含义以及它们之间的层级关系。
2.1 描述符层级关系总览
USB 描述符的组织形式类似于文件系统的目录树。一个 USB 设备有且仅有一个设备描述符,它包含一个或多个配置描述符;每个配置描述符包含一个或多个接口描述符;每个接口描述符包含零个或多个端点描述符。字符串描述符通过索引被其他描述符引用。
2.2 设备描述符 (Device Descriptor)
设备描述符是 USB 设备的根描述符,位于描述符链的最顶层。主机通过控制传输的 GET_DESCRIPTOR 请求(描述符类型为 0x01)获取它。设备描述符提供了设备的全局信息,包括 USB 规范版本、设备类、厂商和产品标识等。
| 偏移量 | 字段名 | 大小 (字节) | 说明 |
|---|---|---|---|
| 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/16/32/64) |
| 8-9 | idVendor | 2 | 厂商 ID (由 USB-IF 分配) |
| 10-11 | idProduct | 2 | 产品 ID (由厂商分配) |
| 12-13 | bcdDevice | 2 | 设备版本号 (BCD 码) |
| 14 | iManufacturer | 1 | 厂商字符串描述符索引 |
| 15 | iProduct | 1 | 产品字符串描述符索引 |
| 16 | iSerialNumber | 1 | 序列号字符串描述符索引 |
| 17 | bNumConfigurations | 1 | 支持的配置描述符数量 |
lsusb -v 可以查看设备的完整描述符信息。如果设备描述符中的 bMaxPacketSize0 设置错误(例如 USB 2.0 高速设备设为 8 而非 64),主机可能在枚举阶段直接复位设备,导致枚举失败。
2.3 配置描述符 (Configuration Descriptor)
配置描述符描述设备的电源管理属性和接口数量。一个设备可以有多个配置(例如低功耗模式和高性能模式),但同一时刻只能激活一个配置。主机通过 SET_CONFIGURATION 请求选择配置。
| 偏移量 | 字段名 | 大小 (字节) | 说明 |
|---|---|---|---|
| 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) |
wTotalLength 必须精确等于所有子描述符的长度之和。如果该值偏小,主机可能无法完整读取后续的接口和端点描述符,导致枚举失败。在 Gadget 驱动中,常见错误是忘记更新 wTotalLength 字段。
2.4 接口描述符 (Interface Descriptor)
接口描述符定义了一个功能单元,它由一组端点组成,共同实现一个 USB 功能(如 HID、Mass Storage、CDC ACM)。一个配置可以包含多个接口,接口可以设置备用设置(Alternate Setting)来切换不同的端点配置。
| 偏移量 | 字段名 | 大小 (字节) | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 描述符长度,固定为 9 (0x09) |
| 1 | bDescriptorType | 1 | 描述符类型,接口描述符为 0x04 |
| 2 | bInterfaceNumber | 1 | 接口编号(从 0 开始) |
| 3 | bAlternateSetting | 1 | 备用设置编号 |
| 4 | bNumEndpoints | 1 | 该接口使用的端点数量(不包括端点 0) |
| 5 | bInterfaceClass | 1 | 接口类代码 |
| 6 | bInterfaceSubClass | 1 | 接口子类代码 |
| 7 | bInterfaceProtocol | 1 | 接口协议代码 |
| 8 | iInterface | 1 | 接口字符串描述符索引 |
usb_interface_descriptor 结构体定义。当实现复合设备(如同时支持 RNDIS 和 ACM)时,需要为每个功能分配独立的 bInterfaceNumber,并确保 bNumEndpoints 与实际注册的端点数量一致。
2.5 端点描述符 (Endpoint Descriptor)
端点描述符定义数据传输的端点属性,包括传输方向、传输类型和最大包大小。除了默认的控制端点(端点 0)外,每个非零端点都必须有对应的描述符。
| 偏移量 | 字段名 | 大小 (字节) | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 描述符长度,固定为 7 (0x07) |
| 1 | bDescriptorType | 1 | 描述符类型,端点描述符为 0x05 |
| 2 | bEndpointAddress | 1 | 端点地址:Bit 7 方向 (0=OUT, 1=IN),Bit 3-0 端点号 |
| 3 | bmAttributes | 1 | 传输类型:0=控制,1=等时,2=批量,3=中断 |
| 4-5 | wMaxPacketSize | 2 | 最大包大小(Bit 10-0 为大小,Bit 12-11 为附加事务数) |
| 6 | bInterval | 1 | 中断/等时传输的轮询间隔(单位:帧/微帧) |
2.6 字符串描述符 (String Descriptor)
字符串描述符提供人类可读的 Unicode 文本信息,如厂商名称、产品名称和序列号。它是可选的,但强烈建议实现,因为操作系统通常使用字符串来识别设备并显示给用户。字符串描述符通过索引号被其他描述符引用(如 iManufacturer、iProduct)。
字符串描述符的结构如下:
| 偏移量 | 字段名 | 大小 (字节) | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 描述符长度(包括头部和字符串数据) |
| 1 | bDescriptorType | 1 | 描述符类型,字符串描述符为 0x03 |
| 2 ... | bString | 可变 | Unicode 字符串(UTF-16LE 编码) |
2.7 描述符间的依赖关系与枚举流程
主机枚举 USB 设备时,按照固定的顺序读取描述符。理解这个顺序对于调试枚举故障至关重要。
- 步骤 1:主机检测到设备连接,复位设备,分配地址(SET_ADDRESS)。
- 步骤 2:主机发送 GET_DESCRIPTOR 请求读取设备描述符(前 8 字节,用于获取
bMaxPacketSize0)。 - 步骤 3:主机再次读取完整的设备描述符(18 字节)。
- 步骤 4:主机读取配置描述符(前 9 字节,获取
wTotalLength)。 - 步骤 5:主机根据
wTotalLength读取完整的配置描述符集合(包括所有接口和端点描述符)。 - 步骤 6:主机解析配置集合,提取接口和端点信息。
- 步骤 7:主机根据
iManufacturer、iProduct等索引读取字符串描述符(可选)。 - 步骤 8:主机选择配置(SET_CONFIGURATION),设备进入配置状态。
2.8 描述符体系在 Linux Gadget 中的实现
在 Linux USB Gadget 框架中,描述符通过结构体数组和指针链表组织。以下是一个典型的 Gadget 描述符定义示例:
/* 设备描述符 */
static struct usb_device_descriptor gadget_device_desc = {
.bLength = USB_DT_DEVICE_SIZE,
.bDescriptorType = USB_DT_DEVICE,
.bcdUSB = cpu_to_le16(0x0200),
.bDeviceClass = 0,
.bDeviceSubClass = 0,
.bDeviceProtocol = 0,
.bMaxPacketSize0 = 64,
.idVendor = cpu_to_le16(0x1D6B), /* Linux Foundation */
.idProduct = cpu_to_le16(0x0104),
.bcdDevice = cpu_to_le16(0x0100),
.iManufacturer