控制传输详解:设置包、数据阶段、状态阶段,标准设备请求

控制传输是USB里最基础、也最容易出幺蛾子的传输类型。我当年刚接触USB时,总觉得控制传输就是发几个命令而已,直到抓包发现设备不回ACK、状态阶段卡死……才老老实实把每个阶段都吃透。

说白了,控制传输就是“一问一答”的流程。主机发一个请求,设备回应数据,最后确认结果。整个过程分三个环节:设置阶段、数据阶段(可选)、状态阶段。嗯,我们一个一个拆。

设置阶段——一切请求的起点

设置阶段由主机发起,固定8字节。这个包叫Setup包,结构很紧凑:

偏移字段说明
0bmRequestType方向(主机→设备/设备→主机)、类型(标准/类/厂商)、接收方(设备/接口/端点/其他)
1bRequest请求码(如GET_DESCRIPTOR=0x06)
2-3wValue参数,比如描述符类型和索引
4-5wIndex接口号或端点号
6-7wLength数据阶段期望传输的字节数(0表示无数据阶段)

举个实战例子。我在调试一块HID键盘时,主机发送了GET_DESCRIPTOR请求:

bmRequestType = 0x80  (设备→主机,标准,设备)
bRequest     = 0x06  (GET_DESCRIPTOR)
wValue       = 0x0100 (描述符类型=设备描述符,索引=0)
wIndex       = 0x0000
wLength      = 0x0012 (18字节)

为什么抓包看到这个包是SETUP令牌+DATA0+ACK?因为控制传输默认用DATA0做设置阶段的数据包。硬件自动处理,我们只要关注这8字节内容就行。

我个人习惯:每次抓控制传输先把Setup包解析出来,确认bmRequestType的最高位(方向位)——0是主机发,1是设备发。方向搞反了,后面数据阶段分析全错。

数据阶段——该来的数据总会来

数据阶段不是必选的。如果wLength=0,就没有数据阶段,直接进状态阶段。有数据时,方向由Setup包的bit7决定。

  • 主机到设备(OUT):主机发送DATA1/DATA0交替,设备回复ACK。
  • 设备到主机(IN):主机发IN令牌,设备回复DATA包,主机回ACK。

这里有个坑:标准设备请求中,数据阶段长度由wLength指定,但设备实际返回的数据可能更少。比如请求100字节,设备只返回20字节,然后状态阶段提前握手。我曾经遇到一个U盘,厂商描述符声称有40字节,实际只发16字节就把状态阶段走了,导致我的分析仪一直等剩余数据——超时了才反应过来。

USB协议规定:设备可以在数据阶段提前返回短包(小于端点最大包长),或者返回空包,来结束数据阶段。状态阶段必须由主机发起控制传输的最后一笔事务。

状态阶段——收尾定生死

状态阶段的方向和前面数据阶段相反。如果数据阶段是IN(设备发数据),状态阶段就是OUT(主机发0长度包,设备回ACK);反之亦然。

说白了,状态阶段就是主机说“活干完了,你觉得行吗?”设备用ACK说“行”,或者用STALL说“不行”。

打个比方:

  • 主机发SET_CONFIGURATION(1),配置成功了,状态阶段设备回ACK。
  • 主机发一个不存在的请求,设备直接STALL,状态阶段都不正常走完。
注意:状态阶段的数据包必须是DATA1(除非该控制传输只有设置阶段,但实际很少见)。我发现很多初学者抓包时看到状态阶段是DATA0,就觉得异常——其实标准规定状态阶段用DATA1,但如果设备搞错,主机可能认为传输失败。我曾经在自研USB设备上踩过这个雷,FIFO没切换,状态包用了DATA0,主机一直不回复ACK。查了三天,才发现是状态包数据PID设置错了。

标准设备请求一览

USB 2.0定义了11个标准设备请求。我列几个常见的:

请求名bRequest用途
GET_DESCRIPTOR0x06读取描述符(设备、配置、字符串等)
SET_ADDRESS0x05给设备分配地址
SET_CONFIGURATION0x09选择一个配置
GET_CONFIGURATION0x08获取当前配置值
GET_INTERFACE0x0A获取接口的备用设置
SET_INTERFACE0x0B切换接口的备用设置
CLEAR_FEATURE0x01清除端点暂停等特性
SET_FEATURE0x03设置特性

你想想看,枚举过程就是靠这些请求一个个握手完成的。主机先发GET_DESCRIPTOR读设备描述符,再发SET_ADDRESS分配地址,然后发GET_DESCRIPTOR读配置描述符,最后发SET_CONFIGURATION激活设备。

抓包实战:如何快速定位控制传输问题

我在项目中常用这条排查思路:

  1. 看设置阶段:bmRequestType方向对不对?wLength合理吗?
  2. 看数据阶段:有没有短包结束?数据内容是否符合描述符结构?
  3. 看状态阶段:是ACK还是STALL?STALL通常意味着设备不认识这个请求。
  4. 如果状态阶段卡住:检查DATA PID,或者设备没有及时完成内部处理。

举个实际案例。某次调试一个复合设备,枚举到一半断开了。抓包发现:主机发了GET_DESCRIPTOR请求配置描述符,wLength=0x0200,设备正常返回了前9字节,之后又返回了剩余数据。但状态阶段主机发了OUT包,设备居然回了STALL。原来设备在处理配置描述符时,内部解析出错,没来得及完成状态控制。解决方法是让设备在返回数据后,必须准备好接收状态阶段,不能因为内部忙就STALL。

核心总结:控制传输就是“发请求→传数据→确认结果”的三段式。每一段的PID、方向、数据长度都不能错。多抓几次枚举包的顺序,你能把USB协议骨架记在脑子里。
控制传输三个阶段与标准请求 设置阶段 8字节 Setup包 固定 DATA0 设备必须 ACK 核心字段 bmRequestType bRequest wValue wIndex wLength 数据阶段 可选(wLength>0) DATA1/DATA0交替 短包结束标志 方向由 bmRequestType.bit7 0: OUT (主机→设备) 1: IN (设备→主机) 状态阶段 0长度数据包 PID = DATA1 方向与数据阶段相反 设备应答 ACK: 成功 STALL: 不支持 或设备忙 标准请求:GET_DESCRIPTOR | SET_ADDRESS | SET_CONFIGURATION | GET_CONFIGURATION | …

这张图把控制传输的三阶段和标准请求串在一起了。看明白它,你就能看懂大部分枚举抓包了。