4. BAR空间大小计算:通过写入全1再回读的方法确定BAR大小、预取属性与地址对齐规则
在PCIe设备枚举过程中,系统软件必须准确获取每个BAR(Base Address Register)所请求的地址空间大小、类型(内存或I/O)以及预取属性。由于硬件设计时BAR的地址译码逻辑只对特定范围的地址产生响应,软件可以通过“向BAR写入全1,再回读”的方法,逆向推导出BAR的大小和对齐要求。本节将详细解析这一经典方法的原理、操作步骤以及地址对齐规则。
4.1 写入全1回读法原理
PCIe规范规定,BAR寄存器中可编程的地址位对应硬件实际使用的地址译码位。当软件向BAR写入全1(0xFFFFFFFF)时,硬件会将未使用的低位强制清零,而实际使用的地址位则保持写入值。回读后,软件通过分析被清零的低位数量,即可计算出BAR的大小。
例如,若写入 0xFFFFFFFF 后回读得到 0xFFFF0000,则低16位被硬件清零,表示该BAR请求的地址空间大小为 216 = 64 KB。
4.2 操作步骤详解
以下为标准的BAR大小探测流程,适用于PCIe配置空间中的每个BAR寄存器(BAR0~BAR5以及扩展ROM BAR)。
- 保存原始值: 读取BAR当前值并保存,以便后续恢复。
- 写入全1: 向BAR写入
0xFFFFFFFF(对于64位BAR,需先写低32位,再写高32位)。 - 回读并分析: 读取BAR值,识别被硬件强制清零的低位。
- 计算大小: 根据清零位数计算地址空间大小。
- 恢复原始值: 将第一步保存的原始值写回BAR。
4.3 地址对齐规则与预取属性
BAR的地址对齐规则与BAR类型和大小直接相关。下表总结了不同类型BAR的地址对齐要求和属性编码。
| BAR类型 | bit[2:0] 编码 | 对齐要求 | 预取属性 | 典型应用 |
|---|---|---|---|---|
| 32位内存空间(非预取) | 000 | 大小对齐(如4KB BAR必须4KB对齐) | 不可预取 | 控制寄存器、状态寄存器 |
| 32位内存空间(预取) | 001 | 大小对齐 | 可预取 | 帧缓冲、大容量数据缓冲区 |
| 64位内存空间(非预取) | 100 | 大小对齐,低32位BAR与高32位BAR连续 | 不可预取 | 64位地址的控制寄存器 |
| 64位内存空间(预取) | 101 | 大小对齐,低32位BAR与高32位BAR连续 | 可预取 | 大容量显存、高性能数据缓冲区 |
| I/O空间 | 011 | 大小对齐(最小4字节) | 不适用 | 传统I/O端口(x86平台) |
4.4 预取属性与地址空间分配
预取(Prefetchable)属性指示该BAR对应的地址空间是否允许CPU进行预取访问。预取空间通常用于存储只读或可重复读取的数据(如显存、固件代码),允许CPU缓存和合并写操作,从而提升性能。非预取空间则用于包含状态寄存器的设备,每次读取必须返回当前真实状态,不允许缓存。
系统软件在分配地址空间时,通常会将所有预取BAR集中分配在64位地址空间的高端,以支持大容量映射;非预取BAR则分配在32位地址空间内。这种策略有助于优化地址空间利用率并减少碎片。
4.5 计算示例与SVG流程图
以下示例演示一个32位非预取BAR的探测过程:
// 假设BAR原始值为 0xF0000000
// 步骤1:保存原始值
original_val = 0xF0000000;
// 步骤2:写入全1
write_bar(0xFFFFFFFF);
// 步骤3:回读
read_val = read_bar(); // 假设回读值为 0xFFFF0000
// 步骤4:计算大小
// 低16位为0,表示地址空间大小为 2^16 = 64KB
bar_size = (~(read_val & 0xFFFFFFF0)) + 1; // 结果为 0x10000 (64KB)
// 步骤5:恢复原始值
write_bar(original_val);
下图展示了BAR大小探测的完整流程:
4.6 常见问题与避坑指南
- BAR被硬件修改: 某些设备在写入全1后,BAR值可能因硬件状态变化而改变。务必在探测前保存原始值,并在探测后立即恢复。
- 64位BAR的高32位探测: 写入全1到低32位BAR后,高32位BAR的bit[2:0]必须为0,否则设备可能被错误识别为32位BAR。探测高32位时,需单独写入全1并回读。
- I/O BAR的最小大小: I/O BAR的最小大小为4字节,且bit[1:0]必须为01。写入全1后,回读值的低2位会被硬件强制为01。
- 未实现的BAR: 如果BAR未实现(即硬件不支持),写入全1后回读值将为0。此时应跳过该BAR的探测。
pci_read_bases() 函数实现了上述探测逻辑。驱动开发者可以直接使用 pci_resource_start()、pci_resource_end() 和 pci_resource_flags() 等API获取BAR信息,无需手动探测。但在裸机或RTOS环境下,理解并实现此方法至关重要。
4.7 地址对齐规则图解
下图展示了不同大小BAR的地址对齐边界要求,帮助理解基地址与大小之间的关系:
4.8 小结
写入全1回读法是PCIe设备枚举中确定BAR空间大小的标准方法。通过理解硬件如何强制清零未使用的地址位,驱动开发者可以准确计算出每个BAR请求的地址范围、类型和预取属性。地址对齐规则是地址分配的基础,系统软件必须确保BAR基地址以其大小为对齐边界。掌握这些知识,是后续实现BAR空间映射和DMA配置的关键前提。