HOOOS

彻底解决 AMD 显卡 PVE 直通 Win11 报错 Code 43:深度排查指南与重置脚本实战

0 27 极客折腾家 Proxmox VE显卡直通AMD显卡
Apple

在玩转 PVE(Proxmox VE)All in One 或是高帧率云游戏主机的路上,AMD 显卡(如 RX 580、RX 5700XT、RX 6600XT 等)因其出色的性价比和对 macOS 的友好度,成为了很多折腾党的不二之选。

然而,当你满怀信心在 PVE 里建好 Windows 11 虚拟机,勾选了 PCIe 直通,开机装完 AMD 驱动后,迎来的却大概率是设备管理器里那个冰冷的黄色感叹号——“由于该设备有问题,Windows 已将其停止。(代码 43)”

这几乎是每个搞显卡直通的人都会撞上的“成人礼”。本文不讲虚的空话,直接从底层原理出发,给出全网最详尽的排查步骤,并提供针对 AMD 显卡特有**“重置缺陷(Reset Bug)”的解决脚本**。


一、 为什么 AMD 显卡直通会报 Code 43?

要解决问题,先要揪出幕后黑手。AMD 显卡直通报 Code 43,主要有以下四个元凶:

  1. 宿主机抢占驱动:PVE 宿主机在开机时已经加载了 amdgpuradeon 驱动,把显卡“据为己有”,导致直通给 VM 时设备状态不干净。
  2. KVM 虚拟化特征暴露:AMD 驱动检测到自己运行在虚拟机里,主动“罢工”(虽然近年 AMD/NVIDIA 官方声称放开了虚拟化限制,但安全起见,依然需要对 CPU 进行隐藏伪装)。
  3. 缺少干净的 VBIOS:物理显卡的 VBIOS(ROM)在宿主机引导时已经被污染或读取不完整,虚拟机拿不到正确的 ROM 映射。
  4. AMD 著名的 Reset Bug(重置缺陷):这是最头疼的问题。AMD 很多显卡不支持标准的 FLR(Function Level Reset)。虚拟机第一次开机正常,一旦重启或关机,显卡无法恢复到初始未初始化状态,再次开机必然 Code 43。

二、 排查与解决步骤:四步法

第一步:彻底解放宿主机,完善 IOMMU 配置

必须确保 PVE 宿主机完全不触碰这张显卡。

1. 修改 GRUB / Systemd-boot

编辑 PVE 的启动配置文件。
如果是 GRUB 引导:

nano /etc/default/grub

找到 GRUB_CMDLINE_LINUX_DEFAULT,修改为:

  • Intel CPU: quiet intel_iommu=on iommu=pt initcall_blacklist=sysfb_init
  • AMD CPU: quiet amd_iommu=on iommu=pt initcall_blacklist=sysfb_init

避坑提示initcall_blacklist=sysfb_init 非常关键!它能防止 PVE 系统在启动时抢先占用显卡的 UEFI 帧缓冲区,避免直通时显卡被死锁。

保存后更新 grub:

update-grub

2. 屏蔽 AMD 驱动

在 PVE 宿主机中,将 AMD 显卡驱动拉入黑名单,强制让 vfio-pci 接管显卡。

nano /etc/modprobe.d/pve-blacklist.conf

添加以下内容:

blacklist radeon
blacklist amdgpu

3. 绑定 GPU 硬件 ID 到 VFIO

使用 lspci -nn 找到你的显卡和其自带音频设备的 ID。例如:

01:00.0 VGA compatible controller [0300]: Advanced Micro Devices, Inc. [AMD/ATI] Navi 23 [Radeon RX 6600/6600 XT / 6600M] [1002:73ff] (rev c1)
01:00.1 Audio device [0403]: Advanced Micro Devices, Inc. [AMD/ATI] Navi 21 HDMI Audio [1002:ab28]

这里的 1002:73ff1002:ab28 就是我们要锁定的 ID。

编辑 vfio.conf

nano /etc/modprobe.d/vfio.conf

写入:

options vfio-pci ids=1002:73ff,1002:ab28 disable_vga=1

保存后,运行 update-initramfs -u -k all 并重启 PVE 宿主机。


第二步:正确配置 Win11 虚拟机硬件

进入 PVE 网页后台,点击你的 Windows 11 虚拟机(假设 VM ID 为 101),严格按照以下标准调整硬件配置:

  1. 机器 (Machine):选择 q35(建议使用较高版本如 pc-q35-8.1,不要用默认的 i440fx)。
  2. BIOS:必须选择 OVMF (UEFI)
  3. 添加 EFI 磁盘,并确保勾选了 Pre-enrolled keys
  4. 处理器 (CPU):类别(Type)选择 host
  5. 添加 PCI 设备
    • 选择你的 AMD 显卡(只选 .0 的那个,会自动带上 .1 的音频)。
    • 勾选 All Functions(全部功能)。
    • 勾选 ROM-Bar
    • 勾选 PCI-Express
    • 不要在主卡直通前盲目勾选 Primary GPU(主 GPU 选项建议在配置好 Romfile 且安装好驱动后,再行决定是否勾选,防止调试期间黑屏无输出)。

第三步:伪装 KVM 虚拟化与加载干净的 Romfile

Windows 的 AMD 驱动对虚拟化极为敏感,我们需要对虚拟机配置文件进行“修剪”。

1. 隐藏 KVM 特征

在 PVE 命令行下,编辑虚拟机的配置文件:

nano /etc/pve/qemu-server/101.conf

在第一行或合适位置添加或修改:

cpu: host,hidden=1,flags=+amd-no-ssb

并在文件底部,加入以下几行,欺骗 Hyper-V 厂商 ID:

args: -cpu 'host,hv_vendor_id=authenticamd,kvm=off'

2. 配置专属 VBIOS(Romfile)

由于宿主机启动时可能已经对显卡的 VBIOS 进行了破坏,虚拟机拿到的 VBIOS 不完整就会引发 Code 43。

  1. TechPowerUp VBIOS Database 找到你对应显卡型号的 ROM 文件下载。
  2. 将下载好的 .rom 文件(例如 rx6600.rom)上传到 PVE 的 /usr/share/kvm/ 目录下。
  3. 修改虚拟机配置文件:
    nano /etc/pve/qemu-server/101.conf
    
    找到 hostpci0 那一行,在末尾加上 ,romfile=rx6600.rom。例如:
    hostpci0: 0000:01:00,pcie=1,romfile=rx6600.rom,x-vga=1
    

此时,启动 Windows 11 虚拟机,通常情况下,你的 AMD 显卡已经能够正常工作,Code 43 应该已经消失。

但是!如果你点击“重新启动”虚拟机,它极有可能在第二次启动时再次死锁并报 Code 43。这就是接下来要解决的终极难题。


三、 终极武器:编写 AMD 显卡重置脚本 (vendor-reset)

AMD 显卡的 Reset Bug 导致显卡在 VM 关机后无法回到低电平初始状态。由 GNIF 开发的开源项目 vendor-reset 通过内核模块介入,可以在 VM 启动和关闭时,强行对 AMD 显卡实施底层的硬件级重置。

1. 在 PVE 宿主机上安装 vendor-reset

登录 PVE 命令行,安装编译所需的依赖和内核头文件:

apt update
apt install -y git dkms pve-headers build-essential

克隆 vendor-reset 仓库并编译安装:

cd /usr/src
git clone https://github.com/gnif/vendor-reset.git
cd vendor-reset
dkms install .

检查模块是否编译并加载成功:

modinfo vendor-reset

如果能输出一堆关于 vendor-reset 的信息,说明安装成功。

2. 配置开机自启并应用重置

我们需要让 PVE 在开机时自动加载这个模块:

echo "vendor-reset" >> /etc/modules

为了确保安全,我们需要写一个重置策略配置脚本,并在 PVE 每次开机时,将你的 AMD 显卡绑定到 vendor-reset 的重置策略中。

编写 PVE 显卡重置服务脚本

在 PVE 宿主机上,创建脚本:

nano /usr/local/bin/pve-amd-reset.sh

写入以下脚本内容(请根据你的实际 PCI 槽位 ID 修改,这里以 0000:01:00.0 为例):

#!/bin/bash

# 你的 AMD 显卡 PCI 地址,用 lspci 确认
GPU_PCI_ADDR="0000:01:00.0"

echo "============================================="
echo "Initializing AMD GPU Reset Policy..."
echo "============================================="

# 检查 vendor-reset 模块是否加载
if ! lsmod | grep -q "vendor_reset"; then
    echo "Error: vendor-reset module is not loaded! Attempting to load..."
    modprobe vendor-reset
    if [ $? -ne 0 ]; then
        echo "Critial: Failed to load vendor-reset module!"
        exit 1
    fi
fi

# 检查设备是否存在
if [ ! -d "/sys/bus/pci/devices/${GPU_PCI_ADDR}" ]; then
    echo "Error: Device ${GPU_PCI_ADDR} not found!"
    exit 1
fi

# 解除当前的驱动绑定(防止意外占用)
if [ -e "/sys/bus/pci/devices/${GPU_PCI_ADDR}/driver/unbind" ]; then
    echo "Unbinding device from current driver..."
    echo "${GPU_PCI_ADDR}" > "/sys/bus/pci/devices/${GPU_PCI_ADDR}/driver/unbind"
fi

# 将显卡重新绑定到 vfio-pci
echo "Binding device to vfio-pci..."
echo "vfio-pci" > "/sys/bus/pci/devices/${GPU_PCI_ADDR}/driver_override"

# 配置 vendor-reset 的重置方式
# 针对 Polaris (RX580) 使用 'pm_method',针对 Navi (RX5000/6000) 通常使用 'device_specific'
RESET_METHOD_PATH="/sys/bus/pci/devices/${GPU_PCI_ADDR}/reset_method"
if [ -f "${RESET_METHOD_PATH}" ]; then
    echo "Current reset method: $(cat ${RESET_METHOD_PATH})"
    echo "device_specific" > "${RESET_METHOD_PATH}"
    echo "New reset method applied: $(cat ${RESET_METHOD_PATH})"
else
    echo "Warning: ${RESET_METHOD_PATH} not found!"
fi

echo "AMD GPU Reset Policy initialized successfully."

赋予可执行权限:

chmod +x /usr/local/bin/pve-amd-reset.sh

配置 Systemd 开机服务

为了让该脚本每次开机自动运行,我们为其编写一个 Systemd 服务:

nano /etc/systemd/system/pve-amd-reset.service

写入以下内容:

[Unit]
Description=Configure AMD GPU Reset Policy for PVE VM Passthrough
After=pve-cluster.service
Before=pve-ha-crm.service

[Service]
Type=oneshot
ExecStart=/usr/local/bin/pve-amd-reset.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

启用并运行该服务:

systemctl daemon-reload
systemctl enable --now pve-amd-reset.service

通过 systemctl status pve-amd-reset.service 观察服务运行状态,如果显示 active (exited) 且输出了 New reset method applied: device_specific,则大功告成!


四、 联动虚拟机:PVE Hook 脚本自动重置显卡(双保险)

为了确保万无一失,我们还可以利用 PVE 的 Hook Script 机制,在虚拟机开机前和关机后,自动执行一次硬件重置。

1. 编写 VM Hook 脚本

创建 /var/lib/vz/snippets/amd-reset-hook.sh

mkdir -p /var/lib/vz/snippets
nano /var/lib/vz/snippets/amd-reset-hook.sh

写入以下代码(这是一个通用的 PVE 虚拟机 Hook 脚本模板):

#!/bin/bash

VMID=$1
PHASE=$2

# 配置你的 GPU PCI 地址
GPU_PCI_ADDR="0000:01:00.0"

if [ "$PHASE" = "pre-start" ]; then
    echo "VM $VMID is starting. Forcing AMD GPU reset before VM boot..."
    if [ -f "/sys/bus/pci/devices/${GPU_PCI_ADDR}/init_reset" ]; then
        echo 1 > "/sys/bus/pci/devices/${GPU_PCI_ADDR}/init_reset"
        echo "Reset signal sent to ${GPU_PCI_ADDR}"
    else
        # 备用方案:通过 pci-sysfs 触发重置
        echo 1 > "/sys/bus/pci/devices/${GPU_PCI_ADDR}/reset"
        echo "Standard PCI reset signal sent to ${GPU_PCI_ADDR}"
    fi
fi

if [ "$PHASE" = "post-stop" ]; then
    echo "VM $VMID has stopped. Cleaning up and resetting GPU..."
    if [ -f "/sys/bus/pci/devices/${GPU_PCI_ADDR}/init_reset" ]; then
        echo 1 > "/sys/bus/pci/devices/${GPU_PCI_ADDR}/init_reset"
    fi
fi

exit 0

赋予权限:

chmod +x /var/lib/vz/snippets/amd-reset-hook.sh

2. 绑定 Hook 脚本到虚拟机

将该脚本绑定到你的 Win11 虚拟机(假设 VM ID 为 101):

qm set 101 --hookscript local:snippets/amd-reset-hook.sh

五、 总结与终极验证

通过以上一套组合拳,你已经完成了以下深度的调优工作:

  1. 屏蔽宿主机抢占:彻底杜绝了 PVE 宿主机初始化时锁死显卡的隐患。
  2. 底层隐藏:完美隐藏了 KVM 虚拟化标识,通过了 AMD 驱动的虚拟化检测。
  3. 干净 VBIOS:给虚拟机喂了未被污染的 Romfile。
  4. 底层硬重置:利用 vendor-reset 内核级模块和 Hook 脚本,彻底干掉了由于 VM 重启引发的 AMD Reset Bug

现在,你可以登录 Win11 虚拟机,无论是冷启动、热重启,还是高强度的 3D 跑分和游戏测试,那个恶心的 Code 43 错误都将不复存在。显卡直通,至此完美毕业!

点评评价

captcha
健康