2. PCIe分层结构:事务层(TLP)、数据链路层(DLLP)、物理层(PHY)的作用与交互

各位做PCIe开发的朋友,咱们今天聊聊PCIe的分层结构。说实话,我刚接触PCIe那会儿,看到这三层结构——事务层、数据链路层、物理层,第一反应是「这玩意儿有必要分这么细吗?」后来踩了几个坑才明白,每一层都有它存在的道理。

打个比方,PCIe的分层就像快递系统。事务层是「下单的人」,只管说「我要发什么货」;数据链路层是「快递分拣中心」,负责打包、贴单、确认签收;物理层就是「快递员和货车」,管的是怎么把包裹从A送到B。各司其职,互不干扰。

核心要点:PCIe的分层设计,本质上是把复杂问题拆解成三个独立模块。每一层只关心自己的事,通过明确定义的接口跟上下层通信。这种设计让调试变得容易——哪层出问题,就查哪层。

2.1 事务层(Transaction Layer)—— 发号施令的指挥官

事务层是PCIe架构里最「上层」的一层,也是跟软件打交道最多的。我个人习惯把事务层理解为「翻译官」——它把软件发起的读写请求,翻译成标准的TLP(事务层包)。

TLP长什么样?说白了就是一个数据包,包含头部(Header)、数据负载(Data Payload)和可选的CRC校验。头部里最关键的是:

  • Fmt/Type字段:告诉接收方「这是个读请求还是写请求」
  • Requester ID:谁发的这个包
  • Address:要读/写哪个地址
  • Length:数据长度

我在项目中遇到过一个问题:RC端发了一个Memory Read请求,EP端死活没响应。查了半天,发现是TLP头部的Length字段填错了——填了0。嗯,PCIe规范里Length=0是保留值,EP端直接把这个包当无效包丢弃了。从那以后,我写TLP生成代码时都会加一个断言:Length必须大于0。

避坑指南:我曾经在调试DMA驱动时,发现数据总是少传几个字节。后来定位到是事务层的Max Payload Size没配对。RC和EP的Max Payload Size必须一致,否则TLP会被拆分或丢弃。这个配置在Device Control寄存器里,记得两边对齐。

2.2 数据链路层(Data Link Layer)—— 靠谱的快递分拣员

数据链路层,我管它叫「中间人」。它夹在事务层和物理层之间,干的是脏活累活。事务层把TLP丢下来,数据链路层要负责:

  • 加序列号:每个TLP分配一个递增的序列号,方便接收方排序和去重
  • 加LCRC:链路层CRC,检测传输过程中的比特错误
  • ACK/NAK重传:接收方收到TLP后,回一个DLLP(数据链路层包)告诉发送方「收到了」或「重传」

你想想看,如果没有数据链路层,事务层直接跟物理层通信会怎样?物理层传数据时可能丢包、可能出错,事务层还得自己处理重传逻辑,那代码就乱套了。数据链路层把这些脏活全包了,事务层只管发TLP,不用操心可靠性。

DLLP是什么?数据链路层自己也会发一些控制包,比如:

  • ACK DLLP:确认收到TLP,告诉发送方可以释放缓存了
  • NAK DLLP:要求重传某个序列号的TLP
  • PM DLLP:电源管理相关的握手

我记得有一次调试EP的DMA回环,发现RC端总是收不到数据。抓PCIe分析仪一看,数据链路层一直在发NAK。为什么?因为EP端的接收缓冲区满了,来不及处理TLP,导致序列号对不上。解决办法很简单——增大EP端的接收缓冲区深度,或者降低RC端的发送速率。

注意:数据链路层的重传机制是硬件自动完成的,软件一般感知不到。但如果你发现链路性能异常低,很可能是链路质量差导致频繁重传。这时候查一下物理层的信号完整性,别在数据链路层上死磕。

2.3 物理层(Physical Layer)—— 真正的搬运工

物理层,说白了就是「干苦力」的。它负责把数据链路层传下来的DLLP和TLP,变成电信号在PCIe链路上传输。物理层又分成两个子层:

  • 逻辑子层:做8b/10b编码(Gen1/Gen2)或128b/130b编码(Gen3+),加扰码,做字节拆分
  • 电气子层:管差分信号、阻抗匹配、时钟恢复

物理层最让我头疼的是链路训练(Link Training)。每次上电,RC和EP的物理层要经历一个复杂的握手过程:

  1. 检测链路是否连通(Detect状态)
  2. 协商链路宽度(Polling状态)
  3. 协商链路速率(Configuration状态)
  4. 进入正常工作模式(L0状态)

我曾经在一个项目里,EP板卡插到RC上,链路死活训练不到x4宽度。用示波器看差分信号,发现有一对差分线虚焊了。物理层检测到那条lane不通,自动降级到x2。嗯,硬件焊接问题,软件再怎么调也没用。

经验之谈:调试物理层问题,我建议先看LTSSM(链路训练状态机)的状态。大多数PCIe调试工具都能读出当前状态。如果卡在Polling或Configuration状态,基本可以断定是物理层问题——要么信号质量差,要么时钟有问题。

2.4 三层如何协同工作?

讲完每一层的作用,咱们看看它们怎么配合。假设RC要读EP的某个寄存器:

  1. 事务层(RC端):软件发起读请求,事务层构造一个Memory Read TLP,填好地址、长度、Requester ID,丢给数据链路层
  2. 数据链路层(RC端):给这个TLP加上序列号(比如#5),计算LCRC,然后交给物理层
  3. 物理层(RC端):把数据编码、加扰、串行化,通过差分线发出去
  4. 物理层(EP端):接收信号,时钟恢复、解扰、解码,还原成数据链路层格式
  5. 数据链路层(EP端):检查LCRC,确认序列号正确,发一个ACK DLLP回去。然后把TLP交给事务层
  6. 事务层(EP端):解析TLP,执行读操作,构造Completion TLP(带读回来的数据),原路返回

整个过程,每一层只跟相邻层通信。事务层不知道数据是怎么在线上传输的,物理层也不知道TLP里装的是什么数据。这就是分层设计的精髓——解耦。

层级 主要职责 数据单元 典型问题
事务层 构造/解析TLP,管理事务 TLP Max Payload Size不匹配,TLP格式错误
数据链路层 可靠传输,重传控制 DLLP, TLP+序列号+LCRC 缓冲区溢出,频繁NAK
物理层 信号编码,链路训练 有序集(Ordered Sets) 链路训练失败,信号质量差

调试建议:遇到PCIe通信问题,先判断是哪一层的问题。我的经验是:如果链路能训练成功(LTSSM进入L0),那物理层基本没问题。如果TLP能发出去但收不到响应,查数据链路层的ACK/NAK。如果软件报超时错误,大概率是事务层的问题——比如地址不对、权限不够。

最后说一句,PCIe的分层结构不是凭空设计的。它借鉴了网络协议栈的分层思想,每一层都经过了几十年的验证。你想想看,从PCI到PCI-X再到PCIe,总线架构一直在变,但分层设计的理念始终没变。这就是经典的魅力。