第1章:USB协议基础
大家好,我是你们的讲师。做嵌入式这么多年,我有个习惯——不管做什么系统,先把物理层搞明白。USB这东西,看着简单,真要出起问题来,能把人折腾得怀疑人生。这一章,咱们先把地基打牢。
1.1 USB发展史:从1.0到4.0
USB(Universal Serial Bus)从诞生那天起,目的就很明确——统一外设接口。你想想看,早年间键盘用PS/2,鼠标用串口,打印机用并口,那叫一个乱。所以我一直觉得,USB最大的贡献不是速度,是「统一」这两个字。
| 版本 | 发布时间 | 最高速率 | 核心变化 |
|---|---|---|---|
| USB 1.0 / 1.1 | 1996 / 1998 | 1.5 Mbps(低速)/ 12 Mbps(全速) | 定义了基础协议框架,Host-Device模型 |
| USB 2.0 | 2000 | 480 Mbps(高速) | 引入微帧、事务拆分,兼容1.x |
| USB 3.0 / 3.1 / 3.2 | 2008 / 2013 / 2017 | 5 Gbps(3.0)/ 10 Gbps(3.1 Gen2)/ 20 Gbps(3.2×2) | 新增SDP差分对,全双工,独立物理层 |
| USB4 | 2019 | 40 Gbps | 基于Thunderbolt 3协议,强制Type-C接口 |
个人经验:我做项目时,遇到过很多开发人员只关心USB2.0的480Mbps理论值,结果实际吞吐率连40MB/s都跑不到。为什么?协议开销比你想象的大得多。别被数字骗了。
USB 1.0/1.1:定义了两根数据线D+和D-,采用差分传输。低速(1.5 Mbps)和全速(12 Mbps)靠D+/D-的不同上拉电阻来区分。我记得第一次调试USB 1.1设备时,拿示波器看D+线上的波形,那会儿觉得这协议真简单——现在看来,真是天真。
USB 2.0:真正的转折点。480 Mbps的高速速率,引入了微帧(microframe)概念,把原来的1ms帧拆成8个125μs的微帧。为什么会这样?为了满足等时传输(比如音频)的实时性要求。
USB 3.x:从半双工变成了全双工。说白了,就是加了独立的发送和接收通道,能同时读写。物理层也变了,不在用传统的D+/D-,而是用了SDP(SuperSpeed Differential Pair)。嗯,这里要注意:USB 3.0向下兼容2.0,但物理连接完全独立。
USB4:基于Thunderbolt 3架构,强制Type-C接口。我最欣赏的一点是,它引入了隧道协议——把PCIe、DisplayPort和USB数据包直接塞进一个通道里。这给故障注入带来了新挑战,咱们后面章节会细聊。
避坑指南:我曾经有个客户,拿着USB 3.0的线去跑USB 2.0的高速设备,结果死活协商不上。检查半天,发现焊盘上D+/D-的差分阻抗没控制好。别以为线材不重要——差分阻抗控制不好,信号反射能淹了你的眼图。
1.2 物理层与电气特性
做硬件安全,物理层是第一道防线,也是最后一道。很多故障注入的手法,都是先搞乱物理层的信号。所以这一节,必须扎扎实实吃透。
1.2.1 信号定义
USB 2.0及以下,使用一对差分信号(D+ / D-)。电压标准是3.3V(驱动端实际在3.0~3.6V之间)。D+和D-不是随便接的——线缆的差分阻抗要控制在90Ω±15%。
- 空闲状态(Idle):D+和D-同时被下拉到GND(全速/低速的设备通过上拉电阻区分)。
- J状态 / K状态:差分数据流的两种逻辑电平。
- SE0(Single-Ended Zero):D+和D-都拉低,用于复位和帧起始(SOF)信号。
我刚开始学USB时,一直搞不懂怎么判断设备是低速还是高速。其实很简单:看D+还是D-被上拉。全速/高速设备:D+上拉一个1.5kΩ电阻到3.3V。低速设备:D-上拉同样的电阻。这个上拉电阻在上电后会被Host检测到,用来识别设备类型。
| 设备类型 | 上拉电阻位置 | 位速率 | 驱动器类型 |
|---|---|---|---|
| 低速(LS) | D-上拉 | 1.5 Mbps | 电流源 |
| 全速(FS) | D+上拉 | 12 Mbps | 电流源 |
| 高速(HS) | D+上拉(先FS握手再切HS) | 480 Mbps | 电流源 + 终端匹配 |
警告:千万不要用万用表去量D+/D-的电压来判断信号状态。差分信号是高速跳变的,你的万用表看到的是平均电压,毫无意义。必须用示波器——而且采样率至少1GSa/s,带宽不低于200MHz。
1.2.2 终端匹配与眼图
USB 2.0高速模式下,Host和Device内部都有45Ω的终端匹配电阻(连到GND)。为什么是45Ω?因为差分阻抗90Ω,单端对地就是45Ω。匹配好了,反射就小,眼图就干净。
我在项目中遇到过一个人,总是抱怨USB 2.0链路误码率高。我让他量眼图,他说「眼图很好啊」。结果我一看截图——眼宽只有300ps,眼高只有200mV,这能好吗?做硬件安全,眼图是衡量物理层健壮性的金标准。我个人的习惯是:眼高至少留30%的裕量(至少400mV),眼宽至少留20%的裕量(至少500ps)。
// 一段伪代码,用来理解眼图测量逻辑
// 不是真正的USB协议代码,只是个概念演示
uint32_t sample_eye(uint8_t *rx_buffer, int length) {
int eye_width = 0;
int eye_height = 0;
for (int i = 0; i < length; i++) {
// 在眼图中心位置采样
if (rx_buffer[i] & 0x80) {
eye_width++;
}
if (rx_buffer[i] & 0x40) {
eye_height++;
}
}
return (eye_width << 16) | eye_height;
}
1.3 拓扑结构:Host-Device-Hub
USB的拓扑结构,说白了就是一级一级往下挂。一个Host下面可以挂Hub,Hub下面再接设备或Hub。层级最多7层(包括Root Hub在内),设备数量最多127个(包括Hub本身)。
- Host(主机):唯一的主控者。所有传输由Host发起,设备不能主动发数据。你想想看,为什么USB键盘按一下键就能被电脑识别?因为Host每秒轮询设备无数次。
- Device(设备):被动响应。有四种传输类型:控制传输、批量传输、中断传输、等时传输。
- Hub(集线器):中继转发。负责信号整形、电源管理、设备连接/断开检测。
我记得有一次调试USB Hub的兼容性问题,设备在直连Host时正常,一旦经过Hub就断连。查了两天才发现——Hub的上游端口(Upstream Port)在复位时没等够100ms。这就是时序问题。所以说,做USB不仅要懂协议,还要懂硬件时序。
上图标注了故障注入点的位置。你想想看,如果我要模拟一次物理层攻击,我会在F1位置注入信号抖动或共模干扰。如果要做协议层篡改,F2位置最合适——Hub是透明转发,在这里修改数据包很难被Host直接发现。
1.3.1 枚举过程
USB设备上电后,Host通过枚举来识别设备。这个过程是故障注入的黄金窗口——很多攻击都在枚举阶段搞手脚。
- 复位(Reset):Host把D+/D-拉到SE0(至少10ms),设备收到复位信号后进入默认状态。
- 获取设备描述符(Get Device Descriptor):Host发送默认地址0,请求64字节的设备描述符。这里我提醒一句——很多设备在复位后只能返回8字节,必须分两次获取。
- 分配地址(Set Address):Host给设备分配一个唯一地址(1~127)。注意,地址不能重复,否则链路直接瘫痪。
- 获取配置描述符(Get Configuration Descriptor):读取设备的完整描述符集(配置、接口、端点)。
- 配置设备(Set Configuration):Host选择一个配置,设备进入就绪状态。
实用技巧:我自己调试时,拿USB分析仪抓枚举阶段的报文,会重点关注「Set Address」之后Host有没有收到设备的确认包(ACK)。如果缺了ACK,多半是物理层或协议层出了问题。你试着想象一下:设备明明回复了ACK,但Host没收到,链路就不通了。这就是故障注入的目标之一——把正常ACK淹没掉,模拟一个错误。
1.3.2 电源管理
USB的电源管理是个容易被忽视的细节。VBUS标准是5V,但允许的波动范围是4.75V~5.25V。我遇到过很多次,因为VBUS压降太大导致设备重启。
- 低功率模式:单个端口最大供电100mA(USB 2.0规范)。
- 高功率模式:设备枚举后可以申请到500mA(USB 2.0)或900mA(USB 3.0)。
- 挂起(Suspend):3ms无总线活动,设备自动进入挂起状态,电流小于2.5mA。
危险:千万不要在设备枚举期间突然拉低VBUS。这么做不仅会导致设备掉电,还可能损伤Host的USB控制器。我在实验室里亲眼见过一个学生这么做——直接把Host的USB端口烧了。故障注入也要讲究分寸,不是乱来。