Something went wrong. Try again.
A Nordic stronghold for declarative computing.
flakes nix nixos nix-darwin
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312# A drop-in NixOS module that reads the nix-facter hardware report and# automatically configures the correct NVIDIA driver.## Supported driver branches (newest → oldest):# stable Pascal GP1xx / Turing TU1xx / Ampere GA1xx /# Ada Lovelace AD1xx / Blackwell GB1xx / future# legacy_470 Maxwell GM2xx (GeForce GTX 9xx)# legacy_390 Fermi GF1xx + Kepler GK1xx/GK110 (GTX 4xx–7xx)# legacy_340 Tesla G8x/G9x/GT21x (GeForce 8/9/GT2xx){ config, lib, ...}:let cfg = config.hardware.facter.detected.graphics.nvidia;
# --------------------------------------------------------------------------- # Hex parser (pure Nix, no builtins.fromTOML / lib.toHexInt required) # --------------------------------------------------------------------------- hexCharVal = c: if c == "0" then 0 else if c == "1" then 1 else if c == "2" then 2 else if c == "3" then 3 else if c == "4" then 4 else if c == "5" then 5 else if c == "6" then 6 else if c == "7" then 7 else if c == "8" then 8 else if c == "9" then 9 else if c == "a" then 10 else if c == "b" then 11 else if c == "c" then 12 else if c == "d" then 13 else if c == "e" then 14 else if c == "f" then 15 else 0;
fromHex = s: let chars = lib.stringToCharacters (lib.toLower (lib.removePrefix "0x" s)); in builtins.foldl' (acc: c: acc * 16 + hexCharVal c) 0 chars;
# --------------------------------------------------------------------------- # PCI device extraction from the facter report # --------------------------------------------------------------------------- # facter.json structure: # { "hardware": { "pci": [ { "vendor": "0x10de", # "device": "0x2204", # "class": "0x030000" } ] } } # # nixos-facter-modules exposes the parsed JSON as: # config.hardware.facter.report (attrset)
facterReport = config.hardware.facter.report;
pciDevices = facterReport.hardware.pci or [ ];
# NVIDIA vendor = 0x10de # Display classes: 0x030000 VGA, 0x030200 3D controller, etc. isNvidiaDisplay = device: lib.toLower (device.vendor or "") == "0x10de" && lib.hasPrefix "0x03" (lib.toLower (device.class or ""));
nvidiaGpus = builtins.filter isNvidiaDisplay pciDevices;
# --------------------------------------------------------------------------- # Driver-family tables # --------------------------------------------------------------------------- # Format: list of { min : int; max : int } ranges (device-ID in decimal). # A device ID falling in any range of a table → that family. # Nothing matches → "stable" (latest, covers all current & future GPUs). # # Reference: https://www.nvidia.com/en-us/drivers/unix/legacy-gpu/ # Device-ID ranges are derived from the per-branch README/supportedchips.html
inRanges = idInt: ranges: builtins.any (r: idInt >= r.min && idInt <= r.max) ranges;
h = fromHex; # shorthand
# ── legacy_340 (G8x · G9x · GT21x) ───────────────────────────────────── ranges340 = [ { min = h "0190"; max = h "019F"; } # GeForce 8800 GTS (G80) { min = h "01D0"; max = h "01DF"; } # GeForce 7900 GS / 7950 GT { min = h "0390"; max = h "039F"; } # GeForce 7900 GX2 { min = h "0400"; max = h "042F"; } # GeForce 8xxx G92/G94 { min = h "0600"; max = h "064F"; } # GeForce 9xxx G94/G96 { min = h "0750"; max = h "077F"; } # GeForce 9xxx G96/G98 { min = h "0840"; max = h "087F"; } # GeForce 9xxx M / ION { min = h "08A0"; max = h "08AF"; } # GeForce 9xxx M { min = h "0A20"; max = h "0A6F"; } # GeForce GT 2xx (GT215–GT218) { min = h "0CA0"; max = h "0CAF"; } # GeForce GT 215 ];
# ── legacy_390 (Fermi GF1xx · Kepler GK104/GK106/GK107/GK110) ────────── ranges390 = [ { min = h "0DC0"; max = h "0DFF"; } # Fermi GF106/GF104 { min = h "0E20"; max = h "0E3F"; } # Fermi GF104 mobile { min = h "0E80"; max = h "0EBF"; } # Fermi GF108/GF104 mobile { min = h "1040"; max = h "107F"; } # Kepler GK104 { min = h "1080"; max = h "10BF"; } # Kepler GK106 / GK107 { min = h "1140"; max = h "117F"; } # Kepler GK107M { min = h "1180"; max = h "11BF"; } # Kepler GK110 / GK110B { min = h "11C0"; max = h "11FF"; } # Kepler GK106 desktop { min = h "1200"; max = h "121F"; } # Kepler GK104 (GTX 680) { min = h "0FC0"; max = h "0FFF"; } # Kepler GK107 (GT 640/650) ];
# ── legacy_470 (Maxwell GM2xx · late Kepler GK208) ────────────────────── ranges470 = [ { min = h "1280"; max = h "12BF"; } # Kepler GK208 (GT 720/730/740) { min = h "13B0"; max = h "13FF"; } # Maxwell GM204 (GTX 970/980) { min = h "1401"; max = h "143F"; } # Maxwell GM206 (GTX 960/950) { min = h "1617"; max = h "163F"; } # Maxwell GM204/GM206 mobile { min = h "1710"; max = h "171F"; } # Maxwell GM204 mobile high-end { min = h "17C0"; max = h "17FF"; } # Maxwell GM200 (Titan X / GTX 980 Ti) { min = h "1B00"; max = h "1B0F"; } # Maxwell GM200 NVS/Quadro ];
# Pascal (0x1B80+) and all future arches → "stable" (no range needed).
# --------------------------------------------------------------------------- # Map a single PCI device-ID string to a driver family # --------------------------------------------------------------------------- familyForId = idStr: let idInt = fromHex idStr; in if inRanges idInt ranges340 then "legacy_340" else if inRanges idInt ranges390 then "legacy_390" else if inRanges idInt ranges470 then "legacy_470" else "stable"; # Pascal, Turing, Ampere, Ada, Blackwell, future
# --------------------------------------------------------------------------- # Determine the single best driver for the machine # --------------------------------------------------------------------------- # When multiple GPUs are present (e.g., dGPU + iGPU bypass, or mixed legacy # setup), we choose the *newest* driver family — i.e., the one closest to # "stable" — because: # 1. The stable driver cannot drive a 340-class card, but a 340-class card # typically lives on a machine that also has a modern dGPU only in rare # cases (mixed system). Users with true mixed legacy systems should # override `hardware.nvidia.package` manually. # 2. Unknown / future device IDs always resolve to "stable", which is safe. familyOrder = [ "stable" "legacy_470" "legacy_390" "legacy_340" ];
gpuFamilies = map (dev: familyForId (dev.device or "0x0000")) nvidiaGpus;
bestFamily = let present = lib.unique gpuFamilies; winner = lib.findFirst (f: builtins.elem f present) null familyOrder; in if winner == null then "stable" else winner;
hasNvidiaGpu = nvidiaGpus != [ ];
selectedPackage = config.boot.kernelPackages.nvidiaPackages.${bestFamily} or config.boot.kernelPackages.nvidiaPackages.stable;in{ # --------------------------------------------------------------------------- # Module options # --------------------------------------------------------------------------- options.hardware.facter.detected.graphics.nvidia = { enable = lib.mkOption { type = lib.types.bool; default = hasNvidiaGpu; description = '' Enable the automatic configuration of the NVIDIA driver based on the nix-facter hardware report. Set to false to manage hardware.nvidia manually. ''; }; };
# --------------------------------------------------------------------------- # Module configuration # --------------------------------------------------------------------------- config = lib.mkIf (config.hardware.facter.enable && cfg.enable && hasNvidiaGpu) { services.xserver.videoDrivers = [ "nvidia" ];
hardware.graphics.enable = lib.mkDefault true;
hardware.nvidia = { # The auto-selected package; users can still override with mkForce. package = lib.mkDefault selectedPackage;
# Open kernel modules only work on Turing and newer (driver ≥ 560). # For legacy families they are unavailable, so we hard-disable them. open = lib.mkDefault (bestFamily == "stable");
modesetting.enable = lib.mkDefault true; nvidiaSettings = lib.mkDefault true; }; };}