Linux学习笔记:PCIe内核篇(1):初始化与枚举流程
根据system.map 查看内核中PCIe加载流程:
root@zh-vm:~# cat /boot/System.map-5.15.0-130-generic | grep pci | grep initcall
ffffffff8350ff68 d __initcall__kmod_pci__453_6907_pci_realloc_setup_params0
ffffffff83510098 d __initcall__kmod_probe__266_110_pcibus_class_init2
ffffffff8351009c d __initcall__kmod_pci_driver__419_1685_pci_driver_init2
ffffffff8351014c d __initcall__kmod_pci_acpi__284_1504_acpi_pci_init3
ffffffff83510158 d __initcall__kmod_pci__370_214_register_xen_pci_notifier3
ffffffff83510170 d __initcall__kmod_init__253_51_pci_arch_init3
ffffffff835102c0 d __initcall__kmod_slot__281_380_pci_slot_init4
ffffffff8351043c d __initcall__kmod_legacy__254_77_pci_subsys_init4
ffffffff83510444 d __initcall__kmod_pci_eisa__257_89_pci_eisa_init_early4s
ffffffff83510554 d __initcall__kmod_i386__269_373_pcibios_assign_resources5
ffffffff83510558 d __initcall__kmod_quirks__360_195_pci_apply_final_quirks5s
ffffffff83510564 d __initcall__kmod_pci_dma__265_136_pci_iommu_initrootfs
ffffffff83510758 d __initcall__kmod_pwm_lpss_pci__258_120_pwm_lpss_driver_pci_init6
ffffffff83510760 d __initcall__kmod_pcieportdrv__258_274_pcie_portdrv_init6
ffffffff83510764 d __initcall__kmod_proc__257_469_pci_proc_init6
ffffffff83510768 d __initcall__kmod_pci_hotplug__288_573_pci_hotplug_init6
ffffffff83510770 d __initcall__kmod_pci_ep_cfs__267_731_pci_ep_cfs_init6
ffffffff83510774 d __initcall__kmod_pci_epc_core__286_849_pci_epc_init6
ffffffff83510778 d __initcall__kmod_pci_epf_core__284_561_pci_epf_init6
ffffffff8351077c d __initcall__kmod_pcie_designware_plat__259_192_dw_plat_pcie_driver_init6
ffffffff835107f4 d __initcall__kmod_virtio_pci__289_638_virtio_pci_driver_init6
ffffffff83510810 d __initcall__kmod_platform_pci__364_193_platform_driver_init6
ffffffff83510830 d __initcall__kmod_8250_pci__275_6463_serial_pci_driver_init6
ffffffff83510834 d __initcall__kmod_8250_mid__263_402_mid8250_pci_driver_init6
ffffffff835108d4 d __initcall__kmod_pata_sis__333_909_sis_pci_driver_init6
ffffffff835108d8 d __initcall__kmod_ata_generic__318_250_ata_generic_pci_driver_init6
ffffffff83510900 d __initcall__kmod_vfio_pci_core__326_2248_vfio_pci_core_init6
ffffffff83510904 d __initcall__kmod_vfio_pci__271_264_vfio_pci_init6
ffffffff83510914 d __initcall__kmod_ehci_pci__268_432_ehci_pci_init6
ffffffff83510920 d __initcall__kmod_ohci_pci__274_321_ohci_pci_init6
ffffffff835109a0 d __initcall__kmod_intel_scu_pcidrv__253_55_intel_scu_pci_driver_init6
ffffffff83510ae0 d __initcall__kmod_pci__450_6732_pci_resource_alignment_sysfs_init7
ffffffff83510ae4 d __initcall__kmod_pci_sysfs__304_1424_pci_sysfs_init7
ffffffff83510b64 d __initcall__kmod_mmconfig_shared__282_718_pci_mmcfg_late_insert_resources7
同时:
root@zh-vm:/lib# cat /boot/System.map-5.15.0-130-generic | grep acpi_init | grep initcall
ffffffff835102cc d __initcall__kmod_acpi__408_1364_acpi_init4
因此,整体启动顺序如下:
--> pcibus_class_init()
--> pci_driver_init()
--> acpi_pci_init()
--> pci_arch_init()
--> pci_slot_init()
--> acpi_init()
--> pci_subsys_init()
整个过程主要分为两部分 扫描设备 和 分配资源 :
(1)pcibus_class_init(): 注册pci_bus class,完成后创建了/sys/class/pci_bus目录。
(2)pci_driver_init(): 注册pci_bus_type, 完成后创建了/sys/bus/pci目录。
(3)acpi_pci_init(): 注册acpi_pci_bus, 并提供了一系列系统资源管理操作,包括:
- ACPI寄存器
- ACPI BIOS
- ACPI Tables
(4)acpi_init(): apci启动所涉及到的初始化、枚举流程,PCIe基于acpi的启动流程从该接口进入。也是扫描设备的主要流程入口,主要包括以下几部分:
1)ACPI 子系统初始化
acpi_init() # ACPI 子系统总入口(drivers/acpi/bus.c)|--> mmcfg_late_init() # acpi扫描MCFG表,MCFG表中定义了ecam资源|--> acpi_bus_init() # 初始化 ACPI 总线|--> acpi_scan_init() # 初始化 ACPI 设备扫描框架|--> acpi_pci_root_init() # 初始化 PCI Root Bridge 扫描(关键入口)|--> acpi_scan_add_handler_with_hotplug(&pci_root_handler, "pci_root");|--> .attach = acpi_pci_root_add|--> acpi_pci_link_init()|--> acpi_bus_scan() # 扫描 ACPI 命名空间中的设备|--> acpi_walk_namespace() # 遍历全部device,为这些acpi device创建数据结构|--> acpi_bus_attach() # 探测匹配的 ACPI 设备|--> handler->attach() # 调用 PCI Root Bridge 的 attach 回调|--> acpi_pci_root_add() # 创建并注册 PCI Root Bridge|--> pci_acpi_scan_root() # 核心 PCIe 枚举入口(见下文)
2)PCIe 总线枚举与设备扫描
| pci_acpi_scan_root() # 处理 ACPI PCI Root Bridge(drivers/acpi/pci_root.c)
|--> acpi_pci_root_create() # 创建 PCI Host Bridge|--> pci_create_root_bus() # 创建 PCI 根总线(drivers/pci/probe.c)|--> pci_alloc_host_bridge() # 分配 Host Bridge 结构体|--> pci_register_host_bridge() # 注册 Host Bridge|--> pci_scan_child_bus() # 递归扫描子总线(核心扫描逻辑)|--> pci_scan_child_bus_extend(bus, 0)|--> for (devfn = 0; devfn < 256; devfn += 8) # 遍历所有可能的设备/功能号|--> pci_scan_slot(bus, devfn) # 检查指定插槽是否存在设备|--> pci_scan_single_device()|--> pci_scan_device(bus, devfn) # 初始化设备|--> pci_bus_read_dev_vendor_id() # 读取设备厂商ID|--> pci_alloc_dev() # 分配设备结构体|--> pci_setup_device() # 配置设备 BAR、Class 等|--> pci_device_add(dev, bus) # 将设备添加到总线|--> pci_configure_device() # 配置设备寄存器|--> pci_init_capabilities() # 初始化 PCIe Capabilities|--> pcibios_add_device() # 平台特定设备添加逻辑|--> for_each_pci_bridge(dev, bus) |--> pci_scan_bridge_extend() # 扫描桥接器扩展总线|--> pci_add_new_bus() # 添加新总线|--> pci_scan_child_bus_extend() # 递归扫描新总线
总体流程便是:
ACPI 初始化 → MCFG 解析 → 发现根桥 → 创建根总线 → 递归扫描设备 → 资源分配 → 中断设置 → 驱动绑定
(1) BIOS预扫描与Kernel扫描启动
- BIOS阶段会先进行初步扫描,但结果可能不完整或不可直接使用。
- 内核启动后,通过
acpi_init()
初始化ACPI子系统,扫描MCFG表(mmcfglateinit()
)获取ECAM资源,为PCIe枚举做准备。
(2) 设备扫描阶段(深度优先)
- 从主桥(Host Bridge)开始,调用
pci_acpi_scan_root()
进入核心枚举流程。 - 通过
pci_create_root_bus()
创建根总线,并调用pci_scan_child_bus()
扫描根总线下的设备。 - 遍历所有设备槽位
pci_scan_slot()
,调用pci_scan_device()
检查设备是否存在并初始化。 - 若遇到桥接器
for_each_pci_bridge(dev, bus)
,则扩展总线号(pci_scan_bridge_extend()
),递归扫描子总线(pci_scan_child_bus_extend()
)。 - 扫描过程中构建PCIe拓扑结构,并收集所有设备所需的资源信息(如内存、I/O、中断)。
(3) 资源分配阶段(深度优先)
- 从主桥开始,按深度优先顺序分配资源。
- 为每个设备分配地址空间(内存/I/O)和中断资源,确保x86域和PCI域地址不冲突。
- 对于桥接器,根据其下设备分配的资源更新到设备的配置空间,供PCI总线路由使用。