Centralising powersaving through udev

Updated: 2018-12-30

With computer usage becoming increasingly mobile - first laptops, then handhelds, and now smartphones and tablets - power consumption has become a hot topic. Linux has traditionally been a bit lacking in this department, playing catch-up to Windows; and while it's still not nearly as easy on a regular Linux installation to obtain similar power efficiency as on the younger siblings of the Windows family, you can get Linux to behave nicely with a few general tricks.

A neat - and widely used - program to find the culprits is powertop, originally developed by Intel. It checks for the most common energy consumers and allows you to fix them (but this is not permanent). Traditionally, the tweaks the tool recommends are then thrown into some custom script or in a catch-all startup script like rc.local. However, a lot of those settings can be configured through udev, which allows you create custom rules. Those rules are simple scripts you drop in /etc/udev/rules.d. You can add all the rules below into one file, but I like them separate for a better overview.

I will list three of the most frequent 'weak links' on a Linux system that can be configured for minimal power consumption. Keep in mind, for optimal results, you need to have all powersaving features (C-states, EIST, ASPM, etc.) turned on in the BIOS.

PCI runtime power management

A lot of distros will leave this untouched, unless you have powersaving scripts like pm-powersave or laptop-tools running. I use TLP myself and I'm quite happy with it - it works well, but it doesn't cover all the bases (like PCI runtime power management).

Create a rules file called 10-runtime-pm-rules:

SUBSYSTEM!="pci", GOTO="power_runtime_rules_end"
ACTION!="add", GOTO="power_runtime_rules_end"

PROGRAM="/bin/sleep 0.1"

ATTR{power/control}=="*", ATTR{power/control}="auto"


This will activate power management on all PCI devices at boot, and also for hotplugged devices. If this somehow breaks shutdown and your system reboots instead of powering off, use this init script I wrote to set the powersaving on all PCI devices and disable it before rebooting. Make sure you remove the udev rules above, they are redundant in this case. You enable the script on Debian (or derivatives) like this:

$ sudo rc-update.d runtime-pm-pci defaults

S-ATA ALPM (Agressive Link Power Management)

Create a rules file called 10-sata-alpm.rules:

ACTION=="add", SUBSYSTEM=="scsi_host", KERNEL=="host*", ATTR{link_power_management_policy}="min_power"

Keep in mind activating power management on eS-ATA ports will disable hotplugging, thus rendering them useless. My server e.g. has 6 S-ATA ports, of which the last is an eS-ATA port; there, the rules file reads like this:

ACTION=="add", SUBSYSTEM=="scsi_host", KERNEL=="host[0-4]", ATTR{link_power_management_policy}="min_power"

As you can see it loops through port 0 to 4 (in human count, 1 to 5).

USB autosuspend

This can be set through a module argument in a modprobe file (this is the classical way). However, you can also set it through udev. Create a rules file called 10-usb-autosuspend.rules:

ACTION=="add", SUBSYSTEM=="usb", TEST=="power/control", ATTR{power/control}="auto"
ACTION=="add", SUBSYSTEM=="usb", TEST=="power/autosuspend", ATTR{power/autosuspend}="60"

The number 2 specifies the timeout in seconds. Adjust this to your liking.

GPU power saving

This is specific for Radeon hardware. Dynamic Power Management (DPM) was introduced in the Linux 3.11 kernel, and is enabled by default from 3.13 on. It allows dynamic changes to clocks and voltage depending on load, and also enables power and clock gating. DPM can be enabled or disabled through the radeon.dpm kernel parameter on boot.

$ cat 15-radeon.rules
# Set power saving profile. Possible values: performance, balanced, battery.
ACTION=="add", KERNEL=="card0", SUBSYSTEM=="drm", DRIVERS=="radeon", ATTR{device/power_dpm_state}="battery"

Backlight brightness

On laptops, it can be handy to set the backlight to a predefined value. I used some rc.local hackery for this, but in hindsight I should have done it through udev right from the start. Find the numerical val ue for your ideal brightness level (the relevant values are stored under /sys/class/backlight/acpi_video0/ ) and put it in a file called 15-backlight.rules:

SUBSYSTEM=="backlight", ACTION=="add", KERNEL=="acpi_video0", ATTR{brightness}="12"

That's it. Now you have a few major powersaving measures centralised, all handled by udev, and you can leave rc.local to the really pesky stuff. Udev is a powerful framework, it e.g. also allows you to set your screen brightness according to whether you're on AC or on battery. For further reading, I recommend the excellent Arch wiki entry on powersaving. Check this Serverfault post for more information on PCI runtime power management.

Network interfaces

We all know wireless can be a power hog, so if your wireless chip supports powersaving, you should enable it. Create a file called 70-wifi-powersave.rules:

ACTION=="add", SUBSYSTEM=="net", KERNEL=="wlan*", RUN+="/sbin/iw dev %k set power_save on"

The location of iw may be different on your distribution so double-check its path. If that the command doesn't seem to work, run the iw command manually to make sure your device supports powersaving. For example, this is what happens on a Realtek RTL8723AE wireless adapter:

$ sudo iw dev wlan0 set power_save on
command failed: Operation not supported (-95)

Your Ethernet interface may also benefit from Wake on LAN being turned off (that is, if you don't use it). For this, create a udev rule called 70-disable-wol.rules:

# Disable Wake on LAN. Requires ethtool to be installed.
ACTION=="add", SUBSYSTEM=="net", KERNEL=="eth*", RUN+="/sbin/ethtool -s %k wol d"

Just like with iw the path of ethtool may be different on your distribution of choice. On Debian, it's in /sbin/, on Arch, it seems to hide in /usr/bin/.

If you happen to rename your network interfaces (newer Linux versions stick to 'unique' interface names that are hard to remember - e.g. enp7s0 - then make sure your udev rules aren't parsed before the distribution rules in /lib/udev/rules.d/ kick in.

Testing udev rules

You can debug your rules using udevadm test. For more info on writing udev rules (and find out the info udev needs to make rules work), check out this article. To test e.g. whether udev will turn off Wake on Lan on eth0:

$ udevadm test /sys/class/net/eth0

PCI Express ASPM

I haven't found a way to set this through udev yet, but it's a been a pretty hot issue so I will mention it anyway. You can force Linux to enable PCI-E ASPM, but be sure to check whether your hardware supports it before making it permanent (e.g. by editing your bootloader's kernel line interactively at boot time).

If you want to keep ASPM enabled, add it to your bootloader configuration permanently. You might also want to set the default policy to powersave rather than performance (the value defaults to... You guessed it right: default).

$ cat /sys/module/pcie_aspm/parameters/policy
[default] performance powersave

In GRUB, add this:

GRUB_CMDLINE_LINUX_DEFAULT="quiet pcie_aspm=force pcie_aspm.policy=powersave"

The former forces ASPM, the latter sets the policy to powersave. Remember to run update-grub to make things permanent.