4. BAR 空间类型:Memory BAR 与 I/O BAR 的区别、Prefetchable 属性、64 位 BAR 与 32 位 BAR
PCIe 设备的 Base Address Register (BAR) 是设备与主机软件(驱动程序)进行地址空间交互的核心窗口。每个 BAR 定义了设备在系统中的地址映射类型、大小、属性和访问方式。理解 BAR 的类型与属性,是正确进行 ioremap 和后续寄存器读写的前提。
4.1 Memory BAR 与 I/O BAR 的核心区别
PCIe 规范定义了两种 BAR 空间类型:Memory BAR 和 I/O BAR。现代 PCIe 设备几乎全部使用 Memory BAR,I/O BAR 仅用于兼容老旧设备或特定嵌入式场景。
| 对比维度 | Memory BAR | I/O BAR |
|---|---|---|
| 地址空间 | 映射到系统的物理内存地址空间(32 位或 64 位) | 映射到独立的 I/O 地址空间(x86 上 16 位,最大 64KB) |
| 访问指令 | 使用 ioread32/iowrite32 或直接内存访问(如 memcpy) |
必须使用 inb/outb 等专用 I/O 指令 |
| 性能 | 高,支持 CPU 缓存、预取、合并写操作 | 低,每次访问需通过 I/O 控制器,不可缓存 |
| Prefetchable | 支持(可设置 Prefetchable 位) | 不支持(始终为 Non-Prefetchable) |
| 地址宽度 | 支持 32 位和 64 位 | 仅支持 32 位(实际有效地址 ≤ 16 位) |
| 设备适用性 | 所有现代 PCIe 设备(GPU、NVMe、网卡、FPGA) | 仅老旧 ISA 兼容设备或特定低速控制寄存器 |
| Linux 映射方式 | ioremap() 或 ioremap_wc() |
ioport_map() + ioport_unmap() |
开发建议: 在编写新 PCIe 驱动时,优先检查设备是否支持 Memory BAR。如果设备同时提供两种 BAR,应优先使用 Memory BAR 以获得更好的性能和更简单的编程模型。I/O BAR 在 x86 以外的架构(如 ARM、RISC-V)上可能根本不存在或需要特殊处理。
4.2 Prefetchable 属性详解
Prefetchable(可预取)是 Memory BAR 独有的属性位(BAR 寄存器的 bit 3)。该位告诉系统软件:该 BAR 对应的寄存器区域是否允许 CPU 进行推测性读取和合并写操作。
4.2.1 Prefetchable 与 Non-Prefetchable 对比
| 属性 | Prefetchable (可预取) | Non-Prefetchable (不可预取) |
|---|---|---|
| 读取行为 | 允许 CPU 提前读取、缓存、推测执行 | 每次读取必须精确命中,不允许推测 |
| 写入行为 | 允许 CPU 合并多次写入、延迟提交 | 每次写入必须立即提交,不允许合并 |
| 典型用途 | 设备内存(帧缓冲、数据缓冲区、RAM) | 控制/状态寄存器、FIFO、有副作用的 MMIO |
| 副作用风险 | 读取无副作用(如读帧缓冲不会改变状态) | 读取可能有副作用(如读 FIFO 会消费数据) |
| Linux 映射 | ioremap_wc()(写合并)或 ioremap() |
ioremap()(默认不可缓存) |
关键警告: 如果设备将“读取即清除中断状态”的寄存器标记为 Prefetchable,CPU 的推测性读取可能导致中断丢失或状态异常。驱动程序必须通过
ioremap()(而非 ioremap_wc())映射 Non-Prefetchable BAR,并使用 ioread32 等带内存屏障的访问函数。
4.2.2 Prefetchable 属性在 BAR 中的编码
BAR 寄存器的 bit 3 编码如下:
- bit 3 = 0:Non-Prefetchable(不可预取)
- bit 3 = 1:Prefetchable(可预取)
注意:只有 Memory BAR 的 bit 3 有意义;I/O BAR 的 bit 3 保留且必须为 0。
4.3 64 位 BAR 与 32 位 BAR
BAR 的地址宽度由 BAR 寄存器的 bit 2 和 bit 1 共同决定。64 位 BAR 使用连续的两个 BAR 寄存器(如 BAR0 和 BAR1)来存储 64 位基地址,而 32 位 BAR 仅使用一个寄存器。
4.3.1 地址宽度编码规则
| bit 2:1 | BAR 类型 | 地址宽度 | 占用寄存器数 | 最大地址空间 |
|---|---|---|---|---|
| 00 | 32 位 Memory BAR | 32 位 | 1 个 | 4 GB |
| 10 | 64 位 Memory BAR | 64 位 | 2 个(连续) | 16 EB |
| 01 | I/O BAR | 32 位(有效 16 位) | 1 个 | 64 KB |
4.3.2 64 位 BAR 的布局示例
假设设备使用 BAR0 和 BAR1 组成一个 64 位 BAR:
PCI 配置空间偏移量:
0x10 (BAR0) : 低 32 位基地址 [31:0] + 属性位
0x14 (BAR1) : 高 32 位基地址 [63:32] + 属性位(bit 0 固定为 0)
属性位分布(BAR0):
bit 0 : 0 (Memory BAR)
bit 1 : 0 (32 位) / 1 (64 位)
bit 2 : 0 (32 位) / 1 (64 位)
bit 3 : Prefetchable
bits [31:4] : 基地址(对齐到 16 字节边界)
注意:BAR1 的 bit 0 必须为 0,表示该寄存器是 64 位 BAR 的高位部分,
而不是一个独立的 BAR。
4.3.3 驱动开发中的处理要点
- 检测 64 位 BAR:读取 BAR0 的 bit 2:1,若为
10,则 BAR0 和 BAR1 组成一个 64 位 BAR。 - 跳过被占用的 BAR 索引:当检测到 64 位 BAR 时,驱动应跳过下一个 BAR 索引(如从 BAR0 直接跳到 BAR2)。
- 地址对齐:64 位 BAR 的基地址必须按 BAR 大小对齐(如 1 MB 的 BAR 必须对齐到 1 MB 边界)。
- ioremap 处理:使用
ioremap(pci_resource_start(pdev, bar), pci_resource_len(pdev, bar)),Linux PCI 核心会自动处理 64 位地址。
实战经验: 在 64 位系统上,如果设备 BAR 位于 4 GB 物理地址以上,必须使用 64 位 BAR。32 位 BAR 只能映射到 4 GB 以下的低端物理内存区域。对于高端 GPU 或 FPGA 的大容量板载内存(>4 GB),64 位 BAR 是唯一选择。
4.4 BAR 类型与属性综合判定流程
驱动程序在初始化阶段,需要解析每个 BAR 的类型和属性,以选择合适的映射策略。以下 SVG 流程图展示了完整的判定逻辑:
4.5 实际 BAR 配置示例
以下是一个真实 PCIe 网卡的 BAR 配置示例(通过 lspci -vvv 输出):
Region 0: Memory at f8000000 (64-bit, prefetchable) [size=16M]
Region 2: Memory at f9000000 (64-bit, non-prefetchable) [size=256K]
Region 4: I/O ports at 3000 [size=32]
解析:
- Region 0:64 位 Memory BAR,Prefetchable,大小 16 MB,适合映射为
ioremap_wc()(用于数据缓冲区)。 - Region 2:64 位 Memory BAR,Non-Prefetchable,大小 256 KB,适合映射为
ioremap()(用于控制寄存器)。 - Region 4:I/O BAR,大小 32 字节,使用
ioport_map()访问(仅用于兼容性管理功能)。
4.6 本章小结
| 知识点 | 核心要点 |
|---|---|
| Memory vs I/O BAR | Memory BAR 性能高、支持 64 位地址、可预取;I/O BAR 仅用于兼容,地址空间有限 |
| Prefetchable 属性 | 允许 CPU 推测读取和合并写入;仅适用于无副作用的存储区域(如帧缓冲) |
| 64 位 vs 32 位 BAR | 64 位 BAR 占用两个连续寄存器,支持 >4 GB 地址空间;32 位 BAR 限制在 4 GB 以下 |
| 驱动处理要点 | 检测 bit 2:1 判断宽度;检测 bit 3 判断预取;跳过被占用的 BAR 索引 |
下一阶段准备: 在掌握了 BAR 类型与属性后,第 5 章将深入讲解如何通过
ioremap() 将 BAR 物理地址映射到内核虚拟地址空间,并处理不同 BAR 属性下的缓存策略差异。