2. BAR地址空间类型
说白了,BAR(Bar Address Register)就是设备向系统申报的“地址户口本”。
系统在枚举PCIe总线时,会读取每个BAR的配置信息,然后给设备分配一段连续的物理地址空间。分配完以后,CPU就能通过内存访问指令直接读写设备寄存器了。
我自己刚入行那会儿,总觉得BAR配置随便写写就行。后来吃过一次亏才发现——BAR类型选错,系统可能根本不认你的设备。
2.1 Memory BAR:最常用的类型
现在99%的PCIe设备都用Memory BAR。为什么?因为CPU访问内存空间最快、延迟最低。
Memory BAR的几个关键属性:
- 可预取(Prefetchable):允许CPU提前读取数据,适合存放只读数据结构
- 不可预取(Non-Prefetchable):读取有副作用,比如读一次状态寄存器会清除中断标志
- 宽度:32-bit或64-bit,32-bit最多寻址4GB,64-bit可以寻址整个系统内存
我做过一个项目,把一块网卡的接收描述符环放在不可预取区,结果性能惨不忍睹。后来改成可预取区,吞吐量直接翻了一倍。
实战建议:
- 设备控制寄存器 → 不可预取
- 数据缓冲区、FIFO → 可预取
- 我个人习惯:所有Memory BAR都配上64-bit地址,除非芯片面积实在受限
2.2 IO BAR:快被淘汰的老前辈
IO空间是个老东西,从x86时代就有了。它的地址范围只有64KB(0x0000 ~ 0xFFFF)。
你会问:谁还在用IO BAR?嗯,主要是老旧设备或者兼容性需求。
| 对比项 | Memory BAR | IO BAR |
|---|---|---|
| 地址空间范围 | 取决于位数,最大2^64 | 固定64KB |
| 访问方式 | 普通load/store指令 | 专用IN/OUT指令 |
| 可预取 | 可以设置 | 始终不可预取 |
| 排序保证 | 有弱排序/强排序之分 | 严格排序 |
| 当下推荐 | 强烈推荐 | 不推荐新设计使用 |
避坑指南:
我曾经在一个嵌入式项目里选了IO BAR,结果发现ARM架构根本不支持IO空间!那套代码在x86上跑得好好的,一交叉编译到ARM就报错。从那以后,除非客户明确要求x86兼容,否则我都只用Memory BAR。
2.3 64-bit Memory BAR:大内存时代的标配
现在的系统内存都是GB起步,32-bit的4GB上限根本不够用。所以64-bit BAR成了主流。
配置64-bit BAR要注意一个细节:它实际占用两个连续的BAR寄存器。BAR0配低32位,BAR1就自动变成高32位。
// 伪代码:配置一个64-bit非预取Memory BAR
// 假设设备内部寄存器占用4KB地址空间
pci_write_config_dword(dev, PCI_BASE_ADDRESS_0,
PCI_BASE_ADDRESS_MEM_TYPE_64 |
PCI_BASE_ADDRESS_SPACE_MEMORY |
0x00001000); // 低32位地址+请求大小
// BAR1自动变为高32位映射
pci_write_config_dword(dev, PCI_BASE_ADDRESS_1,
0x00000000); // 高32位,这里演示设为0
我遇到过一个坑:某款SoC的PCIe控制器在64-bit BAR模式下,地址对齐要求是1MB而不是通常的4KB。文档里用一行小字写了,我根本没看到。结果板子一上电,系统响应BAR请求时总是报错。找了两天才定位到是alignment mismatch。
64-bit BAR的硬性规则:
- 两个BAR必须连续使用,不能中间有其他BAR
- 地址必须对齐到请求大小的自然边界
- 系统可能会因为地址空间碎片而分配失败——我一般采用分配两次失败就降级到32-bit的兜底策略
2.4 三种类型速查表
| 类型 | BAR寄存器占用 | 最大空间 | 常见场景 | 我个人的选择原则 |
|---|---|---|---|---|
| Memory 32-bit | 1个 | 4GB | 小规模控制寄存器 | 几乎不用了 |
| Memory 64-bit | 2个(连续) | 2^64 | 大缓冲区、高性能设备 | 默认首选 |
| IO | 1个 | 64KB | x86老旧兼容设备 | 仅在x86且有明确兼容要求时才用 |
一点经验:
我设计多BAR设备时,习惯把最重要的、访问频率最高的寄存器组放在BAR0,并且配成64-bit非预取Memory。
这样做有两个好处:一是系统分配地址时优先给BAR0分配连续大空间;二是调试时用BAR0做快速验证,其他BAR出了问题也不会堵死核心功能。