3. USB描述符详解:设备描述符、配置描述符、接口描述符、端点描述符、字符串描述符、其他标准描述符的结构与字段含义
USB描述符是USB设备向主机报告自身能力与属性的数据结构。主机通过标准设备请求(如GET_DESCRIPTOR)逐层获取这些描述符,从而构建完整的设备拓扑与功能视图。每个描述符以长度(bLength)和类型(bDescriptorType)字段开头,形成可扩展的层级结构。
usb_device结构体正是从这些描述符解析而来。
3.1 设备描述符(Device Descriptor)
设备描述符是USB设备返回的第一个描述符,描述设备的整体属性。每个USB设备有且仅有一个设备描述符,其长度为18字节。
| 偏移(字节) | 字段 | 大小(字节) | 含义 |
|---|---|---|---|
| 0 | bLength | 1 | 描述符长度,固定为0x12(18) |
| 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的最大包大小(字节) |
| 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_device->descriptor直接访问。idVendor和idProduct是驱动匹配的关键标识。
3.2 配置描述符(Configuration Descriptor)
配置描述符描述设备的供电模式、最大功耗以及包含的接口数量。一个设备可以有多个配置(通常只有一个),主机通过SET_CONFIGURATION请求选择激活的配置。配置描述符长度为9字节。
| 偏移(字节) | 字段 | 大小(字节) | 含义 |
|---|---|---|---|
| 0 | bLength | 1 | 描述符长度,固定为0x09(9) |
| 1 | bDescriptorType | 1 | 描述符类型,配置描述符为0x02 |
| 2-3 | wTotalLength | 2 | 整个配置信息的总长度(包括配置、接口、端点等所有描述符) |
| 4 | bNumInterfaces | 1 | 该配置支持的接口数量 |
| 5 | bConfigurationValue | 1 | 用于SET_CONFIGURATION请求的配置值 |
| 6 | iConfiguration | 1 | 配置字符串描述符的索引值 |
| 7 | bmAttributes | 1 | 配置特性:D7=总线供电,D6=自供电,D5=远程唤醒 |
| 8 | bMaxPower | 1 | 最大功耗(单位:2mA),如0x32表示100mA |
3.3 接口描述符(Interface Descriptor)
接口描述符描述一个功能接口(如HID、Mass Storage、CDC ACM等)。一个配置可以包含多个接口,接口是驱动绑定的基本单位。接口描述符长度为9字节。
| 偏移(字节) | 字段 | 大小(字节) | 含义 |
|---|---|---|---|
| 0 | bLength | 1 | 描述符长度,固定为0x09(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 | 接口字符串描述符的索引值 |
struct usb_interface_descriptor中的bInterfaceClass、bInterfaceSubClass、bInterfaceProtocol字段与驱动的usb_device_id表进行匹配。这是大多数USB驱动绑定的核心机制。
3.4 端点描述符(Endpoint Descriptor)
端点描述符描述一个非默认端点(端点0除外)的传输属性。每个端点描述符长度为7字节。接口描述符中的bNumEndpoints字段指示其后跟随的端点描述符数量。
| 偏移(字节) | 字段 | 大小(字节) | 含义 |
|---|---|---|---|
| 0 | bLength | 1 | 描述符长度,固定为0x07(7) |
| 1 | bDescriptorType | 1 | 描述符类型,端点描述符为0x05 |
| 2 | bEndpointAddress | 1 | 端点地址:D7=方向(0:OUT, 1:IN),D3-D0=端点号 |
| 3 | bmAttributes | 1 | 传输类型:0x00=控制,0x01=等时,0x02=批量,0x03=中断 |
| 4-5 | wMaxPacketSize | 2 | 最大包大小(字节),高2位用于等时传输的额外事务数 |
| 6 | bInterval | 1 | 轮询间隔(单位:帧/微帧),中断和等时端点使用 |
3.5 字符串描述符(String Descriptor)
字符串描述符提供人类可读的文本信息,如厂商名称、产品名称、序列号等。字符串描述符使用Unicode(UTF-16LE)编码,长度可变。设备描述符中的iManufacturer、iProduct、iSerialNumber字段指向对应的字符串描述符索引。
字符串描述符的索引0是一个特殊的语言ID描述符,用于枚举设备支持的语言。
| 偏移(字节) | 字段 | 大小(字节) | 含义 |
|---|---|---|---|
| 0 | bLength | 1 | 描述符总长度(包括bLength和bDescriptorType) |
| 1 | bDescriptorType | 1 | 描述符类型,字符串描述符为0x03 |
| 2..N | bString | 可变 | Unicode字符串(UTF-16LE编码),以NUL结尾 |
usb_string()函数获取字符串描述符内容。该函数会处理语言ID协商和Unicode转换。驱动应避免直接解析原始字符串描述符,除非有特殊需求。
3.6 其他标准描述符
除了上述五种核心描述符外,USB规范还定义了以下标准描述符,用于扩展设备功能描述:
- 设备限定描述符(Device Qualifier Descriptor):用于高速设备在切换为全速运行时描述其能力变化,长度为10字节,类型码0x06。
- 其他速度配置描述符(Other Speed Configuration Descriptor):描述设备在其他速度下的配置信息,类型码0x07。
- 接口功率描述符(Interface Power Descriptor):用于USB PD(Power Delivery)场景,描述接口的功耗需求,类型码0x08。
- OTG描述符(OTG Descriptor):用于USB On-The-Go设备,描述双角色能力,类型码0x09。
- 调试描述符(Debug Descriptor):用于USB调试功能,类型码0x0A。
- BOS描述符(Binary Device Object Store Descriptor):用于USB 3.0及以上版本,描述设备支持的扩展特性,类型码0x0F。
3.7 描述符解析实战:从原始数据到内核结构
在Linux内核中,USB描述符的解析由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));
驱动中获取描述符信息的典型方式:
/* 在probe函数中访问设备描述符 */
static int my_usb_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct usb_device *udev = interface_to_usbdev(intf);
struct usb_device_descriptor *desc = &udev->descriptor;
dev_info(&intf->dev, "Vendor:0x%04x, Product:0x%04x\n",
le16_to_cpu(desc->idVendor),
le16_to_cpu(desc->idProduct));
dev_info(&intf->dev, "bcdUSB: %d.%d\n",
(desc->bcdUSB >> 8) & 0xff,
desc->bcdUSB & 0xff);
/* 遍历接口的端点 */
struct usb_host_interface *altsetting = intf->cur_altsetting;
for (int i = 0; i < altsetting->desc.bNumEndpoints; i++) {
struct usb_endpoint_descriptor *ep = &altsetting->endpoint[i].desc;
dev_info(&intf->dev, "EP 0x%02x: max packet=%d\n",
ep->bEndpointAddress,
le16_to_cpu(ep->wMaxPacketSize));
}
return 0;
}
struct usb_device:包含设备描述符(descriptor字段)和配置描述符列表(config字段)struct usb_host_config:包含配置描述符(desc字段)和接口描述符数组(intf_cache字段)struct usb_interface:包含当前激活的备用设置(cur_altsetting字段)struct usb_host_interface:包含接口描述符(desc字段)和端点描述符数组(endpoint字段)
3.8 描述符验证与常见错误
在USB设备驱动开发中,描述符错误是导致枚举失败或驱动无法加载的常见原因。以下是典型的描述符问题及排查方法:
| 错误类型 | 现象 | 排查方法 |
|---|---|---|
| bLength值错误 | 主机拒绝描述符,枚举失败 | 使用USB分析仪抓包,检查bLength是否与规范一致 |
| wTotalLength不匹配 | 配置描述符解析不完整或溢出 | 计算所有子描述符长度之和,与wTotalLength对比 |
| bNumEndpoints与实际不符 | 驱动无法找到预期的端点 | 检查接口描述符后的端点描述符数量是否匹配 |
| 字符串描述符索引无效 | 读取字符串时返回错误 | 确认索引值不为0,且设备确实支持该字符串 |
| 接口类/子类/协议不匹配 | 驱动无法绑定设备 | 使用lsusb -v查看实际值,与驱动usb_device_id对比 |
lsusb -v命令可以查看所有描述符的完整内容。对于驱动开发,建议在probe函数中打印关键描述符字段,使用dev_dbg()输出调试信息。如果设备枚举失败,检查内核日志(dmesg)中的USB核心错误信息。