3. BAR空间原理:BAR寄存器作用、内存映射与IO映射、BAR地址对齐与大小计算

Base Address Register(BAR)是PCIe设备配置空间中最关键的寄存器组,它决定了主机CPU如何访问设备内部的寄存器、FIFO或内存缓冲区。理解BAR的工作原理是编写正确PCIe驱动的基石。

3.1 BAR寄存器的作用与结构

每个PCIe Function最多支持6个BAR(Type 0 Header)或2个BAR(Type 1 Header,用于Bridge)。BAR的主要作用包括:

  • 声明资源需求:设备通过BAR向系统报告它需要的内存空间或I/O空间的大小。
  • 接受基地址分配:BIOS/UEFI或VxWorks PCIe总线枚举器将系统物理地址写入BAR,完成地址映射。
  • 使能访问通路:一旦BAR被正确配置,CPU即可通过映射后的地址直接访问设备内部资源。
关键概念:BAR本身是一个可读写的寄存器,但它的只读位决定了资源类型和大小。写入全1再回读,是探测BAR大小的标准方法。

3.1.1 BAR寄存器位域定义(32位BAR示例)

位域 宽度 描述
Bit 0 1 空间类型指示:0 = Memory Space,1 = I/O Space
Bit 2:1 2 内存类型(仅Memory BAR):00 = 32位,10 = 64位
Bit 3 1 预取属性(仅Memory BAR):1 = Prefetchable,0 = Non-Prefetchable
Bit 31:4 28 基地址:实际映射的物理地址高28位(32位BAR)
实战提示:64位BAR占用连续的两个BAR寄存器(如BAR0和BAR1),低BAR存放低32位地址,高BAR存放高32位地址。在VxWorks中处理64位BAR时,必须将两个BAR视为一个整体进行探测和配置。

3.2 内存映射与I/O映射

PCIe设备支持两种地址空间映射方式,它们在访问机制和性能上存在显著差异:

  • Memory-Mapped I/O (MMIO):将设备寄存器映射到CPU的物理内存地址空间,CPU使用普通的load/store指令访问。这是现代PCIe设备的主流方式,支持突发传输和缓存一致性。
  • I/O Space Mapping:映射到x86特有的I/O地址空间,必须使用in/out指令访问。I/O空间大小受限(64KB),且不支持预取,性能较低。
架构差异:在PowerPC或ARM架构下,通常不存在独立的I/O空间。VxWorks BSP在移植时,必须将I/O BAR请求转换为MMIO方式处理,否则设备无法正常工作。
MMIO 与 I/O 映射对比 MMIO (Memory-Mapped I/O) CPU Load/Store 物理内存地址空间 支持突发传输 / 缓存一致 性能高,现代设备首选 I/O Space Mapping CPU in/out 指令 I/O 地址空间 大小受限 (64KB) 不支持预取,性能低

3.3 BAR地址对齐与大小计算

PCIe规范要求BAR的基地址必须与BAR所请求的大小自然对齐。这意味着如果BAR请求16MB空间,其基地址的低24位必须为0(即16MB对齐)。

3.3.1 探测BAR大小的标准算法

  1. 保存BAR当前值(读回保存)。
  2. 向BAR写入全1(0xFFFFFFFF)。
  3. 读回BAR值,忽略低4位(Type位和属性位),对高位取反加1,即得到BAR请求的大小。
  4. 恢复BAR原始值。
计算公式:
BAR_SIZE = (~(BAR_VALUE & 0xFFFFFFF0) + 1)
对于64位BAR,需要将低BAR和高BAR组合为一个64位值进行计算。

3.3.2 对齐规则与常见陷阱

BAR请求大小 对齐要求 基地址低N位必须为0
4 KB 4 KB 对齐 Bit 11:0 = 0
1 MB 1 MB 对齐 Bit 19:0 = 0
16 MB 16 MB 对齐 Bit 23:0 = 0
64 MB 64 MB 对齐 Bit 25:0 = 0
256 MB 256 MB 对齐 Bit 27:0 = 0
常见陷阱:BAR大小必须是2的幂次方。如果设备设计时BAR大小不是2的幂次方(如3MB),PCIe枚举器将无法正确分配地址,导致驱动初始化失败。在VxWorks中,如果遇到BAR探测结果为0或异常大小时,应首先检查设备硬件设计是否符合2的幂次方对齐规则。
BAR 大小探测流程 步骤1:保存BAR原始值 步骤2:写入 0xFFFFFFFF 步骤3:回读BAR值 步骤4:取反 + 1 计算大小 步骤5:恢复BAR原始值 注意:忽略低4位 (Type & 属性位)

3.4 VxWorks中的BAR配置实战要点

在VxWorks中,PCIe总线枚举器(通常由BSP中的pciConfigLib或pciAutoConfigLib实现)负责自动探测和配置BAR。驱动开发者需要关注以下环节:

  • 资源分配冲突:当多个设备请求的总空间超过系统地址窗口时,枚举器可能分配失败。此时需要检查BSP中的PCI内存窗口大小设置。
  • 64位BAR处理:在32位系统上,64位BAR可能无法获得高位地址,导致映射失败。VxWorks 7支持通过MMU配置高位映射。
  • 预取属性:Prefetchable BAR允许CPU合并写操作,但要求设备寄存器读取无副作用。如果设备寄存器读取有副作用(如清中断),必须设置为Non-Prefetchable。
调试技巧:在VxWorks shell中使用 pciDevShow 命令可以查看所有PCIe设备的BAR配置状态。如果某个BAR显示为0或无效地址,说明枚举阶段配置失败,需要检查硬件连接或BSP配置。

3.4.1 典型BAR配置代码片段

/* VxWorks PCIe BAR 探测与映射示例 */
STATUS pcieBarProbe
    (
    UINT32  busNo,
    UINT32  deviceNo,
    UINT32  funcNo,
    UINT32  barIndex,
    UINT32 *pBarSize
    )
    {
    UINT32  barOrig;
    UINT32  barMask;
    UINT32  barValue;

    /* 1. 读取BAR原始值 */
    pciConfigInLong (busNo, deviceNo, funcNo,
                     PCI_CFG_BASE_ADDRESS_0 + (barIndex * 4),
                     &barOrig);

    /* 2. 写入全1探测大小 */
    pciConfigOutLong (busNo, deviceNo, funcNo,
                      PCI_CFG_BASE_ADDRESS_0 + (barIndex * 4),
                      0xFFFFFFFF);

    /* 3. 回读并计算 */
    pciConfigInLong (busNo, deviceNo, funcNo,
                     PCI_CFG_BASE_ADDRESS_0 + (barIndex * 4),
                     &barValue);

    /* 4. 忽略低4位,取反加1 */
    barMask = barValue & 0xFFFFFFF0;
    *pBarSize = (~barMask) + 1;

    /* 5. 恢复原始值 */
    pciConfigOutLong (busNo, deviceNo, funcNo,
                      PCI_CFG_BASE_ADDRESS_0 + (barIndex * 4),
                      barOrig);

    return OK;
    }
核心要点总结:
• BAR寄存器通过只读位向系统声明资源需求,写入全1回读是探测大小的标准方法。
• MMIO是现代PCIe设备的主流映射方式,性能优于I/O映射。
• BAR基地址必须自然对齐到其请求的大小,大小必须是2的幂次方。
• VxWorks中通过pciConfigLib完成BAR配置,驱动开发者应关注64位BAR和预取属性的正确处理。