3. USB 描述符体系:设备描述符、配置描述符、接口描述符、端点描述符、字符串描述符
USB 描述符(Descriptor)是 USB 设备向主机报告自身能力与属性的结构化数据。主机通过标准设备请求(如 GET_DESCRIPTOR)逐层获取这些描述符,从而构建完整的设备拓扑与功能视图。描述符体系采用严格的层级结构,每一层都包含指向下一层的长度与偏移信息。
3.1 描述符层级结构总览
USB 描述符遵循“设备 → 配置 → 接口 → 端点”的树形层级。每个 USB 设备只有一个设备描述符,但可以有多个配置描述符;每个配置下可包含多个接口描述符;每个接口下可包含多个端点描述符。字符串描述符为可选,用于提供可读的文本信息。
3.2 设备描述符(Device Descriptor)
设备描述符是 USB 设备的第一级描述符,位于描述符树的根节点。主机通过 GET_DESCRIPTOR(DEVICE) 请求获取,长度为 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/16/32/64) |
| 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 | 支持的配置描述符数量 |
struct usb_device_descriptor,定义在 <linux/usb/ch9.h>。驱动可通过 usb_get_descriptor() 或直接从 struct usb_device->descriptor 读取。
3.3 配置描述符(Configuration Descriptor)
配置描述符描述设备的供电模式、最大电流以及接口数量。一个设备可以有多个配置(如低功耗模式与高功耗模式),但同一时刻只能激活一个配置。配置描述符长度为 9 字节,其后跟随接口描述符与端点描述符。
| 偏移 | 字段 | 大小 | 说明 |
|---|---|---|---|
| 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 | 配置特性(自供电/远程唤醒等) |
| 8 | bMaxPower | 1 | 最大电流(单位 2mA),如 50 表示 100mA |
wTotalLength 包含配置描述符本身以及其下所有接口描述符、端点描述符和其他可选描述符的总字节数。驱动在解析时必须使用该字段来界定描述符边界,不能假设固定长度。
3.4 接口描述符(Interface Descriptor)
接口描述符描述一个 USB 功能单元(如键盘、音频、存储)。一个配置下可以有多个接口,接口可以分组为接口关联(IAD)。接口描述符长度为 9 字节,定义了接口的类、子类、协议以及使用的端点数量。
| 偏移 | 字段 | 大小 | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 描述符长度,固定为 9 |
| 1 | bDescriptorType | 1 | 描述符类型,接口描述符为 0x04 |
| 2 | bInterfaceNumber | 1 | 接口编号(从 0 开始) |
| 3 | bAlternateSetting | 1 | 备用设置值,用于切换接口配置 |
| 4 | bNumEndpoints | 1 | 该接口使用的端点数量(不包括端点 0) |
| 5 | bInterfaceClass | 1 | 接口类代码(如 HID 为 0x03) |
| 6 | bInterfaceSubClass | 1 | 接口子类代码 |
| 7 | bInterfaceProtocol | 1 | 接口协议代码 |
| 8 | iInterface | 1 | 接口字符串描述符索引 |
bAlternateSetting 提供多种配置变体。驱动使用 usb_set_interface() 切换备用设置,从而改变端点数量或类型。例如,UVC 视频设备在流接口中使用不同备用设置来切换带宽。
3.5 端点描述符(Endpoint Descriptor)
端点描述符是描述符体系中最底层的描述符,直接定义了 URB 传输的目标地址、传输类型、最大包大小和轮询间隔。每个非端点 0 的端点都对应一个端点描述符,长度为 7 字节(同步传输为 9 字节)。
| 偏移 | 字段 | 大小 | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 描述符长度,固定为 7(同步为 9) |
| 1 | bDescriptorType | 1 | 描述符类型,端点描述符为 0x05 |
| 2 | bEndpointAddress | 1 | 端点地址:bit7 方向(0=OUT,1=IN),bit3-0 端点号 |
| 3 | bmAttributes | 1 | 传输类型:bit1-0(0=控制,1=等时,2=批量,3=中断) |
| 4-5 | wMaxPacketSize | 2 | 最大包大小(bit10-0 为大小,bit12-11 为附加事务数) |
| 6 | bInterval | 1 | 轮询间隔(中断/等时端点,单位:帧/微帧) |
3.6 字符串描述符(String Descriptor)
字符串描述符为可选描述符,用于提供厂商名称、产品名称、序列号等可读文本。字符串使用 Unicode(UTF-16LE)编码。字符串描述符索引 0 是一个特殊的语言 ID 描述符,列出设备支持的语言代码。
| 偏移 | 字段 | 大小 | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 描述符长度(可变,取决于字符串长度) |
| 1 | bDescriptorType | 1 | 描述符类型,字符串描述符为 0x03 |
| 2..N | bString | 可变 | Unicode 字符串(UTF-16LE 编码) |
usb_string() 函数从设备读取字符串描述符,该函数自动处理语言 ID 协商和 UTF-16LE 到 UTF-8 的转换。驱动中常用 dev_info() 打印设备名称,如:dev_info(&udev->dev, "Product: %s\n", udev->product);
3.7 描述符解析实战流程
在 Linux 内核中,USB 核心子系统在枚举阶段自动解析所有描述符,并将结果填充到 struct usb_device 和 struct usb_interface 中。驱动开发者通常不需要手动解析原始描述符,但理解其结构对调试和高级配置至关重要。
- 获取设备描述符:主机发送
GET_DESCRIPTOR(DEVICE),获取 18 字节设备描述符,校验bcdUSB和bMaxPacketSize0。 - 获取配置描述符:主机发送
GET_DESCRIPTOR(CONFIGURATION),先读 9 字节获取wTotalLength,再读取完整配置描述符集合。 - 解析接口与端点:遍历配置描述符数据,根据
bDescriptorType区分接口描述符(0x04)和端点描述符(0x05),构建接口与端点列表。 - 选择配置:主机发送
SET_CONFIGURATION激活选中的配置,设备进入可用状态。 - 驱动绑定:USB 核心根据接口描述符的
bInterfaceClass/bInterfaceSubClass/bInterfaceProtocol匹配驱动,调用驱动的probe()函数。
static int my_driver_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct usb_host_interface *iface_desc;
struct usb_endpoint_descriptor *ep_desc;
int i;
iface_desc = intf->cur_altsetting;
dev_info(&intf->dev, "Interface %d has %d endpoints\n",
iface_desc->desc.bInterfaceNumber,
iface_desc->desc.bNumEndpoints);
for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) {
ep_desc = &iface_desc->endpoint[i].desc;
dev_info(&intf->dev,
"EP 0x%02x: type=%d, max_pkt=%d, interval=%d\n",
ep_desc->bEndpointAddress,
ep_desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK,
le16_to_cpu(ep_desc->wMaxPacketSize),
ep_desc->bInterval);
}
return 0;
}
3.8 描述符类型标识速查表
| bDescriptorType 值 | 描述符类型 | 固定长度 | 内核结构体 |
|---|---|---|---|
| 0x01 | 设备描述符 | 18 | struct usb_device_descriptor |
| 0x02 | 配置描述符 | 9 | struct usb_config_descriptor |
| 0x03 | 字符串描述符 | 可变 | struct usb_string_descriptor |
| 0x04 | 接口描述符 | 9 | struct usb_interface_descriptor |
| 0x05 | 端点描述符 | 7(同步为 9) | struct usb_endpoint_descriptor |
| 0x0F | 接口关联描述符(IAD) | 8 | struct usb_interface_assoc_descriptor |
lsusb -v 命令可以完整打印 USB 设备的描述符树。在驱动开发中,如果遇到 URB 提交失败或端点配置错误,首先使用 lsusb -v 确认端点描述符的 bEndpointAddress、wMaxPacketSize 和 bmAttributes 是否与驱动代码一致。