4. Configuration 状态详解

Configuration 状态,说白了就是链路双方在完成基本的位锁定和符号锁定之后,开始坐下来正经谈事儿了。谈什么呢?谈链路宽度、谈极性是否要翻转、谈数据速率怎么降速——全是关于链路该怎么配的事。

我刚开始接触 PCIe 时,总觉得 Configuration 就是个简单的握手状态。后来在项目里踩过一次坑才发现...嗯,这里面的道道可不少。

4.1 进入前的准备

进入 Configuration 之前,链路必须先走完 Polling 状态。Polling 做的事主要是练基本功——找到时钟、锁定位边界、找到符号边界。我记得有个项目的 FPGA 原型上,Polling 怎么都过不去,后来发现是参考时钟的抖动太大,导致位锁定总是超时。换了个低抖动时钟,问题就解决了。

Polling 状态的关键输出:

  • 位锁定完成 —— 每个 Lane 都能稳定识别 0/1 电平
  • 符号锁定完成 —— 能正确切出 10-bit 编码的符号边界
  • 电气空闲退出 —— 链路不再处于电气空闲状态

你想想看,如果连符号边界都找不准,那上层谈链路宽度和极性就是空中楼阁。所以 Configuration 状态对下面的物理层是有硬性要求的。

注意:不同代次的 PCIe(Gen1/2/3/4/5)在进入 Configuration 时的符号锁定流程略有差异。Gen3 及以上有独立于 8b/10b 的加扰机制,STS(串行 TS)的识别逻辑更复杂。千万不要用 Gen1 的锁定流程去硬套 Gen4 的链路。

4.2 核心子状态机

Configuration 状态内部其实还有一个原子状态机。我经常跟团队说,别把 Configuration 看成黑盒,它分三个关键子阶段:

  1. Configuration.Linkwidth —— 确定链路的实际宽度
  2. Configuration.Lanenum —— 确定每个 Lane 的逻辑编号
  3. Configuration.Complete —— 确认链路配置已完成

每个子阶段之间通过发送和接收特定的 TS1/TS2 有序集来推进。说白了就是——两边互相发训练集,看对方回的什么,然后决定下一步。

4.2.1 Configuration.Linkwidth

这个阶段主要做两件事:

  • 位宽协商 —— 两端都报自己支持多少条 Lane,取最小值
  • Lane 极性检测 —— 检测每个 Lane 的电气极性是否一致

举个例子,如果你一端是 x8 卡,另一端是 x16 插槽,那么链路只能跑 x8。这时候卡得把多余的 8 条 Lane 给关掉,不能让它一直悬空。我的经验是,Lane 关掉之后一定要发电气空闲序列,不然对面可能会误认为链路断了。

核心观点:Configuration.Linkwidth 是整条链路从宽到窄的过程。优先用最大宽度,失败则逐步降级。x16 降 x8,x8 降 x4,x4 降 x2,x2 降 x1。每一步都要花时间重训,所以初期链路宽度如果配错了,后面的性能直接打折扣。

4.2.2 Configuration.Lanenum

我记得第一次看 PCIe 协议的时候,不太理解为什么宽度确定后还要再定一次编号。后来做了一次倒装焊的 PCB 设计就明白了——因为布线的时候会有 Lane 交叉。比如 Lane0 连到了对端的 Lane3,如果硬件不通知软件,那就全乱了。

Configuration.Lanenum 做的就是:

  • 发送带 Lane 编号的 TS1 有序集
  • 接收端根据收到的编号重新映射内部逻辑
  • 保证链路两端的 Lane 编号能对齐到底层的物理连接

这里有个易错点——Lane 编号的映射必须在 Configuration.Linkwidth 完成后才能做,因为宽度没定下来时,有些 Lane 可能被关掉了,编号映射就没意义了。

经验之谈:Lane 编号映射是很多 PCB 工程师容易忽视的地方。我曾经遇到一个项目,卡在 LTSSM 的 Configuration 状态死活出不去,查到最后发现是 PCB 走线时把 Lane0 和 Lane3 交换了,但卡端的 LTSSM 没有做反标。把映射关系配正确后,链路直接就到了 L0。

4.2.3 Configuration.Complete

这是 Configuration 的最后一个子状态。两边互发 TS2 有序集,确认一切就绪后,一起跳到 L0。我经常对团队说:

「Configuration.Complete 做的是双重确认——确认宽度对了、编号对了、速率定了,然后发 TS2 上报对方也同意,最后同时切到 L0。」

如果任意一边在收到 TS2 后还检查到参数不匹配,就会退回 Configuration.Linkwidth 重新开始。这种重训在正常的链路初始化过程中很少见,但在热插拔或者 Surprise Down 恢复时经常遇到。

4.3 关键信号和时序

接下来我整理了一个表格,把 Polling 和 Configuration 的几个关键信号做个对比。熟悉这个对比,你在调试链路时就能快速定位问题出在哪个阶段。

信号/参数 Polling 状态 Configuration 状态
Transmit Speed 只能发 2.5 GT/s 可以发 2.5 GT/s 或更高(协商后)
Bit Lock 必须完成 继续保持
Symbol Lock 必须完成 继续保持
Lane 极性 不关心 必须检测并处理
发送的有序集 TS1(无编号) TS1(有编号)、TS2
Ack 机制 无 必须有(通过 TS2 确认)

你看看这个表,有没有发现一个规律?——Polling 是「单方向搞定物理层」,Configuration 是「双向握手确定逻辑层」。两个阶段的关注点完全不同。

Configuration 状态机流程 Polling.Complete Configuration.Linkwidth Configuration.Lanenum Configuration.Complete L0

4.4 常见的踩坑点

凭我这些年的调试经验,Configuration 状态最容易出问题的地方有三个。我分别说一下,你以后遇到了可以少走弯路。

4.4.1 位宽协商不一致

最常见的是两端都支持 x8,但协商出来却变成了 x4。这种问题多半是某个 Lane 在 Polling 阶段没锁定成功,LTSSM 自动把它视为坏 Lane,然后宽度就降了。我曾经在实验室里拿抓包日志一个个对 TS1 的训练集——发现 Lane 3 的电气空闲序列一直在跳,最终确定是 PCB 走线没做等长导致信号质量差。

4.4.2 Lane 编号映射错误

硬件上如果做了 Lane 交叉,但固件或者驱动里没做映射,Configuration.Lanenum 阶段就会卡死。我记得有个项目,板子上 Lane0 连到了 Slot 的 Lane2,Lane2 连到了 Slot 的 Lane0——这种设计是为了布线方便,但如果你不在 LTSSM 里做反标,链路就永远卡在 Configuration 出不去。

4.4.3 速率协商和降速触发过早

进入 Configuration 后,如果链路条件不好,双方会尝试降速。但有时候降速触发得太早或者太晚,都会导致链路不稳定。我在一个产品里遇到的问题是:卡在 Gen3 的 Configuration 里不断重训,原因是降速到 Gen2 的时机不对。

警告:不要试图在 Configuration.Complete 未完成之前做任何数据层或事务层操作。此时链路尚未就绪,发出去的数据不会被对端确认。如果你需要在链路刚刚建立时就发送热复位或者电源管理消息,等链路进入 L0 后再做。

4.5 总结一下

Configuration 状态是整个 LTSSM 里最容易出现「反复重训」的阶段。宽度协商、编号映射、速率协商,这三个环节只要有一个环节没对上,链路就会反复回到 Configuration.Linkwidth 再试一次。

我个人习惯的做法是:在调试初期先把 LTSSM 的 trace log 打全,把每一个进入 Configuration 子状态的时机点和退出条件全记下来。这样一旦链路卡住,你就能知道是 Linkwidth 卡了还是 Lanenum 卡了——定位范围一下子就缩小了。

说实话,Configuration 状态弄懂了,后续的 L0 和 Recovery 状态理解起来就没什么难度了。它就是整个链路训练中最关键的枢纽环节。