3. USB 描述符体系:设备描述符、配置描述符、接口描述符、端点描述符、字符串描述符

USB 描述符(Descriptor)是 USB 协议中用于描述设备属性、功能和配置的数据结构。主机通过枚举过程读取这些描述符,从而识别设备类型、加载对应驱动并建立通信管道。在 Linux USB Gadget 驱动中,描述符的构造与注册是驱动开发的核心环节。

核心概念:描述符是 USB 设备向主机“自我介绍”的标准格式。每个描述符以长度(bLength)和类型(bDescriptorType)字段开头,形成层级嵌套的树状结构。

3.1 描述符层级结构总览

USB 描述符体系遵循“设备 → 配置 → 接口 → 端点”的层级关系。字符串描述符作为可选补充,提供人类可读的文本信息。下图展示了完整的描述符树状结构:

USB 描述符层级结构 设备描述符 配置描述符 接口描述符 0 接口描述符 1 接口描述符 2 端点描述符 IN 端点描述符 OUT 端点描述符 IN 端点描述符 OUT 字符串描述符(可选) 说明 每个配置下可包含多个接口,每个接口可包含多个端点。字符串描述符通过索引被其他描述符引用。

3.2 设备描述符(Device Descriptor)

设备描述符是 USB 设备的第一份“身份证”,位于描述符树的根节点。主机通过控制传输的 GET_DESCRIPTOR 请求获取,长度固定为 18 字节。

偏移 字段 大小 说明
0 bLength 1 描述符长度,固定为 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/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 支持的配置数量
Gadget 驱动中的设备描述符:在 Linux 内核中,设备描述符通过 struct usb_device_descriptor 结构体定义,驱动开发者需填充该结构体并注册到 Gadget 框架中。
/* Linux 内核中的设备描述符结构体 */
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));

3.3 配置描述符(Configuration Descriptor)

配置描述符描述设备的电源管理属性和接口数量。一个设备可以有多个配置(如低功耗模式和高性能模式),但同一时刻只能激活一个配置。配置描述符长度不固定,因为它需要包含其下所有接口和端点的描述符总长度。

字段 大小 说明
bLength 1 固定为 9
bDescriptorType 1 配置描述符类型码 0x02
wTotalLength 2 整个配置返回的数据总长度(含接口和端点描述符)
bNumInterfaces 1 该配置支持的接口数量
bConfigurationValue 1 用于 SET_CONFIGURATION 请求的配置值
iConfiguration 1 配置字符串描述符索引
bmAttributes 1 电源属性:D7=总线供电,D6=自供电,D5=远程唤醒
bMaxPower 1 最大电流需求,单位 2mA
注意:配置描述符的 wTotalLength 字段必须精确计算,包含该配置下所有接口描述符、端点描述符以及可能的类特定描述符的总字节数。计算错误会导致主机枚举失败。

3.4 接口描述符(Interface Descriptor)

接口描述符描述一个功能单元(如键盘、音频流、大容量存储)。一个配置可以包含多个接口,接口可以备用设置(Alternate Setting),用于在运行时切换接口参数(如改变端点带宽)。

字段 大小 说明
bLength 1 固定为 9
bDescriptorType 1 接口描述符类型码 0x04
bInterfaceNumber 1 接口编号(从 0 开始)
bAlternateSetting 1 备用设置编号
bNumEndpoints 1 该接口使用的端点数量(不包括端点 0)
bInterfaceClass 1 接口类代码(如 HID=0x03, Mass Storage=0x08)
bInterfaceSubClass 1 接口子类代码
bInterfaceProtocol 1 接口协议代码
iInterface 1 接口字符串描述符索引

3.5 端点描述符(Endpoint Descriptor)

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

字段 大小 说明
bLength 1 固定为 7
bDescriptorType 1 端点描述符类型码 0x05
bEndpointAddress 1 Bit 3-0: 端点号;Bit 7: 方向(0=OUT, 1=IN)
bmAttributes 1 Bit 1-0: 传输类型(00=控制, 01=等时, 10=批量, 11=中断)
wMaxPacketSize 2 最大包大小(高 2 位用于高速模式下的额外事务数)
bInterval 1 中断/等时端点的轮询间隔(单位:帧/微帧)
传输类型与端点属性:
  • 控制传输:仅端点 0,用于枚举和控制命令。
  • 批量传输:用于大块数据(如 U 盘),保证数据完整性。
  • 中断传输:用于周期性小数据(如鼠标、键盘),保证延迟。
  • 等时传输:用于实时数据流(如音频、视频),不保证重传。

3.6 字符串描述符(String Descriptor)

字符串描述符提供人类可读的文本信息,如厂商名称、产品名称和序列号。字符串使用 Unicode(UTF-16LE)编码,通过索引被设备/配置/接口描述符中的 i* 字段引用。字符串描述符 0 是特殊的语言 ID 描述符,用于声明设备支持的语言。

字段 大小 说明
bLength 1 描述符总长度(含头部)
bDescriptorType 1 字符串描述符类型码 0x03
bString 可变 Unicode 字符串(UTF-16LE 编码)
/* Linux Gadget 驱动中字符串描述符的典型定义 */
static struct usb_string strings_dev[] = {
    { .id = 0, .s = "My USB Gadget" },          /* 产品名称 */
    { .id = 1, .s = "Vendor Inc." },             /* 厂商名称 */
    { .id = 2, .s = "SN1234567890" },            /* 序列号 */
    {  } /* 结束标记 */
};

static struct usb_gadget_strings stringtab_dev = {
    .language   = 0x0409,  /* 英语(美国) */
    .strings    = strings_dev,
};

3.7 描述符在枚举过程中的交互

主机通过标准设备请求获取描述符,典型的枚举流程如下:

  1. 获取设备描述符:主机发送 GET_DESCRIPTOR(DEVICE),设备返回 18 字节设备描述符。
  2. 设置地址:主机发送 SET_ADDRESS,设备进入地址状态。
  3. 再次获取设备描述符:使用新地址重新获取,验证设备。
  4. 获取配置描述符:主机发送 GET_DESCRIPTOR(CONFIGURATION),设备返回配置描述符及其下所有接口和端点描述符。
  5. 获取字符串描述符:主机根据索引请求字符串描述符(可选)。
  6. 选择配置:主机发送 SET_CONFIGURATION,激活选中的配置。
USB 枚举过程中描述符交互流程 主机(Host) 设备(Gadget) GET_DESCRIPTOR(DEVICE) 返回设备描述符 SET_ADDRESS 确认地址 GET_DESCRIPTOR(CONFIG) 返回配置+接口+端点 GET_DESCRIPTOR(STRING) 返回字符串描述符 SET_CONFIGURATION 配置激活,开始通信 枚举完成后,主机即可通过端点与 Gadget 设备进行数据通信

3.8 Linux Gadget 驱动中的描述符注册

在 Linux Gadget 驱动中,描述符通过 usb_gadget_driver 结构体中的 bind 回调函数注册。驱动开发者需要构造描述符数组,并调用 usb_assign_descriptors() 或手动绑定到 Gadget 实例。

/* Linux Gadget 驱动中描述符注册的典型模式 */
static int my_gadget_bind(struct usb_gadget *gadget)
{
    struct usb_descriptor_header *fs_descriptors[] = {
        (struct usb_descriptor_header *)&fs_device_desc,
        (struct usb_descriptor_header *)&fs_config_desc,
        (struct usb_descriptor_header *)fs_interface_desc,
        (struct usb_descriptor_header *)fs_endpoint_desc,
        NULL,
    };

    struct usb_descriptor_header *hs_descriptors[] = {
        (struct usb_descriptor_header *)&hs_device_desc,
        (struct usb_descriptor_header *)&hs_config_desc,
        (struct usb_descriptor_header *)hs_interface_desc,
        (struct usb_descriptor_header *)hs_endpoint_desc,
        NULL,
    };

    /* 注册全速和高速描述符 */
    return usb_assign_descriptors(fs_descriptors, hs_descriptors,
                                  NULL, NULL);
}
最佳实践:建议为全速(FS)和高速(HS)分别维护描述符数组,因为高速模式下端点最大包大小和轮询间隔可能与全速不同。使用 usb_assign_descriptors() 可以自动根据当前连接速度选择合适的描述符集合。

3.9 描述符常见问题与调试

  • wTotalLength 计算错误:导致主机无法正确解析配置描述符,枚举失败。建议使用内核提供的 usb_descriptor_fillbuf() 辅助函数。
  • 端点地址冲突:同一接口内不能有相同端点号和方向的端点。
  • bMaxPacketSize 超限:全速批量端点最大 64 字节,高速批量端点最大 512 字节。
  • 字符串索引无效:如果 iManufacturer 等字段指向不存在的字符串索引,主机可能跳过字符串读取或导致枚举异常。
  • 调试工具:使用 lsusb -v 查看设备描述符树,使用内核 usbmon 或 ftrace 跟踪枚举过程。
总结:USB 描述符体系是 USB 通信的基石,理解其层级结构、字段含义和注册方式,是编写稳定可靠的 Linux USB Gadget 驱动的必要条件。在后续章节中,我们将深入探讨如何为具体设备类(如 HID、Mass Storage、CDC ACM)构造描述符,并实现完整的 Gadget 功能。