Tuesday, January 5, 2016

How to fix broken power management in Xubuntu 15.10 (16.04)

As we know Ubuntu has switched to the new "shiny" init system called systemd since the release 15.04. But the integration of that new init system isn't yet completely finished because some things aren't working as before the switch. Power management that utilized pm-utils previously is completely broken for example and no alternative solution is offered. In this article I'll explain how to restore pm-utils's old behaviour as a temporary workaround that can be used until a new and of course better official solution will be implemented.

 

 What is broken:

  1. System doesn't react on the power supply plugged/unplugged events
  2. Lid close events are handled by the systemd by default and not by the xfce4-power-manager
  3. System doesn't execute pm-utils hooks on sleep/resume events
  4. System doens't execute pm-utils hooks after start

 

1. Fix for the plugged/unplugged events

Previously (up to 14.10) the upowerd daemon managed the PSU plug/unplug events and called the pm-powersave to handle them. Now this functionality is removed from the upowerd and no other handling mechanism exists at the moment. The fix is pretty much straightforward -- add a udev rule to call the pm-powersave:
$ cat /etc/udev/rules.d/powersave.rules 
SUBSYSTEM=="power_supply", ATTR{online}=="0", RUN+="/usr/sbin/pm-powersave true"
SUBSYSTEM=="power_supply", ATTR{online}=="1", RUN+="/usr/sbin/pm-powersave false"

Note that since Ubuntu 17.04 release these freak&fucks broke the above solution. It's not allowed any longer for anthing started by the udev daemon to write to /var/log despite the fact that scripts are started with the root credentials! To let it work one has to patch the /usr/lib/pm-utils/pm-functions script. In the function init_logfile() replace the 'exec >> "$1" 2>&1' string with the 'exec >> "/tmp/$(basename $1)" 2>&1' to let it store logs in the /tmp instead.

 

2. Fix for the lid close event

Lid close events should be handled by the xfce4-power-manager because this is the place where users define what should happen on such an event. By default systemd handles the event by sending computer to sleep no matter what settings are defined in the xfce4-power-manager GUI. The fix is also straightforward - just execute the following code:
sudo xfconf-query -c xfce4-power-manager -p /xfce4-power-manager/logind-handle-lid-switch -n -t bool -s false
 
This line of code instructs the xfce4-power-manager to handle lid close events. Note that if the xfce4-power-manager is configured to send computer to sleep upon lid close event then it has a lag of approximately 3 seconds before it suspends. This delay is not configurable.
Additionally execute the following code to instruct login.d to not handle lid close and other power related events:
disabled logind lid close events handling
echo 'HandlePowerKey=ignore' | sudo tee -a /etc/systemd/logind.conf
echo 'HandleSuspendKey=ignore' | sudo tee -a /etc/systemd/logind.conf
echo 'HandleHibernateKey=ignore' | sudo tee -a /etc/systemd/logind.conf
echo 'HandleLidSwitch=ignore' | sudo tee -a /etc/systemd/logind.conf

3. Fix for the sleep/resume pm-utils hooks

This fix is a little bit more complicated. It requires patching of the pm-suspend script and creating and registering a systemd service file.

As far as xfce4-power-manager calls systemd when the system needs to be suspended or resumed an additional systemd service file needs to be registered to call the old pm-utils sleep hooks. But one does not simply walk into Mordor call the pm-suspend command from such a service file because it would try to suspend computer too which would cause a conflict. First the pm-suspend script must be patched so that it would call all the hooks without suspending the machine:
--- /usr/sbin/pm-suspend 2016-05-16 18:59:01.623376619 +0200
+++ /usr/sbin/pm-suspend 2016-05-16 19:03:43.420630720 +0200
@@ -68,6 +68,10 @@
 while [ $# -gt 0 ]
 do
  [ "$1" = "--help" ] && help
+  [ "$1" = "--direct" ] && {
+    shift
+    DO_DIRECT="$1"
+  }
  shift
 done
 
@@ -95,16 +99,19 @@
 load_hook_parameters
 
 # run the sleep hooks
+[ "${DO_DIRECT}" != "false" ] && {
 log "$(date): Running hooks for $ACTION."
 if run_hooks sleep "$ACTION $METHOD"; then
         # Sleep only if we know how and if a hook did not inhibit us.
  log "$(date): performing $METHOD"
  sync
- "do_$METHOD" || r=128
+  [ -z "${DO_DIRECT}" ] && "do_$METHOD" || r=128
  log "$(date): Awake."
 else
  log "$(date): Inhibit found, will not perform $METHOD"
 fi
+}
+[ "${DO_DIRECT}" = "true" ] && exit $r
 log "$(date): Running hooks for $REVERSE"
 # run the sleep hooks in reverse with the wakeup action
 if run_hooks sleep "$REVERSE $METHOD" reverse; then
The script is extended to receive a new boolean switch '--direct'. If '--direct true' is specified then all sleep hooks will be called with 'suspend/hibernate' parameter and if '--direct false' is specified then with 'resume/thaw'. This diff file can also be downloaded from here. If the '--direct' switch is not given then the pm-action script works like before.

Then the following service file needs to be registered:
$ cat /etc/systemd/system/pm-utils-sleep.service 
[Unit]
Description=pm-utils sleep hook
Before=sleep.target
StopWhenUnneeded=yes

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=-/usr/sbin/pm-suspend --direct true
ExecStop=-/usr/sbin/pm-suspend --direct false

[Install]
WantedBy=sleep.target

Register it with the following command:
sudo systemctl enable /etc/systemd/system/pm-utils-sleep.service
 
If hibernate is used then register another service file with different name but with the same content as above. Just replace the 'pm-suspend' call with 'pm-hibernate' and 'sleep.target' with the 'hibernate.target' in two places.

 

4. Execute pm-utils hooks after system start

After system start the pm-utils hooks aren't executed if the sytem is running on battery. This can be solved by regestering another systemd service:
$ cat /etc/systemd/system/pm-powersave-sysinit.service 
[Unit]
Description=pm-powersave after system start hook
After=sysinit.target
StopWhenUnneeded=yes

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=-/bin/sh -c "/home/alex/bin/isonbattery.sh && /usr/sbin/pm-powersave true"

[Install]
WantedBy=sysinit.target

After system start the 'pm-powersave true' is called if the system is not plugged. To check if the system is plugged I'm using the isonbattery.sh script.

 

5. Additional pm-utils hook scripts to reduce power consumption

Some additional power related tweaks are necessary to reduce power consumption even more because not everything is handled by pm-utils default hooks.

First of all install powertop package and start it with 'sudo powertop'. Switch to Tunable section using Tab key and disconnect the power plug from your notebook. If everything is optimally set then all entries should be marked as Good. If not you should check what powertop suggest and add specific pm-utils hook scripts for each problem reported into the /etc/pm/power.d folder.

On my systems I always add the same number of scripts to make everything be marked as 'Good' by the powertop. You may find all my pm-utils related scripts here. Scripts 20-pci_devices and 30-usb_devices and also content of the config.d/defaults are necessary the other scripts are optional. Copy the necessary scripts and defaults file content to your /etc/pm/ folder, reboot the computer and check with the powertop if you're all 'Good' now.

P.S. All scripts described in this article could be found here

2 comments:

mbrown956 said...

Hello,

The first step of editing /etc/udev/rules.d/powersave.rules works great, however I have observed the following:

If I boot the computer without AC power, the power manager works as expected. Rules correctly change behavior when the computer is plugged in, and unplugged again.

If I boot the computer WITH AC power, and then later unplug it, the monitor brightness continues to change in accordance with the AC power rules, not the battery power rules.

alex said...

sorry, I canno't reproduce this problem