4. USB 描述符详解:层级结构与关键类型
USB 描述符(Descriptor)是设备向主机报告自身能力与配置的标准化数据结构。主机通过枚举过程读取描述符,从而获知设备类型、端点数量、传输方式、供电需求等信息。描述符之间具有严格的层级关系,理解这一层级是驱动开发的基础。
4.1 描述符层级关系总览
USB 描述符采用树形层级结构:一个设备只有一个设备描述符,设备描述符下可包含多个配置描述符,每个配置描述符下可包含多个接口描述符,每个接口描述符下可包含多个端点描述符。字符串描述符和其他类特定描述符(如 HID、CDC)则挂接在相应层级上。
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 | 支持的配置描述符数量 |
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 | 接口字符串描述符索引 |
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) |
4.6 字符串描述符(String Descriptor)
字符串描述符提供可读的文本信息,如厂商名称、产品名称和序列号。字符串描述符使用 Unicode(UTF-16LE)编码,通过索引引用。字符串描述符 0 是特殊的语言 ID 描述符,用于声明支持的语言。
| 偏移(字节) | 字段 | 大小(字节) | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 描述符长度(可变) |
| 1 | bDescriptorType | 1 | 描述符类型(字符串描述符为 0x03) |
| 2..N | bString | 可变 | Unicode 字符串(UTF-16LE 编码) |
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_device 结构体。驱动开发者只需注册 struct hid_driver,实现 probe 和 raw_event 回调即可处理输入报告。
4.7.2 CDC 描述符
CDC(通信设备类)用于调制解调器、以太网适配器等通信设备。CDC 使用功能描述符(Functional Descriptor)定义子类功能,常见的有:
- ACM(抽象控制模型):用于调制解调器控制,包含
CDC_ACM功能描述符 - ECM(以太网控制模型):用于 USB 以太网适配器,包含 MAC 地址和统计信息
- NCM(网络控制模型):高速网络传输,支持聚合帧
4.8 描述符解析实战:从原始字节到内核结构
在 Linux 内核中,USB 核心自动解析枚举阶段获取的描述符数据,并填充到对应的内核结构体中。驱动开发者通常不需要手动解析原始字节,而是通过标准 API 访问解析后的数据。
以下是一个典型的描述符解析流程:
- 主机发送 GetDescriptor 请求:读取设备描述符(18 字节)
- 主机发送 SetAddress:分配唯一设备地址
- 主机再次读取设备描述符:确认设备地址生效
- 主机读取配置描述符:包括所有子描述符(接口、端点、类特定描述符)
- 主机选择配置:发送 SetConfiguration 请求激活配置
- 内核构建数据结构:填充
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 访问解析后的数据,从而专注于设备特定功能的实现。