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 四层关系全景图

下面这张图展示了四层之间的包含关系。从上往下,一层套一层。

USB 描述符四层层次结构 设备描述符 (Device) VID/PID | USB版本 | 设备类 配置描述符 (Configuration) 配置值 | 属性 | 最大电流 接口描述符 (Interface) 接口号 | 端点数 | 接口类 端点描述符 (EP 0) 地址: 0x81 | 类型: 中断 端点描述符 (EP 1) 地址: 0x02 | 类型: 批量 一个设备 → 多个配置 → 每个配置多个接口 → 每个接口多个端点

2.7 调试中的应用与避坑

知道这四层有什么用?调试的时候你就知道了。

抓包工具中,描述符是按这四层结构组织的。你看到的一级菜单是设备,二级是配置,三级是接口,四级是端点。哪一层有问题,就从哪一层开始查。

我常用的调试思路:

  1. 先看设备层—— 主机有没有收到设备描述符?VID/PID是不是对的?
  2. 再看配置层—— 配置描述符返回了吗?最大电流够不够?
  3. 然后看接口层—— 接口号对不对?端点数量有没有填对?
  4. 最后看端点层—— 端点地址方向对不对?传输类型匹配吗?
避坑指南:我曾经花了一整天查一个枚举失败的问题。抓包看到设备描述符正常,配置描述符正常,但枚举到某个点就卡住了。最后发现是接口描述符中的bNumEndpoints字段填错了。接口里只用了1个端点,但描述符里填了2。主机枚举到第二个端点时找不到响应,直接超时。从那以后,我每次都会仔细核对接口描述符中的端点计数字段。

还有一个常见坑:配置描述符里包含的所有接口描述符和端点描述符,必须作为一个整体返回。主机只发一次Get Configuration Descriptor请求,设备要把整棵描述符树一次性吐出来。如果返回的数据不完整或者顺序乱了,主机解析就会出错。

我建议你在调试时养成一个习惯:先用抓包工具把完整的描述符树导出来,对照USB规范逐个字段检查。特别是端点地址的方向位和传输类型,这两个字段配错的概率最高。

2.8 四层对比总览

最后用一张表把这四层的关键信息串起来,方便你快速查阅。

层级 对应描述符 关键字段 一句话作用
设备层 设备描述符 VID/PID、USB版本、设备类 标识整个设备,主机据此识别硬件
配置层 配置描述符 配置值、属性、最大电流 定义设备的工作模式与功耗
接口层 接口描述符 接口号、端点数、接口类 划分功能模块,每个接口一个功能
端点层 端点描述符 端点地址、传输类型、最大包长 定义数据传输通道,数据从这里进出

这四层结构贯穿了整个USB协议栈。不管是USB 2.0还是USB 3.x,描述符的组织方式都是这个套路。搞懂了这四层,你就掌握了USB调试的底层地图。