ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PCIe数据报文解析:TLP、DLLP与PLP的分层机制与应用

PCIe数据报文解析:TLP、DLLP与PLP的分层机制与应用 1. 初学PCIe最先遇到的坎为什么数据要拆成TLP、DLLP、PLP三种报文1.1 三层协议栈三张“身份证”学习PCIe数据包绕不开的第一个问题就是为什么通信要搞出TLP、DLLP、PLP这么多种报文直接用一种包把读写请求发过去不行吗答案藏在PCIe的分层架构里。PCIe协议栈从上到下分三层事务层Transaction Layer、数据链路层Data Link Layer、物理层Physical Layer。每一层只关心自己职责范围内的那点事层与层之间通过固定的接口把数据交给对方处理。所谓“报文的种类”其实就是对应到这三层“各自会在链路上发出的东西”事务层产生TLPTransaction Layer Packet事务层报文负责表达“你要读哪个地址”“我要写什么数据”“上次读的结果回来了没有”这类业务语义。数据链路层产生DLLPData Link Layer Packet数据链路层报文负责做可靠性保障比如告诉对方“你刚才发的那包我收到了”“那个TLP校验失败能不能重发”以及协商两个设备之间的缓冲区信用量。物理层则产生PLPPhysical Layer Packet物理层报文也就是PCIe规范里常说的有序集Ordered Set比如TS1/TS2训练序列、SKP时钟补偿序列、EIOS电气空闲序列等负责管理链路本身的物理状态同步、速率协商和电源状态切换。如果做一个生活化类比TLP相当于你寄的“信纸”上面写的是正经内容DLLP相当于“快递回执单”告诉你快递到了没有、要不要重新派送PLP则相当于快递公司内部使用的“运单扫描信息”确保货车在高速上跑的时候不会掉队、能对齐发车节奏。三者缺一不可但分工完全不同。1.2 为什么要分层而不是直接一个包打天下很多做应用层开发的同学第一次看PCIe协议栈会觉得奇怪TCP/IP也分层但那是全球性复杂网络PCIe只是板卡到CPU之间的短距离总线有必要搞这么重吗有。因为PCIe的设计目标当初就不是“点对点传数据”这么简单它要同时满足三件事高带宽、高可靠、低延迟。这三者天然存在矛盾分层是最好的解法。第一可靠性必须和业务解耦。如果TLP自己既管业务又管重传那么事务层的逻辑会变得极其复杂每发一个读请求都要挂起等待还要自己维护超时重传性能必然下降。把重传责任下放到数据链路层事务层只负责“把请求发下去把完成包收上来”发送方就不需要关心物理链路上偶尔丢包的问题了。第二物理层需要不断校准链路状态。链路的速率切换到8GT/s之后两边时钟频率并不严格一致需要通过SKP有序集定期做补偿链路从低功耗状态唤醒时也需要专门的同步序列。这些操作如果混进业务报文里那么业务逻辑就没法专注处理数据了。第三工程实现上分层以后芯片内部的物理层、链路层、事务层可以由不同团队独立设计只要接口符合规范就行。这也是为什么我们看到很多PCIe控制器IP把三层做成了独立模块这样每一层的验证和调试都可以单独进行出现问题也能快速定位是哪一层的锅。理解了这个设计动机后面再去看TLP、DLLP、PLP各自的格式和机制就不会觉得它们是一堆零散规则的拼凑而是一套有清晰目标的体系。2. TLP事务层报文真正承载读写语义的报文2.1 TLP由哪几部分组成TLP是PCIe里信息量最大、也是研究价值最高的一类报文它负责真正的事务处理。一个完整的TLP在链路层被发送之前通常包含以下几部分部分长度说明TLP头3DW或4DW12/16字节包含事务类型、地址、长度、标签等关键信息数据载荷0到最大载荷长度写请求携带的数据或完成报文返回的数据ECRC摘要可选的1DW端到端CRC由事务层或软件使能可选这里的DW是PCIe世界的基本计量单位1DW等于4字节。头为什么要有3DW和4DW两种区别在于地址宽度3DW的头对应32位地址空间4DW的头对应64位地址空间。日常我们访问系统内存、MMIO、配置空间大多用的是3DW头在64位寻址场景下比如某些高性能DMA写超过4GB地址空间时会用4DW头。TLP头是理解整个报文的第一步我建议初学者直接把Fmt/Type字段当成报文的“主键”来记。Fmt占用第7到第5位告诉接收方头部是3DW还是4DW、是否带数据Type占用第4到第0位告诉接收方这是读、写、配置读写还是完成报文。两者组合就能区分以下几种常见的TLP类型报文类型Fmt/Type含义属于哪类事务MRd存储器读读请求不带数据Non-PostedMWr存储器写写请求带数据PostedIORd/IOWrIO读写访问IO地址空间Non-PostedCfgRd0/CfgWr0访问设备配置空间Non-PostedMsg/MsgD消息带数据或不带数据的消息PostedCpl/CplD完成/完成带数据针对Non-Posted请求的返回Completion为什么要有Posted和Non-Posted之分核心区别在于是否需要对方回“完成”报文。MWr和Msg这类是Posted事务发出去就不用管了系统认为它最终会被处理所以不需要等待完成包延迟更低而MRd、CfgRd这类是Non-Posted事务请求方必须等到对方返回CplD或Cpl才能知道结果。这一点对性能影响极大写操作可以“发完就走”读操作必须“停下来等结果”。2.2 头部字段逐位拆解从地址、标签到长度以最常见的3DW头MWr32为例头部32字节中前4个字节最有代表性几乎是所有TLP类型的通用前端第0字节的Fmt/Type决定报文类型第1字节高三位是TCTraffic Class用于QoS调度一般取0第2字节里TD位表示是否带ECRC摘要EP位表示 poisoned 数据低三位是Attr属性第3字节的Length字段表示TLP数据载荷的长度粒度以DW为单位也就是说Length4表示后面跟16字节数据。第4到第7字节分别是Requester ID和Tag。Requester ID由Bus号、Device号和Function号组成每个设备在系统枚举时都会被分配唯一的BDF这样接收方知道“这包是谁发给我的”Tag则是请求方内部维护的事务编号。PCIe允许同一时刻在链路上存在多个未完成的Non-Posted请求靠的就是Tag来区分和配对。非Posted事务的Tag很宝贵因为协议规定同一时间内同一个Tag只能对应一个未完成事务所以高端控制器会维护一个Tag池来管理并发。紧接着是Last BE和First BE字段它们以字节粒度指定访问的首尾字节。整个头里我最想提醒初学者注意的是Length字段它表示的是“数据载荷”的DW数量而不包括头本身。很多人第一次看总线日志时会把总长度和Length搞混然后用错误的方式去切分报文边界结果自然对不上。2.3 TLP数据载荷与最大负载的约束TLP的数据载荷不是想发多大就发多大的最大长度由链路两端的MPSMax Payload Size协商决定。MPS常见取值是128字节、256字节、512字节也有更长的设置。为什么MPS不能随意设大因为数据链路层的重传缓冲区是跟着MPS走的MPS越大接收方在流控上需要预留的缓冲空间就越大硬件成本也越高。所以在设备初始化阶段系统会根据链路两端的能力通过配置空间里的Device Control寄存器把MPS设成一个双方都支持、且缓冲不溢出的值。我之前在FPGA上调试XDMA的时候遇到过软件层读大数据块时吞吐量上不去的情况最后定位下来是MPS被协商成了128字节而驱动每次发起4K字节的读请求结果一个4K读被拆成了32个TLP每个TLP都要消耗一个Tag、等一个完成包延迟开销全花在这上面了。后来在配置空间里把MPS改成512字节后同样的数据量只需要8个TLP性能立刻提上来。所以看到TLP生命周期里的“头”大小和“数据”大小后不要只停留在格式理解上一定要联系到实际性能调优。3. 数据链路层DLLP序列号、LCRC与流控信用才是可靠的底牌3.1 DLLP的格式和“服务对象”如果说TLP是“信纸”那数据链路层要做的就是在“信纸”外面再套一层保护壳然后发出自己的“回执”。TLP从事务层下来之后数据链路层会给它加上2字节的序列号、在尾部追加4字节的LCRC序列号和LCRC会在接收方被校验这是PCIe链路层可靠传输的第一步。接收方校验通过后会回一个ACK DLLP校验失败或者发现序列号不连续则回一个NAK DLLP。DLLP本身格式比TLP简单得多以一个SDP符号开头接着是DLLP类型字段8位、DLLP数据8字节、CRC和结束符号。整个DLLP长度固定不携带上层业务数据所有信息都塞在类型和8字节数据里。常用DLLP类型如下DLLP类型作用ACK确认已正确接收序列号小于等于N的若干个TLPNAK请求对序列号大于等于N的TLP进行重传InitFC1-P/NP/Cpl链路初始化时通告Posted、Non-Posted、Completion的初始信用InitFC2-P/NP/Cpl初始化第二阶段UpdateFC-P/NP/Cpl运行期间动态更新信用值PM_Enter_L1、PM_Enter_L23请求进入低功耗状态Vendor Specific DLLP厂商自定义DLLP数据链路层同时兼具“对TLP做封装”和“收发DLLP”两个功能这两件事需要分开看待封装TLP是把业务数据打包送出去DLLP则是链路层自己维护链路可靠性、缓冲状态所用的管理报文。很多初学者容易把“加了序列号和LCRC的TLP”误称为DLLP实际上它只是被DLL封装过的数据名称上应该叫“DLP数据包”而只有如ACK、NAK、UpdateFC等这些链路层管理报文才叫DLLP。3.2 ACK/NAK重传机制代价与收益的平衡数据链路层最核心的价值在于ACK/NAK重传。发送端维护着一个重传缓冲区每发出一个TLP都留一份副本并给TLP分配一个连续的序列号。接收端收到TLP后先做LCRC校验校验通过就把这条序列号记录到“已接收”状态并回复ACKACK里携带的是“我已经连续收到的最高序列号”。发送端收到ACK后就可以把该序列号之前的TLP副本从缓冲区释放。如果校验失败接收端会回NAK携带“我期望收到的序列号”发送端就从缓冲区里取出对应TLP重新发送。这里有一个容易被忽视的细节DLLP本身不参与重传它只负责重传控制。ACK/NAK一旦发出就不再有后续补偿虽然DLLP也有CRC保护但万一这个CRC坏了接收方检测不到ACK发送方怎么办答案是发送方会启动重传定时器如果超时没有收到ACK同样会重传TLP。这个机制保证了即使管理报文丢了一两个数据也不会永久丢失。我还想多说一句重传机制的性能问题。重传缓冲区大小直接决定了链路能容纳多少在途数据对于高延迟的长链路如果重传缓冲太小发送端很快就会因为没有ACK而停止发送导致带宽浪费。PCIe规范里对重传缓冲能力有最低要求但在实际硬件设计中高性能控制器往往会配更大的重传缓冲区来提升吞吐。3.3 流控信用CreditDLLP里最容易被当成“小透明”的角色除了ACK/NAKDLLP的另一个重任是流控信用Flow Control Credit。流控和重传是两套机制重传解决“发了但没收到”的问题流控解决“你发得太快我会溢出”的问题。接收方在链路初始化阶段会通过InitFC1和InitFC2两类DLLP把自己各个虚拟通道VC的缓冲区大小通告给发送方。缓冲按事务类型分三类Posted、Non-Posted、Completion又分别区分Header Credit和Data Credit。发送方维护一个当前可用信用数每发一个TLP自己的可用信用就减少相应数量接收方每处理掉一个TLP就通过UpdateFC报文把信用补回来。很多初学者不理解为什么流控信用要分etermining这么细。原因在于接收方对三种事务的处理路径和缓冲副本数量完全不一样。比如Posted写报文可以被接收方批量处理缓冲策略更宽松Non-Posted读请求则需要长期保留Tag和上下文占用的信用逻辑更严格。如果统一用一种信用那么最苛刻的类型就会拖垮所有类型导致整个链路的发送速度都受影响。在实际抓包调试中我会特别关注UpdateFC报文的频率。如果某条链路上的UpdateFC特别频繁说明接收方的缓冲比较紧张发送端经常处于等信用状态如果UpdateFC很少但信用值一直充裕那这个方向上的流控基本不是瓶颈。这种观察比单纯看TLP数量更能判断系统的真实负载。3.4 电源管理DLLP与厂商自定义报文还有一类容易被忽略的DLLP是电源管理相关的PM_Enter_L1和PM_Enter_L23。当设备处于空闲状态系统希望把链路切换到低功耗L1或者更深的L2/L3待机时需要通过特定DLLP来协商。这也就解释了为什么我们在讨论链路功耗优化时DLLP的管理功能是不可或缺的。厂商也可以利用Vendor Specific DLLP做私有协议扩展比如通知对端“我这边的固件可以支持某个特性”。这类报文不跨设备边界只被数据链路层处理不会被上报到事务层所以看总线上抓到的包时见到不认识的DLLP类型不要慌去规范的附录里查厂商ID和用途即可。4. PLP物理层报文有序集链路训练、时钟补偿和电源状态管理4.1 关于PLP这个叫法得先澄清一下和TLP、DLLP相比PLP这个称谓在规范原文里并不那么“正式”。在PCIe规范里物理层传递的最小管理单元更多被称为有序集Ordered Set例如TS1、TS2、SKP、FTS、EIOS、EIEOS等。国内很多PCIe学习资料、课程里为了和TLP、DLLP对齐会把这些有序集合统称为PLP即物理层报文。我建议理解的思路是P在提到的这个语境里主要指物理层有序集不必纠结术语本身关键是知道物理层在链路上确实有自己的“报文”在工作。物理层报文的作用范围绝不比TLP/DLLP小。它没有业务内容但负责整个链路的“基础建设”链路训练时通过TS1/TS2协商速率和链路宽度正常通信时周期性发送SKP来实现时钟补偿进入和退出低功耗状态时发送各种电气空闲序列完成这些之后数据才能真正在链路上“跑”起来。4.2 TS1/TS2训练序列与LTSSM状态机链路从物理连接建立到可以传数据要经历一个称为LTSSMLink Training and Status State Machine的过程它由一系列状态构成Detect、Polling、Configuration、L0、Recovery等。每个状态之间的迁移靠的就是物理层有序集。在Detect阶段物理层会检查对端是否在位接收到的信号强度是否足够如果检测到对端就进入Polling阶段链路两端开始对速率和位宽进行协商Polling阶段双方反复发送TS1/TS2交换彼此的链路能力包括最高速率支持、链路宽度、是否支持某种转发模式等。协商完成后进入Configuration状态此时会锁定链路宽度、完成通道反转和极性反转等物理调整最后进入L0工作状态数据通路才真正打开。TS1/TS2报文看起来像一串“乱码”但内部字段非常有规律。它包含Link Number和Lane Number字段用来标识训练序列是在哪条通道上发送的包含速率ID字段标识发送方支持的速率通过更改TS1/TS2中的位模式双方还能表达诸如进入Loopback、Compliance等特殊测试模式的需求。4.3 SKP有序集与弹性缓冲Elastic BufferSKP是初学PCIe时最容易遇见的“老朋友”因为它和本文开头提到的弹性缓冲Elastic Buffer直接相关。PCIe链路两端使用的参考时钟频率并不是完全一致的即使标称都是100MHz晶振之间也可能存在几百ppm的偏差。如果不做补偿时间一长接收端采样就会错位。解决办法是发送端周期性地发送SKP有序集在SKP里可以插入或删除一定数量的SKP符号接收端的弹性缓冲会检测SKP的位置根据自身FIFO的水位决定是吸收掉多余的SKP还是补充缺失的SKP符号。这个过程对上层完全透明TLP和DLLP的边界不会因此错乱。这也是为什么SKP有序集会出现在任意两个TLP/DLLP之间而不是固定在固定间隔。很多做过高速接口调试的工程师都有过这样的经历用示波器去看PCIe通道时发现信号有毛刺怀疑是自己的参考时钟有问题其实只要SKP补偿机制在工作几百ppm的时钟频偏根本不会导致通信失败。真正需要担心的是参考时钟噪声太大、抖动超标那个不是SKP能救回来的。4.4 EIOS、EIEOS、FTS与链路电源状态转换在PCIe链路进入低功耗状态的序列中物理层还会发送EIOSElectrical Idle Ordered Set来通知对端“我要进入电气空闲了”。从L0切换到L0s时发送端会发送EIOS然后拉低差分信号进入空闲状态退出L0s时对端需要通过发送EIEOS或者FTS序列来重新同步因为从电气空闲恢复后比特对齐信息已经丢失了。这里有一个细节值得做硬件/FPGA的朋友注意在8b/10b编码的传统速率下EIEOS和FTS的作用与在128b/130b编码的Gen3/Gen4下有所不同。Gen3及以上速率下训练序列和SKP的组成、长度都有调整逻辑分析仪抓包时看到的符号格式也会不一样。因此做链路调试时一定先确认当前是Gen1/2还是Gen3/4绝不能照搬一种速率下的抓包经验。5. 一次读事务的完整报文之旅从CPU发起MRd到数据返回CplD5.1 发起端视角软件只看得到TLP语义为了把三种报文的关系串起来我们来看一个最典型的场景CPU通过MMIO方式读取PCIe设备配置空间里的一个寄存器。这一步在很多文章里被称为“配置读”本质上它先由软件发起CfgRdTLP但是为了举例清晰我们从更通用的MRd存储器读来理解事务流程。软件层面操作系统调用访问函数最终会转化为一次PCIe总线事务。根复合体Root Complex收到请求后在事务层生成一个MRd TLPTLP头里填写目标地址、Requester IDRC自身BDF、Tag0x01、Length1由于MRd属于Non-Posted事务请求发送后不能立刻认为完成必须等一个CplD回来。MRd TLP发到数据链路层后被加上2字节序列号比如SEQ0x0123和4字节LCRC然后交给物理层。物理层在链路空闲时把这个TLP封装成一个带STP起始符号和END结束符号的“物理帧”按照链路速率编码发送到差分线对上。如果链路上正好有发送周期性的SKP有序集可能会被插在报文之间这完全正常。5.2 接收端视角从物理帧到TLP再到完成包对端设备的物理层先执行比特同步、符号对齐利用弹性缓冲滤掉SKP扰动恢复出比特流数据链路层检测到STP开始接收TLP解析序列号计算LCRC和收到的LCRC比对。如果校验成功返回一个ACK DLLP给发送方同时把去掉序列号和LCRC的TLP头与数据提交给事务层。事务层根据TLP头里的Fmt/Type识别出这是一个MRd首先检查它是否指向自己管理的内存/配置空间确认是自己的资源后按照请求地址从寄存器或内存中读出数据。随后生成一个CplD TLP完成报文头中包含Completer ID、请求者的Requester ID用于原路返回、Tag必须和请求包一致、BCM/Byte Count字段、Lower Address等。CplD再经过数据链路层加序列号LCRC物理层编码发送回去。最初发起读请求的RC收到CplD后同样做LCRC校验随后把TLP头里的Tag和之前发出的那个MRd一一匹配。匹配成功后数据被送到CPU/软件层一个完整的读事务结束。5.3 抓一根真实总线日志来看三种报文用PCIe协议分析仪在Gen3 x4链路上抓一段典型数据你会看到类似这样的序列示意序号报文类型关键字段说明1SKP Ordered Set-周期性时钟补偿物理层2MRd TLPAddr0xE000_1000, Tag0x2C, Len1CPU发起读3ACK DLLPAck Seq0x0F对端确认收到MRd4CplD TLPTag0x2C, Data0x0000_0001完成数据返回5ACK DLLPAck Seq0x10发起端确认收到CplD6UpdateFC DLLPPosted Credit更新接收方缓冲释放信用更新这个顺序几乎就是PCIe读事务的教科书级样例。看日志时我建议先按类别筛选把SKP过滤掉然后按TLP的Tag字段排序这样能很清晰地把同一笔事务的请求和完成串在一起。5.4 带宽估算报文开销到底吃掉了多少性能最后用一个计算题收掉这部分。假设链路是Gen3 x8即每条通道8GT/sx8总共64GT/s原始比特率。Gen3用128b/130b编码有效数据传输率是64×128/130≈63.0GT/s约7.88GB/s。如果MPS是256B发送一个写TLP需要开销头16字节LCRC4字节序列号2字节物理层STP/END等约4字节总计约26字节开销。因此理想净荷效率是256/(25626)≈90.8%理论最大吞吐约7.16GB/s。实际系统里还会有ACK/NAK、UpdateFC、SKP等管理报文占用链路带宽再加上写响应、读完成排队等干扰实际吞吐很难达到理论值。有些厂商测试出来的“有效带宽”偏低往往不是芯片能力不行而是MPS设置、Tag深度、流控信用配置某一个环节出现了瓶颈。所以当你打算给PCIe吞吐做性能验证时一定先按这个公式算一下理论上限再对比实测值差值过大再去排查协议层面。6. 学习与调试PCIe数据包的几条实战经验6.1 学习资料千万别只盯标题要按包类型分别整理PCIe数据包相关的学习资料非常多但是初学者最容易犯的毛病是把TLP、DLLP、PLP混在一起看导致概念越看越乱。我自己的做法是建一个表把每种报文分别记录“在哪个层产生”“在哪个层被消费”“主要字段有哪些”“常见交互场景是什么”每看到一个总线日志片段就往表里填。这种方法坚持两周对PCIe报文的整体框架就非常清晰了。遇到TS1/TS2、SKP这类物理层有序集时不要试图在普通逻辑分析仪上抓取细节这些需要专用协议分析仪或者支持PCIe SerDes内部环回的调试接口才能观察到完整信息。软件层最常打交道的还是TLPDLLP只有在做可靠性、流控排查时才需要仔细研究。6.2 FPGA开发者的经验先跑IP核demo再啃协议栈如果你像我一样用FPGA做PCIe开发我强烈建议先跑通Xilinx XDMA/7系列Integrated Block的官方demo它在链路训练完成之后会自动上报Link Up状态对应LTSSM的L0状态。Link Up没点亮之前讨论TLP没有任何意义因为物理层压根还没就绪。只有Link Up之后才去读配置空间使能Bus Master位再发起DMA读写。我踩过的一个典型坑是Verilog逻辑触发抓包时把物理层还没完成速率协商时的信号拿到事务层去解析结果得到一堆乱码。后来老老实实地在Xilinx IP核的user link up信号拉高之后才启动抓包数据才完全正确。建议FPGA初学者确保对LTSSM的基本状态有概念至少要能看懂IP核日志里“Detect”“Polling”“Configuration”“L0”这几个状态的含义很多问题其实都在这一层。6.3 硬件调试中容易被忽略的几件事硬件层面PCIe链路要想稳定达到Gen3/Gen4速率AC耦合电容的摆放位置、参考时钟的阻抗匹配、走线长度差都会直接影响信号质量。很多工程师会遇到“插上就能识别跑着跑着突然掉链路”的情况这多半是物理层信号裕量不足导致某个LTSSM状态迁移失败链路进入Recovery机制。这种问题用协议分析仪能看到TS1/TS2反复发送但根源往往在PCB耦合电容位置、过孔残桩、连接器质量这些地方。做链路调试时还要特别留意参考时钟的频偏和抖动。前面提到的弹性缓冲能吸收几百ppm的频率偏差但前提是参考时钟的抖动满足规范要求。实测里我曾经遇到过一块PCIe Gen3网卡在A平台上工作正常换到B平台就频繁报PCIe错误最后测出来是B平台给PCIe参考时钟的走线旁边有一条高频信号线串扰导致参考时钟抖动超标。这种问题在协议分析仪上表现成反复出现CRC错误处理起来最让人头疼。6.4 从“看懂报文”到“定位问题”的经验传递最后分享一个我判断PCIe问题定位顺序的土办法。遇到数据传输出错先看数据链路层有没有大量NAK/重传如果有说明链路物理质量或者电气环境有问题先查硬件如果NAK很少但TLP层面总是出现timeout错误说明事务被对端丢弃或完成包根本没回来重点查配置空间的Bus Master位、MPS设置、Tag管理如果TLP收发都正常但吞吐率低则重点看流控信用、MPS、以及软件一次请求的数据量是否太小。按这个顺序排查我在实际项目中解决过不少“看起来像驱动bug其实是硬件问题”和“看起来像硬件问题其实是MPS配置不合理”的案例。PCIe的每一层都把问题“包”在了自己的报文里学会读TLP、DLLP、PLP等于拿到了拆解这些包的钥匙剩下的就是不断对着总线日志练习手感了。
返回列表