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 状态转换图:从死到活的路径

下面这张图是我自己从规范里提炼出来的核心路径,不是把所有状态都画进去,而是聚焦在典型枚举路径上。你在实际调试时,端口状态几乎都在这一条链路上反复跳动。

USB 3.0 主机端口状态机核心路径 USB 3.0 主机端口状态机 — 典型枚举路径 Rx.Detect Polling U0 U1 / U2 / U3 Recovery Disabled / Inactive 核心路径方向:Rx.Detect → Polling → U0;U0可转入睡眠(U1/U2/U3)或Recovery 检测到设备 训练成功 空闲超时 / 软件请求 链路错误 / 信号恶化 唤醒事件 / 软件恢复 恢复成功 硬件故障 / 超时 / 软件禁用

从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那种“掉线就重新插拔”的处理方式要优雅得多。

避坑指南: 我曾经有一款产品在低温环境下频繁进Recovery状态。排查下来是PCB上SS信号线对外部噪声太敏感,导致U0下的轻微抖动被误判为链路异常。解决方案其实很简单——把Equalization阶段的De-emphasis系数调硬一点,增加信号抗干扰能力。但要注意,这个修改会引入更多的功率损耗,不是随便能改的。

3.5 状态机与枚举的关联

枚举流程的控制权在主机端口的U0状态时才真正激活。在U0下,主机控制器才会开始发送Setup包、读取设备描述符、分配地址。所以你可以把端口状态机理解为一个“前处理流程”——它确保物理层和链路层准备好了,设备状态机才能登场。

我曾经在调试的时候用过这么一个判断标准:如果端口状态不是U0,就别去分析设备描述符的返回值。先把链路训练搞定,否则一切都是白费功夫。

另外,有一个非常实用的技巧:调试时在驱动层保留一份端口状态寄存器映射表。每次枚举失败,先读寄存器的Port Status and Control(PORTSC)域。它能告诉你最后的有效状态和上一次转换的原因。很多时候,你不需要看波形,只看这个寄存器的值就能定位问题。