PCIe 层次结构:事务层、数据链路层、物理层的功能与分工

我们在前一章聊过 PCIe 的基本概念。你可能会问:为什么一个总线协议要搞成三层?直接像 PCI 那样并口通信不更简单吗?

我刚开始接触 PCIe 时也这么想。后来在调试一块 FPGA 板卡时,发现链路偶尔丢包,但设备寄存器读出来全是 0x0 —— 那时候我才意识到,分层设计是为了把复杂问题拆解。每一层只管自己那一摊事,出了问题可以独立定位,互不干扰。

说白了,PCIe 的三层就像快递公司:

  • 事务层(TL) —— 写字、打包、贴快递单
  • 数据链路层(DLL) —— 装车、核对清单、确认签收
  • 物理层(PL) —— 卡车、马路、交通信号

事务层(Transaction Layer)—— 生成与解析 TLP

事务层是最接近软件的一层。驱动发一个读配置空间的请求,CPU 写一个 Memory Write 命令,这些都先落到事务层。它负责把软件层的操作转化为 TLP(Transaction Layer Packet)。

我习惯把事务层看作“翻译官”。驱动告诉它“我想读设备 A 的 BAR0 空间”,它就把这个意图翻译成一个特定格式的 TLP,带上地址、长度、类型等信息,然后往下送。

TLP 主要类型:
  • Memory Read / Write——最常见的读写操作
  • IO Read / Write——兼容旧设备,新系统基本不用
  • Configuration Read / Write——枚举阶段使用
  • Message——中断、错误通知等
  • Completion——用于带数据的读响应

事务层还有个关键功能:流量分类(TC)和虚拟通道(VC)。在高吞吐场景下,视频数据流和普通寄存器访问可以走不同的虚拟通道,避免互相阻塞。我在做视频采集卡驱动时就遇到过,如果不在事务层配好 TC,显示画面偶尔会卡顿,因为 DMA 和配置消息抢了同一条通道。

数据链路层(Data Link Layer)—— 可靠性保障

数据链路层在中间,夹在事务层和物理层之间。它的核心任务就一个:保证 TLP 传输的可靠性。

怎么保证?两个关键机制:

  1. ACK / NAK 重传 —— 发送方发出 TLP 后,接收方必须回复一个确认包。如果收到 NAK 或超时,发送方会重新发送。
  2. CRC 校验 —— 每个 TLP 尾部附带一个 LCRC(链路 CRC),物理层传完后数据链路层会校验。一旦发现错误,直接扔 NAK。

我当年调试一个 PCIe 交换机时,链路经常报“Bad TLP”,查了好多天。后来用协议分析仪一看,是物理层信号质量差导致偶尔误码,但数据链路层反复重传,最终超时断开。嗯,这种问题只能从底层修,光改驱动没用。

注意:数据链路层只保证点到点链路的可靠性,不保证端到端的完整性。如果数据在设备内部被改坏了,数据链路层管不了。

除了 TLP 的可靠性,数据链路层还负责 链路管理。比如链路初始化、电源状态转换(L0s / L1 / L2)、流控初始化等。这些通过 DLLP(Data Link Layer Packet) 来实现,DLLP 不暴露给事务层,是数据链路层自己的“内部信令”。

物理层(Physical Layer)—— 电信号的传输

物理层是离硬件最近的一层。它负责:

  • 将数据链路层送来的字符流 串行化 为差分信号对(Tx+/Tx-)
  • 在接收端做 时钟恢复 和 解串
  • 处理电气特性(摆幅、预加重、均衡等)
  • 完成链路的 训练与初始化(LTSSM)

物理层又分为两个子层:

子层 功能 示例
逻辑子层 8b/10b 编码(Gen1/2)或 128b/130b 编码(Gen3+),加扰 确保直流平衡,信号中不会出现长串 0 或 1
电气子层 差分驱动、接收均衡、阻抗匹配 Per-lane 的 TX 摆幅可配置(如 800mV、400mV)

物理层还有一个非常重要的状态机——LTSSM(Link Training and Status State Machine)。链路从复位开始,依次经历 Detect、Polling、Configuration、L0(正常工作)、Recovery、L0s/L1 等状态。我曾经在一次板卡调试中,发现链路始终卡在 Configuration 状态,测量时钟发现参考时钟抖动太大,导致锁相环失锁。换掉晶振后,LTSSM 顺利进入 L0,一切正常。

三层如何协作?

用一个典型的读操作来串讲一遍:

  1. 软件(驱动)向 Root Complex 的某个寄存器发起 Memory Read。
  2. 事务层构造一个 Memory Read TLP,包含地址、长度、Requester ID、Tag 等信息,交给数据链路层。
  3. 数据链路层给 TLP 加上序列号(Sequence Number)和 LCRC,然后放入发送缓冲区,同时启动重传定时器。
  4. 物理层把字符流编码、串行化,通过差分线对发出去。
  5. 接收端物理层解串、时钟恢复,把比特流还原成字符流,上交给数据链路层。
  6. 数据链路层检查 LCRC,如果正确则回复 ACK(DLLP),并将 TLP 送往事务层;如果错误则回复 NAK,请求重传。
  7. 事务层解析 TLP,找到目标 Endpoint,读取数据,然后生成一个 Completion TLP(带数据),再经过同样路径返回给发送端。

整个过程环环相扣。哪一层出问题,表现都不太一样。我整理了一个小经验:

  • 数据全对但链路经常断开 -> 先查 LTSSM 状态,大概率是物理层电气问题
  • 读数偶尔错但链路不丢 -> 查数据链路层 CRC 错误计数器(可通过 PCIe 扩展能力寄存器读取)
  • 请求发出去了但收不到响应 -> 查事务层的 Tag 管理或 Completion Timeout
调试技巧:如果手头有 PCIe 分析仪,直接看 LTSSM 状态和 TLP/DLLP 流是最快的。没有分析仪,可以读设备的 PCIe 链路状态寄存器(Link Status Register),很多信息都能直接呈现。

下面这张图能帮你直观理解三层的关系和数据流动方向:

PCIe 层次结构与数据流 事务层(TL) 构造 TLP(Memory Read/Write/Completion) 数据链路层(DLL) 序列号 + LCRC + ACK/NAK 管理 物理层(PHY) 编码 / 串行化 / LTSSM 差分对(TX/RX) 事务层(TL) 解析 TLP,提取数据 数据链路层(DLL) LCRC 校验 + ACK/NAK 物理层(PHY) 解串 / 解码 / 时钟恢复 TLP → DLLP → ↔ 串行比特流 左侧为发送端,右侧为接收端,数据自上而下传输

说到底,这三层分工明确,各自独立却又紧密协作。做驱动开发时,你主要跟事务层打交道——构造请求、解析完成包。数据链路层和物理层虽然不直接暴露编程接口,但它们的健康状态直接影响性能。我建议所有做 PCIe 驱动的人,至少要知道怎么读链路状态寄存器和链路错误计数器。碰到疑难问题时,这些底层信息比猜测有用得多。

检查清单:遇到 PCIe 通信异常,按以下顺序排查:
① 物理层:LTSSM 是否在 L0?链路宽度/速度是否正确?
② 数据链路层:有没有 CRC 错误?是否频繁重传?
③ 事务层:Completer 是否超时?Tag 是否耗尽?