Power Management: Difference between revisions
Hustlerone (talk | contribs) Tested on nixpkgs 7fd36ee82c0275fb545775cc5e4d30542899511d. I looked into this in depth but I can't find the damned post. Why do search engines have to be so dogshit they shuffle results around? |
|||
| (9 intermediate revisions by 3 users not shown) | |||
| Line 30: | Line 30: | ||
<syntaxhighlight lang="nix"> | <syntaxhighlight lang="nix"> | ||
systemd.services.your-service-name = { | |||
description = "Service description here"; | |||
wantedBy = [ "post-resume.target" ]; | |||
after = [ "post-resume.target" ]; | |||
script = '' | |||
echo "This should show up in the journal after resuming." | echo "This should show up in the journal after resuming." | ||
''; | |||
serviceConfig.Type = "oneshot"; | |||
}; | |||
</syntaxhighlight> | </syntaxhighlight> | ||
| Line 45: | Line 45: | ||
Hibernation requires a configured swap device. See [https://nixos.org/manual/nixos/stable/#ch-installation installation instructions] on how to create a swap partition. | Hibernation requires a configured swap device. See [https://nixos.org/manual/nixos/stable/#ch-installation installation instructions] on how to create a swap partition. | ||
Please note that <code>resumeDevice</code> must match the output of <code>swapon -s</code> especially if you're dealing with mapped volumes (LUKS, logical volumes, logical volumes under LUKS, etc.). If you're using a swapfile, you must also [https://search.nixos.org/options?channel=unstable&show=boot.resumeDevice&from=0&size=50&sort=relevance&type=packages&query=resume+offset specify the offset to it] | Please note that <code>resumeDevice</code> must match the output of <code>swapon -s</code> especially if you're dealing with mapped volumes (LUKS, logical volumes, logical volumes under LUKS, etc.). If you're using a swapfile, you must also [https://search.nixos.org/options?channel=unstable&show=boot.resumeDevice&from=0&size=50&sort=relevance&type=packages&query=resume+offset specify the offset to it.] | ||
Therefore, an example configuration could look like this:<syntaxhighlight lang="nix"> | Therefore, an example configuration could look like this:<syntaxhighlight lang="nix"> | ||
/ | // I'm hibernating into a logical volume that's also under LUKS. Pretty cool, right? | ||
swapDevices = [ | swapDevices = [ | ||
| Line 60: | Line 57: | ||
boot.resumeDevice = "/dev/dm-7"; | boot.resumeDevice = "/dev/dm-7"; | ||
</syntaxhighlight>Derived from a system with the following output from <code>swapon -s</code> :<syntaxhighlight> | </syntaxhighlight>Derived from a system with the following output from <code>swapon -s</code> :<syntaxhighlight lang="text">Filename Type Size Used Priority | ||
Filename Type Size Used Priority | |||
/dev/dm-7 partition 67108860 00 | /dev/dm-7 partition 67108860 00 | ||
/dev/zram0 partition 32881148 032767 | /dev/zram0 partition 32881148 032767</syntaxhighlight> | ||
</syntaxhighlight> | |||
Test and use hibernation with following command:<syntaxhighlight lang="nix"> | Test and use hibernation with the following command:<syntaxhighlight lang="nix"> | ||
systemctl hibernate | systemctl hibernate | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| Line 93: | Line 86: | ||
== Troubleshooting == | == Troubleshooting == | ||
=== System immediately wakes up from suspend === | |||
Particularly in some Gigabyte motherboards with NVMe drives, the system may immediately wake up from being suspended. | Particularly in some Gigabyte motherboards with NVMe drives, the system may immediately wake up from being suspended. | ||
This can be worked around by disabling the wakeup triggers for the offending components: | This can be worked around by disabling the wakeup triggers for the offending components: | ||
==== Solution 1: Disabling wakeup triggers for all PCIe devices ==== | |||
If you don't need your system to wakeup via PCIe components you can simply disable it for all without needing to determine which component is causing problems. | If you don't need your system to wakeup via PCIe components you can simply disable it for all without needing to determine which component is causing problems. | ||
| Line 108: | Line 101: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
==== Solution 2: Disable a common NVMe interface ==== | |||
Specifically on Gigabyte motherboards you can try targetting only the NVMe ports. | Specifically on Gigabyte motherboards you can try targetting only the NVMe ports. | ||
| Line 118: | Line 111: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
==== Solution 3: Disable a single device's wakeup triggers ==== | |||
If you wish to be more granular in what components should no longer be able to wakeup your system, you can find out which component is causing the wakeup events. | If you wish to be more granular in what components should no longer be able to wakeup your system, you can find out which component is causing the wakeup events. | ||
First, list all components and their current wakeup status: | First, list all components and their current wakeup status: | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="shell-session"> | ||
$ cat /proc/acpi/wakeup | $ cat /proc/acpi/wakeup | ||
Device S-state Status Sysfs node | Device S-state Status Sysfs node | ||
| Line 143: | Line 136: | ||
After finding out which component is causing unwanted wakeups you can use the sysfs id to find out the "vendor" and "device" fields: | After finding out which component is causing unwanted wakeups you can use the sysfs id to find out the "vendor" and "device" fields: | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="console"> | ||
$ cat /sys/class/pci_bus/0000:04/device/0000:04:00.0/vendor | $ cat /sys/class/pci_bus/0000:04/device/0000:04:00.0/vendor | ||
0x1987 | 0x1987 | ||
| Line 157: | Line 150: | ||
''; | ''; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
=== Suspend blocked by <code>pre-sleep.service</code> === | |||
Sometimes, the system appears to suspend (Wi-Fi turns off, screen locks), but the hardware does not actually suspend, and all subsequent <code>systemctl suspend</code> or <code>systemctl reboot</code> commands are met with: | |||
<syntaxhighlight lang="console"> | |||
# systemctl suspend | |||
Call to Suspend failed: Action suspend already in progress, refusing requested suspend operation. | |||
# systemctl reboot | |||
Call to Reboot failed: Action suspend already in progress, refusing requested reboot operation. | |||
</syntaxhighlight> | |||
If directly telling the kernel to suspend as root works: | |||
<syntaxhighlight lang="console"> | |||
# echo mem > /sys/power/state | |||
</syntaxhighlight> | |||
Then a long-running <code>pre-sleep.service</code> might be hanging the sleep. This can be verified with <code>systemctl list-jobs</code>: | |||
<syntaxhighlight lang="console"> | |||
# systemctl list-jobs | |||
JOB UNIT TYPE STATE | |||
12144 suspend.target start waiting | |||
12149 pre-sleep.service start running | |||
12145 systemd-suspend.service start waiting | |||
12268 post-resume.target start waiting | |||
12148 sleep.target start waiting | |||
12269 post-resume.service start waiting | |||
</syntaxhighlight> | |||
Here, the <code>pre-sleep.service</code> is blocking and halting suspend. To see why, we can use <code>systemctl cat pre-sleep.service</code>: | |||
<syntaxhighlight lang="systemd"> | |||
# systemctl cat pre-sleep.service | |||
# /etc/systemd/system/pre-sleep.service | |||
[Unit] | |||
Before=sleep.target | |||
Description=Pre-Sleep Actions | |||
[Service] | |||
# <... Omitted Environment directives PATH, LOCALE_ARCHIVE, TZDIR ...> | |||
ExecStart=/nix/store/yzf7cpiqzq49san2frijxsh160zjy6fp-unit-script-pre-sleep-start/bin/pre-sleep-start | |||
Type=oneshot | |||
[Install] | |||
WantedBy=sleep.target | |||
</syntaxhighlight> | |||
In this case, the <code>pre-sleep-start</code> script referenced by <code>ExecStart</code> contained directives installed by the [[Displaylink]] package, that contained a flush operation which hung the suspend action. Starting <code>dlm.service</code> or running <code>sudo DisplayLinkManager</code> unblocks the script and made suspend work normally. | |||
==== Cancelling an existing suspend action ==== | |||
An existing suspend operation that is hung may be interrupted using <code>'''systemctl cancel'''</code> in case reboots or internet access is needed. | |||
== See also == | == See also == | ||
Latest revision as of 10:00, 18 January 2026
This article covers configurations related to power management in terms of energy saving modes of various devices and components.
Configuration
Hard drives
Following snippet configures Udev rules which automatically run the program hdparm to enable power saving modes for hard disks, especially rotational drives mapped to /dev/sd*.
services.udev.extraRules =
let
mkRule = as: lib.concatStringsSep ", " as;
mkRules = rs: lib.concatStringsSep "\n" rs;
in mkRules ([( mkRule [
''ACTION=="add|change"''
''SUBSYSTEM=="block"''
''KERNEL=="sd[a-z]"''
''ATTR{queue/rotational}=="1"''
''RUN+="${pkgs.hdparm}/bin/hdparm -B 90 -S 41 /dev/%k"''
])]);
The hdparm parameters -B and -S define power saving modes and in case of -S the standby (spindown) timeout. The number 41 means therefore: Turn off the motor after 205 = 41*5 seconds.
Suspend hooks
NixOS provides the powerManagement.resumeCommands option which defines commands that are added to a global script that will be executed after resuming.
powerManagement.resumeCommands = ''
echo "This should show up in the journal after resuming."
'';
It is also possible to use the post-resume target directly to make a service.
systemd.services.your-service-name = {
description = "Service description here";
wantedBy = [ "post-resume.target" ];
after = [ "post-resume.target" ];
script = ''
echo "This should show up in the journal after resuming."
'';
serviceConfig.Type = "oneshot";
};
Hibernation
Hibernation requires a configured swap device. See installation instructions on how to create a swap partition.
Please note that resumeDevice must match the output of swapon -s especially if you're dealing with mapped volumes (LUKS, logical volumes, logical volumes under LUKS, etc.). If you're using a swapfile, you must also specify the offset to it.
Therefore, an example configuration could look like this:
// I'm hibernating into a logical volume that's also under LUKS. Pretty cool, right?
swapDevices = [
{
device = "/dev/VG/SWAP";
}
];
boot.resumeDevice = "/dev/dm-7";
Derived from a system with the following output from swapon -s :
Filename Type Size Used Priority
/dev/dm-7 partition 67108860 00
/dev/zram0 partition 32881148 032767
Test and use hibernation with the following command:
systemctl hibernate
Tips and tricks
Go into hibernate after specific suspend time
Using following configuration, your system will go from suspend into hibernate after 1 hour:
systemd.sleep.extraConfig = ''
HibernateDelaySec=1h
'';
Or, to disable suspend entirely, consider a configuration like this:
systemd.sleep.extraConfig = ''
AllowSuspend=no
AllowHibernation=no
AllowHybridSleep=no
AllowSuspendThenHibernate=no
'';
Troubleshooting
System immediately wakes up from suspend
Particularly in some Gigabyte motherboards with NVMe drives, the system may immediately wake up from being suspended. This can be worked around by disabling the wakeup triggers for the offending components:
Solution 1: Disabling wakeup triggers for all PCIe devices
If you don't need your system to wakeup via PCIe components you can simply disable it for all without needing to determine which component is causing problems.
services.udev.extraRules = ''
ACTION=="add", SUBSYSTEM=="pci", DRIVER=="pcieport", ATTR{power/wakeup}="disabled"
'';
Solution 2: Disable a common NVMe interface
Specifically on Gigabyte motherboards you can try targetting only the NVMe ports.
services.udev.extraRules = ''
ACTION=="add" SUBSYSTEM=="pci" ATTR{vendor}=="0x1022" ATTR{device}=="0x1483" ATTR{power/wakeup}="disabled"
'';
Solution 3: Disable a single device's wakeup triggers
If you wish to be more granular in what components should no longer be able to wakeup your system, you can find out which component is causing the wakeup events.
First, list all components and their current wakeup status:
$ cat /proc/acpi/wakeup
Device S-state Status Sysfs node
GP12 S4 *enabled pci:0000:00:07.1
GP13 S4 *disabled pci:0000:00:08.1
XHC0 S4 *enabled pci:0000:0a:00.3
GP30 S4 *disabled
....
PT27 S4 *disabled
PT28 S4 *disabled
PT29 S4 *disabled pci:0000:03:09.0
You can temporarily toggle a device by writing its "Device" name back into /proc/acpi/wakeup
echo GPP0 | sudo tee /proc/acpi/wakeup
After finding out which component is causing unwanted wakeups you can use the sysfs id to find out the "vendor" and "device" fields:
$ cat /sys/class/pci_bus/0000:04/device/0000:04:00.0/vendor
0x1987
$ cat /sys/class/pci_bus/0000:04/device/0000:04:00.0/device
0x5013
And finally use those values in a udev rule:
services.udev.extraRules = ''
ACTION=="add" SUBSYSTEM=="pci" ATTR{vendor}=="0x1987" ATTR{device}=="0x5013" ATTR{power/wakeup}="disabled"
'';
Suspend blocked by pre-sleep.service
Sometimes, the system appears to suspend (Wi-Fi turns off, screen locks), but the hardware does not actually suspend, and all subsequent systemctl suspend or systemctl reboot commands are met with:
# systemctl suspend
Call to Suspend failed: Action suspend already in progress, refusing requested suspend operation.
# systemctl reboot
Call to Reboot failed: Action suspend already in progress, refusing requested reboot operation.
If directly telling the kernel to suspend as root works:
# echo mem > /sys/power/state
Then a long-running pre-sleep.service might be hanging the sleep. This can be verified with systemctl list-jobs:
# systemctl list-jobs
JOB UNIT TYPE STATE
12144 suspend.target start waiting
12149 pre-sleep.service start running
12145 systemd-suspend.service start waiting
12268 post-resume.target start waiting
12148 sleep.target start waiting
12269 post-resume.service start waiting
Here, the pre-sleep.service is blocking and halting suspend. To see why, we can use systemctl cat pre-sleep.service:
# systemctl cat pre-sleep.service
# /etc/systemd/system/pre-sleep.service
[Unit]
Before=sleep.target
Description=Pre-Sleep Actions
[Service]
# <... Omitted Environment directives PATH, LOCALE_ARCHIVE, TZDIR ...>
ExecStart=/nix/store/yzf7cpiqzq49san2frijxsh160zjy6fp-unit-script-pre-sleep-start/bin/pre-sleep-start
Type=oneshot
[Install]
WantedBy=sleep.target
In this case, the pre-sleep-start script referenced by ExecStart contained directives installed by the Displaylink package, that contained a flush operation which hung the suspend action. Starting dlm.service or running sudo DisplayLinkManager unblocks the script and made suspend work normally.
Cancelling an existing suspend action
An existing suspend operation that is hung may be interrupted using systemctl cancel in case reboots or internet access is needed.