4. ARM平台PCIe子系统架构:GIC中断控制器、ECAM配置空间、PCIe域与地址映射

从x86迁移到ARM平台,PCIe子系统的底层架构发生了根本性变化。x86依赖IOAPIC/MSI-X与固定内存映射,而ARM则围绕GIC(Generic Interrupt Controller)中断控制器、ECAM(Enhanced Configuration Access Mechanism)配置空间访问机制以及灵活的PCIe域与地址映射展开。本章深入剖析这三个核心差异点,为驱动移植提供架构级指导。

架构迁移核心认知: ARM平台没有x86的IO端口空间和固定的PCI内存窗口。PCIe配置空间通过ECAM映射到系统内存地址空间,中断通过GIC的SPI(Shared Peripheral Interrupt)或LPI(Locality-specific Peripheral Interrupt)传递,地址映射依赖平台DMA/IOMMU拓扑。驱动必须放弃x86的“硬编码”思维,转向“设备树+动态映射”的ARM范式。

4.1 ARM GIC中断控制器与PCIe中断路由

ARM GIC是中断管理的核心,PCIe设备产生的中断(INTx/MSI/MSI-X)最终都要通过GIC分发到CPU核心。与x86的IOAPIC不同,GIC采用分层架构,且中断号与硬件引脚解耦。

4.1.1 GIC中断类型与PCIe映射

中断类型 GIC接口 PCIe使用场景 移植关注点
PPI (Private Peripheral Interrupt) GIC CPU Interface 不用于PCIe 每个CPU私有,PCIe设备不使用
SPI (Shared Peripheral Interrupt) GIC Distributor PCIe INTx、传统MSI 中断号由设备树指定,需与PCIe控制器绑定
LPI (Locality-specific Peripheral Interrupt) GIC Redistributor + ITS MSI-X、大量MSI 依赖ITS(Interrupt Translation Service)进行中断号翻译
移植提示: x86驱动通常直接调用 pci_alloc_irq_vectors() 并假设中断号连续。在ARM上,MSI-X中断通过ITS分配LPI,中断号可能不连续,且需要确保GIC ITS驱动已初始化。检查设备树中 msi-controller 和 interrupt-parent 属性是否正确指向GIC。

4.1.2 GIC-ITS与MSI中断翻译流程

下图展示了PCIe设备通过GIC-ITS发送MSI中断的完整路径:

ARM GIC-ITS MSI中断路由架构 PCIe EP设备 写MSI地址+数据 PCIe RC控制器 转发写请求 GIC ITS 中断翻译+路由 GIC Distributor 中断优先级/分发 CPU Core 0..n 执行中断处理函数 关键点:PCIe设备写入的MSI地址指向ITS Doorbell区域,数据包含DeviceID+EventID;ITS翻译为LPI中断号并路由到目标CPU。

4.2 ECAM配置空间:内存映射访问机制

ARM平台统一使用ECAM(Enhanced Configuration Access Mechanism)访问PCIe配置空间,摒弃了x86的IO端口(CF8/CFC)机制。ECAM将整个PCIe总线配置空间直接映射到一段连续的CPU内存地址区域。

4.2.1 ECAM地址映射公式

ECAM将配置空间映射为4KB的“配置空间页”,每个PCIe Function占用4KB。地址计算公式如下:

// ECAM基地址 + (Bus号 << 20) | (Device号 << 15) | (Function号 << 12) | 寄存器偏移
// 其中:Bus号占8位,Device号占5位,Function号占3位
// 每个Function配置空间大小为4KB(0x1000)

#define ECAM_ADDR(base, bus, dev, func, offset) \
    ((base) + ((bus) << 20) | ((dev) << 15) | ((func) << 12) | (offset))

// 示例:访问Bus 1, Device 2, Function 0的Vendor ID寄存器
// 假设ECAM基地址为 0x40000000
uint32_t *vendor_reg = (uint32_t *)ECAM_ADDR(0x40000000, 1, 2, 0, 0x00);
uint16_t vendor_id = *vendor_reg & 0xFFFF;

4.2.2 x86 vs ARM配置空间访问对比

特性 x86 (IO端口) ARM (ECAM)
访问方式 OUT/IN指令操作CF8/CFC端口 内存Load/Store指令直接访问映射地址
地址空间 独立的IO空间(64KB) 系统内存地址空间(通常256MB~512MB)
总线扫描 通过IO端口逐设备探测 通过ECAM内存映射直接读取配置空间
原子性 需要锁保护IO端口序列 内存访问天然原子(需考虑DMA一致性)
设备树配置 不需要(ACPI固定) 必须提供 reg 属性指定ECAM基地址和大小
移植陷阱: ARM上ECAM映射的配置空间必须使用 ioremap() 或 pci_remap_cfgspace() 建立页表映射,不能直接使用物理地址。同时注意ARM的强序内存模型——配置空间访问通常需要 devicenGnRnE 属性,避免CPU乱序执行导致配置读写失败。

4.3 PCIe域与地址映射

ARM平台支持多个PCIe域(Domain),每个域拥有独立的地址空间和配置空间。地址映射涉及CPU地址、PCIe总线地址和DMA地址三个层次。

4.3.1 PCIe域的概念与设备树表示

每个PCIe控制器(RC)构成一个独立的PCIe域。设备树中通过 pci 节点描述,bus-range 属性定义域内的总线号范围。

// 设备树示例:两个PCIe域
pcie0: pcie@40000000 {
    compatible = "vendor,pcie-controller";
    reg = <0x0 0x40000000 0x0 0x10000000>;  // ECAM基地址和大小
    bus-range = <0x00 0xff>;                // 域0:总线0~255
    ranges = <0x02000000 0x0 0x50000000      // 非预取内存窗口
              0x0 0x50000000
              0x0 0x10000000>;
    #address-cells = <3>;
    #size-cells = <2>;
    device_type = "pci";
};

pcie1: pcie@50000000 {
    compatible = "vendor,pcie-controller";
    reg = <0x0 0x50000000 0x0 0x10000000>;  // 另一个ECAM区域
    bus-range = <0x00 0xff>;                // 域1:总线0~255
    ranges = <0x02000000 0x0 0x60000000
              0x0 0x60000000
              0x0 0x10000000>;
    #address-cells = <3>;
    #size-cells = <2>;
    device_type = "pci";
};

4.3.2 三层地址映射关系

下图展示了PCIe地址映射的三个层次及其转换关系:

PCIe三层地址映射关系 CPU地址空间 0x5000_0000 ~ 0x5FFF_FFFF (虚拟/物理地址) PCIe总线地址 0x5000_0000 ~ 0x5FFF_FFFF (配置/IO/内存空间) DMA地址 设备看到的地址 (经IOMMU/SMMU翻译) 映射1:CPU地址→PCIe总线地址(通过ranges属性,通常1:1映射) 映射2:PCIe总线地址→DMA地址(通过IOMMU/SMMU页表,可重映射) 移植关键点: 1. 驱动使用 pci_resource_start() 获取的是PCIe总线地址,需经 ioremap() 映射到CPU虚拟地址。 2. DMA API(如 dma_alloc_coherent())返回的是DMA地址,ARM上需确保IOMMU/SMMU页表正确配置。 3. 多域场景下,每个域有独立的地址窗口,驱动不能跨域访问设备配置空间。

4.3.3 地址映射移植检查清单

  • 检查设备树 ranges 属性: 确保PCIe内存窗口(非预取/预取)和IO窗口正确映射到CPU地址空间。x86驱动通常硬编码BAR地址,ARM上必须从设备树解析。
  • 确认IOMMU/SMMU拓扑: 如果平台有SMMU,PCIe设备DMA地址需要经过SMMU翻译。驱动应使用 of_dma_configure() 或 iommu_domain_alloc() 建立映射。
  • 处理多域设备枚举: 每个PCIe域独立枚举,驱动通过 struct pci_bus * 区分。x86上所有设备在同一个域(域0),ARM上必须遍历所有域。
  • 验证ECAM映射范围: 确保 reg 属性中的ECAM基地址不与系统内存、外设寄存器地址冲突。
实践建议: 在ARM平台调试PCIe驱动时,优先通过 lspci 或 pciutils 工具验证设备枚举是否正常。如果设备未出现,检查ECAM映射是否生效——读取ECAM基地址处的配置空间头(Vendor ID/Device ID)是否返回正确值。使用 devmem2 工具直接读取ECAM物理地址可以快速定位映射问题。