3. 主机端口状态机(Port State Machine)入门:端口状态定义、状态转换图
端口状态机,说白了这个东西是所有USB 3.0枚举操作的总指挥。我刚开始接触这套协议时,觉得状态图密密麻麻的,挺吓人。后来发现,它其实就是一个严格的流程图——端口在某一个时刻只能处于一种状态,只有满足特定条件,才会跳去下一个状态。
为什么这里要单独讲主机端口状态机?因为设备层状态机与它是上下级关系。端口状态机管的是物理连接与链路训练,设备状态机管的是逻辑配置。你得先让端口“握手”成功,设备才能进行地址分配和配置。我个人习惯是先看端口状态机,因为它决定了枚举能否推进到下一步。
3.1 端口状态由谁定义?
USB 3.0规范在第10章里明确给出了Host Port State Machine的定义。规范用了大篇幅的文字和转移表去描述这13种状态,说实话读起来很累。我当初自学的时觉得太枯燥,画了张图反复看才弄明白。
这是规范的官方状态列表,我整理成了表格,方便你对照:
| 状态名称 | 缩写 | 说人话版本 |
|---|---|---|
| Disabled | Dis | 端口被物理关闭,啥也不干 |
| Inactive | Ina | 链路出问题了,没法通信 |
| Polling | Pol | 主机在发训练序列,找对端设备 |
| Compliance | Cmp | 进入测试模式,合规性测试用 |
| U0 | U0 | 正常工作,全速跑数据 |
| U1 | U1 | 浅睡眠,入口延迟很小 |
| U2 | U2 | 深度睡眠,省电但唤醒慢 |
| U3 | U3 | 挂起,设备几乎停止活动 |
| Rx.Detect | RxDet | 检测接收端有无设备连接 |
| SS.Disable | SSDis | 超速通道被硬件禁用 |
| Recovery | Rec | 从错误或休眠中恢复训练 |
| Hot Reset | HRst | 热复位,重置链路但不掉电 |
| Loopback | LBk | 回环测试,用于摸底 |
这里面最关键的三个状态是:Rx.Detect(上电后第一件事)、Polling(正式链路训练)、U0(终于能干活了)。枚举的大部分过程其实是在Polling阶段完成的。嗯,这里要注意——设备如果死活进不了U0,大概率是Polling阶段的训练参数没协商成功,比如Equalization没通过。
3.2 状态转换图:从死到活的路径
下面这张图是我自己从规范里提炼出来的核心路径,不是把所有状态都画进去,而是聚焦在典型枚举路径上。你在实际调试时,端口状态几乎都在这一条链路上反复跳动。
从Rx.Detect开始,主机硬件先看看物理端口上有没有差分管脚有信号。如果有设备插入,进入Polling。Polling是工作量最大的阶段——链路训练、Equalization、TS1/TS2握手都在这里完成。
Polling通过后,端口就进入U0正常操作状态。这才是真正的枚举开始点。设备控制器终于可以在U0下发送和接收包了。
3.3 一些我踩过的坑
坑一:端口卡在Rx.Detect不动
有一次我调试一块新的主机板,设备插上去后,逻辑分析仪上始终看不到任何链路训练包。寄存器读出来端口状态一直是Rx.Detect。我当时排查了好久,最后发现是差分管上的AC耦合电容焊反了方向。嗯,这种低级错误最容易在新板子上出现。
坑二:Polling阶段Equalization失败导致反复回退
我遇到过一个比较隐蔽的问题:设备进不了U0,在Polling和Recovery之间来回跳。看波形,TS1信号都发出来了,就是得不到对端的确认应答。后来查协议发现是Equalization阶段没有完成全部系数交换。说白了,主机发的系数值超出了设备的接收能力。
遇到这种情况,我建议你用示波器或者逻辑分析仪抓一下LFPS训练序列的时序,看握手跳了几轮、在哪一轮断掉。
坑三:U3睡眠后唤醒失败
U3其实是一个低功耗状态,端口看起来像挂了,但其实还在等唤醒信号。我碰到过软件下发恢复命令后,设备一直没响应。问题出在哪?设备端U3的入口条件没满足,导致它其实不在U3,而主机以为它在U3——状态不同步了。这种问题我最头疼,因为它不会立刻暴露,往往是在误码率积累后才会触发。
3.4 状态转换的有趣细节
为什么要区分U1、U2、U3?其实入口延迟和功耗是反比关系。U1延迟最小,约几微秒,适合频繁进出省电。U2延迟几十到几百微秒,适合空闲稍长的情况。U3则是“请勿打扰”模式,唤醒要走软件流程和LFPS handshake,一般要毫秒级。
另外,Recovery这个状态的存在,让枚举过程有了容错机制。Polling失败可以通过Recovery重试,而不需要物理重新连接。这比USB 2.0那种“掉线就重新插拔”的处理方式要优雅得多。
3.5 状态机与枚举的关联
枚举流程的控制权在主机端口的U0状态时才真正激活。在U0下,主机控制器才会开始发送Setup包、读取设备描述符、分配地址。所以你可以把端口状态机理解为一个“前处理流程”——它确保物理层和链路层准备好了,设备状态机才能登场。
我曾经在调试的时候用过这么一个判断标准:如果端口状态不是U0,就别去分析设备描述符的返回值。先把链路训练搞定,否则一切都是白费功夫。
另外,有一个非常实用的技巧:调试时在驱动层保留一份端口状态寄存器映射表。每次枚举失败,先读寄存器的Port Status and Control(PORTSC)域。它能告诉你最后的有效状态和上一次转换的原因。很多时候,你不需要看波形,只看这个寄存器的值就能定位问题。