ARTICLE DETAIL

资讯详情

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

深入解析PCIe协议:三层架构、链路训练与TLP传输

深入解析PCIe协议:三层架构、链路训练与TLP传输 1. 为什么搞懂PCIe协议比看懂接口图更重要经常有工程师拿着PCIe的接口定义表来问我这个金手指的Tx和Rx为什么要分开排列那个PERST#信号到底该什么时候拉高说实话这些当然重要但如果只停留在引脚层面你永远只是在用PCIe而不是在懂PCIe。真实项目里最典型的场景板卡上电后设备没有被系统识别或者识别到了但带宽一直跑不满。这时候如果你不懂协议的分层结构就只能靠猜——换槽位、换主板、甚至怀疑是芯片坏了。但如果你理解了PCIe协议解析的核心框架也就是事务层、数据链路层、物理层这三层架构你会很自然地想到先看物理层链路训练是否完成LTSSM状态再看数据链路层的TLP是否在正常收发最后排查事务层的配置空间和地址映射。排查链路清晰问题定位快得不是一点半点。这篇文章就围绕PCIe协议最基础也是最核心的部分展开PCIe为什么选择串行差分点对点架构它究竟解决了并行时代的哪些痛点三层架构各自的职责边界以及一条数据请求在层与层之间是怎么流动的物理层里链路训练是怎么完成的LTSSM各个状态推进的逻辑数据链路层怎么保证TLP不丢不乱事务层的TLP格式、四种请求类型和地址空间映射规则最后从软件视角补上枚举过程因为这是把协议和实际驱动开发衔接起来的关键一环。无论你是做FPGA PCIe接口开发、写Linux驱动、还是做硬件板卡设计这篇文章都适合作为第一份速查参考。2. 并行总线的末日与PCIe的登场它到底解决了什么2.1 并行时代的三大痛点等待、偏斜、功耗很多人学PCIe会忽略一段历史背景PCIe之前计算机内部互连是PCI并行总线再往前还有ISA。并行总线看起来带宽应该很高因为一次能传很多位但实际做高速传输的时候并行总线有一个致命的物理问题——信号偏斜Skew。打个比方一队士兵齐步走如果每个士兵的步幅略有差异走久了队伍就散了。并行总线的32根数据线同时从发送端出发但到达接收端的时间不可能完全一致因为板卡上每根走线的长度、过孔数量、介电常数都有微观差异。频率越高、走线越长偏斜越明显。为了对抗偏斜并行总线只能牺牲频率典型的PCI总线跑33MHz或者66MHz数据和时钟同步传输。想要提升带宽就只能加宽总线——从32位加到64位。但总线加宽意味着更多的引脚、更大的封装、更高的成本而且信号完整性问题反而更突出。这就是一个死循环频率不敢升宽度加到极限带宽却还是不够用。2.2 串行差分方案是怎么破局的PCIe的思路是干脆不做并行传输了改成串行差分。一对差分线TX、TX-在同一时刻只传一个比特流接收端通过两个引脚之间的电压差来判断逻辑0或1。这样做的好处是既然不依赖同一时刻到达偏斜问题就基本不存在了每个通道Lane都是独立的串行链路频率可以单独拉高用高速率弥补位宽损失差分信号本身抗共模干扰能力强这对板级噪声环境非常友好。所以PCIe的设计哲学可以总结为一句话不惜用更高的工作频率也要摆脱并行总线的物理诅咒。第一代PCIe一个通道的单向速率是2.5GT/sGiga Transfer per second每秒25亿次传输但由于编码开销实际有效数据带宽约2Gbps。相比之下32位33MHz的PCI总线峰值带宽才约1Gbps。用更少的引脚获得了更高的吞吐这已经赢得很彻底了。补充一个事实PCIe链路可以同时使用多个通道x1、x4、x8、x16本质上是把多条串行链路捆绑在一起。多通道虽然又引入了新的偏斜问题但PCIe协议在物理层和链路层都内置了去偏斜机制比如每个通道使用独立的LFSR加扰种子后面链路训练部分会讲到接收端靠训练序列来对齐通道。所以通道数增加带宽线性增长但物理层设计复杂度也随之上升。2.3 点对点连接和拓扑结构PCIe不是一条总线还有一个容易混淆的点PCIe在物理形态上是点对点连接的不是传统意义上的共享总线。每个PCIe设备Endpoint以下简称EP和根复合体Root Complex以下简称RC之间或者EP和Switch之间都是独立的链路。链路两端同时收发数据互不影响。整个PCIe拓扑最顶层是RC它通常集成在CPU内部负责连接CPU、内存和PCIe域。Switch用来扩展更多的PCIe端口它本质上是多个虚拟PCI-to-PCI桥的集合。EP是终端设备比如网卡、显卡、NVMe SSD。理解这个拓扑对后面枚举和地址分配非常关键PCIe虽然没有共享总线但依然保留了总线号-设备号-功能号BDF的层次寻址方式每个EP在枚举时会分配一个地址系统软件正是通过这个地址访问设备的配置空间完成初始化和驱动加载。3. 三层架构概览事务层、数据链路层、物理层各自管什么3.1 一图看懂数据的传递过程PCIe协议参考了OSI七层模型但简化成三层事务层Transaction Layer最上层负责生成和处理TLPTransaction Layer Packet事务层报文。软件发起一次读或写请求在这层被翻译成标准TLP格式。数据链路层Data Link Layer中间层负责TLP的可靠传输。它给TLP加上序列号和CRC循环冗余校验收到对方的ACK确认才知道数据安全送达。物理层Physical Layer最底层负责把数据变成真正的电信号发出去同时负责链路的建立和训练。一个简单的读请求从软件到物理链路的完整旅途大致是CPU发出一个内存读请求进入RC的事务层事务层把这个请求打包成TLP并确定路由方式TLP下发给数据链路层链路层附加序列号和CRC物理层把报文做编码、加扰、串行化通过差分线发出去接收端的物理层恢复信号、解码、去扰接收端链路层校验CRC确认无误后把TLP交给事务层事务层解析TLP执行真正的内存读取操作读取结果再以完成报文Completion的方式原路返回。每一步都有严格的协议规定。各层之间通过内部的接口传递信息层与层之间不关心对方内部的细节。这种分层的直接好处是物理层的编码方式从8b/10b升级到128b/130b时上层软件和驱动无需任何改动。这也是为什么从Gen1到Gen5软件生态基本不受影响硬件却能持续提速。3.2 每一层加什么和解什么层级发送方向处理接收方向处理核心关注点事务层将软件请求封装为TLP确定路由和事务类型解析TLP头部识别读写请求、完成报文事务语义、地址空间、路由数据链路层附加序列号Sequence Number和CRC实现重传校验CRC按序列号排序应答ACK/NAK可靠性、流量控制、重传机制物理层编码、加扰、并串转换差分驱动输出时钟恢复、串并转换、解码、通道对齐链路训练、信号完整性、编码效率每一层处理的内容相对独立但也有交叉比如物理层的链路训练状态机LTSSM会直接影响链路能否进入正常工作状态而链路训练是否完成又决定了上层能不能开始传TLP。这个顺序关系非常关键——很多人调试PCIe就是卡在这一步。4. 物理层链路训练是PCIe一切工作的前提4.1 逻辑物理层和电气物理层的分工物理层在协议里被进一步分为逻辑物理层Logical PHY和电气物理层Electrical PHY。在正式传输数据之前物理层必须完成链路训练。链路训练的核心目的是确定链路两端的速率能力协商出共同支持的速率比如Gen3还是Gen4确定通道数x1还是x8完成通道对齐让多通道的比特流能同时到达交换加扰种子保证收端能正确解码。物理层除了承担数据传输还要处理两个重要模块加扰Scrambling和解扰。PCIe规定发送端使用一个LFSR线性反馈移位寄存器生成扰码序列与原始数据进行异或。这样做的目的是避免数据中出现连续的0或1长串保证接收端时钟恢复的稳定性。其实很多初学者会忽略一个细节加扰并不是为了加密而是为了保证频谱的分散减少电磁干扰。实测中你会发现如果长时间发送固定的模式链路的眼图质量会明显变差这正是因为频谱集中造成的能量聚集。加扰之后数据频谱被均匀摊开EMI问题小很多。4.2 LTSSM状态机的核心流转逻辑链路训练用一套状态机来管理全称是Link Training and Status State Machine。LTSSM的状态非常多常见的主要有几个Detect检测对端是否存在通过检测接收端是否接收到信号来判断Polling双方进行速率协商和位锁定确认基本的通信参数Configuration确定通道数和通道序号完成多通道的对齐L0正常运行状态数据可以自由传输L0s、L1、L2低功耗状态Recovery重新训练用于速率切换或错误恢复。在调试中最常接触到的是Configuration到L0的转换。Configuration阶段会交换训练序列TS1和TS2这些训练序列里携带了大量链路信息比如通道数、速率、是否支持lane reversal通道翻转等。这里有一个非常实际的排查经验如果设备始终无法进入L0多半是卡在Configuration阶段常见原因有对端并未上电或者复位信号异常PERST#被拉低太久链路两端的能力协商失败比如有一端不支持对方要求的速率通道总数不匹配比如主机认为只有x1但设备配置为x8导致无法对齐。4.3 EP先启动还是RC先启动的玄机这几乎是所有做PCIe板卡的人都会遇到的问题。PCIe规范并没有强制规定RC和EP的上电顺序但是实际工程中习惯是RC先上电EP后上电或者至少保证在RC开始链路训练之前EP已经处于可检测状态。原因是链路训练是RC主动发起的。RC在Detect阶段会周期性发送探测信号检查链路对端是否在线。如果EP比RC晚启动RC可能已经完成了整套训练流程并进入L0此时EP才刚上电就会错过入场时间。EP要么需要等RC重新训练比如RC端发生了热插拔事件或错误恢复要么干脆无法被系统发现。很多项目里会用一个简单粗暴的方式规避这个问题让EP的PERST#信号由RC侧的GPIO来控制EP复位解除的时机晚于RC完成复位几百毫秒。这样保证RC开始训练时EP肯定已经在位。如果你习惯在FPGA里做PCIe EP要注意上电初始化时序PERST#释放后必须给内部PLL稳定预留时间否则链路训练大概率失败。4.4 电气层与信号完整性为什么不建议做眼图孤儿电气物理层就是把0和1变成差分电压。PCIe的信号速率很高Gen3是8GT/sGen4是16GT/sGen5已经是32GT/s级别。这种速率下走线的长度、过孔数量、连接器质量都直接影响信号质量。判断信号质量最直接的手段是看眼图。眼图越睁开说明信号质量越好眼图闭合说明误码率会很高。几个实战心得AC耦合电容就近放在发送端一侧容值典型值选择100nF~220nF之间具体按芯片手册差分对内等长非常重要Gen4的设计一般要求对内误差在5mil以内具体以控制器手册为准避免在差分线上打太多过孔每个过孔都会引入阻抗不连续反射严重的情况下会导致眼图塌陷如果链路上出现偶发性误码可以从物理层训练序列来观察例如Recovery状态频繁发生基本可以怀疑信号质量问题。5. 数据链路层TLP的运输保险5.1 TLP的封装与CRC保护事务层生成的TLP并不能直接发到物理链路上。数据链路层会在每一笔TLP前加上序列号在TLP末尾加上CRC校验值这个CRC称为LCRC。接收端的链路层收到后先检查LCRC是否正确再检查序列号是否连续。检查LCRC只是第一道关卡。如果LCRC错误这一笔TLP会被丢弃接收端返回NAK如果LCRC正确但序列号不连续同样说明中间丢了包也会触发NAK和重传。这里有一个很多人误解的地方PCIe重传是发送端发起的。发送端会缓存所有已经发出但还未被确认的TLP如果收到NAK或者超时就会把缓存里的TLP重新发送一遍。ACK/NAK机制是数据链路层的核心可靠传输保障和TCP的重传思路很像但更轻量、更快。5.2 Ack/Nak机制与重传策略链路层在每个TLP上追加序列号后还维护着对端链路的接收状态。它发送ACK表示N号TLP之前的都收好了NAK表示某号TLP出错了请重传。重传只是全部重发吗不是。为了减小带宽浪费PCIe采用选择性重传Selective Retransmission只重传出错的那一笔及其之后未确认的部分。发送端的重传缓冲区大小有限如果对端一直NAK缓冲区溢出会触发更严重的错误处理甚至触发链路重新训练。实际写驱动或FPGA逻辑时你会发现数据链路层的错误绝大多数情况下不是软件能干预的它们是协议栈内部自动处理的。但如果你看到链路层错误计数器在增长绝对不要忽视说明物理层已经处于亚健康状态。典型处理顺序是先做信号完整性测试改善链路质量再观察计数器是否还在涨。5.3 流量控制Flow Control防止淹没对端链路层还有一个容易被忽视的功能流控。PCIe的流控机制和以太网完全不同它不使用暂停帧PAUSE Frame而是采用基于信用量Credit的机制。简单理解接收端在初始化时告诉对端自己每种类型缓冲区有多大余量信用量。发送端每发一笔TLP就消耗相应的信用量只有当对端返回信用量补充发送端才能继续发。如果信用量耗尽发送端必须停下来等待。信用量并不是统一的一个数字而是按不同的TLP类型分别统计的例如Posteced Header、Posted Data、Non-Posted Header、Non-Posted Data、Completion Header、Completion Data等。为什么要这么拆因为不同类型的TLP处理路径不同缓冲区管理也需要独立。这个机制对吞吐影响很大。如果某个EP的信用量设置很小对端的发送吞吐就会被信用量卡住表现为带宽上不去。排查这类问题很难直接看到因为驱动层面通常还是显示链路速率正常但实际上有效吞吐远低于理论值。6. 事务层TLP格式、四种事务类型与地址空间6.1 TLP的结构拆解事务层的核心产物是TLP。TLP的结构包括三个部分头标Header12字节或16字节携带事务类型、路由信息、请求者/完成者ID、标签Tag等元数据数据载荷Data Payload可选0到4096字节取决于链路速率和长度字段摘要Digest可选通常是端到端CRCECRC。头标里的字段非常多但对初学者来说最需要先掌握的是Fmt和Type字段决定TLP格式和类型比如读请求、写请求、完成报文Length字段数据载荷的长度单位是双字DW4字节Requester ID和Tag区分不同请求事务路由信息可能是地址用于地址路由也可能是ID用于ID路由或隐式路由用于消息。6.2 四种事务类型到底谁在问谁在答PCIe的事务可以分为四大类Memory ReadMRd读内存操作非Posted必须要有完成报文Cpl返回Memory WriteMWr写内存操作Posted不需要完成报文Configuration Read/Write配置空间读写非Posted必须返回完成报文Message消息事务如中断MSI/MSI-X用的是隐式路由。要不要完成报文Completion这是理解PCIe事务的核心逻辑之一。Posted事务如MWr发出去后不需要对方确认Non-Posted事务如MRd则需要对方返回一个Completion报文里面不仅包含读取的数据还包含完成状态。举个例子CPU要读取某个PCIe设备的MMIO寄存器。系统发起一个MRd TLP目标收到后执行实际读取然后构建一个CpLdCompletion with DataTLP返回。CPU侧收到CpLd后才知道这次读操作完成了。这个机制直接影响了驱动编写时的时序处理。很多驱动里的读操作结束后会立刻使用返回值但如果事务层没有正确处理好顺序和完成报文很容易出现读到旧数据或者长时间挂起的问题。写驱动时务必留意读操作的完成等待逻辑必要时加上超时保护。6.3 四种地址空间一路总线上的四个维度PCIe支持以下四种地址空间内存空间Memory Space用于MMIO和内存读写这几乎是最常用的空间IO空间I/O Space为了兼容老设备保留的新设计尽量避免使用配置空间Configuration Space访问设备的寄存器和能力列表是枚举的基础消息空间Message Space用于中断、电源管理等控制信息不占用地址。为什么开发者只需要关心前两个因为实际项目中绝大多数PCIe设备都把控制和状态寄存器映射到内存空间IO空间基本被淘汰。配置空间则由BIOS和操作系统内核在启动时负责初始化驱动通常只读取部分内容。6.4 事务层的路由方式TLP的路由方式有三种地址路由Address Routing用于内存和IO读写TLP头里直接携带目标地址ID路由ID Routing用于配置读写头里携带BDF信息隐式路由Implicit Routing用于消息事务不指定具体目标由中间设备决定传递方向。路由规则看一遍可能记不全但实际工作中只要抓住一点配置读写靠ID路由内存靠地址路由。例如BIOS枚举时读写某个设备的配置空间必须用BDF号来寻址而驱动访问BAR空间则是走地址路由。7. 软件视角配置空间与枚举流程7.1 配置空间里藏着设备的身份证和能力清单PCIe设备的配置空间大小是4KB前256字节是PCI兼容区域后续区域存放PCIe扩展能力结构。驱动开发时最常读的是这几个字段Vendor ID厂商编号Device ID设备编号Command/Status寄存器控制设备行为BARBase Address Register设备分配的地址空间基址Capabilities Pointer指向能力链表比如MSI能力、PCIe能力、PM能力等。系统初始化时软件会扫描整个PCIe总线给每个设备分配BDF编号并对设备的BAR宽度进行探测然后为每个BAR分配相应的系统物理地址范围。7.2 枚举是怎么一步一步完成的枚举的整体过程并不复杂从总线0开始扫描RC本身占据0号总线段在每个设备号上读取Vendor ID如果返回全F说明该位置没有设备跳过如果读到有效的Vendor ID则进一步读取Header Type判断是单一功能还是多功能设备如果设备是Switch桥就会给它分配新的总线号然后递归扫描下游总线对每一个设备依次读取它的BAR确定需要多大的地址空间然后分配地址并写入BAR。这里有一个非常关键的工程细节BAR的宽度探测。软件会先向BAR寄存器写入全10xFFFFFFFF然后回读根据哪些位为0来确定BAR的可编程位宽。比如一个BAR被写入全1后回读得到0xFFFF0000说明它只有高16位是可变的需要的地址空间是64KB。很多FPGA工程师在自己实现PCIe EP的时候最容易犯的错就是BAR的逻辑写得不完整导致BAR返回的值不符合预期软件无法分配地址。正确做法是BAR寄存器必须支持写入后的读回行为且要根据硬件设计正确设置BAR的类型32位还是64位、可预取属性等。7.3 热插拔与链路错误处理的边界热插拔Hot-Plug在PCIe协议里是一套非常复杂的状态管理机制。从软件角度最简单的方式是依赖RC的链路状态变化事件比如检测到链路退出L0、进入Detect然后扫描新设备。热插拔不是简单地把设备拔下来再插上中间涉及电源管理、驱动绑定、资源释放等过程。在做方案设计时我们通常要考虑是否真的需要热插拔如果只是一个嵌入式系统内部固定设备完全没必要支持热插拔关闭相关能力可以省很多麻烦支持热插拔的设备必须区分出错的链路掉线和人为拔卡。前者往往伴随错误状态需要触发恢复流程后者则是正常移除软件需要优雅卸载驱动。错误处理中还有一个容易被忽略的RX Margin概念。PCIe Gen3及以上引入了接收端余量测试Rx Margin Test用来检查接收端在信号劣化情况下的解码能力。如果你是做信号完整性验证或者可靠性测试这项测试很实用。8. 从PCIe协议解析到实际调试我的几点工程体会写了这么多最后分享几条从实际项目里积累的经验。第一点遇到PCIe设备不识别的问题先把LTSSM状态机搞清楚。用逻辑分析仪或者控制器自带的调试寄存器看看链路到底卡在哪个状态。如果是Detect反复循环大概率是物理连接问题如果是Configuration阶段过不去基本不是信号质量问题而是能力协商不对。方向错了后面全白费。第二点多通道链路带宽不达标时不要第一时间怀疑控制器的DMA或驱动实现。先查物理层的通道对齐Lane Alignment很多x8设备因为PCB Layout对角线避让不当导致某几对差分线性能极差链路虽然能训练成功但始终无法运行在最高速率。高通和博通的交换芯片调试中这类问题非常常见。第三点PCIe驱动开发里MSI中断要比Legacy INTx优先选择。MSI本质上是一条内存写事务直接写入驱动指定的地址中断延迟低且不会出现共享中断的争抢。但MSI也有坑如果驱动没有正确设置MSI Capability或者设备侧MSI地址写错中断会静默丢失表现为设备完全不响应。第四点如果你是自己做PCIe EP的FPGA逻辑强烈建议加上协议分析相关的计数器如CRC错误计数、重传计数、流控信用量状态快照。这些计数器也许不是最终产品功能但调试阶段能让你省下大量时间。PCIe协议的内容非常多这篇文章覆盖的是三层架构中最基础的知识框架。后续的文章里我会继续展开TLP的详细字段、事务层的完成机制、MSI/MSI-X中断深入、以及Switch路由和VC机制的细节。最后如果你在产品开发中遇到PCIe相关的问题欢迎带着具体的现象来讨论。光说设备不工作没法判断但能说出卡在哪个状态哪个错误计数器在涨多半就能循着线索找到原因。
返回列表