diff --git a/modules/hardware/common/default.nix b/modules/hardware/common/default.nix index 12c4dfa7c..4ba3c653b 100644 --- a/modules/hardware/common/default.nix +++ b/modules/hardware/common/default.nix @@ -4,6 +4,7 @@ imports = [ ./usb/internal.nix ./usb/external.nix + ./usb/vhotplug.nix ./devices.nix ./kernel.nix ./qemu.nix diff --git a/modules/hardware/common/qemu.nix b/modules/hardware/common/qemu.nix index e7ebbfc16..d9a02c018 100644 --- a/modules/hardware/common/qemu.nix +++ b/modules/hardware/common/qemu.nix @@ -35,7 +35,11 @@ in "acad" ] ++ optionals (hasAttr "yubikey" config.ghaf.hardware.usb.external.qemuExtraArgs) config.ghaf.hardware.usb.external.qemuExtraArgs.yubikey - ++ optionals (hasAttr "fpr0" config.ghaf.hardware.usb.internal.qemuExtraArgs) config.ghaf.hardware.usb.internal.qemuExtraArgs.fpr0; + ++ optionals (hasAttr "fpr0" config.ghaf.hardware.usb.internal.qemuExtraArgs) config.ghaf.hardware.usb.internal.qemuExtraArgs.fpr0 + ++ optionals config.ghaf.hardware.usb.vhotplug.enableEvdevPassthrough builtins.concatMap (n: [ + "-device" + "pcie-root-port,bus=pcie.0,id=${config.ghaf.hardware.usb.vhotplug.pcieBusPrefix}${toString n},chassis=${toString n}" + ]) (lib.range 1 config.ghaf.hardware.usb.vhotplug.pciePortCount); }; }; } diff --git a/modules/hardware/common/usb/vhotplug.nix b/modules/hardware/common/usb/vhotplug.nix new file mode 100644 index 000000000..d67cc0498 --- /dev/null +++ b/modules/hardware/common/usb/vhotplug.nix @@ -0,0 +1,192 @@ +# Copyright 2022-2024 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 +{ + config, + lib, + pkgs, + ... +}: +let + cfg = config.ghaf.hardware.usb.vhotplug; + inherit (lib) + mkEnableOption + mkOption + types + mkIf + literalExpression + ; + + vhotplug = pkgs.callPackage ../../../../packages/vhotplug { }; + defaultRules = [ + { + name = "GUIVM"; + qmpSocket = "/var/lib/microvms/gui-vm/gui-vm.sock"; + usbPassthrough = [ + { + class = 3; + protocol = 1; + description = "HID Keyboard"; + } + { + class = 3; + protocol = 2; + description = "HID Mouse"; + } + { + class = 11; + description = "Chip/SmartCard (e.g. YubiKey)"; + } + { + class = 224; + subclass = 1; + protocol = 1; + description = "Bluetooth"; + disable = true; + } + { + # Currently disabled to leave USB drives connected to the host + class = 8; + sublass = 6; + description = "Mass Storage - SCSI (USB drives)"; + disable = true; + } + ]; + evdevPassthrough = { + enable = cfg.enableEvdevPassthrough; + inherit (cfg) pcieBusPrefix; + }; + } + { + name = "NetVM"; + qmpSocket = "/var/lib/microvms/net-vm/net-vm.sock"; + usbPassthrough = [ + { + # Currently disabled to avoid breaking remote nixos-rebuild, + # which requires an Ethernet adapter connected to the host + class = 2; + sublass = 6; + description = "Communications - Ethernet Networking"; + disable = true; + } + ]; + } + { + name = "ChromiumVM"; + qmpSocket = "/var/lib/microvms/chromium-vm/chromium-vm.sock"; + usbPassthrough = [ + { + class = 14; + description = "Video (USB Webcams)"; + } + ]; + } + { + name = "AudioVM"; + qmpSocket = "/var/lib/microvms/audio-vm/audio-vm.sock"; + usbPassthrough = [ + { + class = 1; + description = "Audio"; + } + ]; + } + ]; +in +{ + options.ghaf.hardware.usb.vhotplug = { + enable = mkEnableOption "Enable hot plugging of USB devices"; + + rules = mkOption { + type = types.listOf types.attrs; + default = defaultRules; + description = '' + List of virtual machines with USB hot plugging rules. + ''; + example = literalExpression '' + [ + { + name = "GUIVM"; + qmpSocket = "/var/lib/microvms/gui-vm/gui-vm.sock"; + usbPassthrough = [ + { + class = 3; + protocol = 1; + description = "HID Keyboard"; + ignore = [ + { + vendorId = "046d"; + productId = "c52b"; + description = "Logitech, Inc. Unifying Receiver"; + } + ]; + } + { + vendorId = "067b"; + productId = "23a3"; + description = "Prolific Technology, Inc. USB-Serial Controller"; + disable = true; + } + ]; + } + { + name = "NetVM"; + qmpSocket = "/var/lib/microvms/net-vm/net-vm.sock"; + usbPassthrough = [ + { + productName = ".*ethernet.*"; + description = "Ethernet devices"; + } + ]; + } + ]; + ''; + }; + + enableEvdevPassthrough = mkOption { + description = '' + Enable passthrough of non-USB input devices on startup using QEMU virtio-input-host-pci device. + ''; + type = types.bool; + default = true; + }; + + pcieBusPrefix = mkOption { + type = types.nullOr types.str; + default = "rp"; + description = '' + PCIe bus prefix used for the pcie-root-port QEMU device when evdev passthrough is enabled. + ''; + }; + + pciePortCount = lib.mkOption { + type = lib.types.int; + default = 5; + description = '' + The number of PCIe ports used for hot-plugging virtio-input-host-pci devices. + ''; + }; + }; + + config = mkIf cfg.enable { + services.udev.extraRules = '' + SUBSYSTEM=="usb", GROUP="kvm" + KERNEL=="event*", GROUP="kvm" + ''; + + environment.etc."vhotplug.conf".text = builtins.toJSON { vms = cfg.rules; }; + + systemd.services.vhotplug = { + enable = true; + description = "vhotplug"; + wantedBy = [ "microvms.target" ]; + after = [ "microvms.target" ]; + serviceConfig = { + Type = "simple"; + Restart = "always"; + RestartSec = "1"; + ExecStart = "${vhotplug}/bin/vhotplug -a -c /etc/vhotplug.conf"; + }; + startLimitIntervalSec = 0; + }; + }; +} diff --git a/modules/microvm/virtualization/microvm/audiovm.nix b/modules/microvm/virtualization/microvm/audiovm.nix index 12b0e04d1..ca224d18f 100644 --- a/modules/microvm/virtualization/microvm/audiovm.nix +++ b/modules/microvm/virtualization/microvm/audiovm.nix @@ -59,7 +59,7 @@ let pkgs.pulseaudio pkgs.pamixer pkgs.pipewire - ]; + ] ++ lib.optional config.ghaf.development.debug.tools.enable pkgs.alsa-utils; }; time.timeZone = config.time.timeZone; @@ -73,7 +73,8 @@ let services.openssh = config.ghaf.security.sshKeys.sshAuthorizedKeysCommand; microvm = { - optimize.enable = true; + # Optimize is disabled because when it is enabled, qemu is built without libusb + optimize.enable = false; vcpu = 1; mem = 256; hypervisor = "qemu"; @@ -102,6 +103,10 @@ let aarch64-linux = "virt"; } .${configHost.nixpkgs.hostPlatform.system}; + extraArgs = [ + "-device" + "qemu-xhci" + ]; }; }; diff --git a/modules/microvm/virtualization/microvm/netvm.nix b/modules/microvm/virtualization/microvm/netvm.nix index e356b48d5..758c18891 100644 --- a/modules/microvm/virtualization/microvm/netvm.nix +++ b/modules/microvm/virtualization/microvm/netvm.nix @@ -94,7 +94,8 @@ let ''; microvm = { - optimize.enable = true; + # Optimize is disabled because when it is enabled, qemu is built without libusb + optimize.enable = false; hypervisor = "qemu"; shares = [ @@ -122,6 +123,10 @@ let aarch64-linux = "virt"; } .${config.nixpkgs.hostPlatform.system}; + extraArgs = [ + "-device" + "qemu-xhci" + ]; }; }; diff --git a/modules/reference/profiles/laptop-x86.nix b/modules/reference/profiles/laptop-x86.nix index 3f94d00c5..a835d72fa 100644 --- a/modules/reference/profiles/laptop-x86.nix +++ b/modules/reference/profiles/laptop-x86.nix @@ -65,6 +65,7 @@ in tpm2.enable = true; usb.internal.enable = true; usb.external.enable = true; + usb.vhotplug.enable = true; }; # Virtualization options diff --git a/packages/qemuqmp/default.nix b/packages/qemuqmp/default.nix new file mode 100644 index 000000000..0979b8b50 --- /dev/null +++ b/packages/qemuqmp/default.nix @@ -0,0 +1,26 @@ +# Copyright 2022-2024 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 +{ + python3Packages, + fetchPypi, + lib, +}: +python3Packages.buildPythonPackage rec { + pname = "qemu.qmp"; + version = "0.0.3"; + + src = fetchPypi { + inherit pname version; + sha256 = "sha256-y8iPvMEV7pQ9hER9FyxkLaEgIgRRQWwvYhrPM98eEBA="; + }; + + pyproject = true; + + nativeBuildInputs = [ python3Packages.setuptools-scm ]; + + meta = { + homepage = "https://www.qemu.org/"; + description = "QEMU Monitor Protocol library"; + license = lib.licenses.lgpl2Plus; + }; +} diff --git a/packages/vhotplug/default.nix b/packages/vhotplug/default.nix new file mode 100644 index 000000000..890bfaa13 --- /dev/null +++ b/packages/vhotplug/default.nix @@ -0,0 +1,29 @@ +# Copyright 2022-2024 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 +{ + python3Packages, + pkgs, + fetchFromGitHub, +}: +let + qemuqmp = pkgs.callPackage ../qemuqmp { }; +in +python3Packages.buildPythonApplication rec { + pname = "vhotplug"; + version = "0.1"; + + propagatedBuildInputs = [ + python3Packages.pyudev + python3Packages.psutil + qemuqmp + ]; + + doCheck = false; + + src = fetchFromGitHub { + owner = "tiiuae"; + repo = "vhotplug"; + rev = "fd05361ed893d06cdb5ac4a538c171e4a86b6f5a"; + hash = "sha256-6fl5xeSpcIIBKn3dZUAEHiNRRpn9LbYC4Imap5KBH2M="; + }; +}