ARTICLE DETAIL

资讯详情

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

PCIe协议入门:三层架构与数据流核心解析

PCIe协议入门:三层架构与数据流核心解析 入行做嵌入式或者系统底层开发几乎没人能绕开 PCIe。以前我在 FPGA 上调 PCIe跟大多数人一样拿一块开发板照着例程接上 M.2 NVMe 硬盘觉得管脚定义对、复位拉一下、时钟给上就能把硬盘点亮。结果现实马上打脸lspci 里什么都没有链路一直停在 Detect 状态甚至看不到任何枚举信息。那一刻我才意识到光靠抄参考设计根本不够必须把 PCIe 协议拆开看。可是真去看规范厚厚一本书事务层、数据链路层、物理层、TLP、DLLP、LTSSM全是术语直接啃很容易劝退。这篇文章是 PCIe 协议解析系列的第一篇先不碰寄存器细节也不急着去背报文格式把三层架构的基本盘打牢PCIe 解决什么问题三层各管什么事情一条读写请求是怎么经过封装和解包最终完成的。搞清楚这些之后再去看 LTSSM 状态迁移、TLP 字段细节、枚举流程你会发现自己有了地图不再是被动跟着报错信息瞎猜。1. 为什么绕不开 PCIe协议背景与核心定位1.1 并行总线的天花板最早的计算机内部总线比如经典的 PCI是并行共享架构一条总线上挂多个设备所有设备共享同一条数据通路谁有空谁上。PCI 跑在 33MHz 或者 66MHz频率不算高但已经是那个年代的准极限。并行总线想要提速就得增加时钟频率可是频率一高几十根数据线之间的串扰、偏斜、时序收敛问题马上爆炸。你让每根线的走线等长已经很难再要求每根线的信号到达时间差保持在极小范围内对 PCB 工艺几乎是灾难。PCI-X 算是在并行架构里做过一次补丁把频率提到 133MHz 甚至更高但它的本质还是共享总线设备一多争抢立刻明显。与此同时CPU 频率在涨、内存带宽在涨、千兆网卡开始普及显卡对带宽的胃口更是无底洞共享并行总线的天花板已经摆在那里必须重新设计。PCIe 就是在这个背景下被推出来的。它彻底放弃并行共享改用串行差分、点对点、多通道架构。2003 年 PCIe 1.0 规范发布之后 2.0、3.0、4.0、5.0、6.0 一路走过来如今不管是消费级主板上的显卡槽、M.2 接口还是服务器里的 NVMe SSD、网卡、RAID 卡底层互联几乎全是 PCIe。做硬件、做驱动、做 FPGA 的人都要跟 pcie 协议打交道这个趋势短期看不会变。1.2 点对点架构带来的革命性变化共享总线和点对点最本质的区别可以从一个生活场景理解共享总线是一条旱桥所有人过桥都要排队人越多越慢PCIe 是给每对设备单独建了一条高速公路正常情况下谁都不堵谁。PCIe 的物理拓扑里CPU 侧有一个 Root ComplexRC相当于整个系统的“枢纽”。RC 下面可以直连设备也可以通过 PCIe Switch 扩展出多个下游端口。Switch 本质上是一个内部转发网络它把上行端口的一笔交易根据地址或者ID转发到对应的下行端口类似一个路由器。因此热词里经常看到的 pcie switch以及“链路带宽分配”“多设备争抢”这些问题本质上都发生在 Switch 内部。点对点架构带了两个直接的好处。第一每条链路有自己独立的握手、训练和电源管理流程一条链路建不起来不会拖垮整台机器第二每个通道组都是全双工发送和接收同时进行这和并行总线那种“半双工共享”有本质区别。我们可以把 pcie x、x16 这类数字理解成并行开几条车道车道越多带宽越宽。1.3 PCIe 和 PCI 到底什么关系这个问题几乎每次培训都有人问。名字里带“PCI”那它和传统 PCI 兼容吗电气上完全不兼容PCI 是并行 3.3V/5V 电平PCIe 是串行差分高速信号插槽也不通用。但为什么还能沿用“PCI”这个名字因为 PCIe 在设计之初就刻意保留了 PCI 时代的软件模型。驱动和操作系统看到的东西比如配置空间、BAR、中断机制、BDFBus/Device/Function编号方式PCIe 都以兼容形式继承下来。你可以理解成换了一副全新的身体但是灵魂和说话方式还是老一套。所以做驱动的人写出来的枚举扫描代码跟二十年前的 PCI 驱动思路几乎一模一样。这也就是为什么 pcie 枚举过程里你会看到大量配置读、配置写的事务它们的存在就是为了让操作系统把设备识别出来并分配资源。2. 三层架构总览事务、链路、信号的合理分工2.1 三层到底长什么样PCIe 协议栈从上到下分成三层事务层Transaction Layer、数据链路层Data Link Layer、物理层Physical Layer。这个结构和 OSI 七层模型有点神似但没有那么复杂每一层只管自己的事。事务层在最上面直接和软件、 DMA 引擎打交道。它负责的事情包括生成和解析事务层包TLP、维护四种地址空间的事务类型、做流量控制、管理虚拟通道。简单说驱动发起一次读或写真正干活的代码就在这一层。数据链路层在中间负责相邻两个设备之间点对点的可靠传输。它给 TLP 加序号和 CRC然后做确认Ack和重传Nak/Replay保证对端收到的报文没有错、没有丢、没有乱序。这一层对软件完全透明你不主动去看寄存器根本感觉不到它的存在。物理层在最底下负责把二进制数据变成差分电信号。包括编码解码、加扰、串并转换、链路训练状态机LTSSM、速率协商、通道绑定等。你平时关心的 PCIe 跑在 Gen3 还是 Gen4、x1 还是 x16全部是这一层说了算。2.2 分层设计解决了什么问题分层设计的核心价值是解耦。我经常用快递系统来类比事务层像是“填快递单的人”它决定寄什么东西、寄给谁数据链路层像是“贴面单和验货的人”它保证包裹能安全完整送到物理层是“运输车辆和道路”负责实际把包裹拉过去。正因为分层每一层可以独立演进。PCIe 从 Gen1 到 Gen5编码方式从 8b/10b 换成了 128b/130b速率涨了十几倍但事务层的 TLP 格式基本没变驱动软件的兼容性得以保留。反过来如果哪天出现全新的物理层实现只要接口约定不变上层协议照样能用。我们在做硬件设计时也继承了这种思想你的用户逻辑只需要关心怎么给事务层下命令至于信号有没有毛刺、链路训练有没有成功那是物理层的事IP 核会替你处理。2.3 报文从上层到下层的封装之旅理解了分层再看报文流转就很清晰。假设 CPU 要读设备里的某个数据流程大致是事务层构造一个 Memory Read TLP包含地址、长度、Tag 等信息数据链路层给这个 TLP 加上序号和 LCRC形成一个更外层的数据单元物理层再做编码、加扰、串行化最终通过差分线发出去。接收方向反过来物理层恢复比特流数据链路层校验 CRC、确认序号事务层解析 TLP取出数据交给设备内部逻辑。整个封装过程很像寄包裹。你写一封信事务层决定用多大信封、写收件人地址数据链路层贴封条、编号物理层装上车出发。收件人拆包时先验封条再读信。这种每一层只关心自己职责的设计极大降低了 PCIe 复杂度也降低了我们学习和调试的难度。3. 事务层PCIe 的“大脑”和数据交互核心3.1 事务、TLP 和完成机制事务层里有个非常核心的概念叫“事务”。PCIe 的事务分两类Posted 和 Non-Posted。Posted 事务发出去后不需要对方回完成包典型例子是内存写、消息Non-Posted 事务发出后必须等对端返回完成包Completion TLP典型例子是内存读、配置读。为什么读一定要有完成包因为读请求本身不携带数据目标设备收到读请求后内部需要时间去取数据然后把数据放在 Completion TLP 里传回来。这种一问一答的机制天然带来延迟。所以你在写高性能驱动时小块的散读永远不如大块连续读快因为每一笔 Read 都要吃一个完整往返。Posted 写为什么不需要完成因为写请求自己就带着数据目标收到即可不必回包。这省掉了一半往返所以大块 DMA 写的吞吐往往比读好看这也是很多 DMA 引擎喜欢优先用写的原因之一。3.2 TLP 的结构和关键字段TLP 是事务层的核心载体它由报头Header、数据负载Data Payload和可选的摘要Digest组成。最常用的是 12 字节报头带数据时再加 4 字节如果开了地址转换之类的扩展能力会变成 16 字节或 20 字节。报头里的字段驱动工程师最需要关注几个Fmt 和 Type 决定事务类型Length 表示数据负载长度Requester ID 是发起方的 BDFTag 是事务标签用来把请求和对应的完成包对上号Address 是目标地址Last DW BE 和 First DW BE 决定数据有效字节。平时用逻辑分析仪抓 PCIe 报文第一眼先确认 Fmt/Type再看地址和 Tag基本能定位大多数异常。数据负载长度受 MPSMax Payload Size限制一般协商为 128B、256B、512B。MPS 不是越大越好越大单次传输效率越高但缓冲区、错误爆炸半径也更大。调试阶段我习惯先把 MPS 设为 128B减少很多兼容性问题。3.3 四种地址空间与它们的分工事务层支持四种地址空间对应 PCIe 中最常用的四种业务地址空间典型用途主要事务类型Memory 空间寄存器映射、DMA 缓冲区MemRd/MemWrPostedI/O 空间老设备命令寄存器新系统很少用IORd/IOWrNon-PostedConfiguration 空间设备识别、BAR、枚举CfgRd/CfgWrNon-PostedMessage 空间中断、错误上报、电源管理Msg/MsgLockPosted日常工作里大部分流量都落在 Memory 和 Configuration 两个空间。pcie 枚举过程的核心就是 RC 通过类型 0 和类型 1 配置事务逐个扫描总线上的 BDF读 Vendor ID、Device ID、Class Code、BAR 寄存器然后为每个设备分配地址空间把它的 MMIO 窗口映射到系统的物理地址范围。只要你看到一个设备在 lspci 里消失大概率就是配置枚举阶段出了问题。3.4 流量控制与虚拟通道事务层的背压机制事务层有个容易被忽略但极其重要的机制基于信用的流量控制Credit-Based Flow Control。发送方不会无限制地往链路上灌数据而是根据接收方公布的缓冲区信用动态决定能发多少。信用按 Header 和 Data 两个维度独立核算接收方在初始化时发送 InitFC 包公布初始信用运行中再用 UpdateFC 包更新。这个机制非常像支付宝先看余额再付款你余额不足交易就只能暂停。发送端信用耗尽时事务层会把待发 TLP 排在内部队列里等对方更新信用再继续。所以“PCIe 有没有背压”这个问题的答案就在事务层有但它是通过信用机制实现的而不是像 SPI 那样拉低电平。虚拟通道VC也在这个层级管理。VC 允许把不同优先级的数据流隔离低优先级流量不能堵死高优先级流量。不过 VC 在实际消费级设备中很少用绝大多数设备只有一个 VC0了解概念即可。4. 数据链路层可靠传输的“快递员”和纠错主力4.1 数据链路层在传输中扮演的角色数据链路层夹在事务层和物理层中间位置尴尬但责任重大。它自己不产生业务数据却要保证所有 TLP 在链路上都能完整无差错到达对端。实现手段就两招加校验、做重传。发送方向数据链路层给每个 TLP 加一个递增的序列号Sequence Number再计算 LCRCLink CRC一起打包成物理层能处理的载荷接收方向先检查序列号是否连续再用 LCRC 校验数据完整性。校验失败接收方发一个 Nak DLLP校验通过发 Ack DLLP。发送方如果一段时间没收到 Ack就会触发 Replay 机制把没被确认的 TLP 原样重发一遍。驱动里常见的 pcie 报错比如“Replay Timer Timeout”根源就是这一层不断重传仍然失败。重传本身是好事它能掩盖很多瞬时噪声但如果链路信号质量差到一定程度重传也会跟不上最终表现为吞吐暴跌或设备离线。4.2 DLLP链路层自己发的“管理私聊”数据链路层除了替 TLP 跑腿还需要自己维护链路状态。它和管理者之间会发送一种短报文叫数据链路层包DLLP包括 Ack/Nak 确认、电源状态转换、流量控制更新等。DLLP 没有序列号也不被重传因为它是管理报文丢了直接重发一个新的就行。这些 DLLP 走的是和 TLP 一样的物理通道但是有特殊的标识接收方一眼就能区分。做 FPGA PCIe 的时候逻辑分析仪里的报文如果能同时看到 TLP 和 DLLP你就能完整还原链路行为哪些 TLP 被确认了、哪些触发了重传、链路有没有进入低功耗状态。这套观察手段比光看事务层的数据要高效得多。4.3 对软件透明但错误绝不沉默数据链路层对上层软件透明这是设计目标但它不是完全隐形。一旦发生重传超时、链路异常等严重情况数据链路层会通过事务层往根联合体上报错误。有些错误是可纠正的比如 LCRC 错误然后重传成功有些是不可纠正的比如重传也失败系统可能直接触发 AERAdvanced Error Reporting中断把设备标记成故障状态。调试时最怕的不是报错而是“静默失败”。比如你往 BAR 写一个寄存器看起来 CPU 侧一切正常但数据根本没到设备。这种问题多半发生在枚举没成功、BAR 没被正确映射或者事务层的 Tag 不匹配。所以我一直强调先确认枚举再确认链路状态最后才做业务读写顺序不能乱。5. 物理层链路如何建立、训练和跑满带宽5.1 物理层的组成与通道概念物理层拆成逻辑子层和电气子层两部分。逻辑子层负责编码、加扰、链路状态机、通道绑定这类“数字活”电气子层负责 SerDes、差分驱动、时钟恢复这类“模拟活”。对做系统集成的人来说要关注的是 Lane 这个概念。一条 Lane 由两组差分对组成TX 差分对和 RX 差分对全双工同时工作。设备可以同时拥有多条 Lane最常见的组合是 x1、x4、x8、x16。链路实际工作的 Lane 数不一定等于物理连接数因为在训练阶段会协商某些 Lane 信号质量太差会被降级甚至整条链路自动降速。PCIe 还支持 Lane Reversal 和极性反转就是为了让 PCB 布线更灵活你可以随意交换 Lane 顺序只要训练时双方能协商回来。5.2 速率演进和编码效率PCIe 从 Gen1 到 Gen6每一代速率翻倍但编码开销不一样所以有效带宽增长更明显。Gen1 和 Gen2 使用 8b/10b 编码每发 8 位数据要带上 2 位冗余位效率只有 80%Gen3 开始改成 128b/130b每 128 位数据只带 2 位开销效率提升到 98.5% 左右这也是 Gen3 相对 Gen2 能效比明显更好的原因之一。Gen12.5 GT/s单 Lane 理论有效带宽约 250 MB/sGen25 GT/s单 Lane 约 500 MB/sGen38 GT/s单 Lane 约 985 MB/sGen416 GT/s单 Lane 约 1969 MB/sGen532 GT/s单 Lane 约 3937 MB/s在 Linux 下想查设备跑在哪个速率lspci -vvv 里找 LnkSta 字段看到 16GT/s 就是 Gen432GT/s 就是 Gen5。很多同学问“ubuntu查看pcie是4.0还是5.0”方法就这一个不用去猜主板型号。5.3 LTSSM链路训练的核心状态机物理层最复杂的地方是 LTSSMLink Training and Status State Machine链路不是插上就能用的它必须经过一套严格的握手机制。整个训练过程包括 Detect、Polling、Configuration、L0、Recovery、L1/L2 等状态每个状态下面还有子状态。链路训练的起点是 PERST# 释放。PERST 是 PCIe 的复位信号低有效。主机电源稳定后撤掉 PERST设备复位释放物理层随即进入 Detect 状态检测链路另一端是否存在。检测成功后就进入 Polling双方互发 TS1/TS2 序列集TS 序列里带链路号和通道号用于速率协商、Lane 锁定和极性判断。之后是 Configuration 阶段决定链路宽度和实际 Lane 映射。全部完成进入 L0链路进入全速运行状态。热词里常见的“pcie ltssm configuration阶段和子阶段报文流转图”指的就是这段 TS1/TS2 的你来我往。你可以用协议分析仪抓包看到双方交替发送带 Link Number 和 Lane Number 的序列集整个协商过程非常有意思。5.4 链接跑起来以后热插拔和复位怎么管链路进入 L0 不代表永远稳定运行期间出现大量错误会触发 Recovery 状态两端重新训练。热词里提到的 pcie recovery 子状态超时往往就是信号质量差导致 Recovery 后仍无法恢复最终链路断开。排查时优先看物理层信号完整性再看是不是供电波动最后检查是不是速率协商过高导致裕量不足。热插拔是 PCIe 规范里一个受控功能标准定义了热插拔控制器和对应流程。但要注意热插拔不等于“随时拔”需要软件、电源、链路状态配合。消费级主板上的 M.2 接口大多不保证热插拔强行热拔轻则丢数据重则烧接口。工程上要分清哪些接口可以热插拔哪些不能别拿服务器背板的习惯套到台式机上。还有个容易混淆的点pcie 半高全高这是指挡板机械尺寸半高挡板适合 2U 机箱全高挡板适合塔式机箱它和电气性能没有关系但采购硬件时必须匹配机箱规格。5.5 信号完整性和接收裕量物理层最后不能不提信号完整性。高速信号对阻抗、串扰、走线长度非常敏感Gen4 以上尤其明显。热词里提到的 pcie rxmargin指的是接收端裕量测试通过注入不同幅度的扰动来评估链路还有多少余量。如果 rxmargin 测试结果很窄说明链路处在“勉强能用但随时会挂”的边缘状态。这也是为什么很多服务器 BIOS 里可以强制降速运行牺牲一点性能换取稳定性。实际项目中我遇到过几次 PCIe 链路在某个特定温度下频繁掉线的现象最后查出来是 PCB 板材损耗过大、走线过长导致的信号质量衰减。这时候单纯调软件没用要么缩短走线、换低损耗板材要么在 BIOS 里选择较低的速率档先保证业务能跑再谈性能。6. 三层如何协同一次读事务的完整旅程6.1 从 CPU 发起读到设备返回数据的全过程把三层机制串起来看一个完整的读事务是这样的驱动层准备好 DMA 描述符并触发一次 Memory Read 操作。事务层检查对方信用是否足够然后将读请求封装成 MemRd TLP填好地址、长度、Tag。数据链路层给 TLP 加序列号和 LCRC形成可发送的帧。物理层编码、加扰、串行化通过差分线发出去。对端物理层恢复比特流数据链路层校验 LCRC 和序号并回送 Ack。对端事务层解析 TLP把请求交给设备内部逻辑。设备内部完成读操作构造 Completion TLP填上相同的 Tag反向再走一遍三层封装。发起端事务层收到 Completion TLP校验 Tag 匹配后把数据交给驱动或 DMA 引擎。这里可以看到读事务至少经历一次完整往返延迟高于写事务。优化系统性能时大到块 DMA 读比很多小读更划算因为一次往返能带回大量数据。6.2 枚举和地址分配的作用开机阶段操作系统并不知道你插了什么设备所以 RC 会通过配置读写事务逐总线扫描。扫描到某个 Bus 上存在设备时会给它分配 BDF读取配置空间里的 BAR 寄存器获取设备需要的地址空间大小再在系统物理地址空间里分配对应窗口。这个窗口映射完成后设备和 CPU 之间的 MMIO 通信才变得可能。pcie 枚举过程如果出错常见现象是 lspci 看不到设备、设备被分配了错误的资源、或者中断无法注册。排查时要先确认配置访问是否成功再检查设备端 BAR 是否返回了合法的 size 和 type。6.3 分层思想对硬件实现的影响分层不仅让协议演进方便也让硬件设计变得模块化。以 FPGA 为例Xilinx 的 XDMA、Intel 的 PCIe Hard IP其实都是把三层大部分实现封装进 IP 核。你看到的用户接口通常是 AXI 或者 Streaming 接口底层 TLP、DLLP、LTSSM 已经由 IP 处理。但这不代表你不用懂协议。实际调试中xilinx pcie xdma 出现的很多问题不是 IP 本身坏了而是用户逻辑的事务层语义错了比如 Tag 用尽、描述符错误、Completion 不匹配。懂协议你才能判断问题发生在你的代码里还是在链路里。7. 实测踩坑常见问题与排查技巧实录7.1 启动阶段最常遇到的问题速查现象可能原因排查方法lspci 看不到设备PERST# 时序不对、电源不稳示波器看 PERST# 和主电源时序链路速率比预期低信号完整性差、协商失败看 LnkSta尝试强制低速率读写请求超时枚举失败、BAR 未分配检查 dmesg 资源分配传输间歇性丢包Replay 超时、信号串扰看 AER 计数、测量眼图设备突然消失链路进入 Recovery 后无法恢复看链路状态、供电、波形7.2 恼人的“无线网卡掉线”最近有个朋友跟我吐槽他用一个采用 RTL8852BE 方案的 PCIe Wi-Fi 6 无线网卡网页测速到一半就断连。这个现象很典型测速时 PCIe 链路进入高吞吐状态负载一大如果供电纹波超标或者信号裕量不足链路就会掉进 Recovery再恢复时连接已经断了。表面看是 Wi-Fi 驱动不稳定实际根子很可能在 PCIe 物理层和供电。遇到这种问题第一步进入 Linux 看 lspci -vvv 里设备的 LnkSta 和 DevSta确认训练速率是否正常、有没有可纠正错误计数。第二步看 dmesg 有没有 AER 或链路降速的记录。第三步才能轮到大改软件先确认硬件链路稳定再做驱动优化顺序反了容易白忙。7.3 调试工具和独门建议调试 PCIe 的手段按成本从低到高排列Linux 下的 lspci、dmesg、/proc/interruptsFPGA 内部的逻辑分析仪PCIe 协议分析仪。软件手段能解决不少问题看不到的信号问题才需要上硬件。几个亲测有效的建议。第一FPGA 调试时先把 MPS 和 MRRS 强制设成 128B能规避大量潜在的兼容性死锁第二链路训练不稳定时先用 BIOS/系统强制 Gen2 甚至 Gen1快速区分是物理层高速问题还是上层逻辑问题第三观察 pcie 信号如何建链时不要把目光只放在数据线上PERST# 时序、参考时钟的抖动和摆幅同样关键时钟不好链路根本上不高速第四遇到 Recovery 子状态超时先检查是不是板卡功耗过高导致供电跌落很多莫名其妙断链最后都是电源纹波惹的祸。还有个很实用的小技巧Linux 下除了看 lspci还可以看 /sys/bus/pci/devices/ 下的各类计数器文件。有些平台会暴露 Link Control、AER Capability 等诊断字段配合 lspci -xxx 去读原生配置空间往往能直接定位到具体错误位。写在最后分层的思维比背寄存器更有价值写到最后说点个人体会。学 PCIe最容易踩的坑就是钻进寄存器海洋里出不来。寄存器很多但绝大多数问题不需要你背寄存器表需要的是分层定位的能力。遇到任何 pcie 问题先问自己一句“这到底是哪一层的事”链路训练失败是物理层和 LTSSM 的事重传超时是数据链路层的事Completion 不匹配或者 Tag 丢失是事务层的事。能定位到层问题就解决了一半接下来再去翻规范或者抓包都不会迷路。这篇先把三层架构和基本数据流讲清楚。后续我会接着写 TLP 的详细字段解析、LTSSM 每个状态的报文流转、PCIe 枚举实战、以及基于 XDMA 和 NVMe 的实操案例。如果你手头正好有 PCIe 问题要排查不妨先按这篇文章的分层思路理一遍应该比你现在对着错误码瞎猜要快很多。
返回列表