windows/pci: vIOMMU for nested passthrough, and a data disk built with the image
Three things, all in service of putting Proxmox in a VM that can still hand a
GPU to its own guests, and of a Windows VM whose profile survives its OS disk.
pci.viommu.enable emits `-device intel-iommu,intremap=on,caching-mode=on` and
forces kernel-irqchip=split, which interrupt remapping requires. Without an
IOMMU of its own a guest cannot bind a passed-through device to vfio-pci, so
it can never forward one on. The device leads the command line because QEMU
realizes devices in order and intel-iommu must precede what it translates.
pci.vgaPassthrough (default true, so nothing changes for existing VMs) makes
x-vga=on optional. It was forced on the first passthrough device, which is
wrong for a card the guest only forwards onward: it claims the VGA path the
emulated console adapter needs.
customizeImage gains extraDisk, a blank disk attached for the Audit Mode boot
and emitted as the derivation's `data` output. generalize uses it for dataDisk
and profilesDirectory, so the disk is partitioned and the profile relocated
under OOBE in the build VM. That is what removes the need for delayOobeRun --
previously the volume ProfilesDirectory names could not exist until the image
reached real hardware. A second output rather than a directory keeps ${image}
meaning the OS qcow2 for every existing consumer.
generalize also picks up staticIP and profilesDirectory.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117qMyjpuXsjpVAcpJbFD8g
This commit is contained in:
parent
9784736260
commit
c213fc4db9
4 changed files with 122 additions and 4 deletions
|
|
@ -79,6 +79,11 @@ let
|
|||
|
||||
# Auto-detect Windows from _vmixOsType marker on the disk image
|
||||
isWindows = vmCfg.windows.enable || (hasOsDisk && (vmCfg.disks.os.file._vmixOsType or "linux") == "windows");
|
||||
# Interrupt remapping in the virtual IOMMU only works on a split irqchip,
|
||||
# so viommu wins over the full in-kernel irqchip hideVirtualized asks for.
|
||||
machineIrqchipArg =
|
||||
if vmCfg.pci.viommu.enable then ",kernel-irqchip=split"
|
||||
else optionalString vmCfg.cpu.hideVirtualized ",kernel_irqchip=on";
|
||||
|
||||
# Linux VMs: apply customizeImage with 9p fstab and machine-id setup
|
||||
linuxOsImage = vmixLib.linux.customizeImage vmCfg.disks.os.file {
|
||||
|
|
@ -174,6 +179,9 @@ let
|
|||
''}
|
||||
exec qemu-system-${vmCfg.arch} \
|
||||
${if vmCfg.nographic && vmCfg.pci.passthrough != [] then "-display none -vga none" else optionalString vmCfg.nographic "-nographic"} \
|
||||
${# QEMU realizes devices in command-line order and intel-iommu must
|
||||
# exist before anything it translates, so it leads the device list.
|
||||
optionalString vmCfg.pci.viommu.enable "-device intel-iommu,intremap=on,caching-mode=on"} \
|
||||
${optionalString (vmCfg.vnc.enable && vmCfg.vnc.passwordFile != null) "-object secret,id=vnc-pass-${vmCfg.name},file=${escapeShellArg vmCfg.vnc.passwordFile}"} \
|
||||
${optionalString vmCfg.vnc.enable "-vnc ${vncArgs}"} \
|
||||
${optionalString (vmCfg.spice.enable && vmCfg.spice.passwordFile != null) "-object secret,id=spice-pass-${vmCfg.name},file=${escapeShellArg vmCfg.spice.passwordFile}"} \
|
||||
|
|
@ -189,7 +197,7 @@ let
|
|||
${optionalString vmCfg.mem.balloon "-device virtio-balloon-pci"} \
|
||||
-smp cores=${toString vmCfg.cpu.cores} \
|
||||
-cpu ${vmCfg.cpu.model}${optionalString vmCfg.cpu.hideVirtualized ",kvm=off,hv_vendor_id=1234567890ab,-hypervisor"} \
|
||||
-machine type=${vmCfg.pc.type}${optionalString vmCfg.cpu.hideVirtualized ",kernel_irqchip=on"} \
|
||||
-machine type=${vmCfg.pc.type}${machineIrqchipArg} \
|
||||
${optionalString vmCfg.bios.efi "-bios ${pkgs.OVMF.fd}/FV/OVMF.fd"} \
|
||||
${optionalString vmCfg.bios.tpm "-chardev socket,id=chrtpm,path=/tmp/mytpm-sock -tpmdev emulator,id=tpm0,chardev=chrtpm -device tpm-tis,tpmdev=tpm0"} \
|
||||
${# Windows: localtime RTC, USB tablet for mouse, disable S3/S4 sleep
|
||||
|
|
@ -220,7 +228,7 @@ let
|
|||
'') allMacvtaps)} \
|
||||
${concatStrings (imap1 (i: pciAddr: ''
|
||||
-device pcie-root-port,id=pci-passthrough${toString i},chassis=${toString i},slot=${toString i} \
|
||||
-device vfio-pci,host=${pciAddr},bus=pci-passthrough${toString i}${optionalString (i == 1) ",x-vga=on${optionalString (vmCfg.pci.romFile != null) ",romfile=${vmCfg.pci.romFile}"}"} \
|
||||
-device vfio-pci,host=${pciAddr},bus=pci-passthrough${toString i}${optionalString (i == 1) "${optionalString vmCfg.pci.vgaPassthrough ",x-vga=on"}${optionalString (vmCfg.pci.romFile != null) ",romfile=${vmCfg.pci.romFile}"}"} \
|
||||
'') vmCfg.pci.passthrough)} \
|
||||
${concatMapStrings (usbDev: ''
|
||||
-device usb-host,vendorid=0x${usbDev.vendorId},productid=0x${usbDev.productId} \
|
||||
|
|
|
|||
|
|
@ -262,11 +262,36 @@ with lib;
|
|||
default = [];
|
||||
description = "PCI device addresses to passthrough via VFIO (e.g. [\"0000:03:00.0\" \"0000:03:00.1\"]).";
|
||||
};
|
||||
pci.vgaPassthrough = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Route legacy VGA to the first passthrough device (x-vga=on), which a
|
||||
guest needs in order to drive that card as its own display.
|
||||
|
||||
Turn it off when the guest only forwards the device onward to a nested
|
||||
guest: x-vga=on claims the VGA path the emulated adapter wants, and the
|
||||
nested guest does its own routing anyway.
|
||||
'';
|
||||
};
|
||||
pci.romFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = "GPU VBIOS ROM file for the first passthrough device. Required when GPU PCI ROM BAR doesn't expose the full VBIOS (common with AMD Navi+).";
|
||||
};
|
||||
pci.viommu.enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Give the guest a virtual Intel IOMMU, so a guest that is itself a
|
||||
hypervisor can bind a passed-through device to vfio-pci and hand it on
|
||||
to a nested guest. Without one the guest sees no IOMMU and cannot
|
||||
re-assign anything it was given.
|
||||
|
||||
Implies kernel-irqchip=split, which interrupt remapping requires and
|
||||
which replaces the full in-kernel irqchip cpu.hideVirtualized asks for.
|
||||
'';
|
||||
};
|
||||
|
||||
usb.hostDevices = mkOption {
|
||||
default = [];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue