Add NixVirt support and GPU passthrough configuration
This commit is contained in:
Generated
+20
@@ -706,6 +706,25 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixvirt": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1748140003,
|
||||
"narHash": "sha256-DNBZmuk1YRM2PmwbHzVdXumRjCUzQkMarg4iI/37rOQ=",
|
||||
"rev": "5dfe108fd859b122f9a96981cb6bc12297653d6c",
|
||||
"revCount": 407,
|
||||
"type": "tarball",
|
||||
"url": "https://api.flakehub.com/f/pinned/AshleyYakeley/NixVirt/0.6.0/0197059a-e45f-7446-86b5-411ccc894ab0/source.tar.gz"
|
||||
},
|
||||
"original": {
|
||||
"type": "tarball",
|
||||
"url": "https://flakehub.com/f/AshleyYakeley/NixVirt/%2A.tar.gz"
|
||||
}
|
||||
},
|
||||
"nur": {
|
||||
"inputs": {
|
||||
"flake-parts": "flake-parts_5",
|
||||
@@ -809,6 +828,7 @@
|
||||
"nixcord": "nixcord",
|
||||
"nixos-raspberrypi": "nixos-raspberrypi",
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"nixvirt": "nixvirt",
|
||||
"nur": "nur",
|
||||
"oom-hardware": "oom-hardware",
|
||||
"personal-website": "personal-website",
|
||||
|
||||
@@ -105,6 +105,11 @@
|
||||
};
|
||||
|
||||
navi.url = "github:cafkafk/navi";
|
||||
|
||||
nixvirt = {
|
||||
url = "https://flakehub.com/f/AshleyYakeley/NixVirt/*.tar.gz";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = inputs:
|
||||
|
||||
@@ -139,9 +139,17 @@ let
|
||||
keyringExtra = lib.optionalString keyringEnable keyringNiriStartupKdl;
|
||||
extraConfigMerged = keyringExtra + rpi5Extra + (userExtra.extraConfig or "");
|
||||
windowRules = (base.window-rules or [ ]) ++ (userExtra.window-rules or [ ]);
|
||||
userExtraNoSpecial = lib.removeAttrs userExtra [ "window-rules" "extraConfig" "binds" ];
|
||||
mergedBinds =
|
||||
(base.binds or { })
|
||||
// (userExtra.binds or { })
|
||||
// (niriCfg.extraBinds or { });
|
||||
in
|
||||
lib.recursiveUpdate base (
|
||||
lib.removeAttrs userExtra [ "window-rules" "extraConfig" ]
|
||||
userExtraNoSpecial
|
||||
// {
|
||||
binds = mergedBinds;
|
||||
}
|
||||
// lib.optionalAttrs (windowRules != [ ]) {
|
||||
window-rules = windowRules;
|
||||
}
|
||||
@@ -195,6 +203,15 @@ in
|
||||
Merged into shared defaults → `config.kdl` via wrapper-modules. Put raw KDL in `extraConfig`.
|
||||
'';
|
||||
};
|
||||
|
||||
extraBinds = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.anything;
|
||||
default = { };
|
||||
description = ''
|
||||
Keybind overrides merged with shared defaults and `extraSettings.binds`.
|
||||
Use this when multiple modules need to add binds without overwriting each other.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkMerge [
|
||||
|
||||
@@ -1,61 +1,99 @@
|
||||
# Monitor layout for 14900k (ported from NixOS-New `hosts/clients/14900k/home.nix`).
|
||||
# Niri: `chiasson.desktop.niri.extraSettings` (`extraConfig` KDL + `binds` merged with defaults).
|
||||
# Monitor layout for 14900k.
|
||||
# NVIDIA (default): DP-2 ultrawide, HDMI-A-3 + DP-4 side/top stack.
|
||||
# Intel iGPU (gpu passthru): DP-1 ultrawide, HDMI-A-2 Samsung to the left.
|
||||
# Niri: `extraSettings` (KDL via `extraConfig`, other wrapper-modules keys) + `extraBinds` (merged keybinds).
|
||||
# Hyprland: `chiasson.desktop.hyprland.settings` (merged in HM when `chiasson.desktop.hyprland.enable`).
|
||||
|
||||
#TODO[epic=Moderate] Clean this up, move to host's configuration.nix.
|
||||
{ config, lib, ... }:
|
||||
{
|
||||
chiasson.desktop.niri.extraSettings = {
|
||||
extraConfig = ''
|
||||
output "DP-2" {
|
||||
mode "2560x1080@144"
|
||||
scale 1.0
|
||||
position x=0 y=0
|
||||
focus-at-startup
|
||||
}
|
||||
output "HDMI-A-3" {
|
||||
mode "1920x1080@60"
|
||||
scale 1.0
|
||||
position x=-1920 y=0
|
||||
}
|
||||
output "DP-4" {
|
||||
mode "1920x1080@144"
|
||||
scale 1.0
|
||||
position x=0 y=-1080
|
||||
}
|
||||
let
|
||||
gpuPassthru = config.chiasson.system.gpuPassthru.enable;
|
||||
|
||||
'';
|
||||
niriOutputs =
|
||||
if gpuPassthru then
|
||||
''
|
||||
output "DP-1" {
|
||||
mode "2560x1080@144"
|
||||
scale 1.0
|
||||
position x=0 y=0
|
||||
focus-at-startup
|
||||
}
|
||||
output "HDMI-A-2" {
|
||||
mode "1920x1080@60"
|
||||
scale 1.0
|
||||
position x=-1920 y=0
|
||||
}
|
||||
''
|
||||
else
|
||||
''
|
||||
output "DP-3" {
|
||||
mode "2560x1080@144"
|
||||
scale 1.0
|
||||
position x=0 y=0
|
||||
focus-at-startup
|
||||
}
|
||||
output "HDMI-A-3" {
|
||||
mode "1920x1080@60"
|
||||
scale 1.0
|
||||
position x=-1920 y=0
|
||||
}
|
||||
output "DP-4" {
|
||||
mode "1920x1080@144"
|
||||
scale 1.0
|
||||
position x=0 y=-1080
|
||||
}
|
||||
'';
|
||||
|
||||
binds."XF86Tools".spawn = [
|
||||
"wpctl"
|
||||
"set-mute"
|
||||
"@DEFAULT_AUDIO_SOURCE@"
|
||||
"toggle"
|
||||
];
|
||||
};
|
||||
|
||||
chiasson.desktop.hyprland.settings = lib.mkIf config.chiasson.desktop.hyprland.enable (
|
||||
let
|
||||
monitorList = [
|
||||
"DP-2, 2560x1080@144, 0x0, 1"
|
||||
hyprlandMonitors =
|
||||
if gpuPassthru then
|
||||
[
|
||||
"DP-1, 2560x1080@144, 0x0, 1"
|
||||
"HDMI-A-2, 1920x1080@60, -1920x0, 1"
|
||||
]
|
||||
else
|
||||
[
|
||||
"DP-3, 2560x1080@144, 0x0, 1"
|
||||
"DP-4, 1920x1080@144, 0x-1080, 1"
|
||||
"HDMI-A-3, 1920x1080@60, -1920x0, 1"
|
||||
];
|
||||
workspaceList = [
|
||||
|
||||
hyprlandWorkspaces =
|
||||
if gpuPassthru then
|
||||
[
|
||||
"1, monitor:DP-1, default:true"
|
||||
"2, monitor:DP-1"
|
||||
"3, monitor:DP-1"
|
||||
"4, monitor:HDMI-A-2, default:true"
|
||||
"5, monitor:HDMI-A-2"
|
||||
"6, monitor:HDMI-A-2"
|
||||
]
|
||||
else
|
||||
[
|
||||
"1, monitor:DP-3, default:true"
|
||||
"2, monitor:DP-3"
|
||||
"3, monitor:DP-3"
|
||||
"4, monitor:Unknown-2, default:true"
|
||||
"5, monitor:Unknown-2"
|
||||
"6, monitor:Unknown-2"
|
||||
"4, monitor:HDMI-A-3, default:true"
|
||||
"5, monitor:HDMI-A-3"
|
||||
"6, monitor:HDMI-A-3"
|
||||
"7, monitor:DP-4"
|
||||
"8, monitor:DP-4"
|
||||
"9, monitor:DP-4"
|
||||
];
|
||||
in
|
||||
{
|
||||
monitor = lib.mkBefore monitorList;
|
||||
workspace = workspaceList;
|
||||
}
|
||||
);
|
||||
in
|
||||
{
|
||||
chiasson.desktop.niri.extraSettings = {
|
||||
extraConfig = niriOutputs;
|
||||
};
|
||||
|
||||
chiasson.desktop.niri.extraBinds."XF86Tools".spawn = [
|
||||
"wpctl"
|
||||
"set-mute"
|
||||
"@DEFAULT_AUDIO_SOURCE@"
|
||||
"toggle"
|
||||
];
|
||||
|
||||
chiasson.desktop.hyprland.settings = lib.mkIf config.chiasson.desktop.hyprland.enable {
|
||||
monitor = lib.mkBefore hyprlandMonitors;
|
||||
workspace = hyprlandWorkspaces;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
# 14900k: RTX 2070 VFIO passthrough + Windows 11 gaming VM on Crucial MX500.
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
cfg = config.chiasson.system.gpuPassthru;
|
||||
in
|
||||
{
|
||||
chiasson.system.gpuPassthru = {
|
||||
enable = false;
|
||||
pciIds = [
|
||||
"10de:1f07" # RTX 2070 VGA
|
||||
"10de:10f9" # HDMI audio
|
||||
"10de:1ada" # USB 3.1
|
||||
"10de:1adb" # USB-C UCSI
|
||||
"14c3:7927" # MediaTek MT7927 WiFi (M.2, no Linux driver — Windows only)
|
||||
];
|
||||
windowsDisk = "/dev/disk/by-id/ata-CT1000MX500SSD1_1830E14B054A";
|
||||
lookingGlass = {
|
||||
enable = true;
|
||||
shmSizeMiB = 32;
|
||||
captureOnFocus = false;
|
||||
};
|
||||
vm = {
|
||||
enable = true;
|
||||
name = "win11";
|
||||
memoryGiB = 16;
|
||||
vcpus = 8;
|
||||
installMode = false;
|
||||
};
|
||||
libvirtUsers = [ "olivier" ];
|
||||
};
|
||||
|
||||
chiasson.desktop.niri.extraBinds = lib.mkIf (cfg.enable && cfg.lookingGlass.enable) {
|
||||
"Mod+J"."spawn-sh" = "looking-glass-toggle";
|
||||
};
|
||||
|
||||
# Applied last in configuration.nix imports — wins over system module defaults.
|
||||
virtualisation.libvirtd.qemu.verbatimConfig = lib.mkIf (cfg.lookingGlass.enable) (
|
||||
lib.mkForce ''
|
||||
namespaces = []
|
||||
seccomp_sandbox = 0
|
||||
security_driver = "none"
|
||||
security_default_confined = 0
|
||||
security_require_confined = 0
|
||||
cgroup_device_acl = [
|
||||
"/dev/null", "/dev/full", "/dev/zero",
|
||||
"/dev/random", "/dev/urandom",
|
||||
"/dev/ptmx", "/dev/kvm", "/dev/kqemu",
|
||||
"/dev/rtc", "/dev/hpet",
|
||||
"/dev/vfio/", "/dev/vfio/vfio",
|
||||
"/dev/kvmfr0", "c 234:* rwm",
|
||||
"/dev/disk/", "/dev/disk/by-id/",
|
||||
"/dev/bus/usb/",
|
||||
]
|
||||
''
|
||||
);
|
||||
|
||||
virtualisation.libvirt = lib.mkIf (cfg.enable && cfg.vm.enable) {
|
||||
enable = true;
|
||||
swtpm.enable = true;
|
||||
connections."qemu:///system".domains = [
|
||||
{
|
||||
definition =
|
||||
if cfg.vm.installMode then
|
||||
./win11-domain-install.xml
|
||||
else
|
||||
./win11-domain.xml;
|
||||
active = false;
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
# NVIDIA for host desktop.
|
||||
# NVIDIA for host desktop (disabled when GPU passthrough binds the card to VFIO).
|
||||
{ config, lib, pkgs, ... }:
|
||||
{
|
||||
lib.mkIf (!config.chiasson.system.gpuPassthru.enable) {
|
||||
boot.kernelParams = [ "snd_hda_core.gpu_bind=0" ];
|
||||
boot.kernelPackages = lib.mkDefault pkgs.linuxPackages_latest;
|
||||
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
<!-- Windows 11 install profile: emulated display (virt-manager console), no GPU passthrough.
|
||||
After install + NVIDIA driver: set vm.installMode = false and rebuild for gaming profile. -->
|
||||
<domain type='kvm'>
|
||||
<name>win11</name>
|
||||
<uuid>a7b3c8d1-4e2f-5a6b-9c0d-1e2f3a4b5c6d</uuid>
|
||||
<memory unit='GiB'>16</memory>
|
||||
<currentMemory unit='GiB'>16</currentMemory>
|
||||
<vcpu placement='static'>8</vcpu>
|
||||
<os>
|
||||
<type arch='x86_64' machine='pc-q35-9.0'>hvm</type>
|
||||
<loader readonly='yes' secure='yes' type='pflash'>/run/libvirt/nix-ovmf/edk2-x86_64-secure-code.fd</loader>
|
||||
<nvram template='/run/libvirt/nix-ovmf/edk2-i386-vars.fd'>/var/lib/libvirt/qemu/nvram/win11_VARS.fd</nvram>
|
||||
<bootmenu enable='yes'/>
|
||||
<boot dev='cdrom'/>
|
||||
<boot dev='hd'/>
|
||||
</os>
|
||||
<features>
|
||||
<acpi/>
|
||||
<apic/>
|
||||
</features>
|
||||
<cpu mode='host-passthrough' check='none' migratable='on'/>
|
||||
<clock offset='localtime'>
|
||||
<timer name='rtc' tickpolicy='catchup'/>
|
||||
<timer name='pit' tickpolicy='delay'/>
|
||||
<timer name='hpet' present='no'/>
|
||||
</clock>
|
||||
<on_poweroff>destroy</on_poweroff>
|
||||
<on_reboot>restart</on_reboot>
|
||||
<on_crash>preserve</on_crash>
|
||||
<devices>
|
||||
<emulator>/run/libvirt/nix-emulators/qemu-system-x86_64</emulator>
|
||||
|
||||
<disk type='block' device='disk'>
|
||||
<driver name='qemu' type='raw' cache='none' io='native' discard='unmap'/>
|
||||
<source dev='/dev/disk/by-id/ata-CT1000MX500SSD1_1830E14B054A'/>
|
||||
<target dev='sda' bus='sata'/>
|
||||
<address type='drive' controller='0' bus='0' target='0' unit='0'/>
|
||||
</disk>
|
||||
|
||||
<disk type='file' device='cdrom'>
|
||||
<driver name='qemu' type='raw'/>
|
||||
<source file='/var/lib/libvirt/boot/win11.iso'/>
|
||||
<target dev='sdb' bus='sata'/>
|
||||
<readonly/>
|
||||
<address type='drive' controller='0' bus='0' target='0' unit='1'/>
|
||||
</disk>
|
||||
|
||||
<controller type='sata' index='0'>
|
||||
<address type='pci' domain='0x0000' bus='0x00' slot='0x1f' function='0x2'/>
|
||||
</controller>
|
||||
<controller type='pci' index='0' model='pcie-root'/>
|
||||
<controller type='pci' index='1' model='pcie-root-port'>
|
||||
<model name='pcie-root-port'/>
|
||||
<target chassis='1' port='0x10'/>
|
||||
<address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
|
||||
</controller>
|
||||
<controller type='usb' index='0' model='qemu-xhci' ports='15'>
|
||||
<address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/>
|
||||
</controller>
|
||||
|
||||
<interface type='network'>
|
||||
<source network='default'/>
|
||||
<model type='e1000e'/>
|
||||
<address type='pci' domain='0x0000' bus='0x01' slot='0x00' function='0x0'/>
|
||||
</interface>
|
||||
|
||||
<rng model='virtio'>
|
||||
<backend model='random'>/dev/urandom</backend>
|
||||
<address type='pci' domain='0x0000' bus='0x05' slot='0x00' function='0x0'/>
|
||||
</rng>
|
||||
|
||||
<memballoon model='none'/>
|
||||
|
||||
<graphics type='spice' port='5900' autoport='no' listen='127.0.0.1'>
|
||||
<listen type='address' address='127.0.0.1'/>
|
||||
</graphics>
|
||||
<video>
|
||||
<model type='vga'/>
|
||||
</video>
|
||||
<input type='mouse' bus='virtio'/>
|
||||
<input type='keyboard' bus='virtio'/>
|
||||
<sound model='ich9'>
|
||||
<audio id='1'/>
|
||||
</sound>
|
||||
<audio id='1' type='spice'/>
|
||||
|
||||
<tpm model='tpm-crb'>
|
||||
<backend type='emulator' version='2.0'/>
|
||||
</tpm>
|
||||
</devices>
|
||||
</domain>
|
||||
@@ -0,0 +1,177 @@
|
||||
<!-- Gaming: GPU + Looking Glass (kvmfr) -->
|
||||
<domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0' xmlns:libosinfo='http://libosinfo.org/xmlns/libvirt/domain/1.0'>
|
||||
<name>win11</name>
|
||||
<uuid>a7b3c8d1-4e2f-5a6b-9c0d-1e2f3a4b5c6d</uuid>
|
||||
<memory unit='GiB'>16</memory>
|
||||
<currentMemory unit='GiB'>16</currentMemory>
|
||||
<vcpu placement='static'>8</vcpu>
|
||||
<metadata>
|
||||
<libosinfo:libosinfo>
|
||||
<libosinfo:os id='http://microsoft.com/win/11'/>
|
||||
</libosinfo:libosinfo>
|
||||
</metadata>
|
||||
<os>
|
||||
<type arch='x86_64' machine='pc-q35-9.0'>hvm</type>
|
||||
<loader readonly='yes' secure='yes' type='pflash'>/run/libvirt/nix-ovmf/edk2-x86_64-secure-code.fd</loader>
|
||||
<nvram template='/run/libvirt/nix-ovmf/edk2-i386-vars.fd'>/var/lib/libvirt/qemu/nvram/win11_VARS.fd</nvram>
|
||||
<smbios mode='host'/>
|
||||
<boot dev='hd'/>
|
||||
</os>
|
||||
<features>
|
||||
<acpi/>
|
||||
<apic/>
|
||||
<hyperv mode='custom'>
|
||||
<relaxed state='on'/>
|
||||
<vapic state='on'/>
|
||||
<spinlocks state='on' retries='8191'/>
|
||||
<vendor_id state='on' value='GenuineIntel'/>
|
||||
</hyperv>
|
||||
<kvm>
|
||||
<hidden state='on'/>
|
||||
</kvm>
|
||||
<vmport state='off'/>
|
||||
<smm state='on'/>
|
||||
<ioapic driver='kvm'/>
|
||||
</features>
|
||||
<cpu mode='host-passthrough' check='none' migratable='off'>
|
||||
<topology sockets='1' dies='1' cores='4' threads='2'/>
|
||||
<maxphysaddr mode='passthrough' limit='40'/>
|
||||
<feature policy='disable' name='hypervisor'/>
|
||||
</cpu>
|
||||
<clock offset='localtime'>
|
||||
<timer name='rtc' tickpolicy='catchup'/>
|
||||
<timer name='pit' tickpolicy='delay'/>
|
||||
<timer name='hpet' present='no'/>
|
||||
<timer name='hypervclock' present='yes'/>
|
||||
</clock>
|
||||
<on_poweroff>destroy</on_poweroff>
|
||||
<on_reboot>restart</on_reboot>
|
||||
<on_crash>preserve</on_crash>
|
||||
<pm>
|
||||
<suspend-to-mem enabled='no'/>
|
||||
<suspend-to-disk enabled='no'/>
|
||||
</pm>
|
||||
<devices>
|
||||
<emulator>/run/libvirt/nix-emulators/qemu-system-x86_64</emulator>
|
||||
|
||||
<disk type='block' device='disk'>
|
||||
<driver name='qemu' type='raw' cache='none' io='native' discard='unmap'/>
|
||||
<source dev='/dev/disk/by-id/ata-CT1000MX500SSD1_1830E14B054A'/>
|
||||
<target dev='sda' bus='sata'/>
|
||||
<address type='drive' controller='0' bus='0' target='0' unit='0'/>
|
||||
</disk>
|
||||
|
||||
<controller type='sata' index='0'>
|
||||
<address type='pci' domain='0x0000' bus='0x00' slot='0x1f' function='0x2'/>
|
||||
</controller>
|
||||
<controller type='pci' index='0' model='pcie-root'/>
|
||||
<controller type='pci' index='1' model='pcie-root-port'>
|
||||
<model name='pcie-root-port'/>
|
||||
<target chassis='1' port='0x10'/>
|
||||
<address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
|
||||
</controller>
|
||||
<controller type='pci' index='2' model='pcie-root-port'>
|
||||
<model name='pcie-root-port'/>
|
||||
<target chassis='2' port='0x11'/>
|
||||
<address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x1'/>
|
||||
</controller>
|
||||
<controller type='pci' index='3' model='pcie-root-port'>
|
||||
<model name='pcie-root-port'/>
|
||||
<target chassis='3' port='0x12'/>
|
||||
<address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x2'/>
|
||||
</controller>
|
||||
<controller type='pci' index='4' model='pcie-root-port'>
|
||||
<model name='pcie-root-port'/>
|
||||
<target chassis='4' port='0x13'/>
|
||||
<address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x3'/>
|
||||
</controller>
|
||||
<controller type='pci' index='5' model='pcie-root-port'>
|
||||
<model name='pcie-root-port'/>
|
||||
<target chassis='5' port='0x14'/>
|
||||
<address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x4'/>
|
||||
</controller>
|
||||
<controller type='usb' index='0' model='qemu-xhci' ports='15'>
|
||||
<address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/>
|
||||
</controller>
|
||||
|
||||
<interface type='network'>
|
||||
<mac address='04:7c:16:c9:48:a7'/>
|
||||
<source network='default'/>
|
||||
<model type='e1000e'/>
|
||||
<address type='pci' domain='0x0000' bus='0x01' slot='0x00' function='0x0'/>
|
||||
</interface>
|
||||
|
||||
<input type='keyboard' bus='ps2'/>
|
||||
<input type='mouse' bus='ps2'/>
|
||||
|
||||
<memballoon model='none'/>
|
||||
|
||||
<graphics type='spice' port='5900' autoport='no' listen='127.0.0.1'>
|
||||
<listen type='address' address='127.0.0.1'/>
|
||||
<image compression='off'/>
|
||||
</graphics>
|
||||
<video>
|
||||
<model type='none'/>
|
||||
</video>
|
||||
<sound model='ich9'>
|
||||
<audio id='1'/>
|
||||
</sound>
|
||||
<audio id='1' type='spice'/>
|
||||
|
||||
<hostdev mode='subsystem' type='pci' managed='yes'>
|
||||
<source>
|
||||
<address domain='0x0000' bus='0x01' slot='0x00' function='0x0'/>
|
||||
</source>
|
||||
<address type='pci' domain='0x0000' bus='0x06' slot='0x00' function='0x0' multifunction='on'/>
|
||||
</hostdev>
|
||||
<hostdev mode='subsystem' type='pci' managed='yes'>
|
||||
<source>
|
||||
<address domain='0x0000' bus='0x01' slot='0x00' function='0x1'/>
|
||||
</source>
|
||||
<address type='pci' domain='0x0000' bus='0x06' slot='0x00' function='0x1'/>
|
||||
</hostdev>
|
||||
<hostdev mode='subsystem' type='pci' managed='yes'>
|
||||
<source>
|
||||
<address domain='0x0000' bus='0x01' slot='0x00' function='0x2'/>
|
||||
</source>
|
||||
<address type='pci' domain='0x0000' bus='0x06' slot='0x00' function='0x2'/>
|
||||
</hostdev>
|
||||
<hostdev mode='subsystem' type='pci' managed='yes'>
|
||||
<source>
|
||||
<address domain='0x0000' bus='0x01' slot='0x00' function='0x3'/>
|
||||
</source>
|
||||
<address type='pci' domain='0x0000' bus='0x06' slot='0x00' function='0x3'/>
|
||||
</hostdev>
|
||||
|
||||
<hostdev mode='subsystem' type='pci' managed='yes'>
|
||||
<source>
|
||||
<address domain='0x0000' bus='0x03' slot='0x00' function='0x0'/>
|
||||
</source>
|
||||
<address type='pci' domain='0x0000' bus='0x04' slot='0x00' function='0x0'/>
|
||||
</hostdev>
|
||||
|
||||
<hostdev mode='subsystem' type='usb' managed='yes'>
|
||||
<source startupPolicy='optional'>
|
||||
<vendor id='0x046d'/>
|
||||
<product id='0xc08b'/>
|
||||
</source>
|
||||
</hostdev>
|
||||
|
||||
<hostdev mode='subsystem' type='usb' managed='yes'>
|
||||
<source startupPolicy='optional'>
|
||||
<vendor id='0x0489'/>
|
||||
<product id='0xe10f'/>
|
||||
</source>
|
||||
</hostdev>
|
||||
|
||||
<tpm model='tpm-crb'>
|
||||
<backend type='emulator' version='2.0'/>
|
||||
</tpm>
|
||||
</devices>
|
||||
<qemu:commandline>
|
||||
<qemu:arg value='-device'/>
|
||||
<qemu:arg value='{"driver":"ivshmem-plain","id":"shmem0","memdev":"looking-glass"}'/>
|
||||
<qemu:arg value='-object'/>
|
||||
<qemu:arg value='{"qom-type":"memory-backend-file","id":"looking-glass","mem-path":"/dev/kvmfr0","size":33554432,"share":true}'/>
|
||||
</qemu:commandline>
|
||||
</domain>
|
||||
@@ -6,6 +6,7 @@
|
||||
self.nixosModules."14900kHardware"
|
||||
inputs.home-manager.nixosModules.home-manager
|
||||
inputs.sops-nix.nixosModules.sops
|
||||
inputs.nixvirt.nixosModules.default
|
||||
|
||||
self.nixosModules.system
|
||||
self.nixosModules.desktop
|
||||
@@ -14,6 +15,7 @@
|
||||
self.nixosModules."client-services"
|
||||
./_private/platform.nix
|
||||
./_private/nvidia.nix
|
||||
./_private/gpu-passthru.nix
|
||||
./_private/peripherals.nix
|
||||
# ./_private/printing-epson.nix
|
||||
./_private/displays.nix
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
self.nixosModules.systemIdeapadMrubyOverlay
|
||||
self.nixosModules.systemYtDlpTelequebecPatch
|
||||
self.nixosModules.systemGaming
|
||||
self.nixosModules.systemGpuPassthru
|
||||
self.nixosModules.systemUconsoleKernelBuilder
|
||||
self.nixosModules.systemLibrepods
|
||||
self.nixosModules.systemPalera1n
|
||||
|
||||
@@ -0,0 +1,383 @@
|
||||
{ ... }: {
|
||||
flake.nixosModules.systemGpuPassthru =
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.chiasson.system.gpuPassthru;
|
||||
vfioIds = lib.concatStringsSep "," cfg.pciIds;
|
||||
# Must match the Windows host installer build (B7-244-4bb2c58f).
|
||||
lgRev = "4bb2c58fb6d0df9e863ad45924dd4decc7e9cf4e";
|
||||
lgVersion = "B7-244-4bb2c58f";
|
||||
# Tarball hashes omit submodules; fetchSubmodules needs the git tree hash.
|
||||
lgHash = "sha256-Dc0sFnJVM0xJ0FfIQqGQKxFUj6mhp3qgB772h3Dgowc=";
|
||||
# https://wiki.nixos.org/wiki/Looking_Glass — systemd 258+ needs namespaces=[]
|
||||
# and an explicit ACL; seccomp can block mmap on /dev/kvmfr0.
|
||||
lgQemuVerbatimConfig = ''
|
||||
namespaces = []
|
||||
seccomp_sandbox = 0
|
||||
security_driver = "none"
|
||||
security_default_confined = 0
|
||||
security_require_confined = 0
|
||||
cgroup_device_acl = [
|
||||
"/dev/null", "/dev/full", "/dev/zero",
|
||||
"/dev/random", "/dev/urandom",
|
||||
"/dev/ptmx", "/dev/kvm", "/dev/kqemu",
|
||||
"/dev/rtc", "/dev/hpet",
|
||||
"/dev/vfio/", "/dev/vfio/vfio",
|
||||
"/dev/kvmfr0", "c 234:* rwm",
|
||||
"/dev/disk/", "/dev/disk/by-id/",
|
||||
"/dev/bus/usb/",
|
||||
]
|
||||
'';
|
||||
in
|
||||
{
|
||||
options.chiasson.system.gpuPassthru = {
|
||||
enable = lib.mkEnableOption ''
|
||||
Bind passthrough GPU(s) to VFIO, run the host on Intel iGPU, and enable libvirt +
|
||||
Looking Glass for a Windows gaming VM.
|
||||
|
||||
Prerequisites (manual, one-time):
|
||||
- BIOS: VT-d/IOMMU on, Above 4G Decoding / Resizable BAR on
|
||||
- Move host monitors to motherboard (Intel) ports; dummy HDMI/DP plug on passthrough GPU
|
||||
- Wipe the VM disk before first boot: `wipefs -a <windowsDisk>`
|
||||
- Verify IOMMU groups after switch: GPU functions should be isolated
|
||||
|
||||
Rollout: leave `enable = false` until cabling and disk backup are done, then rebuild and switch.
|
||||
'';
|
||||
|
||||
pciIds = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
example = [ "10de:1f07" "10de:10f9" "10de:1ada" "10de:1adb" ];
|
||||
description = ''
|
||||
PCI vendor:device IDs to bind to vfio-pci (all GPU functions: VGA, audio, USB, etc.).
|
||||
'';
|
||||
};
|
||||
|
||||
windowsDisk = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "";
|
||||
example = "/dev/disk/by-id/ata-CT1000MX500SSD1_1830E14B054A";
|
||||
description = ''
|
||||
Whole-disk by-id path for the Windows VM (raw block passthrough). Must not be mounted by the host.
|
||||
'';
|
||||
};
|
||||
|
||||
lookingGlass = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Load the kvmfr kernel module and grant QEMU access to /dev/kvmfr0 for Looking Glass
|
||||
(required for NVIDIA VFIO; plain /dev/shm IVSHMEM causes DMA mapping failures).
|
||||
'';
|
||||
};
|
||||
|
||||
shmSizeMiB = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
default = 32;
|
||||
description = ''
|
||||
kvmfr shared memory size (MiB). Must match `kvmfr.static_size_mb` and the VM
|
||||
`memory-backend-file` size in the domain XML.
|
||||
'';
|
||||
};
|
||||
|
||||
captureOnFocus = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Grab keyboard/mouse when the Looking Glass window is focused. Set `false` to
|
||||
keep input on the Linux host while viewing the guest.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
vm = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Declaratively define the Windows VM via NixVirt (host module must import NixVirt and set
|
||||
`virtualisation.libvirt.connections`).
|
||||
'';
|
||||
};
|
||||
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "win11";
|
||||
description = "libvirt domain name.";
|
||||
};
|
||||
|
||||
memoryGiB = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
default = 16;
|
||||
description = "VM RAM in GiB (informational; domain XML is authoritative).";
|
||||
};
|
||||
|
||||
vcpus = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
default = 8;
|
||||
description = "vCPU count (informational; domain XML is authoritative).";
|
||||
};
|
||||
|
||||
installMode = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Use install domain (ISO, no GPU). Set `false` after Windows is installed.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
hugepages.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Reserve 2 MiB hugepages for Looking Glass IVSHMEM (enable if frame drops).";
|
||||
};
|
||||
|
||||
libvirtUsers = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
description = "Users added to libvirtd, kvm, and disk groups.";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable (lib.mkMerge [
|
||||
{
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.pciIds != [ ];
|
||||
message = "chiasson.system.gpuPassthru: pciIds must list every passthrough GPU function.";
|
||||
}
|
||||
{
|
||||
assertion = cfg.windowsDisk != "";
|
||||
message = "chiasson.system.gpuPassthru: windowsDisk must be set to a /dev/disk/by-id/ path.";
|
||||
}
|
||||
{
|
||||
assertion = cfg.libvirtUsers != [ ];
|
||||
message = "chiasson.system.gpuPassthru: libvirtUsers must name at least one user.";
|
||||
}
|
||||
];
|
||||
|
||||
boot.kernelParams = [
|
||||
"intel_iommu=on"
|
||||
"iommu=pt"
|
||||
"kvm.ignore_msrs=1"
|
||||
"vfio-pci.ids=${vfioIds}"
|
||||
];
|
||||
|
||||
boot.initrd.kernelModules = [
|
||||
"vfio_pci"
|
||||
"vfio"
|
||||
"vfio_iommu_type1"
|
||||
];
|
||||
|
||||
boot.blacklistedKernelModules = [
|
||||
"nouveau"
|
||||
"nvidia"
|
||||
"nvidia_drm"
|
||||
"nvidia_modeset"
|
||||
"i2c_nvidia_gpu"
|
||||
"mt7921e"
|
||||
"mt7925e"
|
||||
];
|
||||
|
||||
boot.extraModprobeConfig = ''
|
||||
softdep nvidia pre: vfio-pci
|
||||
softdep nvidia_drm pre: vfio-pci
|
||||
softdep nouveau pre: vfio-pci
|
||||
options vfio_iommu_type1 allow_unsafe_interrupts=1
|
||||
'';
|
||||
|
||||
services.xserver.videoDrivers = lib.mkForce [ "modesetting" ];
|
||||
|
||||
hardware.nvidia-container-toolkit.enable = lib.mkForce false;
|
||||
chiasson.system.gaming.sunshine.cudaSupport = lib.mkForce false;
|
||||
|
||||
# libvirt enables ZFS storage pools on Linux by default, which pulls in
|
||||
# zfs-user (currently broken to build on nixos-unstable). Raw block disk
|
||||
# passthrough does not need libvirt ZFS support.
|
||||
nixpkgs.overlays =
|
||||
[
|
||||
(final: prev: {
|
||||
libvirt = prev.libvirt.override { enableZfs = false; };
|
||||
})
|
||||
]
|
||||
++ lib.optionals cfg.lookingGlass.enable [
|
||||
(final: prev: {
|
||||
looking-glass-client = prev.looking-glass-client.overrideAttrs (old: {
|
||||
version = lgVersion;
|
||||
src = prev.fetchFromGitHub {
|
||||
owner = "gnif";
|
||||
repo = "LookingGlass";
|
||||
rev = lgRev;
|
||||
hash = lgHash;
|
||||
fetchSubmodules = true;
|
||||
};
|
||||
patches = old.patches;
|
||||
postUnpack = ''
|
||||
echo ${lgRev} > $sourceRoot/VERSION
|
||||
export sourceRoot="$sourceRoot/client"
|
||||
'';
|
||||
});
|
||||
})
|
||||
];
|
||||
|
||||
programs.virt-manager.enable = true;
|
||||
virtualisation.spiceUSBRedirection.enable = true;
|
||||
|
||||
virtualisation.libvirtd = {
|
||||
enable = true;
|
||||
onBoot = "ignore";
|
||||
onShutdown = "shutdown";
|
||||
qemu = {
|
||||
package = pkgs.qemu_kvm;
|
||||
# GPU + raw block passthrough needs root (or fragile per-device ACLs).
|
||||
runAsRoot = true;
|
||||
swtpm.enable = true;
|
||||
verbatimConfig = lib.mkIf cfg.lookingGlass.enable (lib.mkForce lgQemuVerbatimConfig);
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.libvirtd.restartTriggers = [
|
||||
"${pkgs.qemu_kvm}/bin/qemu-system-x86_64"
|
||||
];
|
||||
|
||||
environment.systemPackages =
|
||||
with pkgs;
|
||||
[
|
||||
virt-manager
|
||||
qemu_kvm
|
||||
OVMF
|
||||
edk2
|
||||
looking-glass-client
|
||||
swtpm
|
||||
]
|
||||
++ lib.optionals cfg.lookingGlass.enable [
|
||||
(writeShellScriptBin "looking-glass-toggle" ''
|
||||
set -euo pipefail
|
||||
pkill -f 'looking-glass-client' || true
|
||||
exec looking-glass-client -F -m 97
|
||||
'')
|
||||
];
|
||||
|
||||
# B7 defaults to /dev/kvmfr0; pin SPICE to the fixed port in the domain XMLs.
|
||||
environment.etc."looking-glass-client.ini" = lib.mkIf cfg.lookingGlass.enable (lib.mkForce {
|
||||
text = ''
|
||||
[app]
|
||||
shmFile=/dev/kvmfr0
|
||||
allowDMA=yes
|
||||
|
||||
[win]
|
||||
fullScreen=yes
|
||||
|
||||
[spice]
|
||||
enable=yes
|
||||
host=127.0.0.1
|
||||
port=5900
|
||||
input=yes
|
||||
audio=yes
|
||||
clipboard=yes
|
||||
|
||||
[input]
|
||||
captureOnFocus=${lib.boolToString cfg.lookingGlass.captureOnFocus}
|
||||
escapeKey=97
|
||||
'';
|
||||
});
|
||||
|
||||
users.groups.libvirtd.members = cfg.libvirtUsers;
|
||||
users.users =
|
||||
lib.genAttrs cfg.libvirtUsers (_: {
|
||||
extraGroups = [
|
||||
"libvirtd"
|
||||
"kvm"
|
||||
"disk"
|
||||
];
|
||||
})
|
||||
// {
|
||||
qemu-libvirtd.extraGroups = [
|
||||
"kvm"
|
||||
"disk"
|
||||
];
|
||||
};
|
||||
}
|
||||
{
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /var/lib/libvirt/boot 0755 root root -"
|
||||
];
|
||||
}
|
||||
(lib.mkIf cfg.lookingGlass.enable {
|
||||
boot.extraModulePackages = lib.mkAfter [ config.boot.kernelPackages.kvmfr ];
|
||||
boot.initrd.kernelModules = lib.mkAfter [ "kvmfr" ];
|
||||
boot.kernelModules = lib.mkAfter [ "kvmfr" ];
|
||||
boot.kernelParams = lib.mkAfter [
|
||||
"kvmfr.static_size_mb=${toString cfg.lookingGlass.shmSizeMiB}"
|
||||
];
|
||||
|
||||
boot.extraModprobeConfig = lib.mkAfter ''
|
||||
options kvmfr static_size_mb=${toString cfg.lookingGlass.shmSizeMiB}
|
||||
'';
|
||||
|
||||
services.udev.packages = [
|
||||
(pkgs.writeTextFile {
|
||||
name = "kvmfr-udev";
|
||||
text = ''
|
||||
SUBSYSTEM=="kvmfr", GROUP="kvm", MODE="0660", TAG+="uaccess"
|
||||
'';
|
||||
destination = "/etc/udev/rules.d/70-kvmfr.rules";
|
||||
})
|
||||
];
|
||||
|
||||
# insmod from the nix store: extraModulePackages only lands in
|
||||
# /run/booted-system/kernel-modules after a reboot.
|
||||
systemd.services.kvmfr = {
|
||||
description = "Looking Glass kvmfr module";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
before = [
|
||||
"libvirtd.service"
|
||||
"libvirtd-config.service"
|
||||
"libvirt-guests.service"
|
||||
];
|
||||
after = [ "systemd-modules-load.service" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
ExecStart = pkgs.writeShellScript "kvmfr-start" (
|
||||
let
|
||||
kvmfrKo = "${config.boot.kernelPackages.kvmfr}/lib/modules/${config.boot.kernelPackages.kernel.modDirVersion}/kernel/drivers/misc/kvmfr.ko";
|
||||
shmMiB = toString cfg.lookingGlass.shmSizeMiB;
|
||||
in
|
||||
''
|
||||
set -euo pipefail
|
||||
if [ -e /dev/kvmfr0 ] && [ ! -c /dev/kvmfr0 ]; then
|
||||
rm -f /dev/kvmfr0
|
||||
fi
|
||||
if [ -c /dev/kvmfr0 ]; then
|
||||
exit 0
|
||||
fi
|
||||
if ${pkgs.kmod}/bin/lsmod | ${pkgs.gnugrep}/bin/grep -q '^kvmfr '; then
|
||||
${pkgs.kmod}/bin/rmmod kvmfr
|
||||
fi
|
||||
if [ ! -f ${kvmfrKo} ]; then
|
||||
echo "kvmfr: module missing at ${kvmfrKo}" >&2
|
||||
exit 1
|
||||
fi
|
||||
${pkgs.kmod}/bin/insmod ${kvmfrKo} static_size_mb=${shmMiB}
|
||||
if [ ! -c /dev/kvmfr0 ]; then
|
||||
echo "kvmfr: /dev/kvmfr0 not created (static_size_mb=${shmMiB})" >&2
|
||||
exit 1
|
||||
fi
|
||||
''
|
||||
);
|
||||
};
|
||||
};
|
||||
systemd.services.libvirtd = lib.mkIf cfg.lookingGlass.enable {
|
||||
after = [ "kvmfr.service" ];
|
||||
requires = [ "kvmfr.service" ];
|
||||
};
|
||||
})
|
||||
(lib.mkIf cfg.hugepages.enable {
|
||||
boot.kernelParams = [ "hugepagesz=2M" "hugepages=64" ];
|
||||
})
|
||||
]);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user