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等通道宽度。
| 组件 | 功能 | 驱动关注点 |
|---|---|---|
| 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 的偏移地址。
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实际大小,避免越界访问。
| 类型 | 位宽 | 地址空间 | 驱动映射函数 |
|---|---|---|---|
| Memory BAR (32-bit) | 32位 | 0 ~ 4GB | pci_ioremap_bar() |
| Memory BAR (64-bit) | 64位 | 0 ~ 2^64-1 | pci_ioremap_bar() (BAR0+BAR1) |
| I/O BAR | 32位 | I/O端口空间 | pci_ioport_map() |
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映射是否成功。