USB 描述符体系:设备描述符、配置描述符、接口描述符、端点描述符、其他描述符

做 USB 开发这些年,我有个很深的感觉:描述符就是 USB 设备的身份证。不管你写固件还是搞驱动,最终都要跟这堆二进制结构打交道。搞懂它们,才算真正入门。

这一章,我们把这些描述符挨个捋一遍。不绕弯子,直接从结构开始。

2.1 描述符的树形结构

你的 USB 设备插上电脑后,主机是怎么知道它有几个功能、几个接口、几个端点的?靠的就是描述符组成的层级树。说白了,像一棵倒挂的树:

  • 根:设备描述符 —— 描述整个设备的基本信息
  • 树干:配置描述符 —— 每个配置下挂一个或多个接口
  • 树枝:接口描述符 —— 每个接口代表一个功能
  • 树叶:端点描述符 —— 实际收发数据的管道
  • 其他特殊描述符(字符串、HID、UVC 等)像藤蔓缠绕在树干上
个人习惯:我调试时,最喜欢先看设备描述符里的 bcdUSB 和 idVendor/idProduct。这两项对不上,后面的配置基本白搭。别问我怎么知道的……当年被坑了三天。

下面这张图把这棵树展示清楚了:

USB 描述符树形结构 设备描述符 配置描述符 接口描述符 #0 接口描述符 #1 …… 端点 IN/OUT 端点 IN 其他描述符 说明: 一个配置下可以挂多个接口,每个接口下挂一到多个端点 其他描述符(字符串、类特定等)可挂在接口或端点下

2.2 设备描述符

设备描述符是所有描述符的起点。每次枚举,主机第一个请求的就是它。结构固定 18 字节,一个都不能少。

偏移 字段 长度 说明
0bLength1本描述符长度,固定 0x12
1bDescriptorType1类型码,设备描述符 = 0x01
2bcdUSB2USB 版本号,BCD 格式,例如 USB 3.0 = 0x0300
4bDeviceClass1设备基类,0x00 表示每个接口自行定义
5bDeviceSubClass1设备子类
6bDeviceProtocol1设备协议
7bMaxPacketSize01端点 0 最大包长
8idVendor2厂商 ID(USB-IF 分配)
10idProduct2产品 ID
12bcdDevice2设备版本号
14iManufacturer1厂商字符串索引
15iProduct1产品字符串索引
16iSerialNumber1序列号字符串索引
17bNumConfigurations1支持的配置数量
注意:bMaxPacketSize0 在 USB 3.0 中必须为 9(即 2^9 = 512 字节)。我遇到过某颗芯片写成了 64,结果 SuperSpeed 枚举直接失败,主机报告“设备无法识别”。查了三天才发现是这个字节写错了。记住:USB 3.0 的端点 0 最大包长是 512 字节,不是 64。

2.3 配置描述符

配置描述符紧随设备描述符之后。它告诉主机:这个配置下有多少接口、多少端点、消耗多少总线电流。结构 9 字节。

偏移 字段 长度 说明
0bLength10x09
1bDescriptorType10x02
2wTotalLength2整个配置的总长度(包括下级描述符)
4bNumInterfaces1该配置下的接口数量
5bConfigurationValue1选择本配置时的值,从 1 开始
6iConfiguration1配置字符串索引
7bmAttributes1电源属性:D7=总线供电,D6=自供电,D5=远程唤醒
8bMaxPower1最大消耗电流,单位 2mA

你想想看,主机拿到这个结构后,才能决定用哪个配置。一个设备可以有多个配置(比如一个高功率配置一个低功耗配置)。但我很少看到实际产品用超过一个配置,太复杂了。

2.4 接口描述符

接口描述符代表一个独立的功能实体。比如一个 USB 摄像头,通常包含一个视频接口和一个音频接口。结构 9 字节。

偏移 字段 长度 说明
0bLength10x09
1bDescriptorType10x04
2bInterfaceNumber1接口编号,从 0 开始
3bAlternateSetting1备用设置,用于切换带宽
4bNumEndpoints1该接口下除端点 0 外的端点个数
5bInterfaceClass1接口类(如 HID 0x03、Mass Storage 0x08)
6bInterfaceSubClass1接口子类
7bInterfaceProtocol1接口协议
8iInterface1接口字符串索引
避坑:bAlternateSetting 用得不好容易出 bug。我记得有一次做 UVC 摄像头,视频数据流带宽不够,后来通过加一个高带宽的备用设置解决了。但注意:有些操作系统(比如早期的 Windows 10)对备用设置切换处理得很蹩脚,频繁切换可能导致枚举超时。

2.5 端点描述符

端点描述符定义了数据管道的方向、类型和大小。每个非零端点都需要一个描述符。结构 7 字节(USB 3.0 下为 7 字节,2.0 下也是 7 字节)。

偏移 字段 长度 说明
0bLength10x07
1bDescriptorType10x05
2bEndpointAddress1D7:方向(0 OUT,1 IN);D3..0:端点号
3bmAttributes1传输类型:00=控制,01=等时,10=批量,11=中断
4wMaxPacketSize2最大包长,USB 3.0 批量可达 1024
6bInterval1查询间隔(等时/中断用),单位 125µs

这里说个我踩过的坑:bEndpointAddress 的低 4 位是端点号,最高位是方向。如果你把 IN 端点的地址写成 0x01(方向位没置),主机不会报错,但数据死活传输不了。我当时调了一个 HID 键盘,敲键没反应,用 Bus Hound 看 IN 请求被主机回绝了——端点号对但方向反了。改过来就好了。

2.6 其他描述符

USB 规范还定义了好几种补充描述符,我挑常见的列一下:

  • 字符串描述符:提供人类可读的厂商、产品、序列号等信息。索引 0 是语言 ID 表。
  • 设备限定描述符:用于说明同时支持 USB 2.0 和 3.0 时的配置变化,很少用到。
  • 其他速度配置描述符:告诉主机在较低速度下的配置大小。
  • 类特定描述符:HID 描述符、UVC 描述符、MS OS 2.0 描述符等,由具体设备类标准定义。
我的看法:别小看字符串描述符。很多驱动兼容问题就出在这里。比如你写了中文序列号,但主机没有对应的语言 ID,读回来的字符串就是乱码或空串。我一般只支持英文(0x0409),省事。

2.7 实战:抓一份描述符看看

光看理论不过瘾。我们来动手看一份真实的 USB 3.0 U 盘设备描述符(用 lsusb -v 在 Linux 下抓的,精简版):

Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               3.00
  bDeviceClass            0 (Defined at Interface level)
  bDeviceSubClass         0
  bDeviceProtocol         0
  bMaxPacketSize0         9
  idVendor           0x0781 (SanDisk)
  idProduct          0x5583
  bcdDevice            1.00
  iManufacturer           1
  iProduct                2
  iSerialNumber           3
  bNumConfigurations      1
Configuration Descriptor:
  bLength                 9
  bDescriptorType         2
  wTotalLength           32
  bNumInterfaces          1
  bConfigurationValue     1
  iConfiguration          0
  bmAttributes         0x80 (Bus Powered)
  bMaxPower             224 (448mA)
  Interface Descriptor:
    bLength               9
    bDescriptorType       4
    bInterfaceNumber      0
    bAlternateSetting     0
    bNumEndpoints         2
    bInterfaceClass       8 Mass Storage
    bInterfaceSubClass    6 SCSI
    bInterfaceProtocol   80 Bulk-Only
    iInterface            0
    Endpoint Descriptor:
      bLength             7
      bDescriptorType     5
      bEndpointAddress   0x81  IN EP 1
      bmAttributes        2 (Bulk)
      wMaxPacketSize   0x0400 (1024)
      bInterval           0
    Endpoint Descriptor:
      bLength             7
      bDescriptorType     5
      bEndpointAddress   0x02  OUT EP 2
      bmAttributes        2 (Bulk)
      wMaxPacketSize   0x0400 (1024)
      bInterval           0

看明白了吗?就是一个很标准的 BOT 大容量存储设备。接口类 0x08(Mass Storage),子类 0x06(SCSI),协议 0x50(Bulk-Only)。两个批量端点,一个 IN(0x81)一个 OUT(0x02)。

说句实话,我每次拿到一个新 USB 设备,第一件事就是 dump 它的全部描述符,对照规范一条条看。这样能提前发现很多坑,比如 wTotalLength 算错了、bNumEndpoints 跟实际端点数对不上等等。养成这个习惯,调试可以少走很多弯路。