4. USB 描述符详解:层级结构与关键类型

USB 描述符(Descriptor)是设备向主机报告自身能力与配置的标准化数据结构。主机通过枚举过程读取描述符,从而获知设备类型、端点数量、传输方式、供电需求等信息。描述符之间具有严格的层级关系,理解这一层级是驱动开发的基础。

核心要点:描述符是 USB 协议中“自描述”机制的体现,驱动开发者必须掌握设备、配置、接口、端点四层描述符的嵌套关系,以及字符串、HID、CDC 等扩展描述符的解析方法。

4.1 描述符层级关系总览

USB 描述符采用树形层级结构:一个设备只有一个设备描述符,设备描述符下可包含多个配置描述符,每个配置描述符下可包含多个接口描述符,每个接口描述符下可包含多个端点描述符。字符串描述符和其他类特定描述符(如 HID、CDC)则挂接在相应层级上。

USB 描述符层级关系 设备描述符 配置描述符 1 配置描述符 N 接口描述符 0 接口描述符 1 端点 0 IN 端点 1 OUT 层级约束规则 • 设备描述符:唯一,位于最顶层 • 配置描述符:至少 1 个,可多个 • 接口描述符:每个配置下可多个 • 端点描述符:每个接口下可多个(除端点 0)
驱动开发提示:在 Linux 内核中,usb_device 结构体包含 descriptor 字段(设备描述符),通过 usb_get_descriptor() 函数族读取各层级描述符。接口与端点描述符通常通过 usb_interface 和 usb_host_endpoint 访问。

4.2 设备描述符(Device Descriptor)

设备描述符是 USB 设备的第一份描述符,主机通过默认控制端点(端点 0)读取,长度为 18 字节。它定义了设备的通用属性,包括 USB 规范版本、设备类、厂商和产品 ID 等。

偏移(字节) 字段 大小(字节) 说明
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-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 支持的配置描述符数量
驱动匹配关键:Linux USB 核心使用 idVendor 和 idProduct 匹配驱动。在驱动代码中,通过 usb_device_id 结构体声明支持的 VID/PID 组合,例如:{ USB_DEVICE(0x1234, 0x5678) }。

4.3 配置描述符(Configuration Descriptor)

配置描述符描述设备的供电模式、最大电流消耗以及包含的接口数量。一个设备可以有多个配置(如低功耗模式和高性能模式),但同一时刻只能激活一个配置。

偏移(字节) 字段 大小(字节) 说明
0 bLength 1 描述符长度(固定 9)
1 bDescriptorType 1 描述符类型(配置描述符为 0x02)
2-3 wTotalLength 2 配置描述符及其所有子描述符的总长度
4 bNumInterfaces 1 该配置下的接口数量
5 bConfigurationValue 1 配置编号(用于 SetConfiguration 请求)
6 iConfiguration 1 配置字符串描述符索引
7 bmAttributes 1 配置属性(位 7:总线供电;位 6:自供电;位 5:远程唤醒)
8 bMaxPower 1 最大电流消耗(单位 2mA,如 50 表示 100mA)
常见错误:驱动开发中,如果设备有多个配置,必须确保在 probe 函数中调用 usb_set_configuration() 选择正确的配置。未选择配置前,除端点 0 外的所有端点不可用。

4.4 接口描述符(Interface Descriptor)

接口描述符定义了一个功能单元,每个接口可以包含一个或多个端点。在 Linux 驱动模型中,一个 usb_interface 对应一个接口描述符。接口描述符中的类、子类和协议代码用于匹配类驱动(如 HID、CDC、Mass Storage)。

偏移(字节) 字段 大小(字节) 说明
0 bLength 1 描述符长度(固定 9)
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 接口字符串描述符索引
备用设置(Alternate Setting):接口描述符支持多个备用设置,用于动态调整带宽。例如,UVC 摄像头在预览模式使用低带宽备用设置,录制模式切换到高带宽设置。驱动通过 usb_set_interface() 切换。

4.5 端点描述符(Endpoint Descriptor)

端点描述符定义数据传输的端点属性,包括地址、方向、传输类型和最大包大小。除默认控制端点(端点 0)外,每个端点都必须有对应的端点描述符。

偏移(字节) 字段 大小(字节) 说明
0 bLength 1 描述符长度(固定 7)
1 bDescriptorType 1 描述符类型(端点描述符为 0x05)
2 bEndpointAddress 1 端点地址(位 3-0:端点号;位 7:方向,0=OUT,1=IN)
3 bmAttributes 1 传输类型(位 1-0:0=控制,1=等时,2=批量,3=中断)
4-5 wMaxPacketSize 2 最大包大小(位 10-0:包大小;位 12-11:等时传输的额外事务数)
6 bInterval 1 轮询间隔(中断/等时端点:单位 ms;高速端点:单位 125µs)
端点描述符关键字段解码 bEndpointAddress (1 字节) 位 7: 方向 (0=OUT, 1=IN) | 位 3-0: 端点号 (0-15) bmAttributes (1 字节) 位 1-0: 00=控制 | 01=等时 | 10=批量 | 11=中断 示例:0x81 表示端点 1 IN;0x02 表示批量传输

4.6 字符串描述符(String Descriptor)

字符串描述符提供可读的文本信息,如厂商名称、产品名称和序列号。字符串描述符使用 Unicode(UTF-16LE)编码,通过索引引用。字符串描述符 0 是特殊的语言 ID 描述符,用于声明支持的语言。

偏移(字节) 字段 大小(字节) 说明
0 bLength 1 描述符长度(可变)
1 bDescriptorType 1 描述符类型(字符串描述符为 0x03)
2..N bString 可变 Unicode 字符串(UTF-16LE 编码)
内核读取示例:Linux 内核通过 usb_string() 函数读取字符串描述符,自动处理语言选择。驱动代码中常用 dev_info(&udev->dev, "Manufacturer: %s\n", udev->manufacturer) 打印设备信息。

4.7 类特定描述符:HID 与 CDC

除了标准描述符,USB 类规范定义了类特定描述符,用于扩展描述设备功能。最常见的包括 HID(人机交互设备)描述符和 CDC(通信设备类)描述符。

4.7.1 HID 描述符

HID 描述符位于接口描述符之后、端点描述符之前,用于描述 HID 设备的报告描述符格式和版本。HID 描述符结构如下:

偏移(字节) 字段 大小(字节) 说明
0 bLength 1 描述符长度(通常为 9)
1 bDescriptorType 1 描述符类型(HID 为 0x21)
2-3 bcdHID 2 HID 规范版本号(BCD 码)
4 bCountryCode 1 国家代码(0x00 表示不指定)
5 bNumDescriptors 1 后续类描述符数量(通常为 1)
6 bDescriptorType 1 类描述符类型(报告描述符为 0x22)
7-8 wDescriptorLength 2 报告描述符的总长度
HID 驱动流程:Linux HID 子系统自动解析 HID 描述符和报告描述符,生成 hid_device 结构体。驱动开发者只需注册 struct hid_driver,实现 probe 和 raw_event 回调即可处理输入报告。

4.7.2 CDC 描述符

CDC(通信设备类)用于调制解调器、以太网适配器等通信设备。CDC 使用功能描述符(Functional Descriptor)定义子类功能,常见的有:

  • ACM(抽象控制模型):用于调制解调器控制,包含 CDC_ACM 功能描述符
  • ECM(以太网控制模型):用于 USB 以太网适配器,包含 MAC 地址和统计信息
  • NCM(网络控制模型):高速网络传输,支持聚合帧
CDC 复合接口描述符布局(以 ACM 为例) 接口 0:控制接口(CDC) CDC 功能描述符(ACM) 中断端点(通知) 接口 1:数据接口 批量端点 IN(数据接收) 批量端点 OUT(数据发送) IAD 关联 CDC 接口关联描述符(IAD) • IAD 将控制接口和数据接口关联为一个功能单元 • Linux 内核通过 usb_cdc_parse_interface() 解析 CDC 功能描述符 • 驱动匹配时使用 USB_DEVICE_INTERFACE_CLASS() 宏

4.8 描述符解析实战:从原始字节到内核结构

在 Linux 内核中,USB 核心自动解析枚举阶段获取的描述符数据,并填充到对应的内核结构体中。驱动开发者通常不需要手动解析原始字节,而是通过标准 API 访问解析后的数据。

以下是一个典型的描述符解析流程:

  1. 主机发送 GetDescriptor 请求:读取设备描述符(18 字节)
  2. 主机发送 SetAddress:分配唯一设备地址
  3. 主机再次读取设备描述符:确认设备地址生效
  4. 主机读取配置描述符:包括所有子描述符(接口、端点、类特定描述符)
  5. 主机选择配置:发送 SetConfiguration 请求激活配置
  6. 内核构建数据结构:填充 usb_device、usb_interface、usb_host_endpoint 等结构体
内核数据结构对应关系:
// 设备描述符 → struct usb_device_descriptor
// 配置描述符 → struct usb_config_descriptor
// 接口描述符 → struct usb_interface_descriptor
// 端点描述符 → struct usb_endpoint_descriptor
// 字符串描述符 → 通过 usb_string() 读取
// HID 描述符 → struct hid_descriptor (在 <linux/hid.h> 中定义)

4.9 描述符验证与调试

在驱动开发过程中,验证设备描述符的正确性是排查枚举失败的关键步骤。以下是一些实用工具和方法:

工具/方法 命令/操作 输出内容
lsusb lsusb -v -d 1234:5678 显示完整的描述符树(设备、配置、接口、端点、字符串)
usbmon cat /sys/kernel/debug/usbmon/0u 捕获 USB 总线上的所有 URB 数据,包括描述符请求
内核调试 echo 'file drivers/usb/core/* +p' > /sys/kernel/debug/dynamic_debug/control 启用 USB 核心调试日志,打印描述符解析过程
Wireshark 配合 usbmon 捕获 USB 数据包 图形化分析描述符请求与响应
常见枚举失败原因:
  • wMaxPacketSize 错误:端点 0 的最大包大小必须与 USB 速度匹配(低速 8,全速 8/16/32/64,高速 64)
  • bInterval 值异常:中断端点的轮询间隔超出规范范围(全速 1-255ms,高速 1-16 × 125µs)
  • 描述符长度不匹配:bLength 字段与实际描述符长度不一致,导致解析偏移错误
  • 接口编号不连续:bInterfaceNumber 跳号可能导致内核接口映射错误

4.10 本章小结

USB 描述符是设备与主机通信的“身份证”,驱动开发者必须熟练掌握其层级结构和关键字段。设备描述符定义设备全局属性,配置描述符定义供电与接口数量,接口描述符定义功能单元,端点描述符定义数据传输通道。字符串描述符提供可读信息,类特定描述符(如 HID、CDC)扩展了标准描述符的能力。在 Linux 内核中,描述符解析由 USB 核心自动完成,驱动开发者通过标准结构体和 API 访问解析后的数据,从而专注于设备特定功能的实现。