← 所有文章
开发

笔记本显卡直通排错:Code 43、虚拟电池与 OVMF 回退

13 分钟阅读wum1n9

这篇记一下我在笔记本上折腾 NVIDIA 显卡直通时,遇到 Code 43 后的处理过程。环境是 AMD 宿主、QEMU/KVM + libvirt、Windows 10 客户机,直通的独显是 RTX 5060 Laptop GPU(GB206M)。

最值得留下的不是一串“万能参数”,而是两个不同层次的问题:先把这套配置里缺失的虚拟电池接进去;后来又撤回一版引入 DMA 回归的实验固件。最终那次开机,我确认了:代码 43 消失了。

下面按 2026 年 10 月 17—18 日的记录复盘。电池注入是这套配置的重要一环,但我没有做出“只增加电池,其余完全不动,43 就消失”的独立对照,所以不把它写成唯一原因。

01 / Code 43 是结果,不是诊断结论

设备管理器里的 43,表达的是驱动向 Windows 报告设备发生了故障,不会直接告诉你故障出在电池、固件、PCI 拓扑还是 DMA 映射。[1] 我当时的离线 Windows 记录里,NVIDIA 驱动和硬件 ID 能对应上,但“驱动装进去了”不等于“显卡初始化成功”。

因此,我把排查分成两条线:一条看客户机看到的硬件环境是否完整;另一条看显卡真正工作时,宿主与虚拟机之间的数据通路是否正常。

02 / 容易漏掉的部分:虚拟电池注入

笔记本独显直通资料里,一个常见的兼容性处理是向客户机提供 ACPI 电池设备。firelightning13 的笔记本直通文章专门写了 fake battery fix,MobilePassThrough 也提供了可读的 ASL 参考实现。[2][3] 这是部分移动显卡场景的经验,不是 NVIDIA 对所有 Code 43 的统一解释。

我这台机器的问题很具体:磁盘上已经有 ssdt-battery.aml,但虚拟机实际加载的还是不带电池的 ssdt.aml。 文件存在和客户机用上它,是两回事。

反编译后,带电池的版本在 \_SB.PCI0 下增加了 BAT0:_HID 是 PNP0C0A,_STA 返回 0x1F,并提供 _BIF 和 _BST。它们分别描述电池设备状态、静态信息和运行状态;Windows 的 ACPI 电池文档可以用来对照这类接口。[4]

这份表返回的是固定值:容量 5055 mAh、电压 12000 mV、剩余容量等于满充容量,充放电状态与速率均为 0。它只是给客户机补出一个电池接口,不会给显卡供电,也没有把宿主真实电池、电量和充电控制透传进去。 这是一份简化的兼容性表,不应当作完整的笔记本电源管理实现。

03 / 我实际改的是哪一项

我没有重新下载一份表就盲目追加,而是把原有 -acpitable 指向的文件替换成带电池版本,保留原来的 EC 表。下面是当时 libvirt XML 中的最小相关片段,放在已有的 qemu:commandline 内;路径是我当时的安装位置。

<!-- 替换原来指向 ssdt.aml 的项,而不是再加一份同名设备 -->
<qemu:arg value='-acpitable'/>
<qemu:arg value='file=/usr/local/share/qemu/realpc/ssdt-battery.aml'/>

<!-- 原有 EC 表保留 -->
<qemu:arg value='-acpitable'/>
<qemu:arg value='file=/usr/local/share/qemu/realpc/ssdt-ec.aml'/>

这里的 qemu: 前缀需要域根节点已有 xmlns:qemu="http://libvirt.org/schemas/domain/qemu/1.0"。QEMU 的 -acpitable file=… 读取包含 ACPI 表头的完整表,不是把 ASL 文本直接交给它。[5]

这一步有个很实际的坑:不能把我这套 ssdt.aml 与 ssdt-battery.aml 同时加载。 两份表不是互不相关的模块,它们包含重复的计时器、温度等对象,只是后者多了 BAT0。离线负对照同时加载时出现了 AE_ALREADY_EXISTS。

当时的 ACPICA 离线检查中,旧表查询 BAT0 方法得到 AE_NOT_FOUND;换成带电池的表后,BAT0 的方法能够返回相应对象。这里验证的是表内容和解释器结果,不等于 Windows 中的显卡已经正常。另外,acpiexec 即使输出 AML 错误,也可能以 0 退出,所以还得看具体输出。

如果只是检查自己的 AML,可以先在副本目录里反编译,确认有没有 BAT0;不要把一份来源不明的表直接覆盖进当前可用配置。

# 在副本目录检查;这里不修改虚拟机配置
cp /usr/local/share/qemu/realpc/ssdt-battery.aml ./ssdt-battery.inspect.aml
iasl -d ssdt-battery.inspect.aml
grep -nE 'BAT0|PNP0C0A|_STA|_BIF|_BST' ssdt-battery.inspect.dsl

04 / PCI 地址也查,但别混淆宿主和客户机

同一轮排查还调整过 GPU 与音频功能的客户机拓扑:当时从两个独立槽位,整理成同槽的 .0 和 .1,并在 function 0 开启 multifunction='on'。这是 libvirt 多功能 PCI 地址的配置规则。[6]

但这只是当时的一步,不是最终修复的单独证据。后续拓扑又有调整,最终恢复快照也并非仍然照搬那一对地址。更不能把宿主的 01:00.0 当成 Windows 里必须出现的地址:hostdev/source/address 选物理设备,外层的 address 才安排客户机槽位。

05 / 43 再次出现,这次线索在 DMA 与固件

后来我尝试了一版带实验性 AMD-Vi DMA 保护的 OVMF。开机后显卡再次报 43,而宿主日志里出现了真实 GPU 对应的 10 次 AMD-Vi: IO_PAGE_FAULT。这时只盯着“有没有电池”已经解释不了问题:电池表还在,新的变化是固件。

这些日志说明 GPU 的 DMA/IOMMU 路径出了问题,但仅靠报错地址,不能断言访问的就是显存,也不能直接定位到 QEMU 的某一行代码。微软的 GPU isolation 文档说明了 GPU 内存访问与 IOMMU 映射之间的关系;它不支持“开 DMA 保护就必然不能直通”这样的结论。[7]

我最后采用的是一次尽量小的回退:只把 XML 的 os/loader 从那版实验固件切回之前能工作的 OVMF CODE 文件。 原来的 QEMU、VARS/NVRAM、系统盘、当时的 GPU 拓扑以及虚拟电池配置都保留,没有顺手重装 NVIDIA 驱动,也没有重建 Windows。

- OVMF_CODE-amd-dma-27e2b8353a7a.4m.fd
+ OVMF_CODE-identity-124ee3376006.4m.fd

上面是我这次的两个本地构建文件名,不是让其他机器照抄的通用固件版本。关键是回到自己已经验证可用、与现有 VARS/NVRAM 匹配的组合,而不是随便换一个 OVMF。

配置校验和回滚检查通过后,我自己启动虚拟机复测,随后确认:“代码43消失了。” 这就是这次恢复结果的依据。现有记录没有保存一套新的 nvidia-smi 输出或完整性能测试,所以这里不延伸成“满性能通过”;也没有证明实验 DMA 保护与 GPU 已经同时正常。

06 / 留给下次自己的检查顺序

如果以后再遇到 43,我会先保存能工作的配置,然后从最近一次变化查起。下面这些是复查命令示例,不是本次恢复后的实测输出;其中虚拟机名称与 PCI 地址按自己的机器替换。

# 宿主:先留存配置,再看设备绑定和错误日志
virsh -c qemu:///system dumpxml --inactive realpc-win10 > before.xml
lspci -nnk -s 01:00.0
lspci -nnk -s 01:00.1
journalctl -k -b | grep -Ei 'AMD-Vi|IO_PAGE_FAULT|vfio'
# Windows:分别看电池是否枚举、显卡状态及驱动查询
Get-CimInstance Win32_Battery |
  Select-Object Name, BatteryStatus, EstimatedChargeRemaining

Get-CimInstance Win32_VideoController |
  Select-Object Name, DriverVersion, ConfigManagerErrorCode

nvidia-smi

我会分别确认:电池是否被枚举,设备管理器是否仍报 43,驱动查询能否工作,以及实际图形负载能否正常运行。某一项通过,不替代其他项;命令没报错,也不能代替看结果。

这次最有用的经验是:虚拟电池要真正加载进去,配置变更要能够回退,新的 43 要结合新的日志重新判断。 补齐电池解决的是客户机硬件环境的一块缺口;撤回实验固件处理的是后来出现的回归。把两件事分清,比再堆一层“修复参数”更有价值。

参考文章与文档

下面列出排查中参考及本次整理时核对的资料。个人文章提供具体机器上的经验,官方文档用于核对接口与行为;都不能代替自己机器的前后对照。

  1. Microsoft — Code 43 / CM_PROB_FAILED_POST_START:错误码的含义。
  2. firelightning13 — GPU Passthrough for Laptop with Fedora:其中的 fake battery fix 小节。
  3. T-vK / MobilePassThrough — fake-battery.asl:固定提交的电池参考实现;不是我本地那份表的逐字副本。
  4. Microsoft — ACPI battery and power subsystem firmware implementation:电池设备与状态接口。
  5. QEMU — Invocation:-acpitable 的用法。
  6. libvirt — Device addresses:PCI 多功能设备的地址规则。
  7. Microsoft — IOMMU-based GPU isolation:GPU DMA 映射与隔离背景。
— 写到这里。继续阅读 ↗