你在一台主机上直通显卡,或在虚拟化环境里用一张卡。折腾了一整天:刷过固件、注入过驱动、改过寄存器,显卡在系统里始终检测不对。驱动探测时抛出一句「BAR 尺寸无效」,虚拟机里更是什么都看不见。你越查越觉得是固件太老、是这卡不支持,于是准备再刷一次固件。
但真相很可能是:这台机器根本不需要刷固件。BAR 的尺寸可以在操作系统运行时直接重设,一分钟就能做完。
现象
典型的现象链是这样的:
$ dmesg | grep -i -E "BAR|BAR 2"
... BAR 2: assigned [mem 0x....-0x.... 64bit]
... BAR 2: failed to assign [mem size 0x10000000]
... 驱动探测失败:BAR 尺寸无效
解读一下:那个 0x10000000 是 256MB。也就是说,这张卡需要一段大得多的地址窗口(比如 2GB),但系统(确切地说是固件在开机时)只给它分配了 256MB。驱动想在 256MB 的窗口里映射 2GB 的显存,映射不了,于是报「BAR 尺寸无效」。
这个报错很容易误导人:它听起来像是「显卡的 BAR 坏了」,实际上它说的是「系统给的窗口太小」。BAR 的大小是显卡的能力(硬件固定),而分配给它的窗口大小是系统决定的(可调整)。出问题的是后者。
根因
BAR(Base Address Register,基地址寄存器)是 PCIe 设备用来声明「我需要多大一块地址空间」的机制。固件在开机枚举时,会读取每个设备声明的需求,然后在物理地址空间里分一块给它。
老固件、老主板上的地址空间规划比较保守,或者干脆不支持「大 BAR」这种能力,于是经常出现「卡要 2GB、固件只给 256MB」的错配。常见的 REBAR(Resizable BAR)功能就是为了解决这个——但很多老平台固件里没有它,或者需要很新的固件版本才算完整支持。
关键认识在于:大 BAR 不需要靠刷固件来获得,PCIe 规范本身就允许在运行时重新协商 BAR 的大小。
硬件上,PCIe 设备通过「可调整 BAR 能力」(Resizable BAR Capability)声明一组它能接受的尺寸档位。系统只要把选定的尺寸写进对应的能力寄存器,然后重新映射这块空间,就能让窗口变大。Linux 内核把这个能力通过 sysfs 暴露出来了,所以可以直接在运行时操作:
/sys/bus/pci/devices/<地址>/resource<N>_resize
往这个文件里写入「尺寸」,就会触发一次 BAR 重设。
解决
三步走。假设目标设备是 0000:01:00.0,我们想把它的 resource2 放大。
第一步,先软移除同一根端口下的兄弟设备。
这一步经常被漏掉,然后就会遇到另一个报错:bridge window ... failed to release / 「桥窗口未能释放」。原因是你想放大的那个窗口,同一张卡的其他功能还占着——最典型的就是显卡的音频功能(在 01:00.1 这样的地址上)。桥必须先腾空,才能重新规划窗口。
# 找到同一张卡的其他功能,逐个软移除
ls /sys/bus/pci/devices/0000:01:00.*
echo 1 > /sys/bus/pci/devices/0000:01:00.1/remove
第二步,写入目标尺寸。
# 写入的是「尺寸位图的位序号」,单位是 MB 的 2 的幂
echo 14 > /sys/bus/pci/devices/0000:01:00.0/resource2_resize
这个数字的含义容易搞错:14 不是 14MB,而是 2 的 14 次方个 MB,也就是 16384MB = 16GB。同理,8 代表 256MB,11 代表 2GB。具体哪些档位可用,可以先读一次这个文件,它会列出该设备支持的位序号列表:
cat /sys/bus/pci/devices/0000:01:00.0/resource2_resize
第三步,重新扫描总线,让被移除的设备回来。
echo 1 > /sys/bus/pci/rescan
rescan 会重新枚举总线,之前被移除的兄弟设备会重新出现,而这次固件(以及内核的重新分配逻辑)会用新的 BAR 尺寸去分配窗口。此时再 dmesg 看一眼,应该能看到更大的地址段,驱动的「BAR 尺寸无效」也随之消失。
把它做成开机自动执行。 这是最容易忽略的一点:上面这些操作都是运行时操作,重启即失效。所以要让它在每次开机后自动跑一遍。写一个 systemd 服务,按顺序执行「移除兄弟设备 → 写入 resize → rescan」,并且处理好时序(要等设备已经枚举出来之后才能操作)。注意 rescan 之后设备名可能变化,服务脚本里最好按稳定的标识(比如 PCI 地址)而不是设备节点名来定位。
延伸与预防
这条经验里最值钱的是一句方法论:改硬件寄存器之前,先查一下操作系统有没有现成的运行时接口。
「要改硬件行为就得刷固件 / 改寄存器 / 打补丁」是一种很常见的思维定式,它带来两个代价:一是刷固件有风险(刷坏就成砖),二是每次升级都得重来。而实际上,现代操作系统为了支持热插拔、虚拟化和各种灵活性,把大量硬件能力都通过 sysfs、/proc、控制接口暴露了出来。这些接口通常:
- 内部已经处理好了锁、时序和与驱动的协作,比你自己写寄存器安全得多;
- 是「运行时」的,试错成本低,不满意重启就恢复原状。
所以遇到「硬件能力用不上」的问题,排查顺序建议是:先看驱动/内核有没有暴露可调接口 → 再看固件设置里有没有对应开关 → 最后才考虑刷固件或改寄存器。
另外,那个障碍「兄弟设备占用窗口」也很有代表性。PCIe 里一张卡上的多个功能(显卡 + 音频 + USB-C 控制器等)共享上游桥的窗口资源,所以调整任何一个功能的地址空间,往往都要先把同一桥下的其他功能让开。记住这个模式,以后遇到「窗口释放失败」这类报错,第一反应就该是去查同一个桥下面还挂着谁。