拓冰建站拓冰建站
首页 / 资讯中心 / 正文

PCIe设备识别全流程:BIOS自检、枚举与Option ROM加载机制

1. 开机自检流程从按下电源键到PCIe设备“露脸”先把话说在前头很多人把BIOS和PCIe当成两个独立的东西一个管开机、一个管外设好像井水不犯河水。但实际上PCIe设备能不能被系统用起来很大程度上在开机那几秒钟就已经决定了。甚至可以说一台机器从按下电源键到操作系统跑起来中间最核心的硬件动作之一就是CPU和BIOS联合起来把PCIe总线上的设备一个个“唤醒、点名、分配资源”。这篇文章接着上一篇继续往下聊重点拆解开机自检POST流程里PCIe扮演的角色以及Option ROM这个很多人在做RAID卡、网卡启动、GPU调试时绕不开的机制。我这几年和PCIe打交道主要集中在两块一是FPGA做PCIe端点Endpoint去连主机二是自己在x86平台上调板卡偶尔还会碰碰ARM平台。说句实话很多问题表面上看是驱动加载失败、设备不识别、系统启动卡住追到根子上都是BIOS在自检阶段就没把PCIe设备伺候好。所以这篇文章不光讲原理也尽量把实际排障的思路揉进去希望能对正在被PCIe设备不识别、Option ROM不生效、开机卡在PCIe枚举这类问题折磨的朋友有点帮助。适合读这篇文章的人大概有这么几类做BIOS/固件开发的朋友做PCIe板卡或者FPGA PCIe方案的硬件工程师运维排障碰见“unconfigured good”这类诡异状态的同学以及单纯好奇电脑开机那几秒到底发生了什么的技术爱好者。基础要求不高懂一点PCIe的概念就行剩下我会尽量用人话讲。1.1 POSTPower-On Self-Test到底自检了什么很多人对POST的理解就是“开机时主板跑一遍自检有硬件坏了就报警”这个说法不算错但太粗糙了。实际上POST在整个主板生态里是一个分阶段、分模块的动作x86平台上的POST通常可以按下面的大致顺序来理解CPU上电复位从复位向量开始执行固件代码。CPU、内存控制器、芯片组的早期初始化。内存训练Memory Training把DDR参数调好内存能用了代码才有地方放栈和堆。芯片组和总线控制器的早期枚举其中就包括PCIe控制器。枚举PCIe总线上的设备分配资源加载Option ROM。初始化存储设备、显示设备找到可启动的操作系统。把控制权交给引导加载程序。这里面第4到第6步就是PCIe深度参与的部分。尤其是第5步可能是整个POST里最繁琐、也最容易出幺蛾子的环节。1.2 PCIe设备在自检阶段经历了哪几个过程你要是从PCIe设备的角度去看这个过程会发现它自己其实“什么都不知道”全靠主机端一步步引导。我在调试FPGA PCIe端点的时候特别喜欢用逻辑分析仪抓链路训练状态机LTSSM的状态跳转看设备从Detect到Polling到Configuration再到L0本质上就是一个两端不断交换Training Sequence的过程。在BIOS自检阶段PCIe设备大体经历了这么几个过程第一个过程链路训练。设备上电后先要参与物理层的链路训练。这个过程由物理层自动完成不依赖BIOS是否“认识”这个设备。链路训练的目标是协商出链路速率Gen1/Gen2/Gen3/Gen4、链路宽度x1/x4/x8/x16以及一些物理层参数。只有当链路训练完成、进入L0状态后设备的事务层才能开始正常工作。反过来如果链路训练失败BIOS后面就算想枚举也没辙。第二个过程配置空间访问。链路通了之后BIOS通过配置读写请求去访问设备的配置空间。PCIe配置空间是256字节或扩展的4096字节里面有Vendor ID、Device ID、Class Code、BAR寄存器等关键信息。BIOS要做的第一件事就是读出这些ID判断总线上到底挂了什么设备。第三个过程资源分配。设备报告自己的资源需求比如需要多大的MMIO空间、需不需要I/O空间、需不需要中断引脚等。BIOS根据这些需求给设备分配总线号、分配内存地址区间、分配中断资源。第四个过程Option ROM加载。如果设备带扩展ROM比如RAID卡的启动固件、网卡的PXE固件、显卡的VBIOSBIOS会在合适的时机把ROM的内容读取出来放到内存的特定区域然后执行里面的初始化代码。这个机制是很多排障场景的焦点。这四个过程环环相扣前面任何一环出了问题后面的流程都会受影响。我碰过不少“设备在Linux下不识别”的问题最后定位到居然是链路训练时速率协商不稳定设备一会儿Gen3一会儿Gen1BIOS枚举时干脆判断设备无效。这种事情在插槽氧化、金手指接触不良或者PCIe时钟质量不好的时候特别常见。2. PCIe枚举机制BIOS是怎么知道总线上挂了什么的2.1 枚举的基本逻辑从Bus 0开始往下扫说到PCIe枚举得先理解PCIe的拓扑结构。PCIe是树状结构从根复合体Root ComplexRC出发通过根端口Root Port接出总线然后可以接PCIe交换机Switch扩展出更多下游端口最终挂上各种端点设备Endpoint。BIOS枚举的总原则是从总线号0开始一级一级往下扫发现桥设备就分配新的总线号然后递归地扫下游。这个过程可以理解成BIOS先扫描Bus 0上每个设备Device读配置空间的Vendor ID和Device ID。如果读到全F说明这个设备号的位置没有设备继续看下一个。如果读到了有效的Vendor ID说明这个位置有设备。接着判断它是普通端点还是桥Bridge。如果是桥设备BIOS就给它下面的子总线分配一个总线号然后继续扫描子总线上的设备。这个递归过程在源码层面看起来很简单但实际上藏了不少坑。比如在早期BIOS里对PCIe交换机级联的支持不够完善明明交换机的下游还挂着设备枚举却在一级Switch那里就停了。后来随着PCIe Switch越来越普及这类问题才逐渐减少。2.2 配置空间与BDF三元组PCIe设备在系统中的“门牌号”是BDF即Bus Number、Device Number、Function Number三个数字的组合。在Linux下用lspci看到的01:00.0就是总线01、设备00、功能0。BIOS枚举过程中实际做的事情就是通过配置读写指令对不同的BDF地址发访问。x86平台上传统的配置访问机制是I/O端口0xCF8/0xCFC后来PCIe时代引入了内存映射配置访问MMCFG也就是把配置空间映射到内存地址空间这样CPU可以直接用内存读写指令访问配置空间。这里有个细节值得说一下PCIe配置空间访问不完全是透明的。在PCIe体系里配置请求通过事务层打包经过各种桥的时候桥设备会根据总线号范围决定转发还是不转发。所以BIOS在枚举时对桥设备的配置空间写入“下游总线号”这一步极其关键——这个总线号一旦设置错了下游设备就永远无法被访问到。2.3 资源分配BAR、MMIO、中断和总线号枚举不只是“发现设备”那么简单发现之后还得给设备“安排住处”。主要分配的资源有总线号Bus Number。桥设备需要通过配置寄存器来声明自己下游的总线范围。如果多个桥的总线号分配冲突设备的配置空间就可能被“遮蔽”导致枚举结果错乱。我见过有人在自定义PCIe Switch配置时把下游总线号范围设得太小结果Switch下面挂了多级设备时后面的设备直接“消失”了。内存地址空间MMIO。设备通过BAR寄存器声明自己需要的地址空间大小。BIOS读取BAR寄存器的时候有一招很经典向BAR寄存器写全1再读回根据哪些位是0来判断设备需要多大的地址空间。这个过程在PCIe枚举中叫BAR sizing。要注意的是大小必须是2的幂次且对齐所以设备逻辑里对BAR的译码范围设置必须准确。I/O地址空间。老设备还得用I/O端口不过PCIe时代I/O资源已经很鸡肋很多新平台甚至直接不支持PCIe设备的I/O空间请求。如果你在调试的设备固件里还开着IO Space在纯UEFI平台和某些ARM服务器上可能直接报错。中断资源。传统PCI设备用INTx中断引脚PCIe设备主要是MSI/MSI-X中断。但在枚举阶段BIOS仍然会分配INTx线路因为MSI-X的配置是操作系统启动之后驱动来做的。老实说如果设备在BIOS阶段不需要响应中断大多数都不需要中断分配只要不冲突就行不必花太多心思。2.4 枚举顺序真的重要吗答案是重要但大多数场景下不明显。枚举顺序为什么会有影响最直接的原因在于Option ROM加载。传统BIOS时代Option ROM的加载顺序是沿着枚举树的顺序来的比如先发现的设备先加载ROM而后发现的设备后加载。如果你的机器上一块老显卡的Option ROM和一块新显卡的Option ROM发生冲突枚举顺序决定了谁先抢到内存空间。不同的Option ROM映射位置是有固定范围的后加载的ROM如果没有空间了可能就无法加载。另外枚举顺序还会影响设备在启动时的命名顺序。比如多个同类设备BIOS按枚举顺序分配PCI Bus号操作系统启动后接到的设备顺序就和枚举顺序密切相关。Linux的网卡命名从eth0变到ens33再到enp4s0f1这些变化本质上就和PCIe枚举顺序以及固件分配的ACPI路径有关。3. 深入Option ROM加载机制它为什么重要、怎么排查问题3.1 Option ROM是什么Option ROM在PCI/PCIe时代指的是设备自带的一段固件程序存放于设备上的闪存芯片里。主机在POST阶段把它读出来、放到内存里执行目的就是让设备在操作系统启动之前就能完成自身初始化甚至提供启动能力。最典型的例子显卡的VBIOS在POST阶段初始化显示输出让开机画面能显示。RAID卡固件在POST阶段让用户按快捷键进入RAID配置界面同时让BIOS认为这个设备可以作为启动设备使用。网卡的PXE ROM在BIOS阶段提供网络启动能力从远端拉取启动镜像。某些NVMe设备的Option ROM在老旧BIOS平台上让系统能识别NVMe固态盘并引导启动。Option ROM之所以重要是因为它处在“设备硬件初始化”和“操作系统引导”之间的真空地带。操作系统驱动还没加载但设备已经需要工作这个活儿只能靠Option ROM干。3.2 传统BIOS与UEFI下的Option ROM差异这是理解Option ROM绕不开的分水岭。传统Legacy BIOS下Option ROM是一种实模式16位代码由BIOS在POST阶段统一加载。BIOS给Option ROM分配的内存区域固定且有限老规矩是映射到C0000h到DFFFFh这段VGA区域通常在C0000h-C7FFFh其他设备从C8000h开始。BIOS扫描Option ROM的时候会先读ROM头部的一个签名55AAh然后根据长度字段去预留空间跳转到初始化入口执行。这段代码在实模式下运行拥有几乎无限的操作权限所以它能把设备、中断向量表、甚至整个内存布局都重新“调教”一遍。UEFI平台的情况就不同了。UEFI的Option ROM不再是自己随便跑的实模式代码而是一个遵守UEFI规范的PE格式驱动。BIOS在枚举PCIe设备时如果发现设备带UEFI Option ROM就会用固件里的LoadImage/StartImage接口把驱动加载起来初始化设备并注册协议比如EFI_DRIVER_BINDING_PROTOCOL、EFI_BLOCK_IO_PROTOCOL等。这些协议会被UEFI引导管理器用到从而支持从RAID卡、NVMe盘、USB设备等启动。这里有个经典坑一台支持UEFI的机器如果装了Legacy Only的Option ROM设备比如某些老RAID卡在UEFI模式下可能根本识别不到设备或者不支持从它启动。反过来纯UEFI的设备比如较新的NVMe RAID卡到了老旧的Legacy BIOS主板上也可能直接成“砖头”。现在的BIOS设置里有“Legacy Option ROM”和“UEFI Option ROM”的开关就是为了协调这个问题。3.3 Option ROM的加载流程从扫描到执行Option ROM加载的完整过程我拆成下面几步第一步扫描。BIOS在枚举完成PCIe设备后会检查每个设备配置空间的扩展ROM基址寄存器Expansion ROM Base Address Register。如果这个BAR有效说明设备有ROM。第二步读取ROM头部信息。BIOS读取ROM的前几个字节判断签名是否为55AA。然后从偏移量2处读取ROM大小单位是512字节通过向BAR地址写全1来实现大小探测。第三步分配内存空间。BIOS把ROM复制到预留的Option ROM内存区域Legacy模式下在C0000-DFFFF或者由UEFI实现分配内存给PE驱动。第四步执行初始化。Legacy模式跳转到初始化入口执行设备的固件初始化程序。UEFI模式下通过StartImage启动驱动驱动会在Bind协议里注册支持的功能。第五步注册启动能力。如果设备支持作为启动设备Option ROM会向BIOS的BBSBoot BBS列表或UEFI的引导列表注册通常表现为INT 18h/INT 19h Hook或者UEFI的EFI_LOAD_OPTION。整个流程看起来不复杂但任何一个环节出问题表现都很诡异。比如ROM大小上报错误BIOS分配的空间不足程序一跑就崩比如ROM里的初始化代码依赖某个服务比如访问PCI配置空间的实模式调用却被劫持了再比如多块设备的ROM同时加载内存之间互相覆盖造成“时好时坏、换了插槽就正常”的玄学问题。3.4 Option ROM加载失败时的排查思路如果你确定某块设备在POST阶段没有正确初始化可以依次排查下面几个方面。一是确认设备是否真的带ROM以及ROM类型。在Linux下用lspci -vvv看设备配置空间的Expansion ROM条目能看到是否有ROM地址被分配以及能否访问。更直接的用dd从配置空间对应的BAR地址读内容看开头是不是55AA。二是看BIOS开关是对的。某些BIOS有“Above 4G Decoding”“CSM Support”——CSMCompatibility Support Module会直接影响Legacy Option ROM能不能加载。如果CSM关闭但设备只有Legacy ROM设备可能直接不可用。另外有些BIOS带有“Option ROM Execution”的单独控制项如果设成了Disabled那ROM自然不加载。三是确认Option ROM内存空间够不够。在Legacy BIOS时代这段内存是稀缺资源。系统里多插几块大ROM的设备就可能出现后面的设备加载失败。这时候可以减少设备、换插槽顺序或者更新固件让设备的ROM更精简一些。四是留意ROM与平台安全启动Secure Boot的兼容性。UEFI安全启动开启后UEFI Option ROM的签名验证可能失败导致驱动无法加载。这通常会在BIOS设置里收到提示关掉Secure Boot或者给固件签名可以解决但如果你在调自己的设备固件需要提前把签名这个问题纳入考虑。4. 实操场景从枚举原理到实战排障4.1 在一台Zynq平台上调试PCIe设备不识别之所以想单独拿Zynq这个平台说事是因为它用的是ARM核没有x86的BIOSPCIe主机端的枚举是自己用代码写出来的。很多在x86上“理所当然”有的流程到了这里都得自己实现。Zynq的PCIe控制器PS端的SII PCIe核心负责的是事务层和链路层的逻辑但枚举和配置空间访问需要ARM核通过寄存器操作来发起。在实际调试“设备不识别”的问题时我的排查路径大致是这样的先查链路状态寄存器确认链路是否已经训练成功L0状态是否建立。如果链路一直是Detect或Polling说明物理层有问题这时候先别碰软件先去量时钟、看复位时序、用逻辑分析仪抓Training Sequence如果L0已经建立再用独立的配置访问工具去读设备Vendor ID确认枚举软件配置的总线号、设备号是否对得上。如果读出来全是FF首先要怀疑配置请求没有到达设备而不是设备坏了。在这个阶段我往往要反过来看桥的配置——Zynq PS侧的PCIe控制器相当于根复合体它自身需要正确设置下游总线范围和配置请求路由。这类问题比x86平台要花更多时间因为x86有BIOS帮你把很多边界条件都处理好而Zynq上你既是BIOS又是驱动所有细节都得自己扛。所以我平时在FPGA上做PCIe调试第一条经验就是“先把链路打通再谈枚举ATR和地址翻译最后再看”。链路训练不过关后面一切都白搭。4.2 在普通主板上用PCIe卡遇到“unconfigured good”状态标题和热词里出现了“unconfigured good”这个词估计不少人在做存储设备直通、RAID卡调试时遇到过类似的情况。“unconfigured good”是SAS/RAID控制器里的一个磁盘状态术语磁盘本身是好的、没有故障但还没有被纳入一个逻辑盘Volume所以系统看不见它的数据。这情况和标题里说的“系统看不到”吻合硬件层面链路正常控制器也能枚举出来但因为没有创建配置或配置缺失盘就不会作为普通磁盘直接呈现在操作系统里。排查这种状态有几个实操建议看RAID卡管理软件是否识别到物理磁盘。如果能识别但状态是unconfigured good说明控制器和卡本身工作正常问题在配置层面新建Volume或直通即可。如果管理软件里也看不到物理磁盘就要往物理层或链路层排查了。此时用lspci看控制器是否为ahci、megasas之类正常驱动再检查背板供电和SAS线缆。有些机器因为开了Virtualization Technology for Directed I/OVT-d把整张卡直通给虚拟机宿主机里卡就被“忽略”了看起来像normal good但上层看不见。实际上不是卡的问题是IOMMU分组和直通配置的问题。老实说多数“unconfigured good”并不是BIOS或者PCIe枚举带来的故障很多人在这里卡住是因为把控制器层面的状态误当成了PCIe链路问题。先把层级分清再动手排查能省下大量时间。4.3 实践技巧用逻辑分析仪和寄存器工具辅助调试排障不能只靠猜。在PCIe调试这个领域我常用的工具和手段有下面几个逻辑分析仪抓LTSSM状态、测Training Sequence、看链路速率协商。对于确定物理层是否健康非常关键尤其是PCIe Gen3/Gen4速率下很多偶发问题就是均衡参数没调好。PCIe Analyzer你要是想看清楚事务层到底有没有Configuration Request发下去最好还是有协议分析仪。不过这玩意儿价格感人很多小团队用不起那就退而求其次用FPGA内部的Integrated Logic Analyzer抓控制器侧的状态。Linux下的pciutils工具setpci可以在系统起来后直接读写配置空间检查BAR、Command寄存器的值是否和预期一致。比如设置setpci -s 01:00.0 COMMAND0x07可以让设备同时打开IO、Memory、Bus Master。ACPI和内核日志在Linux下查看dmesg里的pci相关输出看资源分配有没有告警总线有没有被重新扫描。lspci -vvv里还能看到每个设备的链路状态、错误记录比如Unsupported Request、Poisoned TLP这类错误位能告诉你是不是有错误的PCIe报文在总线上飞来飞去。4.4 排查PCIe ACS冲突与虚拟机直通问题热词里出现了“pcie acs 冲突”这个问题在虚拟化场景下比较常见。ACSAccess Control Services是PCIe的一个可选能力用于控制在同一个PCIe Switch下不同端口之间的访问隔离。虚拟机直通PCIe Passthrough依赖IOMMU和ACS如果没有ACS某些设备之间可能互相干扰宿主机为了保护安全会拒绝把这些设备单独直通给虚拟机于是你会在ESXi或者KVM里看到“设备分配失败”或者“设备分组不符合要求”。排查ACS冲突一般分成两步第一步确认设备的ACS能力。在Linux下用lspci -vvv看设备的ACS扩展能力读比特位看哪些ACS功能被禁用。通常需要看Source Validation、Translation Blocking、Request Redirection等。第二步确认IOMMU分组。查看/sys/kernel/iommu_groups/下的分组情况。如果多个设备被分到同一组说明它们之间的ACS隔离不完善直通时会互相牵连。碰见这种情况看设备固件有没有更新有些厂商在固件里修正了ACS支持的缺陷另外一个做法是给内核打上忽略ACS的补丁这属于实验性行为生产环境慎用。关键在于理解ACS问题不是“驱动没装好”而是PCIe交换拓扑层面的隔离机制作用的结果想明白了排查方向就不会偏。5. 从BIOS到操作系统PCIe枚举之后的交接与常见坑5.1 BIOS自检完PCIe设备怎么把控制权交给系统BIOS完成枚举、资源分配、Option ROM加载之后还剩最后一件大事决定从哪个设备启动系统。这块逻辑在不同的固件里实现差别很大但最终都离不开对PCIe设备的“可引导能力”的判断。Legacy BIOS时代固件通过扫描Option ROM注册的INT 18h/INT 19h钩子来枚举可引导设备排列出启动顺序。UEFI时代则是通过EFI_LOAD_OPTION里的设备路径Device Path来定位启动设备。设备路径本质上是一个描述PCIe设备“地理位置”的数据结构比如PciRoot(0x0)/Pci(0x1,0x0)/Pci(0x0,0x0)这样的形式。系统一旦启动Bootloader会读取这个路径找到对应的设备然后从上面加载操作系统内核。这个交接过程里最容易出的问题就是BIOS阶段设备明明在工作但操作系统的驱动不认识它。原因往往在于设备在BIOS阶段用的资源分配方案和操作系统驱动想用的不一致或者BAR信息在固件和驱动之间没有正确传递。PCIe的优势在于配置空间是标准化的驱动可以重新读取配置空间重新做资源映射所以大多数情况下Linux都能在启动后重新初始化设备。5.2 常见坑总线号冲突、BAR空间溢出与MMIO资源不足如果你自己做过BIOS或者固件开发下面这些坑应该不陌生。总线号冲突是枚举期最隐蔽的问题之一。PCIe配置空间里每个桥设备都有一个Secondary Bus Number和Subordinate Bus Number用来表示下游的总线范围。如果两块桥的总线范围重叠了配置读写请求就可能被多个设备同时响应总线上产生“多驱”现象轻则读到乱值重则挂起。嵌入式平台手动配置总线号时特别容易踩这个坑。我的习惯是先静态规划好整个链路拓扑需要多少条总线再逐级下发。BAR空间溢出指的是设备申请的MMIO空间超出了BIOS预留的地址范围。很多消费主板默认留给PCIe设备的MMIO窗口不大尤其是32位系统下4GB地址空间里还要给PCIe BAR留位置地址空间极其紧张。高端显卡、多块NVMe盘、万兆网卡同时插上去MMIO窗口很容易爆掉。你看到的现象往往是设备被枚举出来了但BAR读回的值不对甚至在操作系统中直接不能工作。这个问题在纯UEFI设置Above 4G Decoding开启之后能得到缓解因为这允许64位BAR映射到4GB之上。MMIO资源不足跟BAR空间溢出是孪生兄弟。传统BIOS里给PCIe MMIO预留的窗口通常受限于北桥的资源配置。有些老平台为了给内存和PCI传统资源让路MMIO窗口只能覆盖512MB或1GB。插了几块大BAR的PCIe设备之后后面的设备就没有地址可以分配了。这时候开机自检阶段可能会弹PCI资源分配错误提示或者干脆某些设备不工作。碰到这种情况先调BIOS里的PCI资源窗口设置能映射到64位空间就尽量用64位。5.3 碰到的其他典型故障PCIe Bus Error、PCIe ACS、高速设备热插拔还有几个高频故障值得单独说。PCIe Bus Error。Linux下dmesg刷屏最常见的类型之一通常表现为PCIe Bus Error: severityCorrected/Uncorrected。这一类错误分为可纠正和不可纠正不可纠正错误还可能分致命Fatal和非致命Non-Fatal。排查时先看lspci -vvv中设备的AER能力寄存器记录下错误来源、错误类型。多数情况下是链路信号质量差、PCIe插槽接触不良、或者PCIe Switch的某些不支持的特性被触发。如果错误量很少且都是Corrected一般不用过度紧张但如果是大量Uncorrected就得查链路物理层了。PCIe ACS问题。前面提过这里再补一个实际场景在VMware ESXi里做PCIe直通时如果设备的ACS能力不足或者没有正确设置虚拟机会因为IOMMU分组受限而无法直通。ESXi也会记录类似“The device is not properly configured for passthrough”的报错。常规解法是打开核显虚拟化相关的中断重映射、VT-d以及在BIOS里让PCIe设备走单独的IOMMU域。高速设备热插拔。PCIe支持热插拔但实际做起来比USB麻烦得多。问题常常出在供电时序、复位时序和链路重新训练上。物理层如果没准备好你热插拔进去的设备可能永远停在Detect状态软件层面BIOS或者OS如果没有及时响应热插拔事件设备即使链路建立也不会被系统接受。在做服务器维护时热插拔PCIe设备最好还是遵循厂商操作规范别太自信。6. 操作系统层面的PCIe配置与调试手段6.1 Linux下如何查看和修改PCIe配置Linux给PCIe排障提供了非常多实用的命令行工具。我常用的有lspci列出所有PCIe设备。加-v、-vv、-vvv能看到逐层细节。setpci直接读写设备的配置空间。比如setpci -s 01:00.0 0x04.w0x07可以将命令寄存器设置成同时允许IO、内存、总线主控。dmesg | grep pci看内核日志里PCIe枚举、资源分配、错误报告的相关信息。lspci -xxx把配置空间的原始字节dump出来适合与逻辑分析仪抓到的事务层做对照。/sys/kernel/debug/pci或者 bpftrace 这类工具可以对PCIe链路做更细的追踪不过需要root权限。说实话Linux的PCIe调试体验比在EFI Shell下用简单的pci命令要舒服太多。能在系统起来之后复现的问题优先在Linux下排查。6.2 UEFI Shell下的PCIe查看与EP调试如果你在BIOS开发或者固件调试的话UEFI Shell是一个绕不开的工具很多板卡厂商的官网也有提供Shell.efi供人写入启动U盘。UEFI Shell里有一条pci命令可以显示设备树和配置空间信息。比如pci会列出当前枚举到的所有设备pci -s 01:00.0 -i能查看设备信息pci -d能dump出配置空间的字节。有一个经验UEFI Shell下系统的PCIe枚举结果基本可以认为就是固件最终枚举结果。如果UEFI Shell里看不到设备那不用指望操作系统能识别到如果UEFI Shell里能看到但操作系统里不识别那就多半是驱动层面的问题。很多做FPGA PCIe方案的朋友第一步就是拿UEFI Shell看设备能不能被枚举到然后再去动自己的驱动代码。这一步能快速把问题分成“物理链路/枚举问题”和“驱动问题”两类省去大量盲调时间。6.3 用IOMMU分组判断设备能否安全直通IOMMU直通在生产环境里的地位越来越高。判断一块PCIe设备能不能安全直通最直接的办法就是查看IOMMU分组。在Linux下for g in /sys/kernel/iommu_groups/*; do ls -l $g/devices/; done每个分组里的设备是一荣俱荣、一损俱损的。如果一个分组里既有你想直通的设备又有其他功能设备那直通时会把整组设备都带过去宿主机的某些驱动可能因此失效。正常情况下根端口、交换机的上游口、端点设备在ACS做得比较好的情况下会有独立分组。但一些消费级平台对ACS支持不佳就会出现多个设备挤在同一组的情况。除了前面提到的固件更新或ACSR实验性绕过另一个思路是换一台ACS隔离能力更好的平台。总之IOMMU分组是硬件拓扑和固件配置共同作用的结果理解PCIe枚举和ACS机制之后这些问题就不难理解了。7. 个人调试经验小结说实话PCIe和BIOS这套东西刚接触的时候最大的感觉是“太底层了什么都要管”。但摸熟了之后会发现它的逻辑其实非常规整链路训练给设备“通网”配置空间访问给设备“报身份”资源分配给设备“分地盘”Option ROM给设备“通电发令”。每个环节都有标准可循能查的寄存器、能抓的报文都有现成的手段。真正难的不是原理而是把不同环节产生的问题正确归类。从我个人的经验来看做PCIe相关调试最重要的习惯是拿到一个问题不要急着改代码先确认问题到底出在哪个阶段。是物理层链路没通是配置访问没到设备是资源分配冲突还是Option ROM启动失败每一个阶段都有自己独有的“症状”和排查工具把层级划分清楚事情就成功了一半。最后分享一个小经验在调试PCIe设备时尽量保持和硬件工程师的沟通带宽足够大。很多时候软件这边百思不解的问题硬件工程师用示波器量一下复位引脚的电平时序半分钟就找到了答案。PCIe从来不是一个纯软件的世界固件、驱动、硬件之间的边界是很模糊的你中有我我中有你。想把这个领域跑通只能耐心地一层一层去啃没有捷径。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门