# Hardware enablement beyond nixos-hardware. # # The nixos-hardware "common-*" profiles the installer selects cover the # BASICS (microcode, the Intel/AMD VA-API media stack, weekly fstrim), and # power.nix adds thermald + power-profiles-daemon. This module fills the GAP # above those: broadly-beneficial bits that default ON when the installer # detects the vendor (opt-OUT), and heavier/experimental bits behind opt-IN # toggles. The installer's hardware-db.sh probes what's present and writes the # matching nomarchy.hardware.* into the generated system.nix. # # Audited against the commons so we don't double-set: we add GuC/HuC, the # amd-pstate governor, the AMD VA-API env, GPU-compute runtimes, fprintd, and # the NPU driver — none of which the commons turn on. { config, lib, pkgs, ... }: let cfg = config.nomarchy.hardware; # Fingerprint PAM can follow theme-state.json (menu toggle → next # sys-rebuild), same bridge as autoTimezone (BACKLOG #55). Missing or # invalid JSON fails closed (theme-state-read.nix) instead of a raw stack. hwState = if config.nomarchy.system.stateFile != null then import ../theme-state-read.nix { inherit lib; } config.nomarchy.system.stateFile else { }; pamFromState = (hwState.settings or { }).fingerprint.pam or false; in { options.nomarchy.hardware = { intel = { enable = lib.mkEnableOption '' Intel CPU/GPU enablement (the installer turns this on when it detects an Intel CPU or GPU). Complements nixos-hardware's common-gpu-intel''; guc = lib.mkOption { type = lib.types.bool; default = cfg.intel.enable; defaultText = lib.literalExpression "config.nomarchy.hardware.intel.enable"; description = '' Load the GPU's GuC/HuC firmware via the i915 param (i915.enable_guc=3) — better power management and HuC-accelerated media. On by default with intel.enable. NOTE: this is the *i915* driver's param; the newer `xe` driver (Lunar Lake / Battlemage / Panther Lake and other recent Xe GPUs) enables GuC by default and ignores it, so the installer turns this off on xe-driver hardware. ''; }; computeRuntime = lib.mkEnableOption '' Intel GPU compute: the OpenCL / Level Zero (intel-compute-runtime) and oneVPL (vpl-gpu-rt) runtimes for GPU compute and transcode. Opt-in (a few hundred MB) — the Intel counterpart to AMD ROCm''; }; amd = { enable = lib.mkEnableOption '' AMD CPU/GPU enablement (installer-set on an AMD CPU or GPU). Complements nixos-hardware's common-cpu-amd / common-gpu-amd''; pstate = lib.mkOption { type = lib.types.bool; default = cfg.amd.enable; defaultText = lib.literalExpression "config.nomarchy.hardware.amd.enable"; description = '' Use the amd-pstate EPP driver (amd_pstate=active) — the modern Zen power/perf governor that power-profiles-daemon drives per profile. On by default with amd.enable (broadly beneficial on Zen 2+). ''; }; vaapi = lib.mkOption { type = lib.types.bool; default = cfg.amd.enable; defaultText = lib.literalExpression "config.nomarchy.hardware.amd.enable"; description = '' Point VA-API at mesa's radeonsi (LIBVA_DRIVER_NAME=radeonsi) for hardware video decode/encode. On by default with amd.enable. ''; }; rocm = { enable = lib.mkEnableOption '' AMD ROCm: the HIP / OpenCL GPU-compute stack (multi-GB closure). Opt-in — unlocks GPU PyTorch / Ollama on Radeon''; gfxOverride = lib.mkOption { type = lib.types.str; default = ""; example = "11.0.0"; description = '' HSA_OVERRIDE_GFX_VERSION for GPUs ROCm doesn't officially list (e.g. an RDNA3 780M iGPU, gfx1103, needs "11.0.0"). Empty = no override. ''; }; }; }; fingerprint = { enable = lib.mkEnableOption '' a fingerprint reader via fprintd (the installer turns this on when it detects a known reader). Enroll with `fprintd-enroll`''; pam = lib.mkOption { type = lib.types.bool; default = pamFromState; defaultText = lib.literalExpression "(settings.fingerprint.pam from theme-state.json) or false"; description = '' Use the fingerprint for login and sudo (PAM). Opt-in — password-only stays the default for the cautious; enroll a finger first. Defaults from theme-state.json `settings.fingerprint.pam` (System › Fingerprint menu) when set; otherwise false. ''; }; }; npu.enable = lib.mkEnableOption '' the on-die NPU (AI accelerator) kernel driver — amdxdna on AMD (Ryzen AI), intel_vpu on Intel (Core Ultra and newer). Opt-in and experimental: this loads the in-kernel driver only; the userspace runtime (AMD XRT / oneAPI Level Zero NPU) is yours to add. Needs a recent kernel (see latestKernel)''; latestKernel = lib.mkEnableOption '' the latest mainline kernel (pkgs.linuxPackages_latest) instead of the distro default — for very new hardware whose drivers (a fresh NPU, the `xe` GPU driver, new-platform enablement) only landed recently. Off by default; the default kernel already carries amd-pstate and amdxdna (6.14+)''; camera = { hideIrSensor = lib.mkEnableOption '' hiding a dual-sensor webcam's IR (face-unlock) node from PipeWire so apps only ever see the colour camera. Such modules (common on recent ThinkPads) expose the IR sensor as a SECOND, identically-named "Integrated Camera"; selecting it gives a dark, 8-bit-greyscale image — the classic "my webcam is dark" symptom. The installer turns this on when it detects a paired RGB+IR webcam. Only the PipeWire node is disabled — the kernel /dev/video* device stays open, so face-unlock (Howdy) still works. Acts on the V4L2 path only; the libcamera monitor is left untouched, so an external camera you plug in is never affected. See irMatch''; irMatch = lib.mkOption { type = lib.types.str; default = "~.*(Integrated I|IR Camera|Infrared).*"; description = '' WirePlumber regex (matched against a V4L2 node's api.v4l2.cap.card) selecting the IR sensor to hide. The default catches the common dual-sensor naming ("… Integrated I", "IR Camera", "Infrared"); set it to your camera's IR card name if it differs — find it with `v4l2-ctl --list-devices` or `wpctl inspect`. Only consulted when camera.hideIrSensor is on. ''; }; }; i2c = { enable = lib.mkEnableOption '' I2C devices support. Enables access to /dev/i2c-* (useful for RGB controllers, sensors, and DDC/CI monitor control)''; ddcci = lib.mkEnableOption '' the ddcci-driver kernel module to expose external monitors as standard backlight devices via DDC/CI. This allows brightness keys and swayosd to natively control external displays''; }; }; config = lib.mkMerge [ # ── Intel ────────────────────────────────────────────────────────── (lib.mkIf cfg.intel.guc { boot.kernelParams = [ "i915.enable_guc=3" ]; }) (lib.mkIf cfg.intel.computeRuntime { hardware.graphics.extraPackages = with pkgs; [ intel-compute-runtime vpl-gpu-rt ]; }) # ── AMD ──────────────────────────────────────────────────────────── (lib.mkIf cfg.amd.pstate { boot.kernelParams = [ "amd_pstate=active" ]; }) (lib.mkIf cfg.amd.vaapi { # radeonsi itself comes from mesa (via common-gpu-amd); this just steers # libva at it. mkDefault so a hand-set value or another module wins. environment.sessionVariables.LIBVA_DRIVER_NAME = lib.mkDefault "radeonsi"; hardware.graphics.extraPackages = [ pkgs.libva ]; }) (lib.mkIf cfg.amd.rocm.enable { hardware.graphics.extraPackages = [ pkgs.rocmPackages.clr pkgs.rocmPackages.clr.icd ]; environment.sessionVariables = lib.optionalAttrs (cfg.amd.rocm.gfxOverride != "") { HSA_OVERRIDE_GFX_VERSION = cfg.amd.rocm.gfxOverride; }; }) # ── Fingerprint ──────────────────────────────────────────────────── # NixOS defaults security.pam.services.*.fprintAuth to # services.fprintd.enable — so turning on fprintd alone would enable # finger auth for login/sudo/greetd/passwd/… even when the user (or # the migration template) left fingerprint.pam commented off. Force # every interactive service we care about to follow our opt-in flag. (lib.mkIf cfg.fingerprint.enable { services.fprintd.enable = true; security.pam.services = lib.genAttrs [ "login" "sudo" "su" "greetd" "hyprlock" "sshd" "passwd" "chsh" "chfn" "chpasswd" "polkit-1" "swaylock" "groupadd" "groupdel" "groupmod" "groupmems" ] (_: { fprintAuth = cfg.fingerprint.pam; }); }) # ── Newest kernel for very-new hardware (opt-in escape hatch) ────── (lib.mkIf cfg.latestKernel { boot.kernelPackages = lib.mkDefault pkgs.linuxPackages_latest; }) # ── NPU (in-kernel driver only; userspace runtime is BYO) ────────── # The driver has to actually be in the running kernel — warn (don't fail) # when it predates the shipped one, pointing at latestKernel. (lib.mkIf (cfg.npu.enable && cfg.amd.enable) { boot.kernelModules = [ "amdxdna" ]; warnings = lib.optional (!lib.versionAtLeast config.boot.kernelPackages.kernel.version "6.14") '' nomarchy.hardware.npu: the amdxdna driver needs kernel >= 6.14, but this config ships ${config.boot.kernelPackages.kernel.version}. Set nomarchy.hardware.latestKernel = true.''; }) (lib.mkIf (cfg.npu.enable && cfg.intel.enable) { boot.kernelModules = [ "intel_vpu" ]; warnings = lib.optional (!lib.versionAtLeast config.boot.kernelPackages.kernel.version "6.11") '' nomarchy.hardware.npu: the intel_vpu driver (especially for newer NPUs) wants a recent kernel, but this config ships ${config.boot.kernelPackages.kernel.version}. Consider nomarchy.hardware.latestKernel = true.''; }) # ── Webcam: hide a dual-sensor module's IR node ──────────────────── # A built-in RGB+IR webcam exposes its IR (face-unlock) sensor as a second, # identically-named camera; an app that picks it gets a dark greyscale # image. Disable that node on the V4L2 PipeWire path so only the colour # camera is offered. Matched by card name (irMatch). libcamera is left # alone on purpose — an external camera may rely on it, and surgical # internal-only libcamera scoping isn't possible (the distinguishing # device props bind after the monitor rule runs). The kernel /dev/video* # stays open, so Howdy face-unlock still reads the IR sensor directly. (lib.mkIf (cfg.camera.hideIrSensor && config.services.pipewire.wireplumber.enable) { services.pipewire.wireplumber.extraConfig."90-nomarchy-hide-ir-camera" = { "monitor.v4l2.rules" = [ { matches = [ { "api.v4l2.cap.card" = cfg.camera.irMatch; } ]; actions."update-props"."node.disabled" = true; } ]; }; }) # ── I2C / DDC/CI ─────────────────────────────────────────────────── (lib.mkIf cfg.i2c.enable { hardware.i2c.enable = true; }) (lib.mkIf cfg.i2c.ddcci { # The driver needs I2C underneath it hardware.i2c.enable = true; boot.extraModulePackages = [ config.boot.kernelPackages.ddcci-driver ]; boot.kernelModules = [ "ddcci_backlight" ]; }) # ── Sanity ───────────────────────────────────────────────────────── { assertions = [ { assertion = cfg.amd.rocm.enable -> cfg.amd.enable; message = "nomarchy.hardware.amd.rocm.enable needs nomarchy.hardware.amd.enable."; } { assertion = cfg.intel.computeRuntime -> cfg.intel.enable; message = "nomarchy.hardware.intel.computeRuntime needs nomarchy.hardware.intel.enable."; } { assertion = cfg.npu.enable -> (cfg.amd.enable || cfg.intel.enable); message = "nomarchy.hardware.npu.enable needs a detected Intel or AMD platform."; } ]; } ]; }