Files
Christopher Baines dd12ee2847 gnu: services: pm: Add hd-idle.
* gnu/services/pm.scm: (hd-idle-disk, hd-idle-disk?, hd-idle-configuration, hd-idle-configuration?):
New prodedures.
(hd-idle-service-type): New variable.
* doc/guix.texi (Power Management Services): Document it.

Change-Id: I205b9a8de9aa8a15314d03962582aa3ae245a31e
2026-08-21 16:42:58 +01:00

723 lines
23 KiB
Scheme

;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2017 Mathieu Othacehe <m.othacehe@gmail.com>
;;; Copyright © 2024 Dariqq <dariqq@posteo.net>
;;; Copyright © 2024 Ian Eure <ian@retrospec.tv>
;;; Copyright © 2025 Nigko Yerden <nigko.yerden@gmail.com>
;;;
;;; This file is part of GNU Guix.
;;;
;;; GNU Guix is free software; you can redistribute it and/or modify
;;; it under the terms of the GNU General Public License as published by
;;; the Free Software Foundation, either version 3 of the License, or
;;; (at your option) any later version.
;;;
;;; GNU Guix is distributed in the hope that it will be useful,
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
(define-module (gnu services pm)
#:use-module (srfi srfi-1)
#:use-module (ice-9 match)
#:use-module (guix gexp)
#:use-module (guix packages)
#:use-module (guix records)
#:use-module (gnu packages admin)
#:use-module (gnu packages freedesktop)
#:use-module (gnu packages hardware)
#:use-module (gnu packages linux)
#:use-module (gnu services)
#:use-module (gnu services base)
#:use-module (gnu services configuration)
#:use-module (gnu services dbus)
#:use-module (gnu services shepherd)
#:use-module (gnu system shadow)
#:export (power-profiles-daemon-service-type
power-profiles-daemon-configuration
tlp-service-type
tlp-configuration
thermald-configuration
thermald-service-type
powertop-configuration
powertop-service-type
hd-idle-disk
hd-idle-disk?
hd-idle-configuration
hd-idle-configuration?
hd-idle-service-type))
;;;
;;; power-profiles-daemon
;;;
(define-configuration/no-serialization power-profiles-daemon-configuration
(power-profiles-daemon
(file-like power-profiles-daemon)
"The power-profiles-daemon package."))
(define (power-profiles-daemon-shepherd-service config)
(match-record
config <power-profiles-daemon-configuration>
(power-profiles-daemon)
(list
(shepherd-service
(provision '(power-profiles-daemon))
(requirement '(user-processes dbus-system))
(documentation "Run the Power Profiles Daemon.")
(start #~(make-forkexec-constructor
(list #$(file-append power-profiles-daemon
"/libexec/power-profiles-daemon"))))
(stop #~(make-kill-destructor))))))
(define %power-profiles-daemon-activation
#~(begin
(use-modules (guix build utils))
(mkdir-p "/var/lib/power-profiles-daemon")))
(define power-profiles-daemon-service-type
(let ((config->package
(compose list power-profiles-daemon-configuration-power-profiles-daemon)))
(service-type
(name 'power-profiles-daemon)
(extensions (list
(service-extension shepherd-root-service-type
power-profiles-daemon-shepherd-service)
(service-extension dbus-root-service-type
config->package)
(service-extension polkit-service-type
config->package)
(service-extension profile-service-type
config->package)
(service-extension activation-service-type
(const %power-profiles-daemon-activation))))
(default-value (power-profiles-daemon-configuration))
(description "Run the Power Profiles Daemon"))))
;;;
;;; tlp
;;;
(define (uglify-field-name field-name)
(let ((str (symbol->string field-name)))
(string-join (string-split
(string-upcase
(if (string-suffix? "?" str)
(substring str 0 (1- (string-length str)))
str))
#\-)
"_")))
(define (serialize-field field-name val)
(format #t "~a=~a\n" (uglify-field-name field-name) val))
(define (serialize-boolean field-name val)
(serialize-field field-name (if val "1" "0")))
(define-maybe boolean)
(define (serialize-string field-name val)
(serialize-field field-name val))
(define-maybe string)
(define (space-separated-string-list? val)
(and (list? val)
(and-map (lambda (x)
(and (string? x) (not (string-index x #\space))))
val)))
(define (serialize-space-separated-string-list field-name val)
(serialize-field field-name
(format #f "~s"
(string-join val " "))))
(define-maybe space-separated-string-list)
(define (non-negative-integer? val)
(and (exact-integer? val) (not (negative? val))))
(define (serialize-non-negative-integer field-name val)
(serialize-field field-name val))
(define-maybe non-negative-integer)
(define (on-off-boolean? val)
(boolean? val))
(define (serialize-on-off-boolean field-name val)
(serialize-field field-name (if val "on" "off")))
(define-maybe on-off-boolean)
(define (y-n-boolean? val)
(boolean? val))
(define (serialize-y-n-boolean field-name val)
(serialize-field field-name (if val "Y" "N")))
(define-configuration tlp-configuration
(tlp
(file-like tlp)
"The TLP package.")
(tlp-enable?
(boolean #t)
"Set to true if you wish to enable TLP.")
(tlp-default-mode
(string "AC")
"Default mode when no power supply can be detected. Alternatives are
AC and BAT.")
(disk-idle-secs-on-ac
(non-negative-integer 0)
"Number of seconds Linux kernel has to wait after the disk goes idle,
before syncing on AC.")
(disk-idle-secs-on-bat
(non-negative-integer 2)
"Same as @code{disk-idle-ac} but on BAT mode.")
(max-lost-work-secs-on-ac
(non-negative-integer 15)
"Dirty pages flushing periodicity, expressed in seconds.")
(max-lost-work-secs-on-bat
(non-negative-integer 60)
"Same as @code{max-lost-work-secs-on-ac} but on BAT mode.")
(cpu-scaling-governor-on-ac
maybe-space-separated-string-list
"CPU frequency scaling governor on AC mode. With intel_pstate
driver, alternatives are powersave and performance. With acpi-cpufreq driver,
alternatives are ondemand, powersave, performance and conservative.")
(cpu-scaling-governor-on-bat
maybe-space-separated-string-list
"Same as @code{cpu-scaling-governor-on-ac} but on BAT mode.")
(cpu-scaling-min-freq-on-ac
maybe-non-negative-integer
"Set the min available frequency for the scaling governor on AC.")
(cpu-scaling-max-freq-on-ac
maybe-non-negative-integer
"Set the max available frequency for the scaling governor on AC.")
(cpu-scaling-min-freq-on-bat
maybe-non-negative-integer
"Set the min available frequency for the scaling governor on BAT.")
(cpu-scaling-max-freq-on-bat
maybe-non-negative-integer
"Set the max available frequency for the scaling governor on BAT.")
(cpu-min-perf-on-ac
maybe-non-negative-integer
"Limit the min P-state to control the power dissipation of the CPU,
in AC mode. Values are stated as a percentage of the available performance.")
(cpu-max-perf-on-ac
maybe-non-negative-integer
"Limit the max P-state to control the power dissipation of the CPU,
in AC mode. Values are stated as a percentage of the available performance.")
(cpu-min-perf-on-bat
maybe-non-negative-integer
"Same as @code{cpu-min-perf-on-ac} on BAT mode.")
(cpu-max-perf-on-bat
maybe-non-negative-integer
"Same as @code{cpu-max-perf-on-ac} on BAT mode.")
(cpu-boost-on-ac?
maybe-boolean
"Enable CPU turbo boost feature on AC mode.")
(cpu-boost-on-bat?
maybe-boolean
"Same as @code{cpu-boost-on-ac?} on BAT mode.")
(sched-powersave-on-ac?
(boolean #f)
"Allow Linux kernel to minimize the number of CPU cores/hyper-threads
used under light load conditions.")
(sched-powersave-on-bat?
(boolean #t)
"Same as @code{sched-powersave-on-ac?} but on BAT mode.")
(nmi-watchdog?
(boolean #f)
"Enable Linux kernel NMI watchdog.")
(phc-controls
maybe-string
"For Linux kernels with PHC patch applied, change CPU voltages.
An example value would be @samp{\"F:V F:V F:V F:V\"}.")
(energy-perf-policy-on-ac
(string "performance")
"Set CPU performance versus energy saving policy on AC. Alternatives are
performance, normal, powersave.")
(energy-perf-policy-on-bat
(string "powersave")
"Same as @code{energy-perf-policy-ac} but on BAT mode.")
(disks-devices
(space-separated-string-list '("sda"))
"Hard disk devices.")
(disk-apm-level-on-ac
(space-separated-string-list '("254" "254"))
"Hard disk advanced power management level.")
(disk-apm-level-on-bat
(space-separated-string-list '("128" "128"))
"Same as @code{disk-apm-bat} but on BAT mode.")
(disk-spindown-timeout-on-ac
maybe-space-separated-string-list
"Hard disk spin down timeout. One value has to be specified for
each declared hard disk.")
(disk-spindown-timeout-on-bat
maybe-space-separated-string-list
"Same as @code{disk-spindown-timeout-on-ac} but on BAT mode.")
(disk-iosched
maybe-space-separated-string-list
"Select IO scheduler for disk devices. One value has to be specified
for each declared hard disk. Example alternatives are cfq, deadline and noop.")
(sata-linkpwr-on-ac
(string "max_performance")
"SATA aggressive link power management (ALPM) level. Alternatives are
min_power, medium_power, max_performance.")
(sata-linkpwr-on-bat
(string "min_power")
"Same as @code{sata-linkpwr-ac} but on BAT mode.")
(sata-linkpwr-blacklist
maybe-string
"Exclude specified SATA host devices for link power management.")
(ahci-runtime-pm-on-ac?
maybe-on-off-boolean
"Enable Runtime Power Management for AHCI controller and disks
on AC mode.")
(ahci-runtime-pm-on-bat?
maybe-on-off-boolean
"Same as @code{ahci-runtime-pm-on-ac} on BAT mode.")
(ahci-runtime-pm-timeout
(non-negative-integer 15)
"Seconds of inactivity before disk is suspended.")
(pcie-aspm-on-ac
(string "performance")
"PCI Express Active State Power Management level. Alternatives are
default, performance, powersave.")
(pcie-aspm-on-bat
(string "powersave")
"Same as @code{pcie-aspm-ac} but on BAT mode.")
(start-charge-thresh-bat0
maybe-non-negative-integer
"Percentage when battery 0 should begin charging.")
(stop-charge-thresh-bat0
maybe-non-negative-integer
"Percentage when battery 0 should stop charging.")
(start-charge-thresh-bat1
maybe-non-negative-integer
"Percentage when battery 1 should begin charging.")
(stop-charge-thresh-bat1
maybe-non-negative-integer
"Percentage when battery 1 should stop charging.")
(radeon-power-profile-on-ac
(string "high")
"Radeon graphics clock speed level. Alternatives are
low, mid, high, auto, default.")
(radeon-power-profile-on-bat
(string "low")
"Same as @code{radeon-power-ac} but on BAT mode.")
(radeon-dpm-state-on-ac
(string "performance")
"Radeon dynamic power management method (DPM). Alternatives are
battery, performance.")
(radeon-dpm-state-on-bat
(string "battery")
"Same as @code{radeon-dpm-state-ac} but on BAT mode.")
(radeon-dpm-perf-level-on-ac
(string "auto")
"Radeon DPM performance level. Alternatives are
auto, low, high.")
(radeon-dpm-perf-level-on-bat
(string "auto")
"Same as @code{radeon-dpm-perf-ac} but on BAT mode.")
(wifi-pwr-on-ac?
(on-off-boolean #f)
"Wifi power saving mode.")
(wifi-pwr-on-bat?
(on-off-boolean #t)
"Same as @code{wifi-power-ac?} but on BAT mode.")
(wol-disable?
(y-n-boolean #t)
"Disable wake on LAN.")
(sound-power-save-on-ac
(non-negative-integer 0)
"Timeout duration in seconds before activating audio power saving
on Intel HDA and AC97 devices. A value of 0 disables power saving.")
(sound-power-save-on-bat
(non-negative-integer 1)
"Same as @code{sound-powersave-ac} but on BAT mode.")
(sound-power-save-controller?
(y-n-boolean #t)
"Disable controller in powersaving mode on Intel HDA devices.")
(bay-poweroff-on-bat?
(boolean #f)
"Enable optical drive in UltraBay/MediaBay on BAT mode.
Drive can be powered on again by releasing (and reinserting) the eject lever
or by pressing the disc eject button on newer models.")
(bay-device
(string "sr0")
"Name of the optical drive device to power off.")
(runtime-pm-on-ac
(string "on")
"Runtime Power Management for PCI(e) bus devices. Alternatives are
on and auto.")
(runtime-pm-on-bat
(string "auto")
"Same as @code{runtime-pm-ac} but on BAT mode.")
(runtime-pm-all?
(boolean #t)
"Runtime Power Management for all PCI(e) bus devices, except
blacklisted ones.")
(runtime-pm-blacklist
maybe-space-separated-string-list
"Exclude specified PCI(e) device addresses from Runtime Power Management.")
(runtime-pm-driver-blacklist
(space-separated-string-list '("radeon" "nouveau"))
"Exclude PCI(e) devices assigned to the specified drivers from
Runtime Power Management.")
(usb-autosuspend?
(boolean #t)
"Enable USB autosuspend feature.")
(usb-blacklist
maybe-string
"Exclude specified devices from USB autosuspend.")
(usb-blacklist-wwan?
(boolean #t)
"Exclude WWAN devices from USB autosuspend.")
(usb-whitelist
maybe-string
"Include specified devices into USB autosuspend, even if they are
already excluded by the driver or via @code{usb-blacklist-wwan?}.")
(usb-autosuspend-disable-on-shutdown?
maybe-boolean
"Enable USB autosuspend before shutdown.")
(restore-device-state-on-startup?
(boolean #f)
"Restore radio device state (bluetooth, wifi, wwan) from previous
shutdown on system startup.")
(cpu-energy-perf-policy-on-ac
maybe-string
"Set CPU energy/performance policy when on AC mode. Possible values are
performance, balance_performance, default, balance_power and power.")
(cpu-energy-perf-policy-on-bat
maybe-string
"Set CPU energy/performance policy when on BAT mode. Possible values are
performance, balance_performance, default, balance_power and power."))
(define (tlp-shepherd-service config)
(let* ((tlp-bin (file-append
(tlp-configuration-tlp config) "/sbin/tlp"))
(tlp-action (lambda (return-value-on-success . args)
#~(lambda _
(if (zero? (system* #$tlp-bin #$@args))
#$return-value-on-success
(not #$return-value-on-success))))))
(list (shepherd-service
(documentation "Run TLP script.")
(provision '(tlp))
(requirement '(user-processes))
(start (tlp-action #t "init" "start"))
(stop (tlp-action #f "init" "stop"))))))
(define (tlp-activation config)
(let* ((config-str (with-output-to-string
(lambda ()
(serialize-configuration
config
tlp-configuration-fields))))
(config-file (plain-file "tlp" config-str)))
(with-imported-modules '((guix build utils))
#~(begin
(use-modules (guix build utils))
(copy-file #$config-file "/etc/tlp.conf")))))
(define tlp-service-type
(service-type
(name 'tlp)
(extensions
(list
(service-extension shepherd-root-service-type
tlp-shepherd-service)
(service-extension udev-service-type
(compose list tlp-configuration-tlp))
(service-extension activation-service-type
tlp-activation)))
(default-value (tlp-configuration))
(description "Run TLP, a power management tool.")))
(define (generate-tlp-documentation)
(generate-documentation
`((tlp-configuration ,tlp-configuration-fields))
'tlp-configuration))
;;;
;;; thermald
;;;
;;; This service implements cpu scaling. Helps prevent overheating!
(define-record-type* <thermald-configuration>
thermald-configuration make-thermald-configuration
thermald-configuration?
(adaptive? thermald-adaptive? ;boolean
(default #f))
(ignore-cpuid-check? thermald-ignore-cpuid-check? ;boolean
(default #f))
(thermald thermald-thermald ;file-like
(default thermald)))
(define (thermald-shepherd-service config)
(list
(shepherd-service
(provision '(thermald))
(requirement '(user-processes))
(documentation "Run thermald cpu frequency scaling.")
(start #~(make-forkexec-constructor
'(#$(file-append (thermald-thermald config) "/sbin/thermald")
"--no-daemon"
#$@(if (thermald-adaptive? config)
'("--adaptive")
'())
#$@(if (thermald-ignore-cpuid-check? config)
'("--ignore-cpuid-check")
'()))))
(stop #~(make-kill-destructor)))))
(define thermald-service-type
(service-type
(name 'thermald)
(extensions (list (service-extension shepherd-root-service-type
thermald-shepherd-service)))
(default-value (thermald-configuration))
(description "Run thermald, a CPU frequency scaling service that helps
prevent overheating.")))
;;;
;;; powertop
;;;
;;; Calls `powertop --auto-tune' to reduce energy consumption.
(define-configuration powertop-configuration
(powertop (package powertop) "PowerTOP package to use."))
(define powertop-shepherd-service
(match-lambda
(($ <powertop-configuration> powertop)
(shepherd-service
(documentation "Tune kernel power settings at boot.")
(provision '(powertop powertop-auto-tune))
(requirement '(user-processes))
(one-shot? #t)
(start #~(lambda _
(zero? (system* #$(file-append powertop "/sbin/powertop")
"--auto-tune"))))))))
(define powertop-service-type
(service-type
(name 'powertop)
(extensions
(list
(service-extension shepherd-root-service-type
(compose list powertop-shepherd-service))))
(compose concatenate)
(default-value (powertop-configuration))
(description "Tune power-related kernel parameters to reduce energy
consumption.")))
;;;
;;; hd-idle
;;;
;;; Spins down hard disks that have been idle for a while.
(define (hd-idle-command-type? val)
(memq val '(scsi ata)))
(define-maybe/no-serialization hd-idle-command-type)
(define (hd-idle-power-condition? val)
(and (exact-integer? val) (<= 0 val 15)))
(define-maybe/no-serialization hd-idle-power-condition)
(define (hd-idle-symlink-policy? val)
(and (exact-integer? val) (<= 0 val 1)))
(define-configuration/no-serialization hd-idle-disk
(name
string
"The device to configure, either a name under @file{/dev} such as
@code{\"sda\"}, or an absolute file name such as
@code{\"/dev/disk/by-uuid/1234-5678\"}.")
(idle-time
maybe-non-negative-integer
"Number of seconds this disk must be idle before it is spun down.")
(command-type
maybe-hd-idle-command-type
"The command used to spin this disk down, either @code{'scsi} or
@code{'ata}.")
(power-condition
maybe-hd-idle-power-condition
"The power condition to use for this disk."))
(define list-of-hd-idle-disks?
(list-of hd-idle-disk?))
(define-configuration/no-serialization hd-idle-configuration
(idle-time
(non-negative-integer 600)
"Number of seconds a disk must be idle before it is spun down. A value
of @code{0} disables spinning down.")
(command-type
(hd-idle-command-type 'scsi)
"The command used to spin disks down, either @code{'scsi} or @code{'ata}.")
(power-condition
(hd-idle-power-condition 0)
"The power condition passed to the @acronym{SCSI, Small Computer System
Interface} @code{START STOP UNIT} command, an integer between @code{0} and
@code{15}. It is ignored when @code{command-type} is @code{'ata}.")
(symlink-policy
(hd-idle-symlink-policy 0)
"How to resolve disk names that are symbolic links. @code{0} resolves
them once at startup, whereas with @code{1} symlinks are also resolved on
runtime until success.")
(log-file
maybe-string
"A file name to which hd-idle logs disk spin-up and spin-down events, for
instance @code{\"/var/log/hd-idle.log\"}.")
(ignore-spin-down-detection?
(boolean #f)
"Whether to skip detection of disks that were spun down by some other
means.")
(debug?
(boolean #f)
"Whether to log the disk statistics that hd-idle reads on each poll.")
(disks
(list-of-hd-idle-disks '())
"A list of @code{hd-idle-disk} records describing per-disk overrides.
Disks that are not listed here are still managed, using the values above.")
(hd-idle
(file-like hd-idle)
"The hd-idle package to use."))
(define (hd-idle-disk-arguments disk)
(match-record disk <hd-idle-disk>
(name idle-time command-type power-condition)
`("-a" ,name
,@(if (maybe-value-set? idle-time)
(list "-i" (number->string idle-time))
'())
,@(if (maybe-value-set? command-type)
(list "-c" (symbol->string command-type))
'())
,@(if (maybe-value-set? power-condition)
(list "-p" (number->string power-condition))
'()))))
(define (hd-idle-arguments config)
(match-record config <hd-idle-configuration>
(idle-time command-type power-condition symlink-policy
log-file ignore-spin-down-detection? debug? disks)
`("-i" ,(number->string idle-time)
"-c" ,(symbol->string command-type)
"-p" ,(number->string power-condition)
"-s" ,(number->string symlink-policy)
,@(if (maybe-value-set? log-file) (list "-l" log-file) '())
,@(if ignore-spin-down-detection? '("-I") '())
,@(if debug? '("-d") '())
,@(append-map hd-idle-disk-arguments disks))))
(define (hd-idle-shepherd-service config)
(list
(shepherd-service
(provision '(hd-idle))
(requirement '(user-processes udev))
(documentation "Spin down idle hard disks.")
(start #~(make-forkexec-constructor
(list #$(file-append (hd-idle-configuration-hd-idle config)
"/bin/hd-idle")
#$@(hd-idle-arguments config))))
(stop #~(make-kill-destructor)))))
(define hd-idle-service-type
(service-type
(name 'hd-idle)
(extensions (list (service-extension shepherd-root-service-type
hd-idle-shepherd-service)))
(default-value (hd-idle-configuration))
(description "Run @command{hd-idle}, which spins down hard disks that have
not been accessed for a configurable period of time.")))