Linux 64位PCI BAR编程疑问:为何64位BAR高位为0x1而非0x21?
先梳理下背景信息,方便理解问题:
根总线资源
pci_bus 0000:00: root bus resource [bus 00-ff] pci_bus 0000:00: root bus resource [io 0x0000-0xffff] pci_bus 0000:00: root bus resource [mem 0x1000000000-0x10ffffffff] (bus address [0x00000000-0xffffffff]) pci_bus 0000:00: root bus resource [mem 0x1100000000-0x11ffffffff pref] (bus address [0x00000000-0xffffffff]) pci_bus 0000:00: root bus resource [mem 0x2000000000-0x2fffffffff] (bus address [0x00000000-0xfffffffff]) pci_bus 0000:00: root bus resource [mem 0x3000000000-0x3fffffffff pref] (bus address [0x00000000-0xfffffffff])
端点设备BAR分配结果
pci 0000:01:00.0: BAR 2: assigned [mem 0x2100000000-0x211fffffff 64bit pref] pci 0000:01:00.0: BAR 0: assigned [mem 0x1000000000-0x100007ffff]
桥窗口信息
pci 0000:00:00.0: PCI bridge to [bus 01] pci 0000:00:00.0: bridge window [mem 0x1000000000-0x10000fffff] pci 0000:00:00.0: bridge window [mem 0x2100000000-0x211fffffff 64bit pref]
端点BAR汇总
endpoint: BAR 0 addr = 0x1000000000, size = 524287 endpoint: BAR 1 addr = 0x2100000000, size = 536870911
核心疑问
BAR1的高32位预期是0x21,但实际编程结果是0x1。排查发现问题源于pcibios_bus_to_resource/pcibios_resource_to_bus函数处理的窗口偏移:
window->res->start = 1000000000 window->offset = 1000000000 window->res->end = 10FFFFFFFF window->res->start = 1100000000 window->offset = 1100000000 window->res->end = 11FFFFFFFF window->res->start = 2000000000 window->offset = 2000000000 window->res->end = 2FFFFFFFFF
系统日志显示的是系统物理地址(已加上偏移),但编程端点BAR寄存器时会减去偏移,导致0x2100000000变为0x100000000。这种逻辑对32位BAR搭配64位地址似乎合理,但为什么64位BAR也遵循这个规则?
解答:这是PCI/PCIe总线地址模型的核心设计
首先要明确两个关键地址概念:
- 系统物理地址:CPU和内存、外设交互时使用的物理地址空间
- PCI总线地址:PCI设备之间、设备与桥之间通信时使用的地址空间
PCI桥的核心作用之一就是完成这两个地址空间的映射转换。从你提供的根总线资源可以看到,每一段系统物理地址范围都对应了一段总线地址范围:
比如mem 0x2000000000-0x2fffffffff对应的总线地址是0x00000000-0xfffffffff——也就是说,系统物理地址0x2000000000对应总线地址0x0,两者的偏移差就是0x2000000000。
为什么64位BAR也要减偏移?
不管BAR是32位还是64位,设备的BAR寄存器中存储的始终是PCI总线地址,而不是系统物理地址。这个规则是PCI/PCIe标准的基础:
- 对于32位BAR:总线地址被限制在32位以内,当系统物理地址超过32位时,桥的偏移转换会自动剥离高地址部分,只保留相对于窗口起始的32位偏移,确保设备能兼容32位地址空间。
- 对于64位BAR:虽然总线地址可以扩展到64位,但桥的地址映射逻辑依然不变——总线地址 = 系统物理地址 - 窗口偏移。设备只需要知道自己在总线地址空间中的位置,桥会负责把这个总线地址转换为系统物理地址供CPU访问。
回到你的场景:
BAR1分配的系统物理地址是0x2100000000,对应的桥窗口起始是0x2000000000,所以总线地址就是0x2100000000 - 0x2000000000 = 0x100000000——也就是高32位为0x1,低32位为0x0,这完全符合PCI地址转换规则。
为什么会有预期值的误解?
系统日志中显示的assigned [mem 0x2100000000...]是系统物理地址,这是给系统管理员看的,方便理解资源在整个物理地址空间的位置。但设备本身并不需要知道系统物理地址,它只需要使用总线地址和桥通信,所以编程BAR时必须转换为总线地址。
内容的提问来源于stack exchange,提问作者Fong P. Wong

