nixos/pid-fan-controller: init

authored by zimward and committed by zimward 16ca4e3f eb333540

+190 -1
+1
nixos/modules/module-list.nix
··· 601 601 ./services/hardware/nvidia-optimus.nix 602 602 ./services/hardware/openrgb.nix 603 603 ./services/hardware/pcscd.nix 604 + ./services/hardware/pid-fan-controller.nix 604 605 ./services/hardware/pommed.nix 605 606 ./services/hardware/power-profiles-daemon.nix 606 607 ./services/hardware/rasdaemon.nix
+188
nixos/modules/services/hardware/pid-fan-controller.nix
··· 1 + { 2 + lib, 3 + config, 4 + pkgs, 5 + ... 6 + }: 7 + let 8 + cfg = config.services.pid-fan-controller; 9 + heatSource = { 10 + options = { 11 + name = lib.mkOption { 12 + type = lib.types.uniq lib.types.nonEmptyStr; 13 + description = "Name of the heat source."; 14 + }; 15 + wildcardPath = lib.mkOption { 16 + type = lib.types.nonEmptyStr; 17 + description = '' 18 + Path of the heat source's `hwmon` `temp_input` file. 19 + This path can contain multiple wildcards, but has to resolve to 20 + exactly one result. 21 + ''; 22 + }; 23 + pidParams = { 24 + setPoint = lib.mkOption { 25 + type = lib.types.ints.unsigned; 26 + description = "Set point of the controller in °C."; 27 + }; 28 + P = lib.mkOption { 29 + description = "K_p of PID controller."; 30 + type = lib.types.float; 31 + }; 32 + I = lib.mkOption { 33 + description = "K_i of PID controller."; 34 + type = lib.types.float; 35 + }; 36 + D = lib.mkOption { 37 + description = "K_d of PID controller."; 38 + type = lib.types.float; 39 + }; 40 + }; 41 + }; 42 + }; 43 + 44 + fan = { 45 + options = { 46 + wildcardPath = lib.mkOption { 47 + type = lib.types.str; 48 + description = '' 49 + Wildcard path of the `hwmon` `pwm` file. 50 + If the fans are not to be found in `/sys/class/hwmon/hwmon*` the corresponding 51 + kernel module (like `nct6775`) needs to be added to `boot.kernelModules`. 52 + See the [`hwmon` Documentation](https://www.kernel.org/doc/html/latest/hwmon/index.html). 53 + ''; 54 + }; 55 + minPwm = lib.mkOption { 56 + default = 0; 57 + type = lib.types.ints.u8; 58 + description = "Minimum PWM value."; 59 + }; 60 + maxPwm = lib.mkOption { 61 + default = 255; 62 + type = lib.types.ints.u8; 63 + description = "Maximum PWM value."; 64 + }; 65 + cutoff = lib.mkOption { 66 + default = false; 67 + type = lib.types.bool; 68 + description = "Whether to stop the fan when `minPwm` is reached."; 69 + }; 70 + heatPressureSrcs = lib.mkOption { 71 + type = lib.types.nonEmptyListOf lib.types.str; 72 + description = "Heat pressure sources affected by the fan."; 73 + }; 74 + }; 75 + }; 76 + in 77 + { 78 + options.services.pid-fan-controller = { 79 + enable = lib.mkEnableOption "the PID fan controller, which controls the configured fans by running a closed-loop PID control loop"; 80 + package = lib.mkPackageOption pkgs "pid-fan-controller" { }; 81 + settings = { 82 + interval = lib.mkOption { 83 + default = 500; 84 + type = lib.types.int; 85 + description = "Interval between controller cycles in milliseconds."; 86 + }; 87 + heatSources = lib.mkOption { 88 + type = lib.types.listOf (lib.types.submodule heatSource); 89 + description = "List of heat sources to be monitored."; 90 + example = '' 91 + [ 92 + { 93 + name = "cpu"; 94 + wildcardPath = "/sys/devices/pci0000:00/0000:00:18.3/hwmon/hwmon*/temp1_input"; 95 + pidParams = { 96 + setPoint = 60; 97 + P = -5.0e-3; 98 + I = -2.0e-3; 99 + D = -6.0e-3; 100 + }; 101 + } 102 + ]; 103 + ''; 104 + }; 105 + fans = lib.mkOption { 106 + type = lib.types.listOf (lib.types.submodule fan); 107 + description = "List of fans to be controlled."; 108 + example = '' 109 + [ 110 + { 111 + wildcardPath = "/sys/devices/platform/nct6775.2592/hwmon/hwmon*/pwm1"; 112 + minPwm = 60; 113 + maxPwm = 255; 114 + heatPressureSrcs = [ 115 + "cpu" 116 + "gpu" 117 + ]; 118 + } 119 + ]; 120 + ''; 121 + }; 122 + }; 123 + }; 124 + config = lib.mkIf cfg.enable { 125 + #map camel cased attrs into snake case for config 126 + environment.etc."pid-fan-settings.json".text = builtins.toJSON { 127 + interval = cfg.settings.interval; 128 + heat_srcs = map (heatSrc: { 129 + name = heatSrc.name; 130 + wildcard_path = heatSrc.wildcardPath; 131 + PID_params = { 132 + set_point = heatSrc.pidParams.setPoint; 133 + P = heatSrc.pidParams.P; 134 + I = heatSrc.pidParams.I; 135 + D = heatSrc.pidParams.D; 136 + }; 137 + }) cfg.settings.heatSources; 138 + fans = map (fan: { 139 + wildcard_path = fan.wildcardPath; 140 + min_pwm = fan.minPwm; 141 + max_pwm = fan.maxPwm; 142 + cutoff = fan.cutoff; 143 + heat_pressure_srcs = fan.heatPressureSrcs; 144 + }) cfg.settings.fans; 145 + }; 146 + 147 + systemd.services.pid-fan-controller = { 148 + wantedBy = [ "multi-user.target" ]; 149 + serviceConfig = { 150 + Type = "simple"; 151 + ExecStart = [ (lib.getExe cfg.package) ]; 152 + ExecStopPost = [ "${lib.getExe cfg.package} disable" ]; 153 + Restart = "always"; 154 + #This service needs to run as root to write to /sys. 155 + #therefore it should operate with the least amount of privileges needed 156 + ProtectHome = "yes"; 157 + #strict is not possible as it needs /sys 158 + ProtectSystem = "full"; 159 + ProtectProc = "invisible"; 160 + PrivateNetwork = "yes"; 161 + NoNewPrivileges = "yes"; 162 + MemoryDenyWriteExecute = "yes"; 163 + RestrictNamespaces = "~user pid net uts mnt"; 164 + ProtectKernelModules = "yes"; 165 + RestrictRealtime = "yes"; 166 + SystemCallFilter = "@system-service"; 167 + CapabilityBoundingSet = "~CAP_KILL CAP_WAKE_ALARM CAP_IPC_LOC CAP_BPF CAP_LINUX_IMMUTABLE CAP_BLOCK_SUSPEND CAP_MKNOD"; 168 + }; 169 + # restart unit if config changed 170 + restartTriggers = [ config.environment.etc."pid-fan-settings.json".source ]; 171 + }; 172 + #sleep hook to restart the service as it breaks otherwise 173 + systemd.services.pid-fan-controller-sleep = { 174 + before = [ "sleep.target" ]; 175 + wantedBy = [ "sleep.target" ]; 176 + unitConfig = { 177 + StopWhenUnneeded = "yes"; 178 + }; 179 + serviceConfig = { 180 + Type = "oneshot"; 181 + RemainAfterExit = true; 182 + ExecStart = [ "systemctl stop pid-fan-controller.service" ]; 183 + ExecStop = [ "systemctl restart pid-fan-controller.service" ]; 184 + }; 185 + }; 186 + }; 187 + meta.maintainers = with lib.maintainers; [ zimward ]; 188 + }
+1 -1
pkgs/by-name/pi/pid-fan-controller/package.nix
··· 16 16 rev = version; 17 17 hash = "sha256-ALR9Qa0AhcGyc3+7x5CEG/72+bJzhaEoIvQNL+QjldY="; 18 18 }; 19 - cargoHash = "sha256-u1Y1k1I5gRzpDHhRJZCCtMTwAvtCaIy3uXQTvmtEx5w="; 19 + cargoHash = "sha256-Y57VSheI94b43SwNCDdFvcNxzkA16KObBvzZ6ywYAyU="; 20 20 21 21 meta = { 22 22 description = "Service to provide closed-loop PID fan control";