010、PCIE枚举过程:系统如何发现设备
010、PCIE枚举过程系统如何发现设备上周调一块新板子系统启动后死活认不到PCIE设备。查了半天发现是RC配置空间的一个BAR设置有问题。这种问题在PCIE开发里太典型了——你以为硬件连好了软件也跑了但设备就是不出来。今天咱们就掰开揉碎讲讲系统到底是怎么把PCIE设备一个个“揪”出来的。从一次真实的调试说起当时的情况是这样的板子能正常启动到系统lspci命令却只显示主机桥下游设备全无。用示波器抓REFCLK和PERST#信号都正常链路训练看起来也成功了。问题出在哪最后发现是BIOS里RC的配置空间没正确映射内存窗口导致后续枚举根本进行不下去。这个坑让我意识到不理解枚举的全过程调这类问题就是盲人摸象。PCIE枚举本质上是个“探路”过程。系统不知道下游挂了什么设备有多少设备这些设备需要多少地址空间。它得像探险家一样沿着总线一棵树一棵树地摸索给每个设备分配唯一的“门牌号”BDF再根据它们的需求划拨地址空间。配置空间设备的身份证每个PCIE设备包括RC、Switch和Endpoint都有个配置空间这玩意儿就像设备的身份证加需求清单。头256字节是标准化的其中头64字节特别关键。Type 0头用于EndpointType 1头用于Bridge比如Switch里的端口。配置空间最开头是Vendor ID和Device ID这俩值硬件固化系统靠它们识别设备类型。后面的BARBase Address Register才是重头戏——设备通过BAR告诉系统“我需要多少内存空间或者IO空间”。系统枚举时的主要工作就是读取这些BAR算出设备要多少资源然后在全局地址地图里找个空位给它安家。// 读BAR的典型操作伪代码uint32_tbarpci_read_config(bus,dev,func,BAR0_OFFSET);if(bar0x01){// 最低位为1表示IO空间// 这里有个坑IO BAR的地址要对齐到4字节边界}else{// 否则是内存空间// 注意bit2和bit1表示内存类型别忽略}写BAR的时候系统会先写全1进去再读回来。设备会忽略那些不能实现的地址位比如设备只需要16MB空间就会把低24位固定为0。这个“读回值”告诉系统地址对齐要求和空间大小。这个机制很巧妙但调试时容易迷惑——你写进去0xFFFFFFFF读回来可能变成0xFF000000这不是错了是设备在告诉你它只要16MB空间。深度优先搜索枚举的核心算法PCIE总线拓扑是树形结构RC是树根。枚举采用深度优先搜索DFS这是理解整个过程的关键。系统从Bus 0开始逐个扫描每个设备的每个功能。遇到BridgeType 1头就分配新的总线号然后递归地搜索下游总线。具体步骤是这样的系统先读Bus 0上Device 0的Vendor ID。如果不是0xFFFF表示设备存在就继续读Header Type判断是Endpoint还是Bridge。如果是Bridge系统给它分配一个新的总线号比如Bus 1然后设置Bridge的Primary Bus、Secondary Bus和Subordinate Bus寄存器。Primary Bus是上游总线号0Secondary Bus是下游总线号1Subordinate Bus是这棵子树里最大的总线号先设为最大值等下游搜完再更新。然后系统跳到新分配的总线Bus 1继续搜索。这个过程递归进行直到某条分支搜到底没有Bridge了再回溯到上一层继续。最终整棵树的所有设备都被访问到每个设备都有了唯一的Bus/Device/Function编号每个Bridge的Subordinate Bus寄存器也填上了正确的值。// 递归枚举的简化逻辑voidenumerate_bus(intbus){for(intdev0;dev32;dev){for(intfunc0;func8;func){if(check_device_exists(bus,dev,func)){// 配置这个设备// 如果是Bridge分配新bus号递归调用enumerate_bus(new_bus)}}}}实际实现要考虑多功能设备、空设备跳过多快等问题。早年有些BIOS实现得糙会漏掉一些设备现在基本规范了。资源分配给设备安家落户找到所有设备后系统开始集中分配资源。这是枚举的第二阶段也容易出问题。系统收集所有设备的BAR需求在地址空间里统筹安排。IO空间和内存空间分开处理Prefetchable内存和非Prefetchable内存也分开。分配策略一般是先放大的、对齐要求高的BAR减少内存碎片。每个BAR最终会被写入一个实际的基地址设备后续就通过这个地址与系统通信。Bridge还要配置内存和IO的基址/限界寄存器只转发落在范围内的请求到下游。这里有个常见的坑64位BAR。如果设备需要超过4GB的地址空间它会用两个连续的BAR寄存器实现64位地址。系统处理时要识别这种情况BAR的bit2为1把高32位和低32位当成一个整体分配。我见过有人只写了低32位高32位没写结果设备只能访问低4GB空间大数据传输必挂。中断路由让设备能“喊”人资源分配完设备还需要中断能力。PCIE设备支持MSIMessage Signaled Interrupt和MSI-X比传统的INTx引脚中断灵活得多。枚举时系统会读取设备的Interrupt Pin寄存器如果使用INTx和MSI/MSI-X Capability结构配置相应的中断路由。对于INTx系统要设置设备的INTx引脚路由到哪个IRQ线。对于MSI要分配Message Address和Message Data值设备通过写这个地址触发中断。MSI-X更复杂每个中断向量都有独立的地址和数据还有专门的表记录这些信息。调试中断问题时先确认配置空间里的中断相关寄存器是否设置正确再查系统中断控制器比如APIC的配置。有时候设备能正常读写内存但中断就是不触发多半是MSI地址没写对或者设备没使能MSI。个人经验与建议调PCIE枚举问题我习惯分三步走。第一步看硬件信号REFCLK、PERST#、差分信号质量。第二步看配置空间用PCIE分析仪或者系统工具比如Linux下的lspci -xxxx抓取配置空间的原始数据重点看BAR有没有正确分配Bridge的Primary/Secondary/Subordinate Bus对不对。第三步看系统资源检查内存和IO窗口是否覆盖了设备BAR中断路由是否建立。遇到设备不出现先确认RC本身配置是否正确。很多问题根源在RC比如内存窗口没开、总线号分配范围太小。然后顺着拓扑一级级往下查看每个Bridge是否正常转发配置周期。Switch的配置尤其要注意它的上游端口是Bridge下游端口也是Bridge每个端口都要正确配置。最后善用工具。硬件上用协议分析仪软件上用调试输出。Linux内核启动时加“pcidebug”参数能看到详细的枚举过程。自己写驱动时初始化阶段多读几次配置空间确认写进去的值生效了。PCIE设备冷复位后配置空间会恢复默认值热复位则可能保留部分设置这个差异也要注意。枚举是PCIE设备工作的基础这步错了后面什么DMA、数据传输都免谈。把它理解透彻了大部分PCIE问题都能快速定位。