3. USB 设备枚举过程:描述符体系详解

USB 设备枚举是主机(Host)识别并配置 USB 设备的核心过程。在枚举阶段,主机通过默认控制管道(Endpoint 0)向设备发送标准请求,设备则以 描述符(Descriptor) 的形式返回自身的能力与属性。描述符是 USB 协议中定义的数据结构,采用层级嵌套的方式组织,从设备级到功能级逐层细化。

关键理解: 描述符是 USB 设备向主机“自我介绍”的语言。驱动开发者必须理解描述符的解析顺序和字段含义,才能正确编写匹配设备的接口驱动。

3.1 枚举流程总览

USB 设备插入后,主机控制器(Host Controller)检测到端口状态变化,启动枚举过程。枚举并非一次性读取所有描述符,而是分阶段、按需获取。标准枚举流程如下:

  1. 复位(Reset): 主机向设备发送复位信号,设备地址恢复为默认地址 0。
  2. 获取设备描述符(前 8 字节): 主机读取设备描述符的前 8 字节,获取端点 0 的最大包长度(bMaxPacketSize0)。
  3. 分配地址(Set Address): 主机为设备分配唯一地址,设备切换到新地址通信。
  4. 获取完整设备描述符: 主机使用新地址读取完整的设备描述符(18 字节)。
  5. 获取配置描述符(含接口和端点): 主机读取配置描述符,并连带读取该配置下的所有接口描述符和端点描述符。
  6. 获取字符串描述符(可选): 如果描述符中指定了字符串索引,主机可请求对应的字符串描述符(如厂商名、产品名、序列号)。
  7. 选择配置(Set Configuration): 主机选择一个配置,设备进入配置状态,功能可用。
驱动开发者关注点: 枚举过程中,主机最终会选择一个配置(Configuration)下的一个接口(Interface)来绑定驱动。接口描述符中的 bInterfaceClass、bInterfaceSubClass、bInterfaceProtocol 是驱动匹配的关键字段。
USB 设备枚举流程 设备插入 / 复位 获取设备描述符(前8字节) 分配地址 (Set Address) 获取完整设备描述符 获取配置描述符集 获取字符串描述符(可选) 选择配置 (Set Configuration) 设备就绪 bMaxPacketSize0 决定后续 控制传输的数据包大小 包含配置描述符 + 接口 描述符 + 端点描述符

3.2 设备描述符 (Device Descriptor)

设备描述符是 USB 设备的根描述符,每个设备有且仅有一个。它描述了设备的通用属性,包括 USB 规范版本、设备类、厂商 ID(VID)、产品 ID(PID)等。主机通过 GET_DESCRIPTOR 请求(bmRequestType = 0x80, bRequest = 0x06, wValue = 0x0100)读取。

偏移 字段 大小 说明
0 bLength 1 描述符长度,固定为 18
1 bDescriptorType 1 描述符类型,设备描述符为 0x01
2-3 bcdUSB 2 USB 规范版本号(BCD 码),如 0x0200 表示 USB 2.0
4 bDeviceClass 1 设备类代码(0x00 表示在接口描述符中定义)
5 bDeviceSubClass 1 设备子类代码
6 bDeviceProtocol 1 设备协议代码
7 bMaxPacketSize0 1 端点 0 的最大包长度(8/16/32/64)
8-9 idVendor 2 厂商 ID(VID),由 USB-IF 分配
10-11 idProduct 2 产品 ID(PID),由厂商自行分配
12-13 bcdDevice 2 设备版本号(BCD 码)
14 iManufacturer 1 厂商字符串描述符的索引
15 iProduct 1 产品字符串描述符的索引
16 iSerialNumber 1 序列号字符串描述符的索引
17 bNumConfigurations 1 支持的配置描述符数量
驱动匹配注意: 在 Linux 内核中,USB 核心层使用 idVendor 和 idProduct 进行设备级匹配(usb_device_id)。如果 bDeviceClass 不为 0,则设备级类代码会覆盖接口级类代码,驱动匹配时需特别留意。

3.3 配置描述符 (Configuration Descriptor)

配置描述符描述了一个 USB 设备的供电模式、最大电流消耗以及该配置下包含的接口数量。一个设备可以有多个配置(如低功耗模式和高性能模式),但同一时刻只能有一个配置被激活。配置描述符的 wTotalLength 字段指明了整个配置描述符集(包括配置描述符本身 + 所有接口描述符 + 所有端点描述符)的总长度。

偏移 字段 大小 说明
0 bLength 1 描述符长度,固定为 9
1 bDescriptorType 1 描述符类型,配置描述符为 0x02
2-3 wTotalLength 2 整个配置描述符集的总字节数
4 bNumInterfaces 1 该配置支持的接口数量
5 bConfigurationValue 1 用于 Set Configuration 请求的配置值
6 iConfiguration 1 配置字符串描述符的索引
7 bmAttributes 1 供电模式标志(Bit7: 总线供电, Bit6: 自供电, Bit5: 远程唤醒)
8 bMaxPower 1 最大电流消耗,单位 2mA(值 × 2 = mA)

3.4 接口描述符 (Interface Descriptor)

接口描述符是驱动开发中最关键的描述符。它描述了一个 USB 设备的功能接口,包括接口的类、子类、协议以及使用的端点数量。一个配置下可以有多个接口,每个接口可以有一个或多个备用设置(Alternate Setting)。接口描述符中的 bInterfaceClass、bInterfaceSubClass、bInterfaceProtocol 是 Linux USB 驱动匹配的核心依据。

偏移 字段 大小 说明
0 bLength 1 描述符长度,固定为 9
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 接口驱动通过 usb_register() 注册,并在 usb_driver 结构体的 id_table 中指定匹配的 USB_DEVICE_INFO(class, subclass, protocol) 宏。当枚举完成且主机选择配置后,USB 核心会遍历已注册的驱动,匹配接口描述符中的类/子类/协议。

3.5 端点描述符 (Endpoint Descriptor)

端点描述符描述了 USB 设备中除默认控制端点(Endpoint 0)以外的每个端点的属性,包括端点地址、传输类型、最大包大小和轮询间隔。每个接口描述符后面跟随若干个端点描述符(由 bNumEndpoints 指定)。

偏移 字段 大小 说明
0 bLength 1 描述符长度,固定为 7
1 bDescriptorType 1 描述符类型,端点描述符为 0x05
2 bEndpointAddress 1 端点地址(Bit3-0: 端点号, Bit7: 方向 0=OUT, 1=IN)
3 bmAttributes 1 传输类型(Bit1-0: 0=控制, 1=等时, 2=批量, 3=中断)
4-5 wMaxPacketSize 2 最大包大小(Bit10-0: 最大包字节数, Bit12-11: 附加事务数)
6 bInterval 1 轮询间隔(单位:帧/微帧,取决于传输类型和速度)
描述符层级结构 设备描述符 配置描述符 接口描述符 端点描述符 1 端点描述符 2 端点描述符 N 字符串描述符 描述符嵌套关系: 设备描述符 → 配置描述符 → 接口描述符 → 端点描述符;字符串描述符由其他描述符中的索引引用。

3.6 字符串描述符 (String Descriptor)

字符串描述符用于提供人类可读的文本信息,如厂商名称、产品名称和序列号。字符串描述符使用 Unicode(UTF-16LE)编码。设备描述符中的 iManufacturer、iProduct、iSerialNumber 字段以及配置/接口描述符中的 iConfiguration、iInterface 字段存储的是字符串描述符的索引值。主机通过 GET_DESCRIPTOR 请求(wValue 高字节为 0x03,低字节为索引值)读取。

字符串描述符 0(索引 0)是一个特殊的 语言 ID 描述符,它返回设备支持的语言代码列表(LANGID)。主机先读取索引 0 获取支持的语言,再使用指定的 LANGID 请求具体的字符串。

偏移 字段 大小 说明
0 bLength 1 描述符长度(可变,取决于字符串长度)
1 bDescriptorType 1 描述符类型,字符串描述符为 0x03
2..N bString / wLANGID 可变 索引 0 时为 LANGID 数组(每个 2 字节);其他索引时为 Unicode 字符串
驱动开发提示: 字符串描述符在驱动匹配中通常不作为必要条件,但序列号(iSerialNumber)可用于区分同一型号的多个设备。在 Linux 中,可以通过 usb_string() 函数从设备获取字符串描述符内容。

3.7 描述符在 Linux 内核中的数据结构

Linux 内核 USB 核心层在枚举完成后,将解析后的描述符信息存储在对应的内核数据结构中。驱动开发者通过访问这些结构体来获取设备信息。

/* 设备描述符内核结构 (include/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 等 */
} __attribute__ ((packed));
实际驱动开发中的访问方式: 在 probe 回调函数中,通过 struct usb_interface *interface 参数可以获取 interface->cur_altsetting 指向当前备用设置的接口描述符,进而访问 desc 成员(struct usb_interface_descriptor)。端点描述符则通过 interface->cur_altsetting->endpoint 数组访问。

3.8 枚举过程中的常见问题与调试

  • 描述符长度不匹配: 设备返回的描述符长度与请求不符,可能导致枚举失败。使用 usbmon 或 lsusb -v 抓取描述符原始数据对比。
  • bMaxPacketSize0 错误: 如果设备在复位后返回的 bMaxPacketSize0 与实际能力不符,后续控制传输可能失败。
  • 配置描述符集总长度溢出: wTotalLength 必须精确等于所有描述符字节数之和,否则主机可能截断或解析错误。
  • 接口备用设置混淆: 驱动应检查 bAlternateSetting,确保操作的是当前激活的备用设置。
  • 字符串描述符索引无效: 如果描述符中的字符串索引为 0,表示没有对应的字符串,主机不应请求。
调试命令速查: lsusb -v -d [VID:PID] 可完整打印设备的所有描述符信息。
cat /sys/kernel/debug/usb/devices 可查看内核枚举后的设备树信息。
usbmon 配合 tcpdump 风格过滤可捕获 USB 总线上的原始描述符请求与响应。
描述符类型标识符速查 类型值 描述符名称 固定长度 内核宏定义 0x01 设备描述符 18 字节 USB_DT_DEVICE 0x02 配置描述符 9 字节 USB_DT_CONFIG ← 上一章 📖 返回目录 下一章 →