022、PCIE配置读写事务:从一次诡异的设备失联说起
022、PCIE配置读写事务从一次诡异的设备失联说起上个月调试一块自研的FPGA加速卡系统启动后设备时有时无。lspci命令能扫到设备但驱动加载时总报配置空间读取失败。用示波器抓链路训练信号都正常最后发现是Type1配置请求的Bus Number字段没处理好——这种问题在PCIE调试中太典型了今天我们就深入聊聊配置事务那些事儿。配置空间PCIE设备的身份证每个PCIE设备都有256字节的标准配置空间前64字节是PCI兼容部分后面是PCIE扩展部分。这个空间就像设备的身份证系统启动时就是通过读取它来识别设备类型、申请资源、分配BAR的。最经典的例子就是读取Vendor ID和Device ID如果读出来全是0xFF说明设备根本不存在或者链路有问题如果读出来是0xFFFF可能是设备还没完成初始化。// 读取配置空间的经典代码片段uint32_tpci_read_config(uint8_tbus,uint8_tdev,uint8_tfunc,uint8_toffset){// 构造配置地址注意Bit31必须置1这是老PCI的遗留设计uint32_taddress(131)|(bus16)|(dev11)|(func8)|(offset0xFC);// 写到0xCF8端口配置地址端口outl(0xCF8,address);// 从0xCFC端口配置数据端口读取数据// 这里有个坑x86是小端但PCIE配置空间是大端视图uint32_tvalueinl(0xCFC);// 根据offset的低2位调整字节对齐return(value((offset3)*8))0xFF;}上面这种IO端口方式只适用于Host CPU直接访问在真实的PCIE交换网络中配置请求是以TLP包的形式传递的。配置请求TLP细节决定成败配置读写事务有两种类型Type0和Type1。Type0用于访问当前总线上的设备Type1用于跨总线访问。我踩过的那个坑就是FPGA逻辑在处理Type1请求时没有正确检查Bus Number是否匹配直接把所有配置请求都当Type0处理了。配置TLP的Header长这样Byte 0: Fmt[7:5] Type[4:0] // Type01000或01001 Byte 1: TC[2:0] Attr[1:0] TH TD EP Byte 2: Length[9:0] // 配置读写永远是DW长度所以是1 Byte 3: Requester ID[15:0] Byte 4: Tag[7:0] Last DW BE[3:0] 1st DW BE[3:0] Byte 5: Bus Number[7:0] Byte 6: Device Number[4:0] Function Number[2:0] Ext Reg Number[7:0] Byte 7: Register Number[3:0] 2b0关键点在于Byte 5的Bus Number字段。对于Type0请求这个字段应该被接收设备忽略对于Type1请求设备需要比较这个值是否等于自己的上游交换机的Secondary Bus Number。很多FPGA的PCIE IP核默认只支持Type0如果你要做多级交换一定要确认IP核是否支持Type1转发。配置访问的三种方式传统PCIE配置访问ECAM方式现在更常见。以x86平台为例每个PCIE Segment有256MB的MMIO空间其中前16MB用于配置空间访问。计算公式是物理地址 0xE0000000 (Segment 27) (Bus 20) (Dev 15) (Func 12) Offset在Linux内核里我们常用pci_read_config_dword()这类函数它们底层最终会转换成对ECAM区域的MMIO访问。在设备驱动开发时直接调用这些API就好不用关心底层是IO端口还是MMIO。但在嵌入式或裸机环境你可能需要自己实现配置访问。这时要注意配置读写不支持突发传输每次只能读/写一个DW4字节。想读一个字节怎么办先读整个DW再掩码出需要的字节——这就是为什么上面的代码示例里有那个移位操作。调试实战当配置访问失败时回到开头那个问题。设备能识别但配置读写失败可以按这个顺序排查确认链路训练成功查看设备的LTSSM状态机是否进入L0状态检查配置请求路由Type0/Type1是否正确Bus/Dev/Func是否匹配观察配置响应设备是否返回了Completion TLPCPL检查Completion状态是CA还是URCACompleter Abort通常表示设备内部错误URUnsupported Request表示地址无效那次调试中我们最终在FPGA逻辑分析仪里看到Host发来Type1请求Bus Number0x02但我们的设备在Bus 0x01上。设备本应忽略这个请求让上游交换机继续转发却错误地尝试处理结果访问了不存在的内部地址返回CA。个人经验配置空间访问的“潜规则”做了十几年PCIE相关开发总结几条教科书上不写的经验配置空间在系统启动早期就被访问这时设备可能还没完全初始化。有些FPGA的PCIE硬核需要几十毫秒才能稳定但BIOS可能在电源稳定后几毫秒就开始枚举了。解决办法是在FPGA代码里加个“就绪”标志位等所有模块初始化完成再置位在此之前对配置访问返回全0xFF。对于多功能设备Single Function vs Multi-FunctionFunction 0的配置空间必须能访问即使这个Function实际不存在。系统可能通过读取Function 0的Header Type寄存器来判断是否有多功能。配置空间的某些字段是只读的但有些FPGA设计允许动态修改比如通过JTAG。这在调试时很方便但正式产品中一定要锁死防止意外修改导致系统识别异常。最后如果你在做PCIE交换芯片或FPGA的PCIE端点设计一定要实现完整的配置错误处理。该返回UR时就返回UR别吞掉请求——吞掉请求会导致Host侧超时调试起来更麻烦。好的错误处理不是负担而是最好的调试工具。