February 16, 2007

Improved ways to suspend and hibernate a laptop under Linux

Author: Manolis Tzanidakis

Last June I wrote about suspending and hibernating laptops under Linux. Since then a few things have changed -- thankfully, for the better -- so it's time to revisit the subject. Also, a few readers have responded offering suggestions for improving the suspend shell script I wrote back then, and I've incorporated these suggestions in a new version; unfortunately most of the comments are anonymous, so I can't give proper credit to their authors.

The most important change since the last article is that laptops with multi-core CPUs are now the de facto standard. Intel Core Duo and Core2 Duo processors not only offer Symmetric Multiprocessing (SMP) functionality to mobile users but also consume less power, and thus produce less heat, than their predecessors.

These new multi-core CPUs are supported by the Linux kernel, but you need a fairly recent version to fully utilize them in SMP mode. Suspend-to-RAM with SMP enabled works in kernel versions 2.6.18 onwards, but it's not 100% stable with occasional crashes on resume. Another problem I faced on a Core Duo system is the occasional miscalculation of the remaining battery life; the battery level might be at 70% one minute and then at 40% the next, and then back to 70%. However, things are greatly improved in kernel version 2.6.20 -- marked as release candidate now -- and if you own a multi-core laptop you should upgrade your kernel.

In the last article I mentioned the three available solutions for hibernating your system: swsusp (part of the kernel), uswsusp (an implementation of swsusp running in user space mode), and suspend2. I used the latter since it was the most stable and reliable method at the time. Suspend2 is a great piece of software, but it's not part of the Linux kernel, so it requires manual patching and kernel compilation.

The good news is that uswsusp is now stable, well integrated with kernel versions 2.6.17 onwards, and supported by most major Linux distributions. Debian (Etch and Sid) and Ubuntu (Edgy Eft and Feisty Fawn) users can install the uswusp package, enter their swap space partition when asked by the package manager, and then run s2disk to hibernate their laptop, as long as they run the stock kernel provided by their distribution.

Suspend script revisited and automation

Some readers suggested that the script I wrote for the previous article should store the system time to the hardware clock during suspend and then restore it from the hardware on resume to avoid clock skew problems. Actually, I use OpenNTPD on my systems to synchronize the clock over the network, so I've never faced such problems. Nevertheless it's a nice suggestion. Another reader with better knowledge of sed than me offered a way to discover the PCI ID of the video card using a single sed command instead of the combination of grep, awk, and sed I used; writing elegant shell scripts is a form of art, so this goes in the new version of the suspend script too.

I've also added the option to suspend or hibernate based on a given argument; you can now run suspend.sh suspend for suspend-to-RAM and suspend.sh hibernate for hibernation.

Here is the new and improved suspend script. Paste it as /usr/local/sbin/suspend.sh and make it executable with chmod +x /usr/local/sbin/suspend.sh.

#!/bin/sh

# discover video card's ID
ID=`lspci | sed -e '/VGA/!d' -e 's/ .*//' -e 's@0000:@@' -e 's@:@/@' -eq`

# securely create a temporary file
TMP_FILE=`mktemp /var/tmp/video_state.XXXXXX`
trap 'rm -f $TMP_FILE' 0 1 15

# switch to virtual terminal 1 to avoid graphics
# corruption in X
chvt 1

# synchronize system clock with hardware
hwclock --directisa --localtime --systohc

# write all unwritten data (just in case)
sync

# dump current data from the video card to the
# temporary file
cat /proc/bus/pci/$ID > $TMP_FILE

# suspend or hibernate
case "$1" in
  suspend)   echo -n mem > /sys/power/state ;;
  hibernate) s2disk  ;;
esac

# restore video card data from the temporary file
# on resume
cat $TMP_FILE > /proc/bus/pci/$ID

# synchronize hardware clock with system
hwclock --directisa --localtime --hctosys

# switch back to virtual terminal 7 (running X)
chvt 7

# remove temporary file
rm -f $TMP_FILE

I've tested this script on Debian Sid and Ubuntu Edgy Eft. Uswsusp offers the s2ram for suspending to RAM, but it didn't work correctly during my tests, so I stuck to the trusted ACPI method.

If your laptop has a GeForce video card and you use the proprietary Nvidia drivers, you don't need to store the video card data in a temporary file and restore it on resume, so remove those lines from the script. You need to follow some extra steps however. First of all, make sure to install the latest version of the 9xxx series of the Nvidia drivers (8xxx drivers don't suspend correctly) and load the nvidia kernel module with the NVreg_Mobile=1 option. Also, in the device section in /etc/X11/xorg.conf, add this line: Option "NvAGP" "1".

At this point I need to apologize to owners of ATI Radeon-based laptops; since I don't own one myself I can't help you any further.

Now, let's automate suspending or hibernating when closing the lid based on whether the AC adapter is present or not using acpid; i.e. suspend when the AC adapter is connected and hibernate when not. First create the /etc/acpi/events/lid file based on the instruction on the previous article, then paste the following as /etc/acpi/actions/lid.sh and make that file executable.

#!/bin/sh
if grep -q off-line /proc/acpi/ac_adapter/AC/state; then
  /usr/local/sbin/suspend.sh hibernate
else
  /usr/local/sbin/suspend.sh suspend
fi

Note that on your laptop the AC adapter might be called something different in the /proc file system, so adjust the script accordingly.

To automate things even further we'll have acpid hibernate the laptop automatically when the battery percentage level reaches 4%. Create the following files for handling battery events and actions:

/etc/acpi/events/battery

event=battery.*
action=/etc/acpi/actions/battery.sh

/etc/acpi/actions/battery.sh

#!/bin/sh
if grep -q on-line /proc/acpi/ac_adapter/AC/state; then
  exit 0
fi

BAT_DIR=/proc/acpi/battery/BAT0
FULL_BAT=`grep 'last full capacity' ${BAT_DIR}/info | awk '{ print $4 }'`
CUR_BAT=`grep 'remaining capacity' ${BAT_DIR}/state | awk '{ print $3 }'`
AVG=`expr $(expr ${CUR_BAT} \* 100) / ${FULL_BAT}`

if [ "$AVG" -le "4" ]; then
  /usr/local/sbin/suspend.sh hibernate
fi

Again, adjust the value of BAT_DIR to match your setup and make the second file executable with chmod +x /etc/acpi/actions/battery.sh. Start the acpid daemon and you're done.

Conclusion

Some readers might argue that this article is useless, since the last version of their favorite distribution does all these things automatically on their laptop. Sure, things have improved, but still they're not perfect; for example, Ubuntu Edgy Eft still doesn't resume my IBM ThinkPad R50e and Sony VAIO VGN-FE21M out of the box. However, distribution and kernel developers are not the ones to blame; the ACPI specification is largely misused by manufacturers. This article along, with sites such as TuxMobil and Linux on Laptops, could be useful for successfully running Linux on your laptop.