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总线路由使用。

二、资源分配流程
| acpi_pci_root_add()  // ACPI PCI根总线添加函数
    |--> pci_acpi_scan_root()  // 扫描ACPI定义的PCI根总线
        |--> root_ops->release_info = pci_acpi_generic_release_info;  // 设置释放信息回调
        |--> root_ops->prepare_resources = pci_acpi_root_prepare_resources;  // 设置资源准备回调
            |--> acpi_pci_root_create()  // 创建ACPI PCI根总线
                |--> ops->prepare_resources(info);  // 调用资源准备回调
                    |--> pci_acpi_root_prepare_resources()
                        |--> acpi_pci_probe_root_resources()
                            |--> acpi_dev_get_resources()
                            |--> __acpi_dev_get_resources()  // 函数执行完毕,ci->resources 链表上面挂载的就是 ACPI PCI device 
                                                                _CRS method 传过来的资源(_MIN, _MAX, _LEN, _TRA).
                        |--> x86_pci_root_bus_resources();  
                            |--> x86_find_pci_root_info()
                            |--> pci_add_resource()
                |--> pci_acpi_root_add_resources()
                |--> pci_add_resource()
                |--> pci_create_root_bus()  // 创建根PCI总线
                    |--> pci_register_host_bridge()  // 注册主机桥
                |--> pci_scan_child_bus(bus);  // 扫描子总线
                    |--> pci_scan_child_bus_extend(bus, 0);  // 扩展扫描子总线
                        |--> pci_scan_slot(bus, devfn);  // 递归扫描插槽,广度优先
                            |--> pci_scan_single_device()  // 扫描单个设备
                                |--> pci_scan_device(bus, devfn);  // 扫描插槽并创建PCI设备
                                    |--> pci_setup_device()  // 初始化设备
                                        |--> pci_read_bases()  // 读取BAR资源
                                |--> pci_device_add(dev, bus);  // 添加PCI设备
                            |--> pci_scan_bridge_extend(bus, dev, max);  // 扫描桥设备
                                |--> pci_add_new_bus(..)  // 为桥创建子总线
                                    |--> child = pci_alloc_child_bus(parent, dev);  // 分配子总线
                                        |--> child->dev.class = &pcibus_class;  // 设置子总线类
                                        |--> dev_set_name(&child->dev, "%04x:%02x", ...);  // 设置子总线名称
                                        |--> child->bridge = get_device(&bridge->dev);  // 获取桥设备
                                        |--> for (i = 0; i < PCI_BRIDGE_RESOURCE_NUM; i++) 
                                                |--> child->resource[i] = &bridge->resource[...];  // 复制桥资源
                                                |--> child->resource[i]->name = child->name;  // 设置资源名称
                                        |--> device_register(&child->dev);  // 注册子总线设备
                                |--> pci_scan_child_bus_extend(bus, 0);  // 扩展扫描子总线
                                    |--> pci_scan_slot(bus, devfn);  // 递归扫描插槽,广度优先
                                    |--> pci_scan_bridge_extend(bus, dev, max);

                                    
            |--> pci_assign_unassigned_root_bus_resources()  // 分配未分配的根总线资源
                |--> __pci_bus_size_bridges()  // 调整桥资源大小
                    |--> pci_bridge_check_ranges()  // 检查桥资源范围
                        |--> pbus_size_io()  // 调整I/O资源大小
                        |--> pbus_size_mem()  // 调整内存资源大小
                |--> __pci_bus_assign_resources()  // 分配总线资源
                    |--> pbus_assign_resources_sorted()  // 排序分配资源
                        |--> __dev_sort_resources()  // 排序设备资源
                            |--> pdev_sort_resources()  // 排序PCI设备资源
                        |--> __assign_resources_sorted()  // 排序分配资源
                            |--> assign_requested_resources_sorted()  // 分配请求资源
                                |--> pci_assign_resource()  // 分配单个资源
                                    |--> __pci_assign_resource()  // 内部分配资源
                                        |--> pci_bus_alloc_resource()  // 从总线分配资源
                                            |--> pci_bus_alloc_from_region()  // 从区域分配资源
                                                |--> pci_clip_resource_to_region()  // 裁剪资源到区域
                                                    |--> pcibios_bus_to_resource()  // 转换总线到资源
                                    |--> pci_update_resource()  // 更新资源
                                        |--> pci_std_update_resource()  // 标准更新资源
                |--> pci_setup_bridge();  // 设置桥资源
                    |--> __pci_setup_bridge()
                        |--> pci_setup_bridge_io()  // 设置桥I/O资源
                        |--> pci_setup_bridge_mmio()  // 设置桥内存资源
                        |--> pci_setup_bridge_mmio_pref()  // 设置桥预取内存资源

资源分配流程分析

(1)ACPI资源获取

  • pci_acpi_root_prepare_resources() 从ACPI的 _CRS 方法中获取根总线资源。
  • pci_acpi_root_add_resources() 将这些资源添加到根总线。

(2)根总线创建与扫描

  • pci_create_root_bus() 创建根PCI总线并注册主机桥。
  • pci_scan_child_bus() 递归扫描子总线,包括设备插槽和桥设备。

(3)设备资源读取

  • pci_scan_device() 读取设备的BAR资源。
  • pci_setup_device() 初始化设备并记录BAR资源。

(4)资源分配

  • pci_assign_unassigned_root_bus_resources() 分配未分配的根总线资源。
  • __pci_bus_size_bridges() 调整桥资源大小。
  • __pci_bus_assign_resources() 分配总线资源,包括排序和分配单个资源。

(5)桥资源设置

  • pci_setup_bridge() 设置桥的I/O和内存资源。
  • __pci_setup_bridge() 分别设置桥的I/O、内存和预取内存资源。

你可能感兴趣的:(嵌入式协议篇,PCIE)