4. BAR空间类型:32位与64位BAR、预取与非预取内存、BAR大小计算与对齐

BAR(Base Address Register)是PCIe设备与主机软件交互的桥梁。理解BAR的空间类型、属性以及大小计算规则,是进行MMIO编程的基础。本章将深入剖析这些核心概念,并通过实例与图表帮助您建立直观认识。

4.1 BAR空间类型:32位 vs 64位

PCIe配置空间中的每个BAR寄存器宽度为32位。根据设备所需地址空间的大小,一个BAR可以单独使用(32位),也可以与下一个BAR寄存器配对使用(64位)。

特性 32位 BAR 64位 BAR
寄存器占用 1个32位寄存器 2个连续的32位寄存器(高32位 + 低32位)
地址空间 最大4GB(232) 最大16EB(264)
适用场景 小规模寄存器、FIFO、控制状态寄存器 大容量显存、DMA缓冲区、大块共享内存
硬件复杂度 低 高(需要支持64位地址解码)
软件配置 写入全1后读取,检查有效位 先写低32位全1,再写高32位全1,分两次读取
判断BAR是32位还是64位: 向BAR写入全1(0xFFFFFFFF),然后读取。如果读取到的低4位为0,且高28位中有部分位被清零,则说明是32位BAR。如果读取到的值中,低4位为0,且高28位全部为0,则说明该BAR与下一个BAR配对,构成一个64位BAR。
32位BAR与64位BAR配置对比 BAR0 (32位) 基地址 [31:0] BAR1 (32位) 基地址 [31:0] 独立地址空间 最大 4GB / 每个BAR BAR2 (低32位) 基地址 [31:0] BAR3 (高32位) 基地址 [63:32] 合并为64位地址空间 最大 16EB / 每对BAR 配对

4.2 预取与非预取内存

BAR的Bit 3用于指示该地址空间是否为“预取(Prefetchable)”内存。这一属性对CPU访问行为有重要影响。

  • 预取内存(Prefetchable): 读取操作没有副作用(side-effect),即多次读取同一地址返回相同数据,且不会改变设备状态。CPU和Cache可以安全地预取、合并读写操作,从而提升性能。典型应用:帧缓冲、大容量RAM。
  • 非预取内存(Non-Prefetchable): 读取操作可能有副作用,例如读取FIFO或状态寄存器会改变内部指针或清除中断状态。CPU必须严格按照程序顺序执行访问,不能预取或合并。典型应用:控制寄存器、FIFO、I/O端口映射。
属性 预取 (Prefetchable) 非预取 (Non-Prefetchable)
BAR Bit 3 1 0
读副作用 无 可能有
CPU Cache 允许Cache 禁止Cache (通常使用Uncacheable)
写合并 允许 禁止
访问顺序 可重排、可预取 严格按序
典型用途 显存、DMA缓冲 控制/状态寄存器、FIFO
驱动开发陷阱: 将非预取内存错误地声明为预取内存,会导致CPU预取或合并访问,从而破坏设备状态(例如,多次读取FIFO导致数据丢失)。反之,将预取内存声明为非预取内存,虽然功能正确,但会损失性能。务必根据设备手册正确设置BAR的预取位。
预取 vs 非预取内存访问模型 CPU Core Cache 预取内存 允许预取 & 写合并 读无副作用 可被Cache缓存 非预取内存 严格按序访问 禁止预取 & 写合并 CPU直接访问 (绕过Cache)

4.3 BAR大小计算与对齐

PCIe规范使用一种精巧的“写全1回读”机制来确定BAR所需地址空间的大小,并且要求BAR的基地址必须与其大小对齐(即基地址是大小的整数倍)。

4.3.1 大小计算原理

  1. 写入全1: 向BAR寄存器写入0xFFFFFFFF。
  2. 回读: 读取BAR寄存器的值。硬件会将不需要的地址位(即超出所需地址范围的位)清零。
  3. 解析大小: 从回读值中,找到最低位的1所在的位置(从0开始计数),该位置对应的2的幂即为BAR的大小。
计算公式:
size = (~(readback_value & 0xFFFFFFF0) + 1) & 0xFFFFFFF0

或者更直观地:size = 2^(lowest_set_bit_position),其中lowest_set_bit_position是回读值中最低位1的索引。

4.3.2 对齐规则

PCIe要求BAR的基地址必须与BAR的大小对齐。例如,一个大小为1MB的BAR,其基地址必须是1MB的整数倍(即低20位为0)。

BAR大小 对齐边界 基地址低地址位要求
4KB 4KB 低12位为0
1MB 1MB 低20位为0
16MB 16MB 低24位为0
1GB 1GB 低30位为0
驱动编程技巧: 在Linux内核中,可以使用pci_resource_start()、pci_resource_end()和pci_resource_len()函数来获取BAR的基地址、结束地址和大小,无需手动计算。但理解底层原理对于调试和解决地址冲突至关重要。
BAR大小计算与对齐示例 (1MB BAR) 步骤1: 写入 0xFFFFFFFF 步骤2: 回读 0xFFF00000 步骤3: 解析大小 最低位1的位置 = 20 大小 = 2^20 = 1MB 对齐要求 基地址必须为1MB整数倍 低20位地址必须为0 物理地址空间 其他设备 BAR空间 (1MB) 其他设备 基地址 = 0x?FF00000 (低20位为0)

4.4 实战:使用lspci查看BAR信息

在Linux系统中,lspci -vvv命令可以显示设备的完整配置空间,包括BAR信息。以下是一个典型输出片段:

# lspci -vvv -s 01:00.0
01:00.0 VGA compatible controller: NVIDIA Corporation GA106 [GeForce RTX 3060 Lite Hash Rate] (rev a1)
        Region 0: Memory at f6000000 (32-bit, non-prefetchable) [size=16M]
        Region 1: Memory at e0000000 (64-bit, prefetchable) [size=256M]
        Region 3: Memory at f0000000 (64-bit, prefetchable) [size=32M]
        Region 5: I/O ports at e000 [size=128]
输出解读:
  • Region 0: 32位非预取内存,基地址0xf6000000,大小16MB。
  • Region 1: 64位预取内存,基地址0xe0000000,大小256MB(与Region 2配对)。
  • Region 3: 64位预取内存,基地址0xf0000000,大小32MB(与Region 4配对)。
  • Region 5: I/O端口空间,基地址0xe000,大小128字节。

通过本章的学习,您应该能够准确识别BAR的类型、属性,并理解其大小计算与对齐机制。这些知识是后续进行MMIO映射和驱动开发的基础。