diff options
Diffstat (limited to 'nixos/modules/services/hardware/undervolt.nix')
-rw-r--r-- | nixos/modules/services/hardware/undervolt.nix | 190 |
1 files changed, 190 insertions, 0 deletions
diff --git a/nixos/modules/services/hardware/undervolt.nix b/nixos/modules/services/hardware/undervolt.nix new file mode 100644 index 00000000000..a743bbf21c8 --- /dev/null +++ b/nixos/modules/services/hardware/undervolt.nix @@ -0,0 +1,190 @@ +{ config, pkgs, lib, ... }: + +with lib; +let + cfg = config.services.undervolt; + + mkPLimit = limit: window: + if (isNull limit && isNull window) then null + else assert asserts.assertMsg (!isNull limit && !isNull window) "Both power limit and window must be set"; + "${toString limit} ${toString window}"; + cliArgs = lib.cli.toGNUCommandLine {} { + inherit (cfg) + verbose + temp + ; + # `core` and `cache` are both intentionally set to `cfg.coreOffset` as according to the undervolt docs: + # + # Core or Cache offsets have no effect. It is not possible to set different offsets for + # CPU Core and Cache. The CPU will take the smaller of the two offsets, and apply that to + # both CPU and Cache. A warning message will be displayed if you attempt to set different offsets. + core = cfg.coreOffset; + cache = cfg.coreOffset; + gpu = cfg.gpuOffset; + uncore = cfg.uncoreOffset; + analogio = cfg.analogioOffset; + + temp-bat = cfg.tempBat; + temp-ac = cfg.tempAc; + + power-limit-long = mkPLimit cfg.p1.limit cfg.p1.window; + power-limit-short = mkPLimit cfg.p2.limit cfg.p2.window; + }; +in +{ + options.services.undervolt = { + enable = mkEnableOption '' + Undervolting service for Intel CPUs. + + Warning: This service is not endorsed by Intel and may permanently damage your hardware. Use at your own risk! + ''; + + verbose = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable verbose logging. + ''; + }; + + package = mkOption { + type = types.package; + default = pkgs.undervolt; + defaultText = literalExpression "pkgs.undervolt"; + description = '' + undervolt derivation to use. + ''; + }; + + coreOffset = mkOption { + type = types.nullOr types.int; + default = null; + description = '' + The amount of voltage in mV to offset the CPU cores by. + ''; + }; + + gpuOffset = mkOption { + type = types.nullOr types.int; + default = null; + description = '' + The amount of voltage in mV to offset the GPU by. + ''; + }; + + uncoreOffset = mkOption { + type = types.nullOr types.int; + default = null; + description = '' + The amount of voltage in mV to offset uncore by. + ''; + }; + + analogioOffset = mkOption { + type = types.nullOr types.int; + default = null; + description = '' + The amount of voltage in mV to offset analogio by. + ''; + }; + + temp = mkOption { + type = types.nullOr types.int; + default = null; + description = '' + The temperature target in Celsius degrees. + ''; + }; + + tempAc = mkOption { + type = types.nullOr types.int; + default = null; + description = '' + The temperature target on AC power in Celsius degrees. + ''; + }; + + tempBat = mkOption { + type = types.nullOr types.int; + default = null; + description = '' + The temperature target on battery power in Celsius degrees. + ''; + }; + + p1.limit = mkOption { + type = with types; nullOr int; + default = null; + description = '' + The P1 Power Limit in Watts. + Both limit and window must be set. + ''; + }; + p1.window = mkOption { + type = with types; nullOr (oneOf [ float int ]); + default = null; + description = '' + The P1 Time Window in seconds. + Both limit and window must be set. + ''; + }; + + p2.limit = mkOption { + type = with types; nullOr int; + default = null; + description = '' + The P2 Power Limit in Watts. + Both limit and window must be set. + ''; + }; + p2.window = mkOption { + type = with types; nullOr (oneOf [ float int ]); + default = null; + description = '' + The P2 Time Window in seconds. + Both limit and window must be set. + ''; + }; + + useTimer = mkOption { + type = types.bool; + default = false; + description = '' + Whether to set a timer that applies the undervolt settings every 30s. + This will cause spam in the journal but might be required for some + hardware under specific conditions. + Enable this if your undervolt settings don't hold. + ''; + }; + }; + + config = mkIf cfg.enable { + boot.kernelModules = [ "msr" ]; + + environment.systemPackages = [ cfg.package ]; + + systemd.services.undervolt = { + description = "Intel Undervolting Service"; + + # Apply undervolt on boot, nixos generation switch and resume + wantedBy = [ "multi-user.target" "post-resume.target" ]; + after = [ "post-resume.target" ]; # Not sure why but it won't work without this + + serviceConfig = { + Type = "oneshot"; + Restart = "no"; + ExecStart = "${cfg.package}/bin/undervolt ${toString cliArgs}"; + }; + }; + + systemd.timers.undervolt = mkIf cfg.useTimer { + description = "Undervolt timer to ensure voltage settings are always applied"; + partOf = [ "undervolt.service" ]; + wantedBy = [ "multi-user.target" ]; + timerConfig = { + OnBootSec = "2min"; + OnUnitActiveSec = "30"; + }; + }; + }; +} |