第2章 PCIe拓扑与分层结构

记得我刚开始接触 PCIe 时,最头疼的就是搞不清设备之间到底怎么连的。后来一位老前辈跟我说了一句大实话:“别把 PCIe 想得太玄乎,它就是一套快递系统。” 这句话让我豁然开朗。Root Complex 相当于总仓库,Endpoint 是收件人,Switch 就是个。这么一比喻,拓扑结构就清楚了。

不过,光知道拓扑还不够。你拿协议分析仪抓到的报文,到底在哪个层级产生的?为什么有的包丢了不会重传,有的包丢了就得重来?这些问题的答案,都藏在分层模型里。今天我们就把这两件事彻底聊透。

2.1 拓扑:谁跟谁说话

PCIe 系统是典型的树形拓扑。根节点只有一个,叫 Root Complex(RC)。下面是各种 Switch、Bridge,最末端是 Endpoint(EP)。我画了个图,你看一眼就明白了。

PCIe 拓扑结构示意图 Root Complex (RC) CPU + 内存接口 Switch PCIe 到 PCIe 桥 Endpoint (EP1) 显卡 / NVMe SSD Endpoint (EP2) 网卡 Endpoint (EP3) FPGA 采集卡 💡 拓扑要点 • RC 是系统的唯一根节点,负责地址转换和事务转发。 • Switch 用于扩展端口,本质上是一个多端口桥。 • Endpoint 就是实际的功能设备,比如显卡、SSD。 • 注意:PCIe 不允许环路存在,必须是严格的树形拓扑。
切身体会: 我遇到过的一个坑是在某国产芯片平台上,它的 Switch 上行端口带宽只能跑到 4.0 x4,但下行挂了三个 x8 的 NVMe SSD。跑 IO 读的时候,端口直接打满,延迟飙升。你说巧不巧?协议分析仪一看,全是 Completion Timeout。所以拓扑结构一定要跟你选型的带宽匹配,别光看“能识别”,要看“跑不跑得动”。

2.2 Root Complex:系统的大管家

Root Complex 是 PCIe 体系里的“交通指挥中心”。CPU 发出的内存访问指令,必须经过 RC 转成 PCIe 事务层包(TLP),才能发给外设。反过来,外设发来的 DMA 数据,也要靠 RC 转换成内存写指令。

一般 x86 平台的 RC 都集成在 CPU 里,比如 Intel 的 PCH 或 AMD 的芯片组。ARM 平台的 RC 则在 SoC 内部。我习惯先把 RC 的配置空间读一遍,尤其是 Bus Number 和 Device Number 的分配方式。因为这直接决定了枚举顺序。

曾经踩过的坑: 有一次项目里,系统枚举出来一个 Endpoint 的 Vendor ID 全为 0xFF。我一查,发现是 RC 的 Secondary Bus Number 配错了,导致下游设备根本没被正确发现。所以分析仪抓包时要是发现链路层都 Link Up 了,但 TLP 层死活没动静,第一件事就是检查 RC 的配置空间。

2.3 Switch:流量转发的中枢

Switch 看起来像个简单的数据转发器,但实际上它承担了 路由 和 仲裁 两件事。每个端口都可以独立配置 Bus Number 范围。当收到的 TLP 的目标 Bus Number 落在某个下游端口的管理范围内时,Switch 就会转发过去。

举个例子:

  • 上游端口:Bus 0
  • 下游端口 1:Bus 1 – 4
  • 下游端口 2:Bus 5 – 8

如果 RC 发了一个目标 Bus=3 的 TLP,Switch 一看,这是端口 1 的辖区,转过去。就这么简单。

调试要点: 协议分析仪抓 Switch 流量时,建议在它的上游和下游同时挂探头。这样能看到 TLP 从进到出的完整路径。我曾遇到过 Switch 内部 Credit 死锁导致丢包的情况,要不是同时在上下游抓了包,光看一端根本没法定位。

2.4 Endpoint:功能的承载者

Endpoint 是实际干活儿的。它可能是个 NVMe SSD,网卡,或者 FPGA 加速卡。作为硬件验证的人,我最关心的是 Endpoint 的 Bar 空间 映射和 中断 行为。

每个 Endpoint 都必须实现至少 1 个 Bar(Base Address Register),用于告诉 RC:“你要找我的数据,往这个地址写就行”。调试时,我习惯先把每个 Endpoint 的 Bar 地址读出来,确认地址范围和类型(Memory 空间还是 IO 空间)。

另外,MSI/MSI-X 中断的配置也是 Endpoint 的重灾区。我见过很多 FPGA 设计的 Endpoint,Bar 空间配对了,但 MSI 的 Message Address 没写对,导致中断永远发不出去。

我个人的习惯: 抓包前,先在分析仪里过滤出 “Configuration Read” 和 “Configuration Write” 类型的 TLP。这些报文可以把 Endpoint 的初始化过程还原出来。看看它到底配了哪些地址、哪些中断向量,一目了然。

2.5 分层模型:从 TLP 到物理层的三驾马车

PCIe 的分层模型分三层:事务层 (Transaction Layer)、数据链路层 (Data Link Layer)、物理层 (Physical Layer)。每一层都干自己那摊事,说白了就是各司其职。

层级 核心职责 生成/处理的包 调试时最常看到的错误
事务层 构造/解析 TLP,处理事务 MemRd, MemWr, CplD, CfgRd Unsupported Request (UR), Completer Abort (CA)
数据链路层 链路管理,ACK/NAK 重传 DLLP (ACK, NAK, InitFC1/2) CRC 错误,重传超时,链路状态抖动
物理层 串并转换,信号编码 Ordered Sets (TS1/TS2, SKP, EIOS) Lane 失锁,极性反转,De-emphasis 不匹配

简单来说,事务层 决定“发什么内容”,数据链路层 保证“内容不能丢”,物理层 负责“怎么把 0/1 送出去”。我调试时看协议分析仪,看 TLP 是查功能问题,看 DLLP 是查可靠性问题,看物理层 Ordered Sets 是查信号完整性。

2.6 事务层:TLP 的结构与类型

TLP 是 PCIe 通信的真正单位。它的核心结构是:

+--------+--------+------------+--------------+------------+
| 前缀   | 头标   | Data Payload  | ECRC          |            |
| (可选) | (12-16B)| (0-4096B)   | (可选 4B)     |            |
+--------+--------+------------+--------------+------------+

最常见的几种 TLP 类型:

  • Memory Read (MemRd):从外设地址空间读取数据。没有 Data Payload。
  • Memory Write (MemWr):向外设地址空间写入数据。带 Data Payload。
  • Completion with Data (CplD):对于 Read 请求,必须返回 CplD。写请求一般不需要。
  • Configuration Read/Write (CfgRd/CfgWr):用于枚举和配置 RC 及 Switch。
专业经验: MemRd 和 MemWr 是性能分析的核心。我在压力测试时,会重点统计这两类 TLP 的 载荷长度 (Payload Size)。很多系统把最大 Payload 设成 128B,但实际调优后改到 512B 甚至 1024B,带宽能提升 30% 以上。怎么改?在 RC 的 Device Control Register 里配 Max_Payload_Size。

2.7 数据链路层:ACK/NAK 与重传

数据链路层的核心是 ACK/NAK 机制。事务层丢一个 TLP 下来,数据链路层会给它加一个 Sequence Number (SeqNum) 和 LCRC。接收端收到后,检查 LCRC 对不对。对的,回 ACK。错的,回 NAK,要求重传。

你可能会想:“那重传会不会影响性能?” 影响不大。因为发送端会维护一个 重传缓冲区 (Replay Buffer),最多放几十个待确认的 TLP。收到 ACK 就释放,收到 NAK 就立即重放缓冲区里的所有 TLP(通过 Replay Timer 触发)。

实战提醒: 我见过一次很隐蔽的调试案例:一个 FPGA 作为 Endpoint,它的 Replay 缓冲区设置太小,导致 Agent 回 ACK 稍微慢一点,RC 那边就超时重传了。结果链路利用率从正常 80% 掉到 20% 以下。分析仪上一看,全是 DLLP 层的 NAK。所以数据链路层的问题往往以性能“掉坑”的形式出现,你得会看 DLLP 的分布比例。

2.8 物理层:训练与链路初始化

物理层是链路能跑起来的根基。上电后,物理层先经历一个 Link Training and Status State Machine (LTSSM) 的过程。从 Detect 状态一路走到 L0(正常工作状态)。如果中间某个步骤卡住了,比如 Polling.Confg 超时,链路就上不去。

协议分析仪物理层抓得最细的数据是 Ordered Sets:

  • TS1/TS2:用于链路训练和位锁定。
  • SKP:用于时钟补偿,调整 bit 偏移。
  • EIOS/EIE:用于进入或退出电气空闲状态。

我记得做 Gen4 信号调试时,卡在 Polling.Active 状态出不来,后来发现是发送端的 De-emphasis 系数没配对,物理层接收端根本识别不到有效信号。换了电阻网络才搞定。

建议: 物理层波形搞不定的时候,别光盯着协议分析仪,上示波器看眼图。PCIe Gen4 的 16GT/s 信号容错窗口只有几十皮秒,眼图一闭合,LTSSM 肯定卡住。

2.9 小结

今天把 PCIe 的拓扑和分层结构捋了一遍。记住三件事:

  1. 拓扑 决定流量路径,RC、Switch、EP 各司其职,调试先从 Bus/Device 枚举查起。
  2. 分层 决定问题域:TLP 查功能,DLLP 查重传,Phy 查信号。
  3. 分析仪 是你最好的朋友,学会在不同层过滤数据、观察分布,解决问题快一倍。

嗯,下一站在哪里?先把这些记扎实了,以后调试时你会感谢今天自己的基本功。