Note: This document is a work in progress and sections will be added as I figure out additional things. Last updated 11/15/05.
Introduction
Suspend to Disk aka Hibernate
The hibernate.conf File
General Power Saving Ideas
Automatically Hibernate when the Battery is Critical
Drive Bay Hot swapping
Video
Networking (Wireless and Wired)
Modem
I really didn't know it until after I got Suspend to Disk working but it seems that it's a hard thing to get working on Linux. Or at least that's what I hear. I guess I just got lucky to get it working. Since it was work intensive on Slackware I figured I'd write about it for both my own notes and to benefit anyone else who may be interested.
My laptop is a Thinkpad T30 that I use from work. My primary goal is to show work that Linux can be used for day to day work and effectively interact in a predominately Windows environment. To do this in my work environment I chose to use KDE as the desktop mostly because it is in my opinion the easiest desktop environment in Linux for Windows folks to adjust to. I chose Slackware 10.2 as the distro to do this little project on mostly because I know it better than any other distribution but also because I wanted to learn as much as I could and have a little hand holding as possible. I haven't tried it but it is my understanding that Suse and Ubuntu deal with laptops and especially suspend to disk fairly well depending on the hardware. Some of the other goals I will attempt to meet are:
Since this is a laptop that I use out on the road, often away from a convenient power source the first thing I chose to tackle was power management. In Windows, in order to maximize battery life I often hibernate (aka Suspend to Disk) my system instead of just going into standby. The obvious advantage of hibernate over standby is that in hibernation the system is totally turned off. When in standby the system still uses power just at a lower rate. Before I go to far though, let me say I've heard that the drivers provided by Nvidia and ATI can have issues with being suspended. Like I said, I've only heard this, not seen it. But I can say that so far the generic Radeon drivers in Xorg work perfectly.
First off Slackware 10.2 installs with the 2.4.31 kernel and I wanted the latest ACPI support so I had to upgrade to the 2.6.13 kernel on CD 2. There is only one catch with this though. The default file system for Slack is reiserfs but the 2.6.13 kernel only has it compiled in as a module forcing you to use an initrd file or to recompile the kernel. In this case I chose to recompile to kernel since I had to patch the kernel for hibernation to work. I the found Suspend2 which has a mess of cool features compared to the default software suspend in the kernel. Plus it had the added advantage of actually working on my hardware. I'm not going to go deep into how I patched the kernel and setup my suspend file because, well, it was really easy and their HOWTO and FAQ are very well written. Just be sure you read the section entitled Avoiding Data Loss to see if any of it applies to you. Also, make this the first thing you setup because until you get it right it's very easy to corrupt your file system beyond repair. I nuked my system once while experimenting with this. Lucky for me I didn't put much effort into anything else at that point. Of course, if you nuke your system with important data on it and don't have a good backup it's not my fault.
At this point, assuming that Suspend2 and the necessary hibernate script is
installed the easiest way to hibernate your system is to just type
hibernate as root. The problem with this is it's manual. I
wanted automatic. In particular I wanted it to automatically hibernate when
the battery got below a certain point and also when I closed the lid. And to
have it do all of this without depending on KDE to do it.
In order to to this I figured acpid would be the way to go. The default configuration of
the 2.6.13 kernel with Slack had everything I needed compiled as modules. So
to get ACPI working I just had to modprobe them all in. In
/etc/rc.d/rc.local I put:
echo "Loading ACPI and Speedstep Modules..." /sbin/modprobe ac /sbin/modprobe battery /sbin/modprobe button /sbin/modprobe processor /sbin/modprobe ibm_acpi /sbin/modprobe thermal /sbin/modprobe video /sbin/modprobe fan /sbin/modprobe speedstep_ichYou may not need the ibm_acpi module or the speedstep_ich modules depending on your laptop. The ibm_acpi module adds Thinkpad specific support for certain acpi events (not sure exactly which ones) and the speedstep_ich adds support for CPU throttling to save battery. Once these are modprobed in make sure
/etc/rc.d/rc.acpid is executable then either reboot (the windows
way) or just run /etc/rc.d/rc.acpid start. Now, if we keep an eye
on /var/log/acpid we can see just what events are generated when
we close the lid and change between battery and AC power.
tail -f /var/log/acpid gave me this when I opened and closed the
lid a bunch of times.
[Wed Oct 27 00:42:17 2005] received event "button/lid LID 00000080 00000001" [Wed Oct 26 00:42:17 2005] executing action "/etc/acpi/acpi_handler.sh button/lid LID 00000080 00000001" [Wed Oct 26 00:42:17 2005] BEGIN HANDLER MESSAGES [Wed Oct 26 00:42:17 2005] END HANDLER MESSAGES [Wed Oct 26 00:42:17 2005] action exited with status 0 [Wed Oct 26 00:42:17 2005] completed event "button/lid LID 00000080 00000001" [Wed Oct 26 00:42:20 2005] received event "button/lid LID 00000080 00000002" [Wed Oct 26 00:42:20 2005] executing action "/etc/acpi/acpi_handler.sh button/lid LID 00000080 00000002" [Wed Oct 26 00:42:20 2005] BEGIN HANDLER MESSAGES [Wed Oct 26 00:42:20 2005] END HANDLER MESSAGES [Wed Oct 26 00:42:20 2005] action exited with status 0 [Wed Oct 26 00:42:20 2005] completed event "button/lid LID 00000080 00000002"This was for closing the lid and then opening it. So it generates an event when the lid is closed and again when opening it each time counting up the event in HEX. So, I needed a rule that would only catch every other lid event and then call
hibernate.
To do this I created an event catcher called lid_button in
/etc/acpid/events/ with the following two lines. Any time you add
an event you have to restart acpid with /etc/rc.d/rc.acpid restart
to make it load the new rule.
event=button[/]lid LID.*[13579bdf]$ action=/usr/local/sbin/hibernateWhat this does is it catches every button/lid event that ends in the odd HEX numbers and runs the hibernate script for you. The first time I did this I put in the even numbers which caused it to hibernate whenever I opened the lid! :P
At this point it's probably wise to take a look at the hibernate.conf file in
/etc/hibernate and to read the man page and related documents on
the suspend2 site if you haven't already. Two entries I use are OnSuspend and
OnResume which allow you to run commands just before suspending or
just after resuming. You can also do other nice things like unload and load
modules, unmount and mount file systems, and up and down network cards.
The hibernate.conf file is nice to take care of those odd things that make suspend/resume mess up. For instance, when I would resume and try to use the wireless network my system would lock. So I figured I'd have to kill dhcpcd, down eth1, and unload the airo and related modules just before suspend. I change wireless networks so I didn't choose to bring it all back up on resume but manually run a script to bring up the wireless depending on where I am. Here is the script for those who care. It's a modified version of a script I was given by someone I can't remember from the #suspend2 channel on freenode.
#/bin/bash
#
# chkconfig: 10 90 9
# description: Starts and stops the wireless interface.
#ESSID=<essid>
case $2 in
home)
ESSID=<home_essid>
#MY_ADDR=10.1.1.2
#NET_MASK=255.0.0.0
DHCP=1
WEP=<home_wep_key>
#WPA_SUPP=1
#GATEWAY=10.1.1.1
#FIREWALL_ARGS=
#CHANNEL=2
#AP=00:0f:b5:53:da:ac
;;
work)
ESSID=<work_essid>
#MY_ADDR=10.1.1.2
#NET_MASK=255.0.0.0
DHCP=1
WEP=<work_wep_key>
#WPA_SUPP=1
#GATEWAY=10.1.1.1
#FIREWALL_ARGS=
#CHANNEL=2
#AP=00:0f:b5:53:da:ac
;;
*)
ESSID=Any
#ESSID=LCA2005
#MY_ADDR=172.24.2.169
#NET_MASK=255.255.0.0
#GATEWAY=172.24.0.1
#DNS=172.24.0.1
#FIREWALL_ARGS=
#CHANNEL=1
DHCP=1
#AP=00:90:4C:60:00:2A
#RATE=
esac
stop() {
echo Removing modules...
/sbin/dhcpcd -k eth1
/sbin/ifconfig eth1 down
/sbin/rmmod airo
/sbin/rmmod aes_i586
#cardctl eject
#cat /etc/resolv.conf.backup > /etc/resolv.conf
}
start() {
echo Loading interface...
#cardctl insert
/sbin/modprobe airo
sleep 5
if [[ $ESSID ]]; then
echo /sbin/iwconfig eth1 essid $ESSID
/sbin/iwconfig eth1 essid $ESSID
#sleep 5
fi
if [[ $WEP ]]; then
echo /sbin/iwconfig eth1 key restricted $WEP
/sbin/iwconfig eth1 key restricted $WEP
#sleep 5
fi
#echo /sbin/iwconfig eth1 nwid CunninghamHome
#/sbin/iwconfig eth1 nwid CunninghamHome
if [[ $CHANNEL ]]; then
echo /sbin/iwconfig eth1 channel $CHANNEL
/sbin/iwconfig eth1 channel $CHANNEL
#sleep 5
fi
if [[ $AP ]]; then
echo /sbin/iwconfig eth1 ap $AP
/sbin/iwconfig eth1 ap $AP
#sleep 5
fi
if [[ $RATE ]]; then
echo /sbin/iwconfig eth1 rate 54M
/sbin/iwconfig eth1 rate 54M
/sbin/iwconfig eth1 rate fixed
#sleep 5
fi
echo /sbin/ifconfig eth1 up
/sbin/ifconfig eth1 up
if [[ $AP ]]; then
iwconfig eth1 ap $AP
#sleep 5
fi
if [[ $WPA_SUPP ]] ; then
echo Starting wpa supplicant
/usr/bin/wpa_supplicant -c/etc/wpa_supplicant.conf -Dmadwifi -ieth1 -B -w
#sleep 5
fi
#/usr/bin/wpa_supplicant -c/etc/wpa_supplicant.conf -Dmadwifi -ieth1 -B
if [[ $DHCP ]]; then
sleep 2
echo /sbin/dhcpcd -d -t 60 eth1
/sbin/dhcpcd -d -t 60 eth1
#/sbin/route add default gw 10.1.1.1
#/etc/rc.d/rc.firewall fdjkl
else
/sbin/ifconfig eth1 $MY_ADDR netmask $NET_MASK
/sbin/route add default gw $GATEWAY
#cat /etc/resolv.conf > /etc/resolv.conf.backup
echo "nameserver $DNS" > /etc/resolv.conf
fi
#/etc/rc.d/rc.firewall $FIREWALL_ARGS
#iptables -A POSTROUTING -t nat -o eth1 -j MASQUERADE
}
restart() {
stop
/sbin/cardctl insert
start
}
case $1 in
start)
start
;;
open)
start-open
;;
stop)
stop
;;
restart)
restart
;;
*)
echo "Unrecognised option: $1"
exit 1
esac
exit $?
This script isn't clean yet but it works for now. Assuming the script is
called "wifi" then wifi stop brings down the wireless, wifi
start brings up the wireless in a generic way, and wifi start
home brings up the wireless with the settings for home.
One other quick trick I picked up somewhere is to put OnSuspend 20
/opt/kde/bin/dcop --all-users --all-sessions kdesktop KScreensaverIface
lock somewhere in your hibernate.conf file to have KDE lock all sessions
on suspend.
In addition to the suspend to disk I did a few other things to prolong the life of my battery. I deviated away slightly from my "don't depend on the window manager" rule and used klaptop for now to have the system automatically suspend to RAM after 5 minutes of idle time when on battery. This is really only temporary until I find out a way to watch the keyboard and mouse for activity.
All I needed to do was catch two more ACPI events. One when the AC was
removed, and the other when it was plugged back in, and have it run a script to
set various power saving options. To do this I used the same tactic of
tail -f /var/log/acpid to see what event codes were generated then
made the rules and scripts. In /etc/acpid/ I made two separate
scripts on called ac.sh for when the system is running on AC and
the other called onbattery.sh for when AC is unplugged. The
contents of ac.sh is:
# Sets the CPU to 1.8 GHz when on wall AC # echo -n 1800000 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed # # Set hard drive sleep time to 20 minutes hdparm -S 240 /dev/hda # # Turn off laptop mode and wifi power management sysctl -w vm.laptop_mode=0 iwconfig eth1 power offMy system only supports two CPU speeds, 1.8GHz and 1.2GHz. When I change to AC I want it running full speed and that's what the first line does. Then I set the hard drive sleep time to 20 minutes, turn off kernel laptop mode, and then turn of wifi power management. I don't fully understand the sysctl command but supposedly what this does is change the way the disk cache is flushed. In laptop mode it flushes less often causing the hard drive to spin up fewer times thus using less battery.
onbattery.sh is similar but it sets the CPU frequency to 1.2GHz,
hard drive sleep time to 5 minutes, turns on laptop mode, and sets up wifi
power saving.
# Sets the CPU to 1.2 GHz when on battery # echo -n 1200000 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed # # Set Hard Drive sleep to 5 minutes hdparm -S 60 /dev/hda # # Turn on laptop mode and set the wifi timeout and wake up periods sysctl -w vm.laptop_mode=1 iwconfig eth1 power timeout 30 iwconfig eth1 power period 20Remember, for the speedstep to work I had to load the speedstep_ich module.
I just created those scripts and put them in /etc/acpid and then
created two rules to catch the battery and ac
events, then to run the scripts. To make the rules just make two new files in
/etc/acpid/events one that contains:
event=ac_adapter AC 00000080 00000001 action=/etc/acpi/ac.shand another than has:
event=ac_adapter AC 00000080 00000000 action=/etc/acpi/onbattery.shThe event files don't need to be executable. They can't be hidden. And to make them take effect you have to
/etc/rc.d/rc.acpid restart. So
far, with these settings I get nearly as long of battery life in Linux as I
did in Windows.
To do this I initially tried to have klaptop called hibernate for me. I had setup sudo to allow anyone on the system to run the hibernate script without having to give the password. But, no matter what I did it never ran. I figured there had to be an easy way to have something run in the background that didn't depend on KDE. A bit of Googling got me this:
#!/bin/bash
LIMIT="1600" ## Suspend if battery level drops below this (in mAh/mWh)
SLEEP="60" ## Seconds between each battery level check
BAT="BAT0" ## Part of path: /proc/acpi/battery/BAT/
HIBERNATE="/usr/local/sbin/hibernate" ## command used to suspend
#HIBERNATE="echo 4 > /proc/acpi/sleep"
ONBATTERY="/etc/acpi/onbattery.sh"
ONAC="/etc/acpi/ac.sh"
while [ true ]; do
if [ -e "/proc/acpi/battery/$BAT/state" ]; then
PRESENT=$(/bin/sed -ne "/present:/{s/^present:[ ]*\([a-z]*\)$/\1/p;q}" /proc/acpi/battery/$BAT/state)
#echo $PRESENT
if [ "$PRESENT" = "yes" ]; then
STATE=$(/bin/sed -ne "/charging state:/{s/^charging state:[ ]*\([a-zA-Z]*\)$/\1/p;q}" /proc/acpi/battery/$BAT/state)
BATTERY=$(/bin/sed -ne "/remaining capacity:/{s/^remaining capacity:[ ]*\([0-9]*\) m[WA]h$/\1/p;q}" /proc/acpi/battery/$BAT/state)
#echo $BATTERY
#echo $STATE
if [ "$BATTERY" -lt "$LIMIT" ] && [ "$STATE" = "discharging" ]; then
## Comment out the following line if you don't
## want to log the event to system log:
logger "Battery at ${BATTERY} mWh. Suspending to disk."
#echo "Battery at ${BATTERY} mWh. Suspending to disk."
## Suspend:
"$HIBERNATE"
elif [ "$STATE" = "discharging" ]; then
# Run script to set powersave settings while on battery
"$ONBATTERY"
elif [ "$STATE" = "charging" ] || [ "$STATE" = "charged" ] ; then
# Run script to set power settings while on AC
"$ONAC"
fi
fi
fi
sleep ${SLEEP}s
done
What this script does is checks the battery state in /proc/acpi/battery every
60 seconds and
runs the hibernate script for you if the battery gets below the level you set as
critical in the $LIMIT variable.
On final thing I noticed though is that when you hibernate while plugged in, unplug the power while hibernated, then wake the system that it never gets the event to run the on battery script. This left the system in a not power saving mode when it needed to be. The original script above didn't have the last two elif statements. I put those in to check the battery and run the power saving scripts as needed. So the worst case would be that the system would not be in power save mode for the max of one minute.
Just call the script whatever you want and set it to run at start up in
whatever way works for your distribution. For Slackware I called it
rc.batstate, put it in /etc/rc.d/ and made an entry in
rc.local to run it in the background on start up.
The utility to use is called Khotswap that sits in your system tray in KDE. The
author also has a Gnome applet for it and it has a command line only version of
itself just called hotswap and an X generic one called
xhotswap.
There really isn't to much to say about the video on this laptop. It uses a Radeon Mobility 7500 and X.org has a driver for it. Just use the generic Radeon driver and it works pretty well. Glxgears gives me about 250 FPS which is more than enough for my purposes. This is a work laptop after all.
As I've said elsewhere in this document I've heard of problems with suspending to disk when using the official Radeon drivers from ATI but I can say from experience that the X.org one works perfectly for both suspend to ram and suspend to disk. Well, at least it works for me.
Networking just worked for me. The 2.6.13 (and I think the 2.4.x) kernel support both the Intel Pro 100/VE and the AIRONET Wireless Cisco cards that are integrated into my system. If for some reason they aren't working for you just make sure the modules are loaded. For the Intel load 'eepro100' and for the Aironet load 'airo'. Like I said though, these just worked right after the Slackware install. Of course, if you have WEP or whatever on your network you have to use iwconfig to set the essid and WEP key.
From everything I've read all versions of the Thinkpad T30 have a winmodem of some sort. In my case it's an integrated Intel modem whose lspci output is:
00:1f.6 Modem: Intel Corporation 82801CA/CAM AC'97 Modem Controller (rev 02)I've searched and it seems that not every T30 has the same model, but every reference indicates that they are winmodems. So if your modem doesn't match the lspci output above just visit Linmodems.org and follow their instructions. That's all I did to get this one working.
If your modem is exactly the same then just do the following:
snd_intel8x0m module slmodemd --alsa -c USA hw:1 & and it should tell you what
device to use. Then use kppp, wxdial, or whatever utility you choose to
configure the modem and establish a connection. I used kppp since I'm running
KDE on this laptop anyway but I'll probably switch to wxdial eventually just to
have one less thing depending on KDE.Since I only use dial up as a very last resort I figured it doesn't make sense to have the module loaded and slmodemd running all the time. So I came up with this little script to setup the modem device and take it back down when not needed.
#/bin/bash
stop() {
echo "/bin/killall slmodemd"
/bin/killall slmodemd
sleep 5
/sbin/rmmod snd_intel8x0m
}
start() {
echo "/sbin/modprobe/snd-intel8x0m"
/sbin/modprobe snd-intel8x0m
echo "/usr/bin/slmodemd --alsa -c USA hw:1 &"
/usr/bin/slmodemd --alsa -c USA hw:1 &
}
restart() {
stop
start
}
case $1 in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
*)
start
;;
esac
exit $?
This has to be run as root. If you need to run it often maybe adding it to
sudo would make sense.