4. Linux PCI 子系统初始化:pci_init() 流程与 pci_scan_bus() 核心逻辑

PCI 子系统的初始化是内核启动过程中将 PCI/PCIe 拓扑从硬件寄存器“翻译”为内核设备模型的关键阶段。本章深入剖析 pci_init() 的调用链路与 pci_scan_bus() 的递归扫描机制,帮助学员建立从入口函数到设备挂载的完整认知。

4.1 pci_init() 初始化流程

pci_init() 是 PCI 子系统的总入口,在 fs_initcall(pci_init) 阶段被调用。其核心职责包括:分配资源、注册驱动、初始化 ACPI 接口、扫描根总线。

关键路径: start_kernel() → rest_init() → kernel_init() → do_basic_setup() → do_initcalls() → pci_init()

4.1.1 主要执行步骤

步骤 函数/操作 说明
1 pci_apply_final_quirks() 应用最终阶段的设备 quirks,修正硬件缺陷
2 pci_resource_alignment() 初始化 设置资源对齐策略,为后续分配做准备
3 pci_init_host_bridge() 初始化主机桥(Host Bridge)数据结构
4 acpi_pci_root_init() 通过 ACPI 表枚举 PCI 根总线(x86/ARM 通用)
5 pci_scan_bus() 或 pci_scan_root_bus() 递归扫描总线,发现所有设备与桥
6 pci_bus_add_devices() 将扫描到的设备添加到内核设备模型并触发驱动绑定
调试提示: 在 pci_init() 入口处设置 initcall_debug 内核参数,可以打印所有 initcall 的调用顺序与耗时,快速定位 PCI 初始化卡死点。

4.1.2 pci_init() 整体流程 SVG

pci_init() 初始化流程 pci_init() 初始化流程 pci_init() 入口 pci_apply_final_quirks() pci_init_host_bridge() acpi_pci_root_init() pci_scan_root_bus() pci_bus_add_devices() 注:x86 平台通过 ACPI 发现根总线;嵌入式平台可能通过 Device Tree 或静态配置

4.2 pci_scan_bus() 核心逻辑

pci_scan_bus() 是递归扫描的引擎。给定一个总线号,它读取该总线上所有设备的 Vendor ID / Device ID,若有效则创建 pci_dev 结构体,并判断是否为 PCI-to-PCI 桥,若是则递归扫描下游总线。

4.2.1 扫描算法伪代码

pci_scan_bus(bus)
    for each devfn (0..255):
        if pci_read_config_word(devfn, PCI_VENDOR_ID) != 0xFFFF:
            dev = pci_scan_device(bus, devfn)
            if dev->hdr_type == PCI_HEADER_TYPE_BRIDGE:
                child_bus = pci_add_new_bus(bus, dev, secondary_bus_nr)
                pci_scan_bus(child_bus)   // 递归扫描下游
    pci_assign_unassigned_resources()
    pci_bus_add_devices(bus)
常见陷阱: 如果桥的 secondary bus number 寄存器未正确配置(例如为 0),递归扫描会提前终止,导致下游设备丢失。调试时务必检查 lspci -t 输出的总线树结构。

4.2.2 递归扫描流程 SVG

pci_scan_bus() 递归扫描流程 pci_scan_bus() 递归扫描流程 Bus 0 (Root) devfn 循环 读 Vendor ID 是桥? 递归 创建设备节点 递归终止条件:桥的 Secondary Bus Number 无效或读取 VID 返回 0xFFFF 每个桥最多扫描 32 个设备 × 8 个功能 = 256 个 devfn

4.3 关键数据结构关系

扫描过程中涉及的核心数据结构包括 pci_bus、pci_dev、pci_ops。理解它们之间的关联是调试链路的基础。

数据结构 作用 关键字段
pci_bus 代表一条 PCI 总线,包含设备链表与子总线链表 number, primary, secondary, subordinate, devices, children
pci_dev 代表一个 PCI 设备或桥 devfn, vendor, device, hdr_type, bus
pci_ops 配置空间读写方法(直接访问或通过 ACPI/ECAM) read, write 函数指针

4.4 调试与故障排查要点

  • 扫描中断: 如果 pci_scan_bus() 在某个 devfn 上卡死,通常是因为配置空间读取超时(如设备未响应)。使用 pci=nommconf 内核参数回退到 IO 端口访问。
  • 总线号冲突: 检查 /sys/bus/pci/devices/ 下的总线号是否连续。若出现跳跃,可能是桥的 subordinate bus number 设置错误。
  • 资源分配失败: 扫描完成后 pci_assign_unassigned_resources() 可能因 MMIO 空间不足而失败,导致设备 BAR 未分配地址。查看 dmesg | grep pci 中的 "no space" 错误。
实战命令: echo 1 > /sys/bus/pci/rescan 可以触发内核重新扫描总线,无需重启即可发现热插拔设备。该操作会再次调用 pci_scan_bus()。

4.5 本章小结