4. USB 描述符体系:设备描述符、配置描述符、接口描述符、端点描述符、字符串描述符

USB 描述符(Descriptor)是 USB 设备向主机报告自身能力与属性的标准化数据结构。主机通过 控制传输(Control Transfer) 中的 标准设备请求(Standard Device Request) 逐层获取这些描述符,从而完成设备枚举(Enumeration)。

描述符体系采用 树形层级结构:一个设备有且仅有一个设备描述符;设备描述符下可包含多个配置描述符;每个配置描述符下可包含多个接口描述符;每个接口描述符下可包含多个端点描述符。字符串描述符为可选,用于提供可读文本信息。

💡 核心原则: 主机总是从获取设备描述符开始,然后根据设备描述符中的配置数量,依次获取每个配置的完整描述符集合(配置 + 接口 + 端点)。字符串描述符仅在设备描述符或其它描述符中声明了字符串索引时才会被请求。
USB 描述符树形层级结构 设备描述符 (1) 配置描述符 A 配置描述符 B 接口 0 (控制) 接口 1 (数据) EP0 IN EP0 OUT EP2 IN EP2 OUT 每个接口至少包含一个端点(EP0 为默认控制端点,所有设备必须支持)

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 设备支持的配置描述符数量
🔍 内核视角: Linux 内核中,设备描述符由 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)
⚠️ 常见误区: 主机获取配置描述符时,会一次性获取整个配置描述符集合(包括所有接口和端点描述符),而不是只获取 9 字节的配置描述符头。wTotalLength 字段正是用于告知主机需要读取的总数据量。

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 接口字符串描述符的索引
💡 接口与驱动绑定: 在 Linux 内核中,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
端点描述符关键字段解码 bEndpointAddress (1 字节) Bit 7: 方向 (0=OUT, 1=IN) | Bit 6-4: 保留 | Bit 3-0: 端点号 (0-15) bmAttributes (1 字节) Bit 1-0: 传输类型 (00=控制, 01=等时, 10=批量, 11=中断) | Bit 7-2: 保留 示例:0x82 = IN 端点 2;0x02 = 批量传输

4.5 字符串描述符 (String Descriptor)

字符串描述符是 可选 的描述符,用于提供人类可读的文本信息,如厂商名称、产品名称和序列号。字符串描述符使用 Unicode (UTF-16LE) 编码,并且支持多种语言。

字符串描述符的获取流程如下:

  1. 获取语言 ID 表: 主机发送 GET_DESCRIPTOR 请求,描述符类型为 0x03,索引为 0,获取设备支持的语言 ID 列表。
  2. 获取具体字符串: 主机使用选定的语言 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 设备枚举过程中,主机按照严格的顺序获取描述符。以下是标准的枚举流程:

USB 描述符获取流程 (枚举顺序) 1. 获取设备描述符 (18 字节) 2. 复位设备 (USB_RESET) 3. 设置地址 (SET_ADDRESS) 4. 获取配置描述符集合 4a. 先读 9 字节配置描述符头 → 获取 wTotalLength → 再读完整集合 5. 获取字符串描述符 (可选) 6. 选择配置 (SET_CONFIGURATION)
💡 枚举关键点:
  • 主机在获取设备描述符后,会立即复位设备,然后分配唯一地址。
  • 配置描述符集合的获取是 一次性 完成的:主机先读取 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 驱动时,通常不需要手动解析描述符。内核提供了便捷的访问宏和函数:
  • 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