内核启动:从 ACPI 到 device_add 的 PCI 枚举调用链
梳理 X86_64 服务器内核启动阶段,ACPI 子系统如何一路调用到 device_add(&dev->dev),完成 PCI 设备的注册与 driver core 接入。
本文中所有代码引用均基于内核 v7.2-rc2-22-g0e35b9b6ec0f 的源码。【Claude-4.7-Opus】
0.1. 调用链全景
1 | |
0.2. 各阶段职责
acpi_pci_root_init:注册员,把pci_root_handler挂进全局链表acpi_scan_handlers_list,仅此而已,不触发任何扫描。acpi_scan_init末段的acpi_bus_scan:遍历员,走遍 ACPI namespace,见到PNP0A03就回调对应 handler。acpi_pci_root_add:翻译员,把 ACPI 语义(_SEG/_CRS/_BBN/MCFG/_OSC)翻译成struct acpi_pci_root。pci_acpi_scan_root→acpi_pci_root_create→pci_scan_child_bus:执行员,建 root bus、递归device_add出所有pci_dev。
三段在时间线上是先备案、再触发、后执行,acpi_pci_root_init 本身只做备案。
0.3. 说明
PNP0A03是传统 PCI 根桥 ID,ACPI 规范强制要求 PCIe/CXL 根桥在_CID里带PNP0A03,所以内核的pci_root_handler.ids只写PNP0A03即可覆盖全部根桥;attach 之后再靠_HID(PNP0A08/ACPI0016)细分 bridge 类型。device_add(&dev->dev)之后由 driver core 触发bus_probe_device → __device_attach → pci_bus_match → pci_device_probe → drv->probe,即 PCI 设备与具体 driver 的绑定流程(见 PCI 设备如何与 driver 建立绑定)。- X86_64 服务器上的 Host Bridge 由 ACPI 描述、走
acpi_pci_root_add路径;drivers/pci/controller/*那批 SoC 桥驱动(及其收尾 helperpci_host_probe)在 X86_64 上不会被调用。
0.4. device_add 与驱动绑定的时机
枚举阶段(pci_scan_child_bus_extend 递归链)只负责”识别设备 + 建树 + 登记”,本身不绑定驱动。但绑定机会在 pci_device_add 末尾的 device_add(&dev->dev) 里就已经埋下了。
device_add 不是纯登记——它会触发 driver core 的 bus_probe_device → __device_attach → pci_bus_type.match + pci_bus_type.probe,去尝试匹配已注册的 pci_driver。
1 | |
匹配结果取决于驱动何时就绪,有两种情形:
- 驱动已注册(编进内核、
initcall早于 PCI 扫描完成):device_add当场匹配并probe。lspci显示的Kernel driver in use: nvme/igb就是这种产物。 - 驱动是模块、尚未加载(
igb/nvme等module_init晚加载,或后续modprobe):device_add时匹配不到,设备先”挂树、待命”;等驱动注册时driver_attach再扫一遍所有未绑定设备完成probe。lspci此时显示Kernel driver in use: N/A。
所以精确结论是:
pci_scan_child_bus_extend递归扫描不负责绑定驱动;- 但它调用的
device_add把”绑定驱动”的机会交给了 driver core——能否命中只看驱动就绪时机。
枚举完成后,所有 pci_dev 已按树形挂在 root 总线结构下(多层桥也已被一层层剥开),与具体 pci_driver 的绑定交给 driver core 异步完成。绑定流程细节见 PCI 设备如何与 driver 建立绑定。
内核启动:从 ACPI 到 device_add 的 PCI 枚举调用链
https://martinbj2008.github.io/2026/07/16/2026-07-16-acpi-to-device-add.ai/