4. Configuration状态详解:链路宽度协商

Configuration状态,说白了就是链路双方在握手之后,开始认真谈「咱们用几条 lane 干活」的阶段。我个人觉得,这是 LTSSM 里最容易出幺蛾子的地方之一。很多板卡插上去不识别,十有八九是这里卡住了。

这个阶段包含五个子状态:Configuration.Linkwidth.StartConfiguration.Linkwidth.AcceptConfiguration.Lanenum.WaitConfiguration.CompleteConfiguration.Idle。咱们一个一个来过。

4.1 Configuration.Linkwidth.Start

进入这个状态,意味着链路已经完成了基本的 bit lock 和 symbol lock。现在要开始「喊话」了。

具体做法是:发送端在每条 lane 上持续发送 TS1 序列,并且把 TS1 里的 Link NumberLane Number 字段填上自己的信息。嗯,这里要注意:TS1 里的 Lane Number 不是物理 lane 号,而是逻辑 lane 号。我刚开始做的时候就被这个坑过——以为填物理编号就行,结果对面根本不认。

举个例子,一个 x4 的设备,它的 lane 0~3 在 TS1 里填的 Lane Number 分别是 0、1、2、3。但如果是 lane 反转的情况,物理 lane 3 可能对应逻辑 lane 0。这个映射关系,由硬件 lane 极性翻转逻辑来处理,固件只需要保证 TS1 内容正确即可。

关键点:在 Linkwidth.Start 状态下,所有 lane 都在发送 TS1,并且等待接收端回传 TS1 或 TS2。如果收到 TS2,说明对方已经进入更后面的状态了。

4.2 Configuration.Linkwidth.Accept

这个状态是「确认对方喊的话」。当设备在 Start 状态下收到有效的 TS1/TS2,并且解析出对方的 Link Number 和 Lane Number 后,就会进入 Accept 状态。

在 Accept 状态下,设备继续发送 TS1,但此时 TS1 里的内容会做一些调整。我记得有一次调试,发现链路宽度协商总是失败,抓了 LTSSM 状态日志一看,发现 Accept 状态下 TS1 的 Lane Number 填错了——因为固件里用了全局变量,被中断服务程序改了值。这种 bug 特别隐蔽,你想想看,时序问题最难查。

这里有个避坑指南:Accept 状态通常只持续几个微秒。如果长时间卡在这里,大概率是 TS1 内容不匹配,或者物理层有信号完整性问题。我曾经遇到过一块板子,在 Accept 状态反复进出,最后发现是某个 lane 的 RX 端电容虚焊,导致 TS1 偶尔丢 symbol。

4.3 Configuration.Lanenum.Wait

这个状态的名字很直白——「等 lane 号分配」。说白了,就是双方在确认到底用哪几条 lane。

在 Lanenum.Wait 状态下,设备会持续发送 TS2 序列。TS2 和 TS1 的区别在于,TS2 里的 Lane Number 字段不再表示逻辑 lane 号,而是表示「我期望的 lane 号」。接收端收到 TS2 后,会和自己期望的 lane 号做比较。

举个例子:

发送端 Lane TS2 中填的 Lane Number 接收端期望值 结果
Lane 0 0 0 匹配
Lane 1 1 1 匹配
Lane 2 2 2 匹配
Lane 3 3 3 匹配

如果所有 lane 都匹配,就进入 Complete 状态。如果某条 lane 不匹配,那这条 lane 就会被标记为「不参与链路」,后续协商会降宽处理。

个人经验:Lanenum.Wait 状态是检测 lane 映射错误的最佳时机。我习惯在这里加一个超时计数器,如果超过 1ms 还没进入 Complete,就打印所有 lane 的 TS2 内容出来看。很多 lane 极性反转的问题,在这里就能发现。

4.4 Configuration.Complete

Complete 状态,顾名思义,就是链路宽度协商完成了。此时所有参与链路的 lane 都已经确认了 lane 号,并且双方达成一致。

在 Complete 状态下,设备继续发送 TS2,但 TS2 里的内容会变成「空闲模式」的指示。接收端收到后,就知道链路已经准备好了,可以进入 Idle 状态。

这里有个细节:Complete 状态通常只持续 2 个 TS2 序列。为什么是 2 个?因为要确保对方也收到了你的确认。如果只发 1 个,万一对方没收到,就会回退到前面的状态。这是 PCIe 协议里典型的「三次握手」思想的体现。

注意:Complete 状态不是终点。如果此时链路出现错误(比如 CRC 校验失败),LTSSM 会回退到 Detect 或 Polling 状态重新开始。所以固件里一定要处理好状态回退的逻辑,不要假设一次就能协商成功。

4.5 Configuration.Idle

Idle 状态是 Configuration 子状态机的最后一个状态。进入 Idle 后,链路开始发送 IDLE 序列,也就是全 0 的符号流。这表示链路已经稳定,可以准备进入 L0 状态进行正常的数据传输了。

在 Idle 状态下,固件可以做的事情不多,主要是等待硬件自动切换到 L0。但我建议在这里加一个状态监控,因为有时候链路会卡在 Idle 状态出不去。原因通常是:

  • 对端设备还没准备好进入 L0
  • 链路训练序列中有残留错误
  • 时钟恢复电路还没锁定

我记得有一次,客户反馈说设备偶尔无法识别,抓了日志发现每次都在 Idle 状态卡 10ms 以上。最后定位到是参考时钟的抖动偏大,导致 CDR 锁定时间变长。换了个晶振就好了。

4.6 链路宽度协商的核心逻辑

说了这么多,其实链路宽度协商的核心逻辑可以用一张图来概括:

Linkwidth.Start Linkwidth.Accept Lanenum.Wait Complete Idle 收到有效TS1/TS2 Lane号确认 所有lane匹配 发送2个TS2 协商失败,回退到Start 发送TS1 发送TS2 回退路径

这张图里,绿色箭头是正常流程,红色虚线是异常回退。你想想看,如果链路宽度协商失败,LTSSM 会从 Idle 或 Complete 一路回退到 Start,重新开始。这个回退路径在固件里一定要处理好,否则会出现死循环。

我的习惯:在固件里加一个「协商次数计数器」。如果连续 3 次协商失败,就主动触发链路复位,而不是让 LTSSM 无限循环。这样可以避免某些硬件 bug 导致系统挂死。

4.7 固件交互要点

最后,总结一下固件在 Configuration 状态需要关注的点:

  1. TS1/TS2 内容检查:确保 Link Number 和 Lane Number 字段正确,特别是 lane 反转场景
  2. 超时处理:每个子状态都要有超时计数器,建议 2ms 超时
  3. 状态回退处理:如果协商失败,要能正确回退到 Start 状态
  4. 日志记录:记录每次协商的 lane 映射结果,方便调试
  5. 降宽处理:如果 x4 协商失败,要能自动降为 x2 或 x1

嗯,Configuration 状态就讲到这里。链路宽度协商是 PCIe 链路训练里最考验固件功底的地方,多花点时间理解透,后面调试会省很多事。


公众号:蓝海资料掘金营,微信deep3321