Date

Configuring hibernate on linux laptop

I recently got a new laptop with arch linux on it, and I was noticing that if I forgot to plug my charging cable for the night and came back a day later, I would lose a big chunk of battery charge overnight. My laptop enters the sleep state when idle, and by running cat /sys/power/mem_sleep I can see the sleep states that are compatible with my system… in my case, I only have an s2idle option and nothing else.

To fix my power consumption issue, I wanted to set my computer to hibernate instead of going to sleep when idle. The arch wiki has a good power article about properly configuring hibernate. I already had a swap partition set up, so I just needed to edit my mkinitcpio.conf to add resume as a hook, and then run mkinitcpio -P to reload.

Then I ran into an issue… If I ran systemctl hibernate, my screen would go dark for a few seconds but then turn back on and return me to a login screen. Once in a while though (I tried hibernating several times), the hibernate would work correctly. Running journalctl -b helped me find some issues after I searched for “hibernate”:

3 20:15:35 my-pc kernel: xhci_hcd 0000:c1:00.3: PM: pci_pm_freeze(): hcd_pci_suspend returns -16
3 20:15:35 my-pc kernel: xhci_hcd 0000:c1:00.3: PM: dpm_run_callback(): pci_pm_freeze returns -16
3 20:15:35 my-pc kernel: xhci_hcd 0000:c1:00.3: PM: failed to freeze async: error -16
# and then later
3 20:15:35 my-pc systemd-sleep[14620]: Failed to put system to sleep. System resumed again: Device or resource busy

So the pci device 0000:c1:00.3 was causing my hibernate to fail. Running lspci indicated that the culprit was c1:00.3 USB controller: Advanced Micro Devices, Inc. [AMD] Device 15b9.

Disabling power wakeup for specific PCI device

I could disable the wakeup capabilities of this pci device by running the following command:

echo disabled > /sys/bus/pci/devices/0000\:c1\:00.3/power/wakeup

This worked great! My hibernations were working consistently. The only problem was that this setting went back to enabled on every reboot, and so if I wanted to fix it this way I would have to add scripts of some sort to disable it on boot or something. I wanted to avoid doing that if possible, so I looked for other options.

Configuring suspend-then-hibernate

After looking at the manpage for systemd-sleep, I noticed that there was an interesting setting called suspend-then-hibernate. This would sleep for a configurable amount of time, and then if still idle, would wake up and immediately hibernate. I liked this for a few reasons:

  • if my laptop is only idle for a short amount of time, I would prefer the immediate wakeup from sleep over a hibernate
  • maybe if the hibernate happens after already being asleep, I won’t run into the pci error from before.

To implement this, I made a folder/file called /etc/systemd/sleep.conf.d/hibernate.conf (hibernate.conf is my arbitrary name for this config)

[Sleep]
AllowHibernation=yes
AllowSuspendThenHibernate=yes
HibernateMode=shutdown
HibernateDelaySec=10s # set to 10s for testing purposes

Then I ran systemctl suspend-then-hibernate … and it worked! Hooray!

Then I started applying it to actual events like my laptop lid closing by making a file/folder called /etc/systemd/logind.conf.d/hibernate.conf

[Login]
HandleLidSwitch=suspend-then-hibernate

However, when I tried closing my lid, nothing happened! My computer went back to a login screen but otherwise nothing. What was going on?

GNOME overrides

Well, it turns out that GNOME was overriding my power events; I could see a list of inhibited stuff via systemd-inhibit. Maybe I could find a gnome setting to do suspend-then-hibernate when idle? Well, it turns out that this is not a thing in GNOME anymore, it was for a while but then got reverted back apparently.

Instead though, I found some GNOME settings that were using sleep (the default) instead of hibernate on idle, and I changed those to hibernate. I did these with a dconf editor gui but the commands to do it would look like this:

gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-battery-type 'hibernate'
gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-battery-timeout 300

# This setting useful for testing out what happens when idle, but the default of 300s is decent I think
gsettings set org.gnome.desktop.session idle-delay 10

Disable GNOME power overrides

I didn’t do this, but I thought it was worth mentioning. If I was dead set on implementing suspend-then-hibernate, then I would have to do something to disable the GNOME power settings. There were some inhibitor override settings in /etc/systemd/logind.conf that might work, or disabling the power plugin somehow from within the gnome settings themselves.

Conclusion

I ended up going the GNOME settings route, and have my computer hibernate after 10 minutes: 5 minutes before being considered idle, at which point the screen will shut off. 5 minutes after that, the computer will hibernate. I think this will give me some nice big power savings when i forget to charge my laptop overnight!

Epilogue

Well, it turns out that entrusting gnome to properly hibernate was a bad assumption! gnome would sometimes silently fail to hibernate my computer, and I would see the same error messages that I was getting before in journalctl -b. So I changed up my settings to test alternatives:

# set to a short value for testing purposes
gsettings set org.gnome.desktop.session idle-delay 10

gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-battery-timeout 0
gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-battery-type 'nothing'

My /etc/systemd/logind.conf.d/hibernate.conf file:

[Login]
HandleLidSwitch=suspend-then-hibernate
LidSwitchIgnoreInhibited=yes
# how many seconds to wait after lid close to initiate HandleLidSwitch event
#  kept it short for testing purposes
HoldoffTimeoutSec=10s

IdleAction=suspend-then-hibernate
# how many seconds to wait after idle to initiate IdleAction
#  kept short for testing purposes
IdleActionSec=5s

and /etc/systemd/sleep.conf.d/hibernate.conf:

[Sleep]
AllowHibernation=yes
AllowSuspendThenHibernate=yes
HibernateMode=shutdown
HibernateDelaySec=300s

With these settings, GNOME will go idle after ten seconds, and then after 5 seconds, systemd will initiate suspend-then-hibernate. After 5 minutes of suspend, the system will go into hibernation. I tested it a few times and it worked! Also, the settings at the top of the logind script say to put the system in suspend-then-hibernate after the lid has been closed for 10 seconds.

After doing all this testing, I set everything to more normal values:

gsettings set org.gnome.desktop.session idle-delay 300

gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-battery-timeout 0
gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-battery-type 'nothing'

/etc/systemd/logind.conf.d/hibernate.conf:

[Login]
HandleLidSwitch=suspend-then-hibernate
LidSwitchIgnoreInhibited=yes
# how many seconds to wait after lid close to initiate HandleLidSwitch event
HoldoffTimeoutSec=20s

IdleAction=suspend-then-hibernate
# how many seconds to wait after idle to initiate IdleAction
IdleActionSec=30s

and /etc/systemd/sleep.conf.d/hibernate.conf:

[Sleep]
AllowHibernation=yes
AllowSuspendThenHibernate=yes
HibernateMode=shutdown
HibernateDelaySec=300s

Conclusion 2

So now I have a new power configuration that should save me battery life! I am still not sure if this is going to work the best. For example, if I am running a long script or download, will my system go idle during it? That might cause me headaches down the road. As a workaround I could set

gsettings set org.gnome.desktop.session idle-delay 999999

to some large value (or maybe 0 disables it?) and prevent GNOME from idling. If gnome never goes idle, then systemd should not hibernate my system. I guess I will see what happens!