USB描述符基础:设备描述符、配置描述符、接口描述符、端点描述符

做USB一致性测试这么些年,我最常被问到的一个问题就是:“枚举到底是怎么跑起来的?”你想想看,一个USB设备插上主机,什么驱动都没装,主机凭啥就知道你是谁、你能干啥?

说白了,答案就藏在描述符里。描述符就是USB设备的身份证、简历、和功能说明书。主机通过读取这些描述符,才能决定怎么跟你打交道。今天我就带你把四个核心描述符彻底理一遍。

1. 描述符的树形结构

USB描述符是典型的树形层级结构。你可能看过一些文档,什么标准描述符、类描述符、厂商特定描述符……一团乱麻。其实没那么复杂。

按我的理解,你只需要抓住一条主线:描述符构成了一棵从顶层到底层的树。设备描述符是树根,然后是配置描述符,再到接口描述符,最后是端点描述符。

嗯,这里要注意:同一个设备可以有多个配置,但同一时刻只能激活一个。同一个配置下又能挂多个接口,接口下面才是端点。就是这个关系。

核心逻辑:枚举过程就是主机从树根开始,一层一层往下读描述符的过程。哪一层读错了,设备就起不来。

USB描述符层级结构 设备描述符 配置描述符 接口描述符 #0 接口描述符 #1 IN端点描述符 OUT端点描述符 中断端点描述符 … 每个USB设备有且仅有1个设备描述符 配置描述符可多个,但同一时刻只能激活1个配置

2. 设备描述符 —— 设备的身份证

设备描述符是所有描述符的老大哥。主机在枚举时,首先发的就是GET_DESCRIPTOR请求获取设备描述符(请求索引为0)。主机从里面读到什么?我上个表给你看:

字段 字节偏移 长度(字节) 说明
bLength 0 1 描述符长度,固定为18
bDescriptorType 1 1 类型码,设备描述符为1
bcdUSB 2 2 USB规格版本号,如0x0200表示USB 2.0
bDeviceClass 4 1 类代码,0表示在接口描述符里定义
bDeviceSubClass 5 1 子类代码
bDeviceProtocol 6 1 协议代码
bMaxPacketSize0 7 1 端点0的最大包长
idVendor 8 2 厂商ID,由USB-IF分配
idProduct 10 2 产品ID,厂商自定义
bcdDevice 12 2 设备版本号,BCD格式
iManufacturer 14 1 厂商字符串索引
iProduct 15 1 产品字符串索引
iSerialNumber 16 1 序列号字符串索引
bNumConfigurations 17 1 支持的配置个数

个人经验:我在项目中遇到过设备描述符的bMaxPacketSize0写错了。USB 2.0的全速设备应该是64字节,结果有人写成了8字节。主机读到后直接认为这个设备是低速设备,后面枚举全乱套了。这个东西必须和硬件实际支持的包大小一致,千万不能瞎填。

3. 配置描述符 —— 设备的配置文件

读完了身份证,主机接着读配置描述符。配置描述符告诉主机这个设备能工作在什么功耗下、需要多少电流、支持多少接口。

你想想看,为什么好多USB设备插上去不用额外的驱动也能用?就是因为配置描述符里把功能描述清楚了。主机靠它来加载合适的驱动。

字段 大小(字节) 说明
bLength 1 固定为9字节
bDescriptorType 1 配置描述符类型码,值为2
wTotalLength 2 整个配置描述符族的总长度,包含所有子描述符
bNumInterfaces 1 这个配置下的接口数量
bConfigurationValue 1 用于SET_CONFIGURATION请求的配置编号
iConfiguration 1 配置字符串索引
bmAttributes 1 供电与远程唤醒属性
bMaxPower 1 最大电流,以2mA为单位

避坑指南:我曾经遇到一个设备,配置描述符的wTotalLength写小了。主机读到一半发现数据不够,直接判定设备故障,设备管理器里显示“无法识别的USB设备”。后来发现是把接口描述符的字节数漏了。切记:wTotalLength必须包含所有子描述符的字节数总和。

4. 接口描述符 —— 设备的功能模块

每个配置下可以有多个接口。每个接口代表一个功能模块。比如一个USB摄像头,它有视频采集接口,还有音频输入接口,这两个就是不同的接口。

接口描述符里最关键的是 bInterfaceClass、bInterfaceSubClass、bInterfaceProtocol 这三个字段。主机通过它们匹配驱动。你看Windows的设备管理器里, “USB视频设备” 这种设备类型就是从这里读出来的。

字段 大小(字节) 说明
bLength 1 固定为9字节
bDescriptorType 1 接口描述符类型码,值为4
bInterfaceNumber 1 接口编号,从0开始
bAlternateSetting 1 备用设置编号,用于切换接口属性
bNumEndpoints 1 接口使用的端点数量(除端点0外)
bInterfaceClass 1 类代码
bInterfaceSubClass 1 子类代码
bInterfaceProtocol 1 协议代码
iInterface 1 接口字符串索引

实际案例:我调试过一个复合设备,它有HID接口和 bulk接口。它的接口描述符里bInterfaceClass分别写了 HID(0x03)和 大容量存储类(0x08)。主机读到HID接口就加载HID驱动,读到mass storage接口就加载磁盘驱动。这就是接口描述符的威力 —— 一个设备能同时扮演多个角色。

5. 端点描述符 —— 数据的通道

端点描述符是最底层的描述符。它描述的是实际数据流的方向、类型、大小。每个接口下面可以有多个端点,但端点0是公用的控制端点,不需要用端点描述符描述。

端点描述符里最让我头疼的是 bmAttributes 字段。它定义了端点的传输类型。会有传输、控制传输、中断传输和同步传输。同步传输又分同步和异步……嗯,这块容易搞混。

字段 大小(字节) 说明
bLength 1 固定为7字节
bDescriptorType 1 端点描述符类型码,值为5
bEndpointAddress 1 端点地址+方向,bit7为1表示IN
bmAttributes 1 传输类型:0控制、1等时、2批量、3中断
wMaxPacketSize 2 端点支持的最大包大小
bInterval 1 轮询时间间隔,以毫秒或帧为单位

个人习惯:我习惯用 Bus Hound 抓取枚举时的描述符数据。抓下来后直接看十六进制,对着表一对比就能发现哪里写错了。比如端点描述符里的wMaxPacketSize,USB 2.0 全速下批量端点最大是64字节,高速下是512字节。写大了,主机拒绝接收;写小了,数据吞吐又不够。

6. 描述符之间的强关联

最后说一个很多人忽略的点:描述符之间的字段是 互相绑定的。比如:

  • 设备描述符中的bNumConfigurations 必须等于设备实际支持的配置数量。你写了2,但后面只有一个配置描述符,主机就会等到死。
  • 配置描述符中的wTotalLength 必须包含该配置下所有接口描述符 + 所有端点描述符 + 可能的类描述符的字节总数。写过的人都知道,这个值算错了会出大问题。
  • 接口描述符中的bNumEndpoints 要等于该接口下挂载的端点个数(除端点0外)。我曾经见过一个设备,这个字段写的3,但实际只提供了2个端点描述符。结果主机在读枚举数据时,发现数据长度对不上,直接放弃这个设备。

特别提醒:USB-IF一致性测试里,描述符校验是一个标准测试项目。测试工具会逐字段检查合法性,包括长度、类型、字段间的逻辑一致性。如果你遇到枚举失败,80%的可能是描述符有问题。嗯,剩下20%就是硬件电路的事了。

这就是四个核心描述符的全部内容。你掌握了这个树形结构,再去看USB枚举日志,会清晰很多。描述符是USB设备通信的基础,也是后面一致性测试里反复绕不开的东西。