Power management/Wakeup triggers

From ArchWiki

Wakeup triggers are event sources that can wake the system from any of the hardware power-saving states. The obvious example is the power or suspend button, the Wake-on-LAN functionality or the lid switch in laptop systems. Wakeup triggers can be controlled through various kernel interfaces listed below. There is no unified interface covering all possible triggers.

Wakeup trigger interfaces

/proc/acpi/wakeup

Reading the /proc/acpi/wakeup file yields list of ACPI-registered wakeup sources with corresponding sysfs IDs where available. Writing an entry from the Device column to the file toggles its state. For example, to disable waking on opening the laptop lid, run:

# echo "LID" > /proc/acpi/wakeup

/sys/module/acpi/parameters/ec_no_wakeup

This file represents the value of ACPI kernel module option ec_no_wakeup, which controls passing the wakeup triggers from embedded controller when the system is in the suspend-to-idle (s2idle) power state [1]. On modern laptops embedded controller wakeups can cause excessive battery drain in some cases.

/sys/devices/

Each sysfs device that supports wakeup contains the file wakeup in a device's power subdirectory. The file contains wakeup trigger's status and can be written to as well. Bus controllers as well as endpoint devices can be capable of waking up the system. For example, to disable wakeups from the first USB controller (bus), run:

# echo "disabled" > /sys/bus/usb/devices/usb1/power/wakeup

An endpoint device should be able to wake the device if the trigger is enabled regardless of the controller's setting, however this might be hardware-dependent.

Program PowerTOP interfaces with sysfs, but it only lists wakeup triggers of networking and USB devices by reading /sys/class/net/ and /sys/bus/usb/devices/ (containing symlinks to /sys/devices/).

/sys/class/wakeup/*

Almost all wakeup triggers can be found in the /sys/class/wakeup directory, which contains symlinks to all relevant devices. This is useful for finding possible wakeup triggers by going through subdirectories. Some of the triggers can correspond to virtual devices while hardware-related wakeup triggers are the ones that contain at least one of these files:

/sys/class/wakeup/*/device/physical_node/power/wakeup
/sys/class/wakeup/*/device/power/wakeup

Some of wakeup triggers in /sys/class/wakeup also provide a link to the cryptic /proc/acpi/wakeup names where the following file is present:

/sys/class/wakeup/*/device/path

Persistent settings

The one-time methods should suffice for setting the /proc/acpi/wakeup states and acpi.ec_no_wakeups kernel parameter while the event-driven approach with udev is the reliable way to configure the sysfs devices.

One-time with systemd

The ec_no_wakeups ACPI kernel module option can be set at boot as described in the article. The standard solution to set the sysfs values at boot are systemd services such as in this troubleshooting case. Another systemd-based manager for /proc/acpi/wakeup is wakeup-triggersAUR.

Some systems can override some of the ACPI wakeup triggers upon power state transition(s) in what is more of a bug rather than a feature. If the hardware is overriding triggers at predictable times that can still be solved with appropriately crafted systemd units. The sleep.target is a generic target covering all different suspended states that might be helpful in this case, but there is no generic wakeup.target [2].

This method only works reliably with sysfs devices that are connected all the time.

Event-driven with udev

Setting the wakeup trigger status with udev rules is an event-driven method that works reliably any time the devices with wakeup triggers are connected. The key is to detect an addition of a new device (ACTION=="add") in a rule and set the wakeup trigger status with ATTR{power/wakeup}="disabled". If the hardware is resetting this setting, udev can try to circumvent it by reapplying rules upon every device change (ACTION=="add|change"). A device tree with possible parameters for matching a particular device found in sysfs can be obtained with udevadm info -q all -a /sys/devices/....

A representative common example here would be a Logitech Unifying USB receiver. Its wakeup trigger should be enabled by default and if that is not desired, a solution could be an udev rule, as follows:

/etc/udev/rules.d/logitech-unifying.rules
ACTION=="add", SUBSYSTEM=="usb", DRIVERS=="usb", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c52b", ATTR{power/wakeup}="disabled"

The reverse case to enable the necessary trigger(s) is described in the udev article.

udev triggers so early in the device enumeration that disabling wakeup trigger with the method above causes (some?) disabled triggers to not be listed in /sys/class/wakeup. That might be dependent on whether the device was already present at boot and needs further clarification.

Troubleshooting

List device and/or bus trees

These auxiliary commands can be helpful when trying to understand all wakeup triggers of a certain system, to aid with udev rule writing or general wakeup source troubleshooting:

# lshw -businfo -numeric
# lspci -DPPnn
# lsusb -tvv

Last wakeup trigger source

The information on which wakeup source was the reason for the last device wakeup is unfortunately platform-specific. On x86 machines dmidecode can be used:

# dmidecode -t system | grep -P '\tWake-up Type\: '

Instantaneous wakeup after suspending

Intel Haswell with LynxPoint(-LP)

For some Intel Haswell systems with the LynxPoint and LynxPoint-LP chipset, instantaneous wakeups after suspending are reported. They are linked to erroneous BIOS ACPI implementations and how the xhci_hcd module interprets it during boot. As a work-around reported affected systems are added to a denylist (named XHCI_SPURIOUS_WAKEUP) by the kernel case-by-case [3].

Wakeup may happen, for example, if a USB device is plugged during suspending and ACPI wakeup triggers are enabled. A viable workaround for such a system is to disable the relevant wakeup triggers. An example to disable wakeup through USB is described as follows [4].

To view the current configuration:

$ cat /proc/acpi/wakeup
Device  S-state   Status   Sysfs node
...
EHC1      S3    *enabled  pci:0000:00:1d.0
EHC2      S3    *enabled  pci:0000:00:1a.0
XHC       S3    *enabled  pci:0000:00:14.0
...

The relevant devices are EHC1, EHC2 and XHC (for USB 3.0). To toggle their state you have to echo the device name to the file as root:

# echo EHC1 > /proc/acpi/wakeup
# echo EHC2 > /proc/acpi/wakeup
# echo XHC > /proc/acpi/wakeup

This should result in suspension working again. However, this settings are only temporary and need to be set at every boot. To automate this, see systemd-tmpfiles or BBS thread for possible solutions.

Gigabyte motherboards

On some Gigabyte motherboards, the GPP bridge to the NVMe drive drive may cause premature wakeups from suspend.

Known boards affected:

  • B550i AORUS,
  • B550 AORUS ELITE V2,
  • B550 AORUS ELITE AX V2 (Rev. 1.5)

Setting the status of GPP0 to disabled may fix the issue:

# echo GPP0 > /proc/acpi/wakeup

Same as the Haswell solution above, this setting is only temporary. An example of automating the fix can be found in this BBS thread.

NVIDIA drivers

Installing NVIDIA proprietary drivers might render suspension and hibernation not possible. In that case the system log might include:

kernel: NVRM: GPU 0000:01:00.0: PreserveVideoMemoryAllocations module parameter is set. System Power Management attempted without driver procfs suspend interface. Please refer to the 'Configuring Power Management Support' section in the driver README.
kernel: PM: pci_pm_suspend(): nv_pmops_suspend+0x0/0x20 [nvidia] returns -5
kernel: PM: dpm_run_callback(): pci_pm_suspend+0x0/0x160 returns -5
kernel: nvidia 0000:01:00.0: PM: failed to suspend async: error -5

See NVIDIA/Tips and tricks#Preserve video memory after suspend.

nouveau driver

If the nouveau driver is used, the reason for instantaneous wakeups may be a bug in the driver, which sometimes prevents GPU from suspending. A possible workaround is unloading the nouveau kernel module right before going to sleep and loading it back after wakeup. To do this, create the following script:

/usr/lib/systemd/system-sleep/10-nouveau.sh
#!/bin/bash

case $1/$2 in
  pre/*)
    # echo "Going to $2..."
    /usr/bin/echo "0" > /sys/class/vtconsole/vtcon1/bind
    /usr/bin/rmmod nouveau
    ;;
  post/*)
    # echo "Waking up from $2..."
    /usr/bin/modprobe nouveau
    /usr/bin/echo "1" > /sys/class/vtconsole/vtcon1/bind
    ;;
esac

The first echo line unbinds nouveaufb from the framebuffer console driver (fbcon). Usually it is vtcon1 as in this example, but it may also be another vtcon*. See /sys/class/vtconsole/vtcon*/name which one of them is a frame buffer device [5].