1. PCIe基础回顾

1.1 PCIe拓扑结构

PCI Express(PCIe)采用点对点串行互连架构,取代了传统PCI的共享并行总线。其拓扑由根复合体(Root Complex, RC)、交换器(Switch)和端点(Endpoint, EP)组成。

关键概念: PCIe拓扑是树形结构,RC位于根部,通过Switch扩展出多个EP。每个链路(Link)由一对差分信号对(TX/RX)组成,支持×1、×4、×8、×16等通道宽度。
PCIe 拓扑结构示意图 Root Complex (RC) Switch Endpoint (EP) 1 Endpoint (EP) 2 Endpoint (EP) 3 CPU / Memory 点对点串行互连
PCIe拓扑组件说明
组件功能驱动关注点
Root Complex (RC)连接CPU/内存与PCIe域,发起配置和内存事务DMA地址映射、MSI-X中断路由
Switch扩展端口,路由TLP(事务层包)无需驱动干预,硬件自动转发
Endpoint (EP)功能设备(网卡、NVMe、GPU等)BAR空间、DMA引擎、中断配置

1.2 配置空间与PCIe Capability结构

每个PCIe设备必须实现4KB配置空间(传统PCI仅256字节)。前256字节兼容PCI规范,包含Vendor ID、Device ID、Class Code、BAR寄存器等。256字节以上为PCIe扩展配置空间,存放Capability结构。

调试技巧: 使用 lspci -vvv -s 01:00.0 可查看完整配置空间。重点关注 Capability 链表中 MSI-X Capability 和 Power Management Capability 的偏移地址。
PCIe 配置空间布局(4KB) 传统配置空间 (256B) Vendor ID / Device ID (0x00) Class Code / Revision ID (0x08) BAR0 ~ BAR5 (0x10 ~ 0x24) Capabilities Pointer (0x34) Interrupt Line / Pin (0x3C) ... PCIe 扩展配置空间 (4KB - 256B) PCI Express Capability (0x100) Device Capability / Control (0x104) Link Capability / Control (0x10C) MSI-X Capability (0x1B0) ARI Capability (可选) ... Capability 链表:通过 Capabilities Pointer (0x34) 遍历所有 Capability 结构

1.3 BAR空间与地址映射

基地址寄存器(BAR, Base Address Register)是PCIe设备向系统声明所需MMIO(内存映射I/O)或I/O空间大小的机制。系统固件(BIOS/UEFI)在枚举时分配物理地址,驱动通过 pci_ioremap_bar() 将BAR空间映射到内核虚拟地址。

常见陷阱: 64位BAR占用连续两个BAR寄存器(如BAR0+BAR1)。驱动必须检查 resource_size() 确认BAR实际大小,避免越界访问。
BAR寄存器类型与属性
类型位宽地址空间驱动映射函数
Memory BAR (32-bit)32位0 ~ 4GBpci_ioremap_bar()
Memory BAR (64-bit)64位0 ~ 2^64-1pci_ioremap_bar() (BAR0+BAR1)
I/O BAR32位I/O端口空间pci_ioport_map()
BAR 地址映射流程 PCIe 设备 BAR0: 0x1000 (4KB) BIOS/UEFI 枚举 分配物理地址: 0xF0000000 内核驱动 ioremap → 虚拟地址 地址映射关键步骤 1. 设备上电后,BAR寄存器存放设备请求的地址空间大小(低bit为属性位) 2. BIOS/UEFI 写入实际物理基地址到BAR,完成系统级地址分配 3. 驱动调用 pci_ioremap_bar() 将物理地址映射到内核虚拟地址空间

1.4 地址映射与DMA的关系

PCIe设备通过DMA(直接内存访问)读写主机内存,不经过CPU。驱动必须使用DMA API(如 dma_alloc_coherent() 或 dma_map_single())将物理地址转换为总线地址(Bus Address),设备才能正确访问。

核心原则: CPU使用虚拟地址,MMU负责虚拟→物理转换;PCIe设备使用总线地址,IOMMU(如Intel VT-d)负责总线→物理转换。驱动必须通过DMA API确保两者一致。
// 典型DMA映射代码片段
struct device *dev = &pdev->dev;
dma_addr_t dma_handle;
void *cpu_addr;

// 分配一致DMA缓冲区
cpu_addr = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL);
if (!cpu_addr) {
    dev_err(dev, "Failed to allocate DMA coherent memory\n");
    return -ENOMEM;
}

// 将总线地址写入设备BAR空间(假设BAR0映射到mmio_base)
iowrite32(lower_32_bits(dma_handle), mmio_base + DMA_ADDR_LOW);
iowrite32(upper_32_bits(dma_handle), mmio_base + DMA_ADDR_HIGH);

// 设备即可通过dma_handle发起DMA传输
地址空间对比
地址类型使用者转换方式驱动接口
虚拟地址 (VA)CPU (内核/用户)MMU页表kmalloc(), vmalloc()
物理地址 (PA)CPU (MMU旁路)直接__pa(), virt_to_phys()
总线地址 (BA)PCIe设备IOMMU / 直接映射dma_map_single(), dma_alloc_coherent()
调试建议: 使用 cat /proc/iomem 查看系统物理地址分配;使用 lspci -vvv 查看设备BAR基地址;使用 dmesg | grep dma 检查DMA映射是否成功。