USB 描述符体系:设备描述符、配置描述符、接口描述符、端点描述符、其他描述符
做 USB 开发这些年,我有个很深的感觉:描述符就是 USB 设备的身份证。不管你写固件还是搞驱动,最终都要跟这堆二进制结构打交道。搞懂它们,才算真正入门。
这一章,我们把这些描述符挨个捋一遍。不绕弯子,直接从结构开始。
2.1 描述符的树形结构
你的 USB 设备插上电脑后,主机是怎么知道它有几个功能、几个接口、几个端点的?靠的就是描述符组成的层级树。说白了,像一棵倒挂的树:
- 根:设备描述符 —— 描述整个设备的基本信息
- 树干:配置描述符 —— 每个配置下挂一个或多个接口
- 树枝:接口描述符 —— 每个接口代表一个功能
- 树叶:端点描述符 —— 实际收发数据的管道
- 其他特殊描述符(字符串、HID、UVC 等)像藤蔓缠绕在树干上
bcdUSB 和 idVendor/idProduct。这两项对不上,后面的配置基本白搭。别问我怎么知道的……当年被坑了三天。
下面这张图把这棵树展示清楚了:
2.2 设备描述符
设备描述符是所有描述符的起点。每次枚举,主机第一个请求的就是它。结构固定 18 字节,一个都不能少。
| 偏移 | 字段 | 长度 | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 本描述符长度,固定 0x12 |
| 1 | bDescriptorType | 1 | 类型码,设备描述符 = 0x01 |
| 2 | bcdUSB | 2 | USB 版本号,BCD 格式,例如 USB 3.0 = 0x0300 |
| 4 | bDeviceClass | 1 | 设备基类,0x00 表示每个接口自行定义 |
| 5 | bDeviceSubClass | 1 | 设备子类 |
| 6 | bDeviceProtocol | 1 | 设备协议 |
| 7 | bMaxPacketSize0 | 1 | 端点 0 最大包长 |
| 8 | idVendor | 2 | 厂商 ID(USB-IF 分配) |
| 10 | idProduct | 2 | 产品 ID |
| 12 | bcdDevice | 2 | 设备版本号 |
| 14 | iManufacturer | 1 | 厂商字符串索引 |
| 15 | iProduct | 1 | 产品字符串索引 |
| 16 | iSerialNumber | 1 | 序列号字符串索引 |
| 17 | bNumConfigurations | 1 | 支持的配置数量 |
bMaxPacketSize0 在 USB 3.0 中必须为 9(即 2^9 = 512 字节)。我遇到过某颗芯片写成了 64,结果 SuperSpeed 枚举直接失败,主机报告“设备无法识别”。查了三天才发现是这个字节写错了。记住:USB 3.0 的端点 0 最大包长是 512 字节,不是 64。
2.3 配置描述符
配置描述符紧随设备描述符之后。它告诉主机:这个配置下有多少接口、多少端点、消耗多少总线电流。结构 9 字节。
| 偏移 | 字段 | 长度 | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 0x09 |
| 1 | bDescriptorType | 1 | 0x02 |
| 2 | wTotalLength | 2 | 整个配置的总长度(包括下级描述符) |
| 4 | bNumInterfaces | 1 | 该配置下的接口数量 |
| 5 | bConfigurationValue | 1 | 选择本配置时的值,从 1 开始 |
| 6 | iConfiguration | 1 | 配置字符串索引 |
| 7 | bmAttributes | 1 | 电源属性:D7=总线供电,D6=自供电,D5=远程唤醒 |
| 8 | bMaxPower | 1 | 最大消耗电流,单位 2mA |
你想想看,主机拿到这个结构后,才能决定用哪个配置。一个设备可以有多个配置(比如一个高功率配置一个低功耗配置)。但我很少看到实际产品用超过一个配置,太复杂了。
2.4 接口描述符
接口描述符代表一个独立的功能实体。比如一个 USB 摄像头,通常包含一个视频接口和一个音频接口。结构 9 字节。
| 偏移 | 字段 | 长度 | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 0x09 |
| 1 | bDescriptorType | 1 | 0x04 |
| 2 | bInterfaceNumber | 1 | 接口编号,从 0 开始 |
| 3 | bAlternateSetting | 1 | 备用设置,用于切换带宽 |
| 4 | bNumEndpoints | 1 | 该接口下除端点 0 外的端点个数 |
| 5 | bInterfaceClass | 1 | 接口类(如 HID 0x03、Mass Storage 0x08) |
| 6 | bInterfaceSubClass | 1 | 接口子类 |
| 7 | bInterfaceProtocol | 1 | 接口协议 |
| 8 | iInterface | 1 | 接口字符串索引 |
bAlternateSetting 用得不好容易出 bug。我记得有一次做 UVC 摄像头,视频数据流带宽不够,后来通过加一个高带宽的备用设置解决了。但注意:有些操作系统(比如早期的 Windows 10)对备用设置切换处理得很蹩脚,频繁切换可能导致枚举超时。
2.5 端点描述符
端点描述符定义了数据管道的方向、类型和大小。每个非零端点都需要一个描述符。结构 7 字节(USB 3.0 下为 7 字节,2.0 下也是 7 字节)。
| 偏移 | 字段 | 长度 | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 0x07 |
| 1 | bDescriptorType | 1 | 0x05 |
| 2 | bEndpointAddress | 1 | D7:方向(0 OUT,1 IN);D3..0:端点号 |
| 3 | bmAttributes | 1 | 传输类型:00=控制,01=等时,10=批量,11=中断 |
| 4 | wMaxPacketSize | 2 | 最大包长,USB 3.0 批量可达 1024 |
| 6 | bInterval | 1 | 查询间隔(等时/中断用),单位 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 描述符等,由具体设备类标准定义。
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 跟实际端点数对不上等等。养成这个习惯,调试可以少走很多弯路。