Configuration 状态详解:协商与编号
各位工程师,今天我们深入聊聊 LTSSM 的 Configuration 状态。这个阶段,说白了就是链路双方互相“认识”的过程——确定跑多宽、跑多快、以及给端口分配编号。我做了这么多年 PCIe 调试,可以很负责任地说:很多疑难问题就卡在这看似简单的“三连问”上。
4.1 链路宽度协商
链路宽度协商,就是在硬件支持的带宽里找到一个大家都接受的“宽度”。
在 Polling 阶段结束后,链路就已经有了一个基本的活动宽度——Common Clock 也锁定了。但到底是用 ×16 跑,还是降到 ×4 跑,这得在 Configuration 里定。为什么会降宽?最常见的原因就是信号完整性达不到要求,或者物理层的链路聚合出了问题。
我个人判断链路宽度协商是否异常,会先抓两个端口的 TS1 和 TS2 序列。这两套序列里嵌入了宽度协商的重要信息。
关键点:链路宽度协商是在 Configuration.LanenumWait 子状态之前完成的。
宽度协商的过程可以抽象为两轮“投票”:
- 第一轮(TS1):两个端口都在链路上发送 TS1 序列。这些序列里携带了它们各自的 链路宽度能力。比如,A 端说我能支持 ×16,B 端说我这头只能支持 ×4。
- 第二轮(TS2):经过几次交换,双方决定出一个公共宽度。硬件会自动取
min(对方能力, 自身能力)的最小值。然后规则是:所有不在公共宽度范围内的通道,会进入电气空闲(Electrical Idle)。
你想想看,如果 A 端是 ×8 的根端口,B 端是 ×16 的端设备,那协商结果只能是 ×8。我在项目中遇到过,B 端设备明明物理上只有 ×4,但软件配置时错误声明了 ×16 能力。结果呢?链路成功训练到 ×16,但实际只有 ×4 的线缆,信号直接乱套。这种坑,就是靠观察 TS1 序列里的宽度能力域(Lane Number)抓出来的。
避坑指南:我曾经调试过一个平台,发现链路宽度一直在 ×1 和 ×16 之间反复跳。翻看逻辑分析仪,发现是某个通道的接收器判决门限有问题,导致该通道一直误报电气空闲。这提醒我们,宽度协商不成功,不一定是能力不匹配问题,物理层信号质量才是最大变量。
4.2 数据速率协商
现在的 PCIe 设备基本都支持多种速率:Gen1 (2.5 GT/s)、Gen2 (5 GT/s)、Gen3 (8 GT/s) 甚至更高。速率协商的逻辑比宽度复杂,因为它需要改变 PHY 的电气序列。
在 Configuration 阶段,速率协商是通过 Speed Change 流程实现的。具体的步骤很清晰:
- 发起速率变更请求:下位端口(Downstream Port)在 TS2 序列中设置
Speed Change位。 - 进入 Recovery 状态:如果需要变更速率,链路必须先从 Configuration 转到 Recovery 状态。在 Recovery 里完成速率切换和电气序列适配。
- 重新训练:速率的切换实际上会在 Recovery 状态通过
Equalization来完成。这一套标准流程被称为 Directed Speed Change。
嗯,这里要注意:速率协商并不一定非要在 Configuration 第一次进入时就完成。 很多产品为了兼容性,会先以 Gen1 的速率完成链路训练,等进入 L0 后再通过软件或者硬件触发速率升级。但标准做法是在 Configuration 里完成。
我个人更关心的反而是速率协商失败后的异常定位。你想想,如果两端都声称支持 Gen3,但协商结果却退回到 Gen2,这种情况怎么查?我记得有一个项目,链路死活上不了 Gen3,抓出来的 TS1 序列看起来一切正常。最后发现是某个通道的扩频时钟(SSC)没有对齐。Gen3 的速率对时钟抖动极度敏感,SSC 偏差大了,均衡器根本没法匹配。
重点:速率协商失败时,设备通常会自动回退到 Gen1 重新训练,或者进入 Polling 的 Polling.Active 状态等待。如果在 L0 状态或 Recovery 状态发生速率异常,链路的整体行为会非常诡异。
4.3 链路编号分配
编号分配这件事,本质上是为了让交换器(Switch)或根端口(Root Port)能建立出内部的层级映射。每个端口在被训练后,都需要一个唯一的 Lane Number 和 Port Number。
在两端的 Configuration 状态里,端口会相互交换 TS2 序列,其中包含了 Port Number 字段。硬件会根据这个值来创建内部的转发表。这个过程并不直接影响链路能否进入 L0,但会影响完整的总线枚举和地址分配。
我曾经遇到过一个非常典型的案例:交换器的下行端口给端设备分配的 Port Number 一直与物理位置对不上。导致 BIOS 在枚举总线时,某些设备无法被正确识别。表面上看,链路训练都是通过的,Link 也 Up 了,但设备就是不能正常工作。
排查这类问题,我习惯先确认:
- 是否为物理端口分配了重复的 Port Number?
- 交换器内部的交叉矩阵是否按照 Port Number 正确映射了数据流?
- 在测试软件里检查端口的
Device/Port Type和Lane Number是否正确。
说白了,编号分配是一个逻辑层面的动作,一旦出错,上层的软件事务(如配置读写)可能会发到错误的设备上。这比物理连接问题更难定位。
4.3 Configuration.LanenumWait 与 Complete 归纳总结
接下来这张图,我建议你好好收藏。它清晰地说明了 Configuration 状态下的几个核心握手流程。
这张图里,Configuration.LanenumWait 和 Configuration.Complete 是两个非常容易混淆的子状态。
4.3.1 Configuration.LanenumWait
这个子状态是干什么的?说白了,就是让链路双方等待所有通道都做好准备,能接收 TS2 序列。在硬件上,它相当于一个同步点。所有通道必须都在这个状态里收到了足够的 TS2,并且完成了宽度协商和编号交换,才能继续前进。
- 异常现象:如果某个通道的物理层一直收不到有效的 TS2,或者 TS2 中的 Lane Number 和自己期待的不一致,链路就会卡在 LanenumWait。
- 调试方法:我建议你直接去检查那个卡住的通道上,是否有 Electrical Idle 持续超时,或者接收到的 TS2 里
Lane Number字段是否有毛刺。
注意:卡在 Configuration.LanenumWait 通常会被误判为物理链路断裂,但实际上可能是因为对端的 TS2 做错了格式,比如 CRC 校验失败。
4.3.2 Configuration.Complete
一旦链路通过了宽度协商和编号分配,且所有通道都确认了 TS2 的收发,链路就会进入 Configuration.Complete。这个状态就是宣告:“哥们儿,我们已经达成一致了。”
在 Complete 阶段,链路已经准备好了所有用于 L0 状态运行的参数。每一对 TX/RX 都知道了自己要跑多宽的链路、哪条通道是自己的逻辑 Lane、以及 Port Number 是什么。完成这个状态后,链路就会直接跳转到 L0 状态。
唯一的例外是:如果发现需要变更速率,链路会先进入 Recovery 再完成最终的速率对齐,然后才进入 L0。这也是为什么我在流程图中,把数据速率协商画成了一个并行的路径。
小提示:我有个习惯,在逻辑分析仪上,通过观察 TS1/TS2 的交换次数来判断 Configuration 阶段是否异常。正常链路交换 8-10 个 TS2 后就应该进入 Complete。如果超过 16 次还在循环,那基本可以断定有问题了。
好了,这就是 Configuration 状态的核心脉络。链路宽度协商、数据速率协商、编号分配、以及 LanenumWait 和 Complete 的准确配合,构成了一个有序的同步握手体系。我个人感觉,只要理解了“先宽后速再编号”这个原则,以及 LanenumWait 作为一个多通道同步点的角色,调试起来会清晰很多。