3. USB描述符详解

USB描述符是USB设备向主机报告自身能力与属性的数据结构。主机通过标准设备请求(如GET_DESCRIPTOR)逐层获取这些描述符,从而构建完整的设备拓扑与功能视图。每个描述符以长度(bLength)和类型(bDescriptorType)字段开头,形成可扩展的层级结构。

核心概念:描述符是USB枚举过程的“身份证”。设备必须按照USB规范正确返回描述符,否则主机无法识别或加载对应驱动。Linux内核中的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 支持的配置描述符数量
驱动开发要点:在Linux内核中,设备描述符通过struct usb_device_descriptor表示,定义在<linux/usb/ch9.h>。驱动可以通过usb_device->descriptor直接访问。idVendor和idProduct是驱动匹配的关键标识。
设备描述符结构示意图 设备描述符 (bLength=18, bDescriptorType=0x01) bcdUSB | bDeviceClass | bDeviceSubClass | bDeviceProtocol | bMaxPacketSize0 配置描述符 (bNumConfigurations=N) 每个配置包含接口描述符集合 接口描述符 (bNumInterfaces=M) 每个接口包含端点描述符集合 端点描述符 (bNumEndpoints)

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
常见误区:wTotalLength字段包含当前配置描述符本身以及其下所有接口描述符、端点描述符和类特定描述符的总长度。驱动解析时不能只读取配置描述符的9字节,必须根据wTotalLength读取完整配置数据块。

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 接口字符串描述符的索引值
驱动匹配关键:Linux USB子系统通过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 轮询间隔(单位:帧/微帧),中断和等时端点使用
端点描述符与传输类型映射 端点描述符 (bLength=7, bDescriptorType=0x05) bEndpointAddress | bmAttributes | wMaxPacketSize | bInterval 批量传输 (Bulk) bmAttributes=0x02 中断传输 (Interrupt) bmAttributes=0x03 等时传输 (Isochronous) bmAttributes=0x01 bEndpointAddress.D7=0 表示OUT端点(主机→设备),D7=1 表示IN端点(设备→主机) wMaxPacketSize 低11位表示最大包大小,高5位用于等时传输的额外事务数

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结尾
驱动开发提示:在Linux内核中,可以使用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。
USB标准描述符层级关系 设备描述符 (0x01) 配置描述符 (0x02) 接口描述符 (0x04) 端点描述符 (0x05)

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对比
调试建议:在Linux主机上,使用lsusb -v命令可以查看所有描述符的完整内容。对于驱动开发,建议在probe函数中打印关键描述符字段,使用dev_dbg()输出调试信息。如果设备枚举失败,检查内核日志(dmesg)中的USB核心错误信息。