共计 8257 个字符,预计需要花费 21 分钟才能阅读完成。
因为之前做 AIO 所以开始捣鼓了下 PVE 虚拟化,又突发奇想想在 PVE 下挂一些 Windows 端的游戏,故有了这篇文章。
还有一个原因是网上的大部分教程对 PVE8/AMD 小主机并不适用。
本次使用的硬件 / 软件:
- 机械师 R7940HS 迷你主机
- 32G U 盘一块
- 引导模式:EFI (Secure Boot)
- PVE 版本:8.2.7
- PVE 内核版本:Linux 6.8.12-4-pve (2024-11-06T15:04Z)
- Windows 版本:Windows11 LTSC 23H2(22631.4037)
- Windows10 PE:微 PEv2.3
准备工作
1、BIOS 开启 svm, iommu 等,这个 ES 主机 BIOS 默认全开这些内容,无需调整
2、提取物理机的 BIOS 文件,以及核显的 vbios 文件
2.1、提取物理机 BIOS
如果你像我一样,已经安装好了 PVE,且手里没有其他硬盘的话,那么你需要准备一块安装了 Windows PE 的 U 盘用来提取物理机 BIOS
UBU:https://winraid.level1techs.com/t/tool-guide-news-uefi-bios-updater-ubu/30357
ffs:https://github.com/pbatard/ffs/releases
下载并解压 AFUWIN5.12(https://www.123pan.com/s/qsLMjv-to3Qh.html 提取码 G1Sz)到做好微 PE 的 U 盘内
重启物理机,快速按 F2 进入 BIOS,修改引导顺序为 U 盘第一并保存重启,等待 PE 自动启动
运行解压后的 AFUWINGUIx64.EXE
文件,点击 储存
保存 BIOS,将保存下来的 bios.bin
放到 U 盘里备用(这个文件名字不一定一样,根据自身情况即可)
下载并解压 UBU 1.79.17(https://www.123pan.com/s/qsLMjv-to3Qh.html 提取码 G1Sz),将储存下来的 bios.bin
复制到 UBU 的软件根目录,并双击运行 UBU.bat
,出现类似下图的情况 按 2 选择核显并回车 , 再按 S 后回车导出 VBIOS
按 2 选择核显并回车
按 S 后回车导出 VBIOS
此时 UBU 软件根目录下会出现 **Extracted
目录,双击进入Extracted
**,再双击进入到GOP
,一直到最后的文件夹里,会存放有导出的AMDGopDriver.efi
,将文件复制出来备用
下载edk2-BaseTools-win32-master(https://github.com/tianocore/edk2-BaseTools-win32/archive/refs/heads/master.zip)
解压后在其根目录打开 CMD/PowerShell 并输入以下命令
.\EfiRom.exe -f 0x1002 -i 0xffff -e D:\Download\AMDGopDriver.efi
使用这个命令进行转换,1002 是 amd 生厂商标识。-i 0xffff 这个是产品 id,这个可以填你的显卡设备 id 后四位,例如图中 15bf,你就会得到AMDGopDriver.rom,保存此文件备用
2.2、提取核显 vbios
使用 xi4oyu 的源代码 编译后导出 vbios 文件 使用,来自Have anyone susscesfully passthroughed the iGPU AMD Radeon 680M to VM? | Proxmox Support Forum
SSH 登录到 PVE 中,创建 vbios.c
文件,复制粘贴以下代码并保存
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
typedef uint32_t ULONG;
typedef uint8_t UCHAR;
typedef uint16_t USHORT;
typedef struct {
ULONG Signature;
ULONG TableLength; // Length
UCHAR Revision;
UCHAR Checksum;
UCHAR OemId[6];
UCHAR OemTableId[8]; // UINT64 OemTableId;
ULONG OemRevision;
ULONG CreatorId;
ULONG CreatorRevision;
} AMD_ACPI_DESCRIPTION_HEADER;
typedef struct {
AMD_ACPI_DESCRIPTION_HEADER SHeader;
UCHAR TableUUID[16]; // 0x24
ULONG VBIOSImageOffset; // 0x34. Offset to the first GOP_VBIOS_CONTENT block from the beginning of the stucture.
ULONG Lib1ImageOffset; // 0x38. Offset to the first GOP_LIB1_CONTENT block from the beginning of the stucture.
ULONG Reserved[4]; // 0x3C
} UEFI_ACPI_VFCT;
typedef struct {
ULONG PCIBus; // 0x4C
ULONG PCIDevice; // 0x50
ULONG PCIFunction; // 0x54
USHORT VendorID; // 0x58
USHORT DeviceID; // 0x5A
USHORT SSVID; // 0x5C
USHORT SSID; // 0x5E
ULONG Revision; // 0x60
ULONG ImageLength; // 0x64
} VFCT_IMAGE_HEADER;
typedef struct {
VFCT_IMAGE_HEADER VbiosHeader;
UCHAR VbiosContent[1];
} GOP_VBIOS_CONTENT;
int main(int argc, char** argv)
{
FILE* fp_vfct;
FILE* fp_vbios;
UEFI_ACPI_VFCT* pvfct;
char vbios_name[0x400];
if (!(fp_vfct = fopen("/sys/firmware/acpi/tables/VFCT", "r"))) {perror(argv[0]);
return -1;
}
if (!(pvfct = malloc(sizeof(UEFI_ACPI_VFCT)))) {perror(argv[0]);
return -1;
}
if (sizeof(UEFI_ACPI_VFCT) != fread(pvfct, 1, sizeof(UEFI_ACPI_VFCT), fp_vfct)) {fprintf(stderr, "%s: failed to read VFCT header!\n", argv[0]);
return -1;
}
ULONG offset = pvfct->VBIOSImageOffset;
ULONG tbl_size = pvfct->SHeader.TableLength;
if (!(pvfct = realloc(pvfct, tbl_size))) {perror(argv[0]);
return -1;
}
if (tbl_size - sizeof(UEFI_ACPI_VFCT) != fread(pvfct + 1, 1, tbl_size - sizeof(UEFI_ACPI_VFCT), fp_vfct)) {fprintf(stderr, "%s: failed to read VFCT body!\n", argv[0]);
return -1;
}
fclose(fp_vfct);
while (offset < tbl_size) {GOP_VBIOS_CONTENT* vbios = (GOP_VBIOS_CONTENT*)((char*)pvfct + offset);
VFCT_IMAGE_HEADER* vhdr = &vbios->VbiosHeader;
if (!vhdr->ImageLength)
break;
snprintf(vbios_name, sizeof(vbios_name), "vbios_%x_%x.bin", vhdr->VendorID, vhdr->DeviceID);
if (!(fp_vbios = fopen(vbios_name, "wb"))) {perror(argv[0]);
return -1;
}
if (vhdr->ImageLength != fwrite(&vbios->VbiosContent, 1, vhdr->ImageLength, fp_vbios)) {fprintf(stderr, "%s: failed to dump vbios %x:%x\n", argv[0], vhdr->VendorID, vhdr->DeviceID);
return -1;
}
fclose(fp_vbios);
printf("dump vbios %x:%x to %s\n", vhdr->VendorID, vhdr->DeviceID, vbios_name);
offset += sizeof(VFCT_IMAGE_HEADER);
offset += vhdr->ImageLength;
}
return 0;
}
未安装 gcc 编译器的,执行下面的命令安装
apt update
apt -y install build-essential
然后执行以下命令
gcc vbios.c -o vbios
./vbios
此时就会导出核显的 vbios 文件,名称一般为 **vbios_1002_xxxx.bin
**,每个机器不一定一样
此时准备工作已经结束,你得到了 AMDGopDriver.rom
和vbios_1002_xxxx.bin
这两个文件,请把他们两个用 winscp(或者命令)拷贝到 /usr/share/kvm
目录
3. 正式操作
3.1 编辑 grub 屏蔽 pve7.2+ 的 bug
vi /etc/default/grub
# 添加或直接编辑下面这行
GRUB_CMDLINE_LINUX_DEFAULT="quiet initcall_blacklist=sysfb_init"
# 保存后执行更新
update-grub
PVE8 的 grub 里面不需要加入
amd_iommu=on
(开启 iommu)pcie_acs_override=downstream,multifunction
(强制 iommu 分组)这些参数,好像默认就开启了一样。
3.2 将核显加入驱动黑名单
屏蔽三大显卡驱动,
屏蔽 hdmi 声音驱动;
options vfio_iommu_type1 allow_unsafe_interrupts=1
允许不安全的设备中断
vi /etc/modprobe.d/pve-blacklist.conf
blacklist nvidiafb
blacklist amdgpu
blacklist i915
blacklist snd_hda_intel
options vfio_iommu_type1 allow_unsafe_interrupts=1
更新initramfs
update-initramfs -u -k all
然后输入 reboot
重启实体机
3.3 直通虚拟机
创建虚拟机时,CPU 选择 host,BIOS 选择默认(OVMF),机型选择 q35
在添加核显显卡 pcie 设备里面勾选主 gpu,rom-bar,pcie-express 这三个选项,并对所有功能不勾选。显示设置为无 或保持默认也可用。
另外似乎 PVE8.1 后,不会在 Web 控制台直接显示 romfile 信息了,但仅仅只是不显示,我们需要手动编辑一下虚拟机配置文件即可
vi /etc/pve/qemu-server/102.conf
102 为你要直通的 windows 虚拟机 ID
添加指定的 romfile,romfile 文件名需要根据你提取出来的 vbios 文件名来修改。
0000:06:00.1
为 AMD 的核显声卡,通常情况是和核显挨着的,请根据自身情况进行修改.
如果不指定核显声卡的 romfile, 那么有可能将会在安装驱动完成并重启后,触发核显 43 错误
hostpci0: 0000:65:00.0,pcie=1,x-vga=1,romfile=vbios_1002_1681.bin
hostpci1: 0000:65:00.1,romfile=AMDGopDriver.rom
如果遇到了 43 错误,那么重启虚拟机是无效的,必须重启 PVE 才可恢复。
在完成以上配置后,启动虚拟机即可
nano /etc/pve/qemu-server/102.conf 最终配置:
#args: -bios /root/OVMF_7940HS.fd
boot: order=scsi0;net0
cores: 8
cpu: host
efidisk0: local-lvm:vm-102-disk-0,efitype=4m,pre-enrolled-keys=1,size=4M
hostpci0: 0000:65:00.0,pcie=1,x-vga=1,romfile=vbios_1002_15bf.bin
hostpci1: 0000:65:00.1,romfile=AMDGopDriver.rom
machine: pc-q35-8.1
memory: 8192
meta: creation-qemu=9.0.2,ctime=1731600826
name: win11
net0: virtio=BC:24:11:13:DB:1E,bridge=vmbr0,firewall=1
numa: 0
ostype: win11
scsi0: local-lvm:vm-102-disk-1,iothread=1,size=256G
scsihw: virtio-scsi-single
smbios1: uuid=44b99bd3-0b43-47f1-a1ea-c21f990051dc
sockets: 1
tpmstate0: local-lvm:vm-102-disk-2,size=4M,version=v2.0
vga: none
vmgenid: 27831fa9-9439-4e0c-980c-f6ae5e64df50
注意安装 win 后,要把 virtio 驱动装上,还有单独去官网下载 amd 显卡驱动安装。
最终效果图
4. 一些常见问题修复
解决AMD GPU Passthrough Reset Bug
经典远古 BUG 传承至今,如果不安装此服务有可能会导致在重启后无法找到 GPU,这个补丁的机制就是修复 pve 内 win 关机和重启的操作导致 gpu 消息,补丁会在关机时卸载 gpu,然后开机时再加载 gpu 驱动,期间可能会 hdmi 直通黑屏,所以每次开机需要等 1 - 2 分钟,等 gpu 加载完显示器就能正常显示了。
下载Release v0.1.7 · inga-lovinde/RadeonResetBugFix 并将其解压到虚拟机 C 盘根目录,以管理员身份打开 CMD/PowerShell 执行下面的命令
./RadeonResetBugFixService.exe install
整个安装过程可能持续最长 15 分钟,期间屏幕可能会多次闪烁
如果你想试试触发这个 BUG,也可以卸载此服务, 同样以管理员身份打开 CMD/PowerShell 执行下面的命令
./RadeonResetBugFixService.exe uninstall
卸载过程最长持续 5 分钟,期间屏幕可能会多次闪烁
反虚拟化防止一些常见的反作弊检测
编辑虚拟机配置文件,增加以下内容
args: -cpu host,hypervisor=off,vmware-cpuid-freq=false,enforce=false,host-phys-bits=true, -smbios type=0,vendor="American Megatrends International LLC.",version=H3.7G,date='02/21/2023' -smbios type=1,manufacturer="Maxsun",product="MS-Terminator B760M",version="VER:H3.7G(2022/11/29)" -smbios type=2,manufacturer="Maxsun",product="MS-Terminator B760M",version="VER:H3.7G(2022/11/29)" -smbios type=17,manufacturer="KINGSTON",speed=3200,serial=DF1EC466,part=SED3200U1888S
总体的配置文件看起来像这样
agent: 1
args: -cpu host,hypervisor=off,vmware-cpuid-freq=false,enforce=false,host-phys-bits=true, -smbios type=0,version=UX305UA.201 -smbios type=1,manufacturer=ASUS,product=UX305UA,version=2021.1 -smbios type=2,manufacturer=Intel,version=2021.5,product='Intel i7-12700' -smbios type=3,manufacturer=XBZJ -smbios type=17,manufacturer=KINGSTON,loc_pfx=DDR4,speed=3200,serial=114514,part=FF63 -smbios type=4,manufacturer=Intel,max-speed=3200,current-speed=3200
bios: ovmf
boot: order=scsi0;net0
cores: 8
cpu: host
efidisk0: local-lvm:vm-102-disk-0,efitype=4m,pre-enrolled-keys=1,size=4M
hostpci0: 0000:75:00.0,pcie=1,x-vga=1,romfile=vbios_1002_1681.bin
hostpci1: 0000:75:00.1,romfile=AMDGopDriver.rom
machine: pc-q35-8.1
memory: 8192
meta: creation-qemu=8.1.5,ctime=1715697001
name: Win11-LTSC
net0: virtio=BC:24:11:38:3F:01,bridge=vmbr0,firewall=1
numa: 0
onboot: 1
ostype: win11
scsi0: local-lvm:vm-102-disk-1,iothread=1,size=52G
scsi1: local-lvm:vm-102-disk-2,iothread=1,size=256G
scsihw: virtio-scsi-single
smbios1: uuid=b9d30bb2-92af-49f1-a1da-170a915ca04e
sockets: 1
tpmstate0: local-lvm:vm-102-disk-3,size=4M,version=v2.0
usb0: host=046d:c547
usb1: host=320f:5055
vmgenid: 9ebb8f62-e66d-4490-b38d-40da5b3ca646
如此配置可以通过大部分的虚拟化检测,但无法通过 SE 虚拟化检测。
如果需要更强的过检测,那么需要重新编译 **pve-qemu-kvm
** 这个包,可参考项目zhaodice/proxmox-ve-anti-detection: A patch to hide PVE itself
但即使重新编译了,也有部分反作弊无法正常通过,例如拳头的 Vanguard。且此补丁可能会导致一些virtIO 设备无法工作,请谨慎使用。