1. PCIe 基础概念:总线拓扑结构、设备层级、配置空间与地址映射
PCI Express(PCIe)作为现代高速串行总线标准,是嵌入式系统、服务器、GPU 及 NVMe 存储等高性能设备的核心互连技术。理解其拓扑结构、设备层级、配置空间与地址映射机制,是进行 PCIe 驱动开发和 DMA 数据搬运优化的前提。本节将从这四个维度展开,建立完整的 PCIe 基础认知框架。
1.1 PCIe 总线拓扑结构
PCIe 采用树形拓扑结构,由 根复合体(Root Complex, RC)、交换器(Switch)、端点(Endpoint, EP) 和 桥(Bridge) 组成。与传统的共享并行总线不同,PCIe 使用点对点串行链路,每个设备拥有独立的物理通道。
1.2 设备层级与 BDF 寻址
PCIe 设备通过 总线号(Bus)、设备号(Device)、功能号(Function) 三级编号唯一标识,简称 BDF。每个 Function 对应一个独立的配置空间(256 字节或 4KB)。
| 层级 | 位宽 | 范围 | 说明 |
|---|---|---|---|
| Bus | 8 位 | 0-255 | 标识 PCIe 总线编号,RC 内部通常为 Bus 0 |
| Device | 5 位 | 0-31 | 标识总线上的设备号,每个总线最多 32 个设备 |
| Function | 3 位 | 0-7 | 标识多功能设备中的子功能,最多 8 个 |
0000:01:00.0 格式表示(Domain:Bus:Device.Function)。Domain 用于非 x86 平台的多 RC 场景,x86 下通常为 0000。驱动开发时,务必通过 lspci 确认实际 BDF 编号。
1.3 配置空间与 Capability 结构
PCIe 配置空间是软件与硬件交互的核心接口。传统 PCI 设备提供 256 字节配置空间,PCIe 扩展为 4KB(前 256 字节兼容 PCI)。配置空间包含 Vendor ID、Device ID、Class Code、BAR 寄存器 等关键字段。
PCIe 引入了 Capability 链表 机制,通过 Capability ID 和 Next Pointer 串联多个扩展功能结构。常见的 Capability 包括:
- Power Management Capability — 电源状态管理
- MSI/MSI-X Capability — 消息信号中断,DMA 完成通知的核心机制
- PCI Express Capability — 链路状态、设备能力、链路控制等 PCIe 特有寄存器
- Device Serial Number Capability — 设备唯一序列号
- Advanced Error Reporting (AER) Capability — 高级错误报告
1.4 地址映射:PCIe 域与系统内存域
PCIe 地址映射是 DMA 编程中最容易出错的部分。系统中有两个独立的地址空间:
- 系统内存域(System Memory Domain) — CPU 视角的物理地址空间,由 MMU 管理。
- PCIe 总线域(PCIe Bus Domain) — PCIe 设备视角的地址空间,包括 Memory Space、I/O Space 和 Configuration Space。
RC 中的 Host Bridge 负责在两个域之间进行地址转换。设备通过 BAR(Base Address Register) 向系统声明所需的地址窗口大小和类型。Linux 内核通过 pci_resource_start() 和 pci_resource_len() 获取映射后的总线地址。
| 地址类型 | CPU 访问方式 | DMA 访问方式 | 典型用途 |
|---|---|---|---|
| Memory Space | MMIO (ioremap) | 直接 Memory Read/Write TLP | 设备寄存器、DMA 缓冲区 |
| I/O Space | in/out 指令 | 不支持(x86 外罕见) | 传统设备控制寄存器 |
| Configuration Space | PCI 配置读写 (CF8/CFC) | 不支持 | 设备枚举、Capability 发现 |
dma_map_single() 或 dma_alloc_coherent() 获取设备可访问的 DMA 地址。
1.5 本节总结与工程要点
- 通过
lspci -vvv确认目标设备的 BDF、BAR 大小、Capability 列表。 - 读取 Capabilities Pointer,遍历 MSI/MSI-X Capability,配置中断向量。
- 使用
pci_resource_start()获取 BAR 映射后的总线地址,调用ioremap()映射到 CPU 虚拟地址空间。 - DMA 缓冲区分配使用
dma_alloc_coherent()或dma_map_single(),确保地址在 PCIe 总线域中可达。 - 注意 32 位 vs 64 位 DMA 地址限制,通过
dma_set_mask()告知设备支持的地址宽度。