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中断的完整路径:
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地址映射的三个层次及其转换关系:
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物理地址可以快速定位映射问题。