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的物理层要经历一个复杂的握手过程:
- 检测链路是否连通(Detect状态)
- 协商链路宽度(Polling状态)
- 协商链路速率(Configuration状态)
- 进入正常工作模式(L0状态)
我曾经在一个项目里,EP板卡插到RC上,链路死活训练不到x4宽度。用示波器看差分信号,发现有一对差分线虚焊了。物理层检测到那条lane不通,自动降级到x2。嗯,硬件焊接问题,软件再怎么调也没用。
经验之谈:调试物理层问题,我建议先看LTSSM(链路训练状态机)的状态。大多数PCIe调试工具都能读出当前状态。如果卡在Polling或Configuration状态,基本可以断定是物理层问题——要么信号质量差,要么时钟有问题。
2.4 三层如何协同工作?
讲完每一层的作用,咱们看看它们怎么配合。假设RC要读EP的某个寄存器:
- 事务层(RC端):软件发起读请求,事务层构造一个Memory Read TLP,填好地址、长度、Requester ID,丢给数据链路层
- 数据链路层(RC端):给这个TLP加上序列号(比如#5),计算LCRC,然后交给物理层
- 物理层(RC端):把数据编码、加扰、串行化,通过差分线发出去
- 物理层(EP端):接收信号,时钟恢复、解扰、解码,还原成数据链路层格式
- 数据链路层(EP端):检查LCRC,确认序列号正确,发一个ACK DLLP回去。然后把TLP交给事务层
- 事务层(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,总线架构一直在变,但分层设计的理念始终没变。这就是经典的魅力。