1. Linux PCI 子系统概述
PCI(Peripheral Component Interconnect)总线是现代计算机系统中最重要的外设连接标准之一。在Linux内核中,PCI子系统负责管理所有PCI/PCIe设备,其核心工作可以概括为两个关键任务:
-
设备发现与拓扑构建:系统启动时,PCI子系统会扫描整条PCI/PCIe链路,识别出所有连接的设备,并构建出完整的"总线树"结构。这包括确定每个设备的位置(位于哪一层桥下面)以及设备之间的层级关系。
-
资源分配与管理:为每个设备分配所需的I/O地址和内存地址空间(即Base Address Registers,BAR),并将这些地址资源纳入内核统一的"资源树"中进行管理,避免不同设备之间的资源冲突。
理解PCI子系统的工作机制对于驱动开发者尤为重要,特别是在以下场景:
- 调试PCI设备无法识别的问题
- 排查资源分配冲突导致的设备异常
- 开发新的PCI设备驱动程序
- 优化PCI设备的性能和资源利用率
2. PCI总线初始化过程
2.1 平台驱动入口:主桥准备
PCI子系统的初始化始于平台特定的主机桥(host bridge)驱动。在ARM64架构中,这一过程通常由pci_host_common_probe()函数触发,主要完成以下关键准备工作:
-
资源清单准备:
- 从设备树(Device Tree)中解析
ranges和bus-range属性 - 确定PCI总线可用的I/O窗口和内存窗口范围
- 记录允许使用的总线号范围
- 这些信息最终会附加到host bridge数据结构中
- 从设备树(Device Tree)中解析
-
配置空间访问方式建立:
- 在ARM平台上通常采用ECAM(Enhanced Configuration Access Mechanism)
- 将一段MMIO区域映射为配置空间访问窗口
- 后续通过内存读写操作访问PCI配置寄存器
-
资源分配策略确定:
- 检查固件是否已完成资源分配(
PCI_PROBE_ONLY标志) - 决定是沿用固件配置还是由内核重新分配资源
- 检查固件是否已完成资源分配(
c复制// 典型的主桥初始化代码路径
pci_host_common_probe()
→ devm_pci_alloc_host_bridge() // 分配host bridge结构体
→ devm_of_pci_bridge_init() // 解析设备树窗口
→ gen_pci_init()/pci_ecam_create() // 建立ECAM访问
→ of_pci_check_probe_only() // 检查资源分配策略
2.2 设备树解析与窗口准备
设备树中的ranges属性描述了PCI地址空间到CPU物理地址空间的映射关系。内核需要将这些信息转换为内部可用的数据结构:
-
地址窗口转换:
- 将设备树中的每条
ranges记录转换为struct resource对象 - 计算PCI地址与CPU物理地址之间的偏移量(offset)
- 区分I/O窗口和内存窗口类型
- 将设备树中的每条
-
资源预留:
- 将解析得到的窗口预先注册到全局资源树中
- 防止不同host bridge之间的窗口重叠
- 确保后续资源分配有可用的地址空间
典型设备树PCI节点示例:
dts复制pcie@10000000 {
compatible = "pci-host-ecam-generic";
reg = <0x0 0x10000000 0x0 0x01000000>
