nixos-framework-laptop-config/nixos/kvmfr.nix

155 lines
No EOL
4.4 KiB
Nix

# from https://github.com/j-brn/nixos-vfio
{ std, lib, pkgs, config, ... }:
with lib;
let
cfg = config.virtualisation.kvmfr;
sizeFromResolution = resolution:
let
ceilToPowerOf2 = n:
std.num.pow 2 (std.num.bits.bitSize - std.num.bits.countLeadingZeros n);
pixelSize = if resolution.pixelFormat == "rgb24" then 3 else 4;
bytes = resolution.width * resolution.height * pixelSize * 2;
in ceilToPowerOf2 (bytes / 1024 / 1024 + 10);
deviceSizes = map (device: device.size) cfg.devices;
devices =
imap0 (index: _deviceConfig: "/dev/kvmfr${toString index}") cfg.devices;
udevPackage = pkgs.writeTextDir "/lib/udev/rules.d/99-kvmfr.rules"
(concatStringsSep "\n" (imap0 (index: deviceConfig: ''
SUBSYSTEM=="kvmfr", KERNEL=="kvmfr${
toString index
}", OWNER="${deviceConfig.permissions.user}", GROUP="${deviceConfig.permissions.group}", MODE="${deviceConfig.permissions.mode}", TAG+="systemd"
'') cfg.devices));
apparmorAbstraction =
concatStringsSep "\n" (map (device: "${device} rw") devices);
permissionsType = types.submodule {
options = {
user = mkOption {
type = types.str;
default = "root";
description = mdDoc "Owner of the shared memory device.";
};
group = mkOption {
type = types.str;
default = "root";
description = mdDoc "Group of the shared memory device.";
};
mode = mkOption {
type = types.str;
default = "0600";
description = mdDoc "Mode of the shared memory device.";
};
};
};
resolutionType = types.submodule {
options = {
width = mkOption {
type = types.number;
description = mdDoc
"Maximum horizontal video size that should be supported by this device.";
};
height = mkOption {
type = types.number;
description = mdDoc
"Maximum vertical video size that should be supported by this device.";
};
pixelFormat = mkOption {
type = types.enum [ "rgba32" "rgb24" ];
description = mdDoc "Pixel format to use.";
default = "rgba32";
};
};
};
deviceType = (types.submodule ({ config, options, ... }: {
options = {
resolution = mkOption {
type = types.nullOr resolutionType;
default = null;
description = mdDoc ''
Automatically calculate the minimum device size for a specific resolution. Overrides `size` if set.
'';
};
size = mkOption {
type = types.number;
description = mdDoc ''
Size for the kvmfr device in megabytes.
'';
};
permissions = mkOption {
type = permissionsType;
default = { };
description = mdDoc "Permissions of the kvmfr device.";
};
};
config = {
size =
mkIf (config.resolution != null) (sizeFromResolution config.resolution);
};
}));
in {
options.virtualisation.kvmfr = {
enable = mkOption {
type = types.bool;
default = false;
description = mdDoc "Whether to enable the kvmfr kernel module.";
};
devices = mkOption {
type = types.listOf deviceType;
default = [ ];
description = mdDoc "List of devices to create.";
};
};
config = mkIf cfg.enable {
# So I can build for kernel 6.13
# til https://github.com/gnif/LookingGlass/pull/1154 is merged
boot.extraModulePackages = with config.boot.kernelPackages; [
kvmfr
# (kvmfr.overrideAttrs (old: {
# patches = [ ]; # The patches have already since been merged
# src = pkgs.fetchFromGitHub {
# owner = "zeule";
# repo = "LookingGlass";
# rev = "7740692e3000c2019e21b9861585960174dd5ddc";
# sha256 = "sha256-2ayH8FXOn4Bflf55WvhMWTDMLwvucmofD3POI72bC+Q=";
# };
# }))
];
services.udev.packages = optionals (cfg.devices != [ ]) [ udevPackage ];
environment.etc = {
"modules-load.d/kvmfr.conf".text = ''
kvmfr
'';
"modprobe.d/kvmfr.conf".text = ''
options kvmfr static_size_mb=${
concatStringsSep "," (map (size: toString size) deviceSizes)
}
'';
"apparmor.d/local/abstractions/libvirt-qemu" =
mkIf config.security.apparmor.enable {
text = mkIf config.security.apparmor.enable apparmorAbstraction;
};
};
virtualisation.libvirtd.deviceACL = devices;
};
}