1 { config, pkgs, lib, ... }:
5 cfg = config.services.undervolt;
7 mkPLimit = limit: window:
8 if (limit == null && window == null) then null
9 else assert asserts.assertMsg (limit != null && window != null) "Both power limit and window must be set";
10 "${toString limit} ${toString window}";
11 cliArgs = lib.cli.toGNUCommandLine {} {
16 # `core` and `cache` are both intentionally set to `cfg.coreOffset` as according to the undervolt docs:
18 # Core or Cache offsets have no effect. It is not possible to set different offsets for
19 # CPU Core and Cache. The CPU will take the smaller of the two offsets, and apply that to
20 # both CPU and Cache. A warning message will be displayed if you attempt to set different offsets.
21 core = cfg.coreOffset;
22 cache = cfg.coreOffset;
24 uncore = cfg.uncoreOffset;
25 analogio = cfg.analogioOffset;
27 temp-bat = cfg.tempBat;
30 power-limit-long = mkPLimit cfg.p1.limit cfg.p1.window;
31 power-limit-short = mkPLimit cfg.p2.limit cfg.p2.window;
35 options.services.undervolt = {
36 enable = mkEnableOption (lib.mdDoc ''
37 Undervolting service for Intel CPUs.
39 Warning: This service is not endorsed by Intel and may permanently damage your hardware. Use at your own risk!
45 description = lib.mdDoc ''
46 Whether to enable verbose logging.
52 default = pkgs.undervolt;
53 defaultText = literalExpression "pkgs.undervolt";
54 description = lib.mdDoc ''
55 undervolt derivation to use.
59 coreOffset = mkOption {
60 type = types.nullOr types.int;
62 description = lib.mdDoc ''
63 The amount of voltage in mV to offset the CPU cores by.
67 gpuOffset = mkOption {
68 type = types.nullOr types.int;
70 description = lib.mdDoc ''
71 The amount of voltage in mV to offset the GPU by.
75 uncoreOffset = mkOption {
76 type = types.nullOr types.int;
78 description = lib.mdDoc ''
79 The amount of voltage in mV to offset uncore by.
83 analogioOffset = mkOption {
84 type = types.nullOr types.int;
86 description = lib.mdDoc ''
87 The amount of voltage in mV to offset analogio by.
92 type = types.nullOr types.int;
94 description = lib.mdDoc ''
95 The temperature target in Celsius degrees.
100 type = types.nullOr types.int;
102 description = lib.mdDoc ''
103 The temperature target on AC power in Celsius degrees.
108 type = types.nullOr types.int;
110 description = lib.mdDoc ''
111 The temperature target on battery power in Celsius degrees.
115 p1.limit = mkOption {
116 type = with types; nullOr int;
118 description = lib.mdDoc ''
119 The P1 Power Limit in Watts.
120 Both limit and window must be set.
123 p1.window = mkOption {
124 type = with types; nullOr (oneOf [ float int ]);
126 description = lib.mdDoc ''
127 The P1 Time Window in seconds.
128 Both limit and window must be set.
132 p2.limit = mkOption {
133 type = with types; nullOr int;
135 description = lib.mdDoc ''
136 The P2 Power Limit in Watts.
137 Both limit and window must be set.
140 p2.window = mkOption {
141 type = with types; nullOr (oneOf [ float int ]);
143 description = lib.mdDoc ''
144 The P2 Time Window in seconds.
145 Both limit and window must be set.
149 useTimer = mkOption {
152 description = lib.mdDoc ''
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 = 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 = 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";