4. USB 描述符体系:设备描述符、配置描述符、接口描述符、端点描述符、字符串描述符
USB 描述符(Descriptor)是 USB 设备向主机报告自身能力与属性的标准化数据结构。主机通过 控制传输(Control Transfer) 中的 标准设备请求(Standard Device Request) 逐层获取这些描述符,从而完成设备枚举(Enumeration)。
描述符体系采用 树形层级结构:一个设备有且仅有一个设备描述符;设备描述符下可包含多个配置描述符;每个配置描述符下可包含多个接口描述符;每个接口描述符下可包含多个端点描述符。字符串描述符为可选,用于提供可读文本信息。
4.1 设备描述符 (Device Descriptor)
设备描述符是 USB 设备枚举时主机获取的 第一个描述符,它描述了设备的整体信息。主机通过默认控制端点(Endpoint 0)发送 GET_DESCRIPTOR 请求,索引为 0x01,语言 ID 为 0,即可获取设备描述符。
| 偏移 (字节) | 字段 | 大小 (字节) | 说明 |
|---|---|---|---|
| 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 的最大包大小 (USB 2.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 | 厂商字符串描述符的索引 (0 表示无字符串) |
| 15 | iProduct | 1 | 产品字符串描述符的索引 |
| 16 | iSerialNumber | 1 | 序列号字符串描述符的索引 |
| 17 | bNumConfigurations | 1 | 设备支持的配置描述符数量 |
struct usb_device_descriptor 表示,定义在 include/uapi/linux/usb/ch9.h。驱动开发者通常通过 usb_get_descriptor() 或直接读取 udev->descriptor 来访问这些字段。
4.2 配置描述符 (Configuration Descriptor)
配置描述符描述了一个 特定的配置,包括该配置下所有接口和端点的信息。一个设备可以有多个配置(例如:低功耗模式和高性能模式),但同一时刻只能有一个配置被激活。
| 偏移 (字节) | 字段 | 大小 (字节) | 说明 |
|---|---|---|---|
| 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=1 必须为 1,D6=1 支持自供电,D5=1 支持远程唤醒 |
| 8 | bMaxPower | 1 | 最大电流需求,以 2mA 为单位 (USB 2.0 最大值为 500mA) |
4.3 接口描述符 (Interface Descriptor)
接口描述符描述了一个 功能接口,它代表设备的一个功能单元。例如,一个 USB 音频设备可能包含一个音频控制接口和一个音频流接口。接口是驱动绑定的基本单位——Linux 内核中的 usb_driver 就是通过匹配接口描述符中的 bInterfaceClass、bInterfaceSubClass 和 bInterfaceProtocol 来绑定设备的。
| 偏移 (字节) | 字段 | 大小 (字节) | 说明 |
|---|---|---|---|
| 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, CDC=0x02) |
| 6 | bInterfaceSubClass | 1 | 接口子类代码 |
| 7 | bInterfaceProtocol | 1 | 接口协议代码 |
| 8 | iInterface | 1 | 接口字符串描述符的索引 |
usb_device_id 结构体用于匹配接口描述符。驱动可以通过 USB_DEVICE_INFO(class, subclass, protocol) 宏来声明支持的接口类型。当主机枚举到某个接口时,内核会遍历所有已注册的 USB 驱动,寻找匹配项。
4.4 端点描述符 (Endpoint Descriptor)
端点描述符描述了 非默认端点(即除端点 0 以外的端点)的传输特性。每个端点描述符定义了端点的地址、方向、传输类型和最大包大小等信息。端点 0 是默认控制端点,没有对应的端点描述符。
| 偏移 (字节) | 字段 | 大小 (字节) | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 描述符长度,固定为 7 (0x07) |
| 1 | bDescriptorType | 1 | 描述符类型,端点描述符为 0x05 |
| 2 | bEndpointAddress | 1 | 端点地址:Bit 3-0 为端点号,Bit 7 为方向 (0=OUT, 1=IN) |
| 3 | bmAttributes | 1 | 传输类型:Bit 1-0 (0=控制, 1=等时, 2=批量, 3=中断) |
| 4-5 | wMaxPacketSize | 2 | 最大包大小 (USB 2.0 批量最大 512 字节,高速等时最大 1024 字节) |
| 6 | bInterval | 1 | 轮询间隔:中断端点为帧/微帧数,等时端点为 1 |
4.5 字符串描述符 (String Descriptor)
字符串描述符是 可选 的描述符,用于提供人类可读的文本信息,如厂商名称、产品名称和序列号。字符串描述符使用 Unicode (UTF-16LE) 编码,并且支持多种语言。
字符串描述符的获取流程如下:
- 获取语言 ID 表: 主机发送
GET_DESCRIPTOR请求,描述符类型为 0x03,索引为 0,获取设备支持的语言 ID 列表。 - 获取具体字符串: 主机使用选定的语言 ID 和字符串索引(来自设备/配置/接口描述符中的 i* 字段),再次发送
GET_DESCRIPTOR请求获取字符串内容。
| 偏移 (字节) | 字段 | 大小 (字节) | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 描述符长度,可变 (2 + 字符串长度 × 2) |
| 1 | bDescriptorType | 1 | 描述符类型,字符串描述符为 0x03 |
| 2 起 | bString | 可变 | Unicode 字符串 (UTF-16LE 编码) |
lsusb -v 命令可以查看 USB 设备的完整描述符树。在 Linux 内核中,可以通过 usb_string() 函数获取字符串描述符的内容,该函数会处理语言选择和 UTF-16LE 到 ASCII 的转换。
4.6 描述符获取流程与枚举顺序
USB 设备枚举过程中,主机按照严格的顺序获取描述符。以下是标准的枚举流程:
- 主机在获取设备描述符后,会立即复位设备,然后分配唯一地址。
- 配置描述符集合的获取是 一次性 完成的:主机先读取 9 字节获取 wTotalLength,然后读取整个集合。
- 字符串描述符的获取是 按需 的:主机只在需要显示或记录信息时才获取。
- 在
SET_CONFIGURATION之后,设备进入配置状态,非控制端点开始可用。
4.7 描述符在 Linux 内核中的数据结构
Linux 内核使用一系列结构体来表示 USB 描述符,这些结构体定义在 include/uapi/linux/usb/ch9.h 中。以下是核心结构体的定义:
/* 设备描述符 */
struct usb_device_descriptor {
__u8 bLength;
__u8 bDescriptorType;
__le16 bcdUSB;
__u8 bDeviceClass;
__u8 bDeviceSubClass;
__u8 bDeviceProtocol;
__u8 bMaxPacketSize0;
__le16 idVendor;
__le16 idProduct;
__le16 bcdDevice;
__u8 iManufacturer;
__u8 iProduct;
__u8 iSerialNumber;
__u8 bNumConfigurations;
} __attribute__ ((packed));
/* 配置描述符 */
struct usb_config_descriptor {
__u8 bLength;
__u8 bDescriptorType;
__le16 wTotalLength;
__u8 bNumInterfaces;
__u8 bConfigurationValue;
__u8 iConfiguration;
__u8 bmAttributes;
__u8 bMaxPower;
} __attribute__ ((packed));
/* 接口描述符 */
struct usb_interface_descriptor {
__u8 bLength;
__u8 bDescriptorType;
__u8 bInterfaceNumber;
__u8 bAlternateSetting;
__u8 bNumEndpoints;
__u8 bInterfaceClass;
__u8 bInterfaceSubClass;
__u8 bInterfaceProtocol;
__u8 iInterface;
} __attribute__ ((packed));
/* 端点描述符 */
struct usb_endpoint_descriptor {
__u8 bLength;
__u8 bDescriptorType;
__u8 bEndpointAddress;
__u8 bmAttributes;
__le16 wMaxPacketSize;
__u8 bInterval;
/* 注意:USB 3.0 扩展了此结构,增加了 bRefresh 和 bSynchAddress */
} __attribute__ ((packed));
usb_endpoint_num(desc)— 从端点描述符中提取端点号usb_endpoint_dir_in(desc)— 判断是否为 IN 端点usb_endpoint_type(desc)— 获取传输类型usb_endpoint_maxp(desc)— 获取最大包大小
4.8 描述符体系总结
| 描述符类型 | 类型值 | 固定长度 | 数量 | 主要作用 |
|---|---|---|---|---|
| 设备描述符 | 0x01 | 18 字节 | 1 个 | 标识设备整体信息 (VID/PID/版本/配置数量) |
| 配置描述符 | 0x02 | 9 字节 | 1 个或多个 | 描述供电模式、电流需求、接口数量 |
| 接口描述符 | 0x04 | 9 字节 | 1 个或多个 | 定义功能接口,驱动绑定的依据 |
| 端点描述符 | 0x05 | 7 |