4. 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。
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的预取位。
4.3 BAR大小计算与对齐
PCIe规范使用一种精巧的“写全1回读”机制来确定BAR所需地址空间的大小,并且要求BAR的基地址必须与其大小对齐(即基地址是大小的整数倍)。
4.3.1 大小计算原理
- 写入全1: 向BAR寄存器写入0xFFFFFFFF。
- 回读: 读取BAR寄存器的值。硬件会将不需要的地址位(即超出所需地址范围的位)清零。
- 解析大小: 从回读值中,找到最低位的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的基地址、结束地址和大小,无需手动计算。但理解底层原理对于调试和解决地址冲突至关重要。
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映射和驱动开发的基础。