第3章:PCIe数据链路层深度解析

数据链路层,说白了就是PCIe协议的「交通警察」。它夹在事务层和物理层中间,干的全是脏活累活——保证数据不丢、不乱、不重复。我调试PCIe这么多年,遇到的大部分疑难杂症,根因都出在这一层。

3.1 数据包格式:TLP/DLLP/PLP

PCIe链路上跑着三种包,你得先分清楚它们谁是谁。

包类型 全称 主要作用 谁产生的
TLP Transaction Layer Packet 承载读写数据、完成报文、消息 事务层
DLLP Data Link Layer Packet ACK/NAK、流控信用量更新、电源管理 数据链路层
PLP Physical Layer Packet 电气训练、位锁定、链路初始化 物理层

TLP 是你最常打交道的包。它包含一个头部(Header)、可选的数据负载(Data Payload),以及一个可选的ECRC校验。头部里藏着地址、长度、标签、TC/VC等信息。我习惯用逻辑分析仪抓TLP时,先看Header的Fmt和Type字段,这两个字节能告诉你这个包是读还是写、是32位地址还是64位地址。

DLLP 是链路层的「内部通信」。它不经过事务层,直接在两个端点的数据链路层之间交换。最常见的DLLP就是ACK和NAK。还有一类叫InitFC1/InitFC2的DLLP,用于初始化流控信用量。嗯,这里要注意:DLLP的长度固定为6字节,你抓包时看到6字节的短包,基本就是DLLP。

PLP 是物理层的「握手信号」。比如TS1和TS2有序集,用于链路训练和宽度协商。PLP不参与正常的数据传输,只在链路初始化或恢复时出现。我刚开始调试时,经常把PLP误当成TLP,浪费了不少时间。

3.2 ACK/NAK重传机制

PCIe的可靠性,靠的就是ACK/NAK这套机制。说白了,就是「你发一个包,我回一个确认」。但这里有个关键点:ACK/NAK是针对TLP的,DLLP和PLP不参与重传。

发送端维护一个序列号(Sequence Number),每发一个TLP,序列号加1。接收端收到TLP后,检查CRC和序列号是否正确。如果正确,回一个ACK DLLP,告诉发送端「这个包我收好了」。如果出错,回一个NAK DLLP,发送端就得重传这个包以及之后的所有包。

重传的坑: 我曾经遇到一个案例,设备偶尔丢包,但ACK/NAK机制一直没触发重传。查了三天,发现是接收端的Replay Buffer溢出导致TLP被静默丢弃,根本没机会发NAK。所以,别光盯着ACK/NAK,Replay Buffer的大小和溢出标志位也得监控。

重传缓冲区(Replay Buffer)是发送端的关键资源。它保存了所有已发送但未收到ACK的TLP。一旦收到ACK,对应的TLP就可以从缓冲区里删掉。如果缓冲区满了,发送端就得暂停发送,等ACK回来腾出空间。

我建议你在调试时,重点关注两个寄存器:ACKNAK_LATENCY_TIMERREPLAY_TIMER。前者控制ACK/NAK的响应超时,后者控制重传的超时。这两个值配不好,轻则性能下降,重则链路挂死。

3.3 流控机制:信用量管理

流控,说白了就是「你告诉我你还能收多少,我再决定发多少」。PCIe用信用量(Credit)来实现这个机制,而不是传统的停等协议。这样能充分利用链路带宽。

每个VC(Virtual Channel)有六种信用量类型:

  • PH(Posted Header): 用于Memory Write等无需响应的请求
  • PD(Posted Data): 上述请求携带的数据
  • NPH(Non-Posted Header): 用于Memory Read等需要响应的请求
  • NPD(Non-Posted Data): 上述请求携带的数据
  • CPLH(Completion Header): 完成报文(Completion)的头部
  • CPLD(Completion Data): 完成报文携带的数据

发送端维护两个值:Credits Consumed(已消耗的信用量)和 Credits Limit(对方允许的最大信用量)。当 Credits Consumed < Credits Limit 时,才能发送对应的TLP。接收端通过DLLP中的 UpdateFC 报文,定期告诉发送端「我又腾出空间了,你的信用量可以增加了」。

调试技巧: 用逻辑分析仪抓流控时,重点关注UpdateFC DLLP中的FC字段。如果某个信用量长时间不更新,说明接收端的对应缓冲区可能卡住了。我遇到过一例,就是NPD信用量卡死在0,导致所有Non-Posted请求发不出去,整个链路性能暴跌。

3.4 链路管理与错误检测

链路管理,包括链路的初始化、训练、宽度协商、电源状态切换等。这些工作主要由物理层完成,但数据链路层负责监控链路状态,并在检测到错误时触发恢复流程。

数据链路层能检测的错误包括:

  • LCRC错误: TLP的链路层CRC校验失败
  • 序列号错误: 收到的TLP序列号不连续
  • DLLP错误: DLLP的CRC校验失败
  • Replay超时: 重传计时器溢出
  • ACK/NAK超时: 等待ACK/NAK响应超时

当检测到错误时,数据链路层会尝试恢复。轻度的错误(如单个TLP的LCRC错误)通过NAK重传就能解决。严重的错误(如连续重传失败)会导致链路进入恢复状态(Recovery),甚至触发链路复位。

注意: 链路恢复期间,所有正在传输的TLP都会丢失。你的软件驱动必须能处理这种「瞬断」场景。我见过一个项目,驱动没做重连逻辑,链路一恢复就死机。最后加了个链路状态变化的中断处理,才算搞定。

链路状态机(LTSSM)是PCIe物理层的核心,但数据链路层也参与其中。比如,当数据链路层检测到连续错误时,会向物理层发送 DL_Down 指示,触发LTSSM从L0(正常工作)进入Recovery状态。

我个人习惯在调试时,同时监控LTSSM状态和链路层的错误计数器。如果错误计数器在快速增长,但LTSSM还停留在L0,说明错误被重传机制掩盖了,但性能已经受影响。这时候就得深挖根因,而不是等链路崩了再查。

好了,数据链路层的内容就这些。下一章我们进入事务层,看看TLP到底是怎么组装和解析的。到时候我会拿一个实际的Memory Read请求,一步步拆给你看。