1. PCIe 地址空间基础:配置空间、MMIO 空间、IO 空间与 BAR 寄存器
在深入 ioremap 的实战陷阱之前,必须首先建立对 PCIe 地址空间的清晰认知。PCIe 设备通过三种独立的地址空间与主机通信,而 BAR(基地址寄存器) 则是连接这些空间与驱动代码的桥梁。理解这些基础概念,是避免后续映射错误的第一步。
1.1 PCIe 三大地址空间概览
PCIe 总线继承了 PCI 的地址空间模型,将设备资源划分为三个逻辑上独立的区域。驱动开发者必须明确区分它们,因为 ioremap 只适用于其中一种。
| 地址空间 | 访问方式 | 大小限制 | 驱动映射方式 | 典型用途 |
|---|---|---|---|---|
| 配置空间 | 通过 CFG 读写(I/O 或内存映射) | 256B / 4KB(ECAM) | 不直接映射,通过 pci_read_config_* 访问 |
设备枚举、BAR 读取、中断配置 |
| MMIO 空间 | 内存映射读写 | 最大 64 位地址范围 | ioremap() / pci_iomap() |
设备寄存器、数据缓冲区、描述符环 |
| IO 空间 | 通过 IN/OUT 指令 | 最大 64KB(x86) | ioport_map()(非 ioremap) |
传统设备控制寄存器(x86 平台) |
实战要点: 绝大多数现代 PCIe 设备使用 MMIO 空间 暴露寄存器。IO 空间在 x86 以外的平台(ARM、RISC-V)上可能不存在或需要特殊处理。配置空间由内核 PCI 子系统管理,驱动不应直接
ioremap 配置空间。
1.2 BAR 寄存器:设备资源的“门牌号”
每个 PCIe 设备功能(Function)最多可拥有 6 个 BAR(Base Address Register),位于配置空间的偏移 0x10 至 0x24。BAR 存储了设备请求的地址空间类型、大小和基地址。系统固件(BIOS/UEFI)或操作系统在枚举时,会为每个 BAR 分配一个物理地址。
BAR 的类型与编码
BAR 寄存器的低比特位决定了其类型,这是驱动解析时必须检查的关键字段:
- Bit 0 = 0:MMIO 空间(Memory Space)
- Bit 0 = 1:IO 空间(I/O Space)
- Bit 2:1(仅 MMIO 时有效):
- 00:32 位地址(BAR 为 32 位宽)
- 10:64 位地址(BAR 占用连续两个 BAR 槽位,共 64 位)
- Bit 3(仅 MMIO 时有效):Prefetchable 标志,表示该区域是否支持预取。
关键陷阱: 64 位 MMIO BAR 会占用 两个连续的 BAR 索引(例如 BAR0 和 BAR1)。驱动在遍历 BAR 时,如果检测到 64 位类型,必须跳过下一个 BAR 槽位,否则会读到错误的地址。
1.3 从 BAR 到物理地址:驱动必须经历的步骤
驱动获取设备 MMIO 物理地址的标准流程如下,这也是后续 ioremap 的输入来源:
- 读取 BAR 原始值:调用
pci_read_config_dword(pdev, PCI_BASE_ADDRESS_0, &bar_raw)。 - 解析类型与掩码:检查
bar_raw & 0x01判断 MMIO/IO;对于 MMIO,使用bar_raw & PCI_BASE_ADDRESS_MEM_MASK(即~0xF)得到对齐后的基地址。 - 获取物理地址:对于 64 位 BAR,需要读取下一个 BAR 寄存器作为高 32 位,组合成
phys_addr_t。 - 获取资源大小:内核已通过
pci_resource_start()和pci_resource_len()封装了上述过程,驱动应优先使用这些 API。
// 推荐做法:使用内核封装 API 获取 MMIO 物理地址和长度
struct pci_dev *pdev = ...; // 从 pci_get_device 或 probe 获得
int bar_index = 0;
unsigned long phys_addr = pci_resource_start(pdev, bar_index);
unsigned long region_len = pci_resource_len(pdev, bar_index);
unsigned long flags = pci_resource_flags(pdev, bar_index);
if (!(flags & IORESOURCE_MEM)) {
dev_err(&pdev->dev, "BAR %d is not MMIO!\n", bar_index);
return -EINVAL;
}
// 检查是否为 64 位 BAR(可选,用于调试)
if (flags & IORESOURCE_MEM_64)
dev_info(&pdev->dev, "BAR %d is 64-bit MMIO\n", bar_index);
dev_info(&pdev->dev, "BAR%d: phys=0x%lx, len=%lu\n",
bar_index, phys_addr, region_len);
1.4 地址空间关系图:从 PCIe 设备到 CPU 视角
下图展示了 PCIe 设备内部的地址空间如何通过 BAR 映射到 CPU 的物理地址空间,以及驱动最终通过 ioremap 建立虚拟地址映射的完整链路。
1.5 常见陷阱与检查清单
⚠️ 本节核心陷阱: 驱动开发者在处理 BAR 时最容易犯的三个错误:
- 混淆 IO 空间与 MMIO 空间:对 IO 空间使用
ioremap会导致映射失败或访问异常。必须通过pci_resource_flags() & IORESOURCE_IO判断。 - 忽略 64 位 BAR 的槽位占用:遍历 BAR 时未跳过被 64 位 BAR 占用的下一个索引,导致读取到无效地址。
- 直接使用 BAR 原始值:未掩码低 4 位(类型/属性位),导致地址错位。
驱动开发者的 BAR 处理检查清单
| 步骤 | 检查项 | 正确做法 |
|---|---|---|
| 1 | 确认 BAR 类型 | 使用 pci_resource_flags() 检查 IORESOURCE_MEM 或 IORESOURCE_IO |
| 2 | 确认 BAR 宽度 | 检查 IORESOURCE_MEM_64 标志,若为 64 位则跳过下一个 BAR 索引 |
| 3 | 获取物理地址 | 使用 pci_resource_start(),不要手动解析 BAR 原始值 |
| 4 | 获取区域长度 | 使用 pci_resource_len(),确保映射范围不越界 |
| 5 | 检查地址非零 | 如果 phys_addr == 0,说明 BAR 未分配或设备未使能 |
1.6 小结:本节知识图谱
本节建立了 PCIe 地址空间的基础框架,这是后续所有 ioremap 实战讨论的基石。请确保你已掌握以下核心概念:
- PCIe 的 三种地址空间(配置、MMIO、IO)及其访问方式差异。
- BAR 寄存器 作为设备资源声明和系统地址分配的核心机制。
- 驱动获取 MMIO 物理地址的 标准 API 流程(
pci_resource_start/len/flags)。 - 64 位 BAR 的 槽位占用规则 和 IO 空间的 映射方法区别。