2. USB协议基础:设备、配置、接口、端点
2.1 为什么你得搞懂这四个层级?
做USB PD调试,描述符是绕不开的坎。而描述符的本质,说白了就是在描述四样东西:设备、配置、接口、端点。
这四层就是USB协议的核心骨架。你抓到的每一个描述符包,都是在说明这四层中的某一部分。哪一层出了岔子,设备就可能无法被识别,或者功能跑偏。
我个人习惯把这四层叫作"USB的身份证系统"。设备层是姓名,配置层是性别,接口层是职业,端点层是技能。一层层往下,越来越细。你想想看,是不是这个理?
2.2 设备层(Device)—— 整个设备的"身份证"
设备层在最顶层。它描述的是整个USB设备的基本信息,对应的是设备描述符(Device Descriptor)。
设备描述符里装了什么?
- USB版本号—— 设备支持USB 2.0还是3.0
- VID/PID —— 厂商ID和产品ID,主机靠这个装驱动
- 设备类、子类、协议—— 大类归属,比如大容量存储类、HID类
- 最大包长 —— 端点0的最大包长,控制传输用的
这些信息是主机识别设备的第一道关卡。枚举一开始,主机第一个请求就是Get Device Descriptor。
我记得有一次调试,设备死活枚举不上。抓包一看,VID全零。厂商ID都没填,主机当然不认。这种低级错误,有时候翻车就翻在这里。从那以后,我拿到一个新板子,第一件事就是确认设备描述符里的VID/PID有没有烧对。
2.3 配置层(Configuration)—— 设备的不同"工作模式"
配置层描述的是设备的工作模式。一个设备可以有多个配置。比如一个摄像头,配置1是UVC模式,配置2是UAC模式。主机根据需求来选。
配置描述符包含:
- 配置值 —— 主机通过SetConfiguration来选择
- 属性 —— 自供电还是总线供电,有没有远程唤醒
- 最大电流 —— 设备从总线抽取的电流值(单位2mA)
嗯,这里要注意:配置切换不是随便来的。主机发SetConfiguration请求,设备收到后要重新枚举。切换过程中如果供电跟不上,设备就可能掉线。
我在PD调试中就遇到过这么一档子事。设备在配置切换时突然掉电,抓包发现主机发了SetConfiguration后,设备就消失了。后来查出来是配置描述符里的最大电流值填得太低,只有100mA,实际负载一上去,供电直接崩了。把电流值改到500mA,问题解决。
2.4 接口层(Interface)—— 功能模块的"划分"
接口层描述的是设备的功能模块。一个配置可以有多个接口。每个接口对应一个功能。比如一个USB耳机,接口0是音频控制,接口1是音频流。
接口描述符包含:
- 接口号 —— 从0开始编号
- 端点数量 —— 这个接口用了多少个端点(不包括端点0)
- 接口类、子类、协议 —— 定义接口的功能类型
接口其实就是"功能"的代名词。主机通过接口号来区分不同的功能模块。如果一个设备支持多个功能,每个功能占一个接口。
你抓包的时候可能会看到接口描述符里有个bAlternateSetting字段,这是用来切换接口的备用设置的。同一个接口可以有多种配置方式,比如一个视频流接口可以切换不同的帧率或分辨率。这个在UVC设备里特别常见。
2.5 端点层(Endpoint)—— 数据传输的"通道"
端点层是最底层。它是数据传输的实际通道。每个接口可以有多个端点。
端点0是特殊的控制端点,所有设备都必须有。其他端点用于实际的数据传输。
端点描述符包含:
- 端点地址 —— 低4位是端点号,最高位表示方向(0=OUT,1=IN)
- 传输类型 —— 控制、批量、中断、同步
- 最大包长 —— 每个数据包的最大字节数
我经常看到有人把批量端点配成中断端点,结果传输速度上不去。为什么?批量端点的最大包长在USB 2.0高速下是512字节,而中断端点只有64字节。配错了,速度差8倍。
端点地址的编码规则也容易搞混。0x81表示端点1 IN,0x02表示端点2 OUT。方向是看最高位,不是看端点号。我见过有人把IN端点配成了OUT方向,结果数据死活发不出去。
2.6 四层关系全景图
下面这张图展示了四层之间的包含关系。从上往下,一层套一层。
2.7 调试中的应用与避坑
知道这四层有什么用?调试的时候你就知道了。
抓包工具中,描述符是按这四层结构组织的。你看到的一级菜单是设备,二级是配置,三级是接口,四级是端点。哪一层有问题,就从哪一层开始查。
我常用的调试思路:
- 先看设备层—— 主机有没有收到设备描述符?VID/PID是不是对的?
- 再看配置层—— 配置描述符返回了吗?最大电流够不够?
- 然后看接口层—— 接口号对不对?端点数量有没有填对?
- 最后看端点层—— 端点地址方向对不对?传输类型匹配吗?
还有一个常见坑:配置描述符里包含的所有接口描述符和端点描述符,必须作为一个整体返回。主机只发一次Get Configuration Descriptor请求,设备要把整棵描述符树一次性吐出来。如果返回的数据不完整或者顺序乱了,主机解析就会出错。
我建议你在调试时养成一个习惯:先用抓包工具把完整的描述符树导出来,对照USB规范逐个字段检查。特别是端点地址的方向位和传输类型,这两个字段配错的概率最高。
2.8 四层对比总览
最后用一张表把这四层的关键信息串起来,方便你快速查阅。
| 层级 | 对应描述符 | 关键字段 | 一句话作用 |
|---|---|---|---|
| 设备层 | 设备描述符 | VID/PID、USB版本、设备类 | 标识整个设备,主机据此识别硬件 |
| 配置层 | 配置描述符 | 配置值、属性、最大电流 | 定义设备的工作模式与功耗 |
| 接口层 | 接口描述符 | 接口号、端点数、接口类 | 划分功能模块,每个接口一个功能 |
| 端点层 | 端点描述符 | 端点地址、传输类型、最大包长 | 定义数据传输通道,数据从这里进出 |
这四层结构贯穿了整个USB协议栈。不管是USB 2.0还是USB 3.x,描述符的组织方式都是这个套路。搞懂了这四层,你就掌握了USB调试的底层地图。