1. PCIe BAR基础概念与寄存器结构
各位工程师朋友,咱们今天聊聊 PCIe BAR 。说实在的,我刚入行那会儿,觉得 BAR 就是几个寄存器,随便配配就行。直到有一次项目,因为一个 BAR 地址没对齐,整块板子在系统里死活认不出来。从那以后,我彻底明白了—— BAR 这东西,看似简单,其实坑不少。
这一章,咱们就把 BAR 的老底儿翻出来。从概念到寄存器,捋得清清楚楚。
1.1 什么是 PCIe BAR?
BAR,全称 Base Address Register,基地址寄存器。翻译成人话就是:设备告诉系统“我需要多大地方、放哪儿合适”的寄存器。
你想想看,系统上电后,枚举器挨个问每个 PCIe 设备:“伙计,你要多大内存空间?你希望放哪儿?” 设备通过 BAR 回答:“我要 4KB,最好是可预取的。” 然后系统分配一个基地址,写回 BAR。这就是 BAR 的使命。
- 向系统声明设备需要的地址空间大小
- 接收系统分配的实际基地址
我记得有次调试一个 FPGA 加速卡,驱动老是加载失败。查了半天,发现是 BAR 空间大小声明错了,驱动分配了 1MB,但设备实际只实现了 256KB。这儿不对,后面所有地址转换都乱了。所以, BAR 这个“第一印象”必须准。
1.2 配置空间与寄存器结构
每个 PCIe 设备都有 256 字节的配置空间。Type 0 头(Endpoint 用)和 Type 1 头(Switch 用)里, BAR0 到 BAR5 占了 24 个字节。
我习惯把这个结构画成一张图,大家跟着我看看:
看到这张图了吧?BAR0 到 BAR5 放在配置空间偏移 0x10 到 0x27 的位置。每个 BAR 32 位,但 64 位的 BAR 会占用两个相邻位置。我有个项目为了省寄存器,想用 32 位 BAR 映射 8GB 空间,结果驱动直接报错——32 位 BAR 最大只能映射 4GB。
1.3 BAR 寄存器位域详解
咱们拿一个典型的 32 位内存 BAR 举例。你把写入 0xFFFFFFFF 再读回来,就能知道它的大小和类型。这事儿干一次记一辈子。
| 位域 | 宽度 | 含义 | 说明 |
|---|---|---|---|
| 位 0 | 1 | 空间指示符 | 0: 内存空间,1: I/O 空间 |
| 位 2:1 | 2 | 类型 | 00: 32 位,10: 64 位,01/11: 保留 |
| 位 3 | 1 | 可预取 | 1: 支持预取,0: 不支持 |
| 位 31:4 | 28 | 基地址 | 对齐到 16 字节边界,实际有效位由大小决定 |
为什么要预取?说白了,可预取的 BAR 允许 CPU 提前读数据,不需要考虑副作用。对于普通帧缓冲或者数据缓冲区,设成可预取能提高性能。但如果是状态寄存器或者 FIFO,读一次就会改变状态,不能预取。我在一块图像采集卡上吃过这个亏——把控制寄存器 BAR 设成了可预取,结果驱动读状态时返回了错误数据。
1.4 地址空间类型:内存 vs I/O
PCIe 支持两种地址空间。不过我得说一句,I/O 空间在 x86 之外越来越少用了。ARM 平台基本不支持 I/O BAR。我建议新设计一律用内存空间。
- 内存空间(位 0 = 0): CPU 用普通内存读写指令访问,效率高,支持缓存和预取。
- I/O 空间(位 0 = 1): 用专用 IN/OUT 指令,仅限于 x86,且大小限制在 256 字节内。
有意思的是,有些老设备还坚持用 I/O BAR,比如传统的串口。但你想想看,今天一个 GPU 要映射几个 GB 显存,I/O 空间那点地址根本不够看。所以大多数现代设备只实现内存 BAR。
1.5 BAR 大小声明机制
设备怎么告诉系统“我要多大空间”?这是 PCIe 协议里非常巧妙的做法。系统枚举时,向 BAR 写入全 1(0xFFFFFFFF),然后读回。设备硬件会把实际地址线的实现反映在回读值中。
举个例子。如果设备需要 4KB 内存空间:
- 写入 0xFFFFFFFF
- 读回 0xFFFFF000 (低 12 位为 0,表示 4KB 对齐)
- 系统解析:低 12 位全 0 → 空间大小 = 2^12 = 4KB
1.6 64 位 BAR 的特殊处理
当一个设备需要映射超过 4GB 的空间时,必须使用 64 位 BAR。但代价是消耗两个连续的 BAR 位置(例如 BAR0/BAR1)。
实现方式如下:
- BAR0 的类型位设为 10(64 位),BAR1 被硬件标记为“保留”
- 系统分配 64 位地址时,高位写入 BAR1,低位写入 BAR0
- BAR1 的低 4 位必须为 0,表示它是高位部分
我个人习惯:如果一个设备只需要 64 位 BAR 但又只有 4GB 以下内存,我用两个 32 位 BAR 替代。为什么不直接用一个 64 位 BAR?因为 64 位 BAR 的枚举过程更长,有些老 BIOS 处理有问题。不过现代系统无所谓了。
1.7 本章要点速查
| 概念 | 关键点 |
|---|---|
| BAR 定义 | 用于声明空间大小和接收系统分配的基地址 |
| 寄存器位置 | Type 0 头偏移 0x10~0x27,6 个 BAR 槽位 |
| 大小检测 | 写全 1 读回,低 N 位为 0 表示 2^N 字节 |
| 类型选择 | 优先用 32 位内存 BAR,超 4GB 用 64 位 |
| I/O BAR | x86 专属,新设计不建议使用 |
| 可预取 | 数据缓冲区设 1,控制寄存器设 0 |