1 { config, pkgs, lib, ... }:
3 cfg = config.services.undervolt;
5 mkPLimit = limit: window:
6 if (limit == null && window == null) then null
7 else assert lib.asserts.assertMsg (limit != null && window != null) "Both power limit and window must be set";
8 "${toString limit} ${toString window}";
9 cliArgs = lib.cli.toGNUCommandLine {} {
15 # `core` and `cache` are both intentionally set to `cfg.coreOffset` as according to the undervolt docs:
17 # Core or Cache offsets have no effect. It is not possible to set different offsets for
18 # CPU Core and Cache. The CPU will take the smaller of the two offsets, and apply that to
19 # both CPU and Cache. A warning message will be displayed if you attempt to set different offsets.
20 core = cfg.coreOffset;
21 cache = cfg.coreOffset;
23 uncore = cfg.uncoreOffset;
24 analogio = cfg.analogioOffset;
26 temp-bat = cfg.tempBat;
29 power-limit-long = mkPLimit cfg.p1.limit cfg.p1.window;
30 power-limit-short = mkPLimit cfg.p2.limit cfg.p2.window;
34 options.services.undervolt = {
35 enable = lib.mkEnableOption ''
36 Undervolting service for Intel CPUs.
38 Warning: This service is not endorsed by Intel and may permanently damage your hardware. Use at your own risk
41 verbose = lib.mkOption {
42 type = lib.types.bool;
45 Whether to enable verbose logging.
49 package = lib.mkPackageOption pkgs "undervolt" { };
51 coreOffset = lib.mkOption {
52 type = lib.types.nullOr lib.types.int;
55 The amount of voltage in mV to offset the CPU cores by.
59 gpuOffset = lib.mkOption {
60 type = lib.types.nullOr lib.types.int;
63 The amount of voltage in mV to offset the GPU by.
67 uncoreOffset = lib.mkOption {
68 type = lib.types.nullOr lib.types.int;
71 The amount of voltage in mV to offset uncore by.
75 analogioOffset = lib.mkOption {
76 type = lib.types.nullOr lib.types.int;
79 The amount of voltage in mV to offset analogio by.
84 type = lib.types.nullOr lib.types.int;
87 The temperature target in Celsius degrees.
91 tempAc = lib.mkOption {
92 type = lib.types.nullOr lib.types.int;
95 The temperature target on AC power in Celsius degrees.
99 tempBat = lib.mkOption {
100 type = lib.types.nullOr lib.types.int;
103 The temperature target on battery power in Celsius degrees.
107 turbo = lib.mkOption {
108 type = lib.types.nullOr lib.types.int;
111 Changes the Intel Turbo feature status (1 is disabled and 0 is enabled).
115 p1.limit = lib.mkOption {
116 type = with lib.types; nullOr int;
119 The P1 Power Limit in Watts.
120 Both limit and window must be set.
123 p1.window = lib.mkOption {
124 type = with lib.types; nullOr (oneOf [ float int ]);
127 The P1 Time Window in seconds.
128 Both limit and window must be set.
132 p2.limit = lib.mkOption {
133 type = with lib.types; nullOr int;
136 The P2 Power Limit in Watts.
137 Both limit and window must be set.
140 p2.window = lib.mkOption {
141 type = with lib.types; nullOr (oneOf [ float int ]);
144 The P2 Time Window in seconds.
145 Both limit and window must be set.
149 useTimer = lib.mkOption {
150 type = lib.types.bool;
153 Whether to set a timer that applies the undervolt settings every 30s.
154 This will cause spam in the journal but might be required for some
155 hardware under specific conditions.
156 Enable this if your undervolt settings don't hold.
161 config = lib.mkIf cfg.enable {
162 hardware.cpu.x86.msr.enable = true;
164 environment.systemPackages = [ cfg.package ];
166 systemd.services.undervolt = {
167 description = "Intel Undervolting Service";
169 # Apply undervolt on boot, nixos generation switch and resume
170 wantedBy = [ "multi-user.target" "post-resume.target" ];
171 after = [ "post-resume.target" ]; # Not sure why but it won't work without this
176 ExecStart = "${cfg.package}/bin/undervolt ${toString cliArgs}";
180 systemd.timers.undervolt = lib.mkIf cfg.useTimer {
181 description = "Undervolt timer to ensure voltage settings are always applied";
182 partOf = [ "undervolt.service" ];
183 wantedBy = [ "multi-user.target" ];
186 OnUnitActiveSec = "30";