ARTICLE DETAIL

资讯详情

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

PCIe RC与EP模式详解:从枚举原理到FPGA实战避坑

PCIe RC与EP模式详解:从枚举原理到FPGA实战避坑 1. 从一次板卡识别失败说起PCIe的RC与EP到底是什么前阵子帮朋友调一块自制的加速卡FPGA烧录正常电源各路电压也对但主机侧就是死活枚举不到设备lspci翻来覆去只有桥片自己的信息。折腾了大半天最后发现问题出在PCIe两端角色的配置上——一端该做RC却按EP的逻辑跑另一端该做EP却把链路训练参数配成了RC的默认值。这个坑其实非常典型很多刚接触PCIe的人都会在谁是主机、谁是从设备这件事上栽跟头。PCIe总线上任何一条链路的两端都必须明确各自的角色一端是RCRoot Complex根复合体另一端是EPEndpoint端点设备。RC通常是CPU侧的那一方负责发起配置请求、管理总线枚举、分配地址空间EP则是挂在总线上的功能设备比如网卡、NVMe盘、显卡、FPGA加速卡。两者不是简单的主从关系而是在协议层、配置空间、事务类型上都有明确分工的两种角色模型。这篇文章想聊的就是这两个模式在实际项目里怎么理解、怎么配、怎么排查。适合正在做FPGA PCIe开发、嵌入式板卡设计、驱动调试或者单纯想搞明白lspci背后发生了什么的读者。不管你是刚上手的新人还是被掉卡、降速、AER报错折磨过的老手下面这些内容应该都能对上你的某些实际场景。2. RC与EP的角色分工与协议差异2.1 为什么PCIe要区分这两种模式要理解RC和EP得先回到PCIe的设计初衷。PCIe是一套点对点的分层协议物理上每条链路只连接两个端口但逻辑上要模拟出传统PCI那种一条总线挂多个设备的拓扑。这个矛盾的解法就是引入RC作为拓扑的根由它向下扩展出一棵设备树。RC的核心职责有三个第一发起配置事务通过配置读写去发现和初始化下游设备第二管理地址空间把系统内存地址、IO地址映射到各个EP的BAR空间第三转发事务把CPU发出的Memory Read/Write请求路由到正确的EP也把EP的中断、DMA请求送回内存。EP的职责就聚焦得多它只需要响应配置请求、实现自己的BAR、按需发起DMA和中断。EP不需要关心总线拓扑也不需要枚举别人它就是一个被管理的功能单元。这种分工带来的直接好处是RC的实现可以很复杂因为它要管整棵树而EP的实现可以相对简单只做自己那一摊。对FPGA开发者来说这意味着如果你要做一块加速卡你实现的是EP侧逻辑如果你要做的是嵌入式主控板那CPU那侧就是RC。2.2 配置空间里的角色线索判断一个端口是RC还是EP最直接的证据在PCIe Capability结构里的PCI Express Capabilities Register。这个寄存器的bit 7:4是Device/Port Type字段取值含义大致如下字段值设备类型典型场景0000PCI Express Endpoint普通功能设备0001Legacy PCI Express Endpoint老式端点0100Root Port of Root ComplexRC的下行端口0101Upstream Port of Switch交换机上行口0110Downstream Port of Switch交换机下行口1000PCI Express to PCI/PCI-X Bridge桥接设备RC本身不是一个单一设备而是由Root Port和内部总线构成的复合体。你在lspci里看到的Root Port就是RC暴露给软件的下行端口每个Root Port下面可以挂一个EP或者一个Switch。注意有些FPGA的硬核PCIe在RC模式下配置空间里的Device/Port Type会显示为Root Port但它的行为并不完全等同于CPU原生的RC。做兼容性测试时不能想当然。2.3 事务类型上的关键区别RC和EP在事务层能发起和接收的TLP类型是不一样的。RC可以发起CfgRd0/CfgWr0Type 0配置请求和CfgRd1/CfgWr1Type 1配置请求而EP只能响应Type 0配置请求不能主动发起配置事务。在Memory事务上RC和EP都可以发起MemRd/MemWr但方向不同RC发起的通常是CPU访问EP BAR的请求EP发起的通常是DMA写内存的请求。中断方向上EP通过MsgMSI/MSI-X或者Intx向RC报告RC负责把这些中断转成CPU能识别的信号。这个差异在实际调试里非常有用。如果你抓TLP发现某个端口在发Type 1配置请求那它一定在扮演RC或者Switch的角色如果它只响应Type 0那基本可以确定是EP。3. 枚举过程RC如何一步步发现EP3.1 枚举的整体流程PCIe枚举是RC的看家本领整个过程可以拆成几个阶段链路训练物理层先完成LTSSM把链路从Detect推到L0状态确定链路宽度和速率。读取Root Port自身信息RC先确认自己的Root Port配置空间可访问。扫描Bus 0从Bus 0 Device 0 Function 0开始逐个读取Vendor ID。如果读到0xFFFF说明该位置没有设备。发现桥或Switch如果读到的设备Header Type是桥类型就给它分配一个次级总线号继续向下扫描。分配BAR空间对每个发现的EP读取BAR的size需求然后往BAR里写基地址。配置完成所有设备地址分配完毕系统可以正常访问。这个过程在Linux内核里由pci_scan_root_bus系列函数完成在UEFI里由PCI Bus Driver完成。不管哪套软件底层依赖的都是同一套配置事务机制。3.2 配置请求的Type 0和Type 1枚举过程中最关键的概念是Type 0和Type 1配置请求的区别。Type 0用于访问当前总线上的设备Type 1用于访问下级总线上的设备。RC的Root Port在收到Type 1请求时会把它转换成Type 0发给下游如果下游还是桥就继续用Type 1往下传。这个机制解释了为什么枚举是逐层深入的。RC先扫Bus 0发现Root Port下面有桥就给桥分配Bus 1然后通过Type 1请求去扫Bus 1以此类推。整个设备树的Bus号就是这样一层层分配出来的。实操心得如果你在调试时发现某个设备时有时无先确认它的Bus号是不是每次枚举都一致。有些设计里Bus号分配依赖枚举顺序顺序一变设备路径就变了驱动可能就找不到设备。3.3 BAR空间分配的计算逻辑BAR分配是枚举里最容易出问题的一环。每个EP的BAR寄存器都有一个size探测机制往BAR里全写1再读回来读到的值里低位为0的位数就反映了这个BAR需要多大空间。举个例子假设一个BAR写全1后读回0xFFFFF000说明低12位是只读0这个BAR需要4KB空间。RC在分配时会把基地址对齐到4KB边界然后把实际地址写进BAR。BAR读回值空间大小对齐要求0xFFFFF0004KB4KB0xFFFF000064KB64KB0xFFF000001MB1MB0xFF00000016MB16MB如果多个EP的BAR需求加起来超过了RC能提供的地址窗口就会出现资源不足的报错。这种情况在嵌入式平台很常见因为很多SoC的PCIe地址窗口是固定的不像x86那样有较大的可配置空间。4. FPGA实现RC与EP的实操要点4.1 Xilinx PCIe硬核的模式选择以Xilinx的PCIe硬核为例在IP配置界面里有一个明确的PCIe Device / Port Type选项可以选Root Port of PCI Express Root Complex或者PCI Express Endpoint。这个选项直接决定了硬核内部状态机的行为。选RC模式时硬核会主动发起链路训练和配置扫描需要你提供配置空间的实现逻辑选EP模式时硬核只响应上游请求你需要实现BAR译码和DMA引擎。注意Xilinx不同系列的PCIe硬核在RC模式下的支持程度不一样。有些系列的RC模式只支持有限的配置空间不能完整模拟x86 RC的行为。选型前一定要查对应系列的PG文档。4.2 链路训练参数的配置链路训练是物理层的事但配置参数会直接影响训练结果。关键参数包括Link SpeedGen1/Gen2/Gen3/Gen4取决于硬核和参考时钟。Link Widthx1/x2/x4/x8/x16取决于板卡走线和连接器。Reference Clock100MHz或125MHz必须和板卡实际时钟一致。如果链路训练失败最常见的原因是参考时钟频率配错或者TX/RX极性反了。有些硬核支持极性反转自动检测有些需要手动配置。4.3 RC模式下配置空间的实现在FPGA里实现RC最麻烦的部分是配置空间的模拟。你需要实现一套逻辑让软件读配置空间时能返回合理的值。至少要包含Vendor ID / Device ID标识你的RC。Header Type必须是桥类型0x01否则软件不会把它当Root Port处理。Bus Number寄存器Primary/Secondary/Subordinate三个字段。Memory Base/Limit定义下游地址窗口。这套逻辑通常用BRAM加状态机实现复杂度不低。如果只是做点对点测试可以简化实现但要做完整枚举就必须老老实实把配置空间做全。4.4 EP模式下BAR和DMA的实现EP侧相对简单但BAR和DMA是绕不开的。BAR实现的核心是地址译码当上游发来的Memory请求地址落在某个BAR范围内时把请求路由到对应的内部逻辑。DMA实现的关键是描述符管理和完成包生成。EP发起DMA读时需要发MemRdTLP等RC返回CplD发起DMA写时直接发MemWr不需要等完成包。中断通过MSI/MSI-X发送需要正确配置MSI Capability结构。5. 常见问题排查与避坑经验5.1 掉卡、降速、AER报错的排查思路掉卡、降速、AERAdvanced Error Reporting报错是PCIe调试的三大经典问题。排查时建议按以下顺序先看链路状态读Link Status Register确认当前速率和宽度是否和预期一致。再看AER日志lspci -vvv里能看到Correctable/Uncorrectable Error状态定位是物理层还是事务层问题。检查参考时钟时钟抖动过大或频率偏差会导致链路不稳定。检查电源和复位PERST#时序不对会导致设备初始化失败。检查走线和连接器信号完整性问题是降速的常见原因。现象可能原因排查手段完全枚举不到链路未训练成功查LTSSM状态、参考时钟枚举到但降速信号完整性差查眼图、走线阻抗运行中掉卡电源不稳或过热查电源纹波、温度AER Correctable Error链路误码查信号质量、均衡设置AER Uncorrectable Error事务层协议错误查TLP格式、配置空间5.2 热插拔功能的实现要点PCIe热插拔Hot-Plug需要RC侧和EP侧配合。RC侧要实现Slot Capabilities和Slot Control寄存器提供电源控制、注意按钮、存在检测等信号EP侧要能承受带电插拔的电气冲击。软件侧Linux的pciehp驱动负责处理热插拔事件。如果热插拔不工作先确认Slot Capabilities里的Hot-Plug Capable位是否置1再看Slot Status里的Presence Detect是否正常变化。实操心得热插拔调试时建议先用setpci手动读写Slot寄存器确认硬件信号通路正常再交给驱动处理。这样能把硬件问题和软件问题分开定位。5.3 兼容性问题的处理PCIe兼容性问题往往出现在不同厂商的RC和EP互连时。常见的不兼容点包括配置空间字段不符合规范有些EP的配置空间实现不规范遇到严格的RC就会枚举失败。时序参数不匹配比如Acceptable Latency设置不合理。MSI/MSI-X支持不一致有些老RC只支持MSI不支持MSI-X。电源管理状态转换异常L1/L2状态转换时序不对。处理兼容性问题的通用方法是抓TLP对比用协议分析仪抓正常工作的组合和异常组合的TLP逐字段对比通常能很快定位差异。6. 几个容易被忽略的细节6.1 32位系统下的PCIe地址映射在32位系统上PCIe设备的BAR必须映射到32位地址空间内。如果EP的BAR需求超过4GB或者RC的地址窗口配置不当就会出现映射失败。Realtek的PCIe GbE控制器在32位系统上有时会遇到这类问题解决方法是确认BAR的64-bit Addressing位是否正确设置以及RC是否为64位BAR分配了合适的地址。6.2 链路速率与宽度的实际协商链路训练时RC和EP会协商出一个双方都支持的速率和宽度。这个协商结果不一定等于双方的最大能力。比如RC支持Gen3 x8EP支持Gen3 x4最终协商结果就是Gen3 x4。如果协商结果低于预期先确认双方的能力寄存器再看训练过程中的LTSSM日志。6.3 仿真环境下的RC/EP建模做PCIe仿真时通常需要一端做RC模型一端做EP模型。RC模型要能发起配置请求和Memory请求EP模型要能响应并生成完成包。仿真环境里最容易出错的是完成包的超时处理和信用量管理这两个机制不对仿真就会卡死。提示仿真时建议先用最简单的配置读写跑通再逐步加入DMA和中断不要一上来就搭完整系统。7. 我个人在实际操作中的几点体会调PCIe这些年最大的感受是协议规范要读但不能只读规范。规范里写的都是应该怎样实际项目里遇到的往往是厂商实现成了怎样。RC和EP的角色划分在规范里很清楚但落到具体芯片上总会有各种细节差异。另一个体会是抓包工具比什么都重要。不管是硬件协议分析仪还是FPGA内部的ILA能看到真实TLP流排查效率会高一个数量级。很多问题靠读寄存器是猜不出来的一看TLP就明白了。最后分享一个小技巧调试PCIe时先把链路降到Gen1 x1跑通基本功能再逐步升速升宽。这样能把物理层问题和协议层问题分开避免一上来就被复杂的信号完整性问题干扰。等基本功能稳定了再优化速率和宽度整个过程会顺畅很多。
返回列表