内核启动:从 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[initcall: subsys_initcall, acpi_init]
acpi_scan_init drivers/acpi/scan.c:2821
├─ acpi_pci_root_init drivers/acpi/pci_root.c:1060
│ ├─ pci_acpi_crs_quirks() ← 只是拨 _CRS 兼容开关
│ └─ acpi_scan_add_handler_with_hotplug(
│ &pci_root_handler, "pci_root") ← 把 handler 挂进 acpi_scan_handlers_list

├─ acpi_pci_link_init / processor_init / ... ← 其他 handler 也依样挂好
├─ acpi_scan_add_handler(&generic_device_handler)

└─ acpi_bus_scan(ACPI_ROOT_OBJECT) ← ★ 遍历 ACPI namespace ★
└─ ...递归遇到 PNP0A08/PNP0A03 节点
└─ acpi_bus_attach
└─ acpi_scan_attach_handler drivers/acpi/scan.c:2309
├─ acpi_scan_match_handler("PNP0A03") 命中 pci_root_handler
└─ handler->attach(device, devid)
= acpi_pci_root_add drivers/acpi/pci_root.c:637
├─ 读 _SEG/_CRS/_BBN/MCFG
├─ 判 _HID → bridge_type (PCI/PCIe/CXL)
├─ negotiate_os_control (_OSC)
└─ pci_acpi_scan_root(root) drivers/pci/pci-acpi.c:1659
├─ pci_acpi_setup_ecam_mapping
└─ acpi_pci_root_create drivers/acpi/pci_root.c:995
├─ pci_create_root_bus ← device_add(桥/root bus)
└─ pci_scan_child_bus drivers/pci/probe.c:3205
└─ ... ─ pci_scan_slot
└─ pci_scan_single_device
└─ pci_device_add
└─ device_add(&dev->dev) ← 每个 pci_dev

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_rootacpi_pci_root_createpci_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 之后再靠 _HIDPNP0A08/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 桥驱动(及其收尾 helper pci_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
2
3
// drivers/pci/probe.c pci_scan_single_device()
dev = pci_scan_device(bus, devfn); // 读 Vendor/Device ID、capability,建 pci_dev
pci_device_add(dev, bus); // 末尾 device_add(&dev->dev)

匹配结果取决于驱动何时就绪,有两种情形:

  • 驱动已注册(编进内核、initcall 早于 PCI 扫描完成):device_add 当场匹配并 probelspci 显示的 Kernel driver in use: nvme / igb 就是这种产物。
  • 驱动是模块、尚未加载igb / nvmemodule_init 晚加载,或后续 modprobe):device_add 时匹配不到,设备先”挂树、待命”;等驱动注册时 driver_attach 再扫一遍所有未绑定设备完成 probelspci 此时显示 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/
Author
Martinbj2008
Posted on
July 16, 2026
Licensed under