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 流程图展示了完整的判定逻辑:

BAR 类型与属性综合判定流程 读取 BAR 寄存器 bit 0 = ? bit 0 = 1 I/O BAR bit 0 = 0 bit 2:1 = ? 00 32-bit Memory BAR 10 64-bit Memory BAR 01 保留/无效 bit 3 = ? bit 3 = 1 Prefetchable bit 3 = 0 Non-Prefetchable 选择映射策略: ioremap() / ioremap_wc() / ioport_map()

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 属性下的缓存策略差异。