Level: Intermediate Nathan Harrington (harrington.nathan@gmail.com), Programmer, IBM
07 Nov 2006 Place your computer on the leading edge of cathartic interfaces by modifying the kernel to reset your Linux® laptop automatically when shaken during a kernel panic. Implement a shake-detection algorithm in the kernel and user space to perform automatic shutdowns and restarts when certain kinetic conditions are met.
In 2003, IBM® started selling ThinkPad laptop computers with integrated accelerometers and associated software for a commercial operating systems to protect the hard disks when the ThinkPad is dropped. Enterprising hackers from IBM and elsewhere have worked to develop modules for the Linux kernel to take advantage of these sensors. On-screen display orientation, desktop switching, even game control and real-time 3-D models of the tilt of the laptop are now available.
In mid-2006, knock-based commands for Linux laptops became available with user-space Perl scripts (as opposed to C-based code buried in kernel space), allowing users to run arbitrary commands based on specific knock sequences. This article describes the process of modifying the Linux kernel to add an oft-demanded feature: feedback on physical input. When the Linux kernel panics, the user can shake the computer (or perform any number of other developer-configurable physical movements of the laptop), and the machine will reset.
Also covered are methods for performing a normal shutdown in nonpanic mode. For example, if the user inadvertently places the computer in a laptop bag still turned on, we want the computer to detect a normal walking or driving motion and turn the computer off.
Requirements
Hardware
Many IBM ThinkPads manufactured in 2003 and later sport hdaps hardware. If you are unsure of your hardware configuration, check out the technical details on Lenovo's Web site. You need a ThinkPad for this code to function. Some MacBooks have the accelerometers and the same general methods available to access them through the kernel. However, the code here was not tested on Apple hardware and was developed and tested on two IBM ThinkPad T42p models. See Resources for links on how to find ThinkPad hardware that will support your desire to get physical with your laptop.
Software
This article assumes familiarity with the kernel build process, as well as experience with the inconsistencies among distributions when it comes to kernel compilation. For an introduction to the kernel build process, as well as some great examples on how to get started, see Resources.
As of kernel V2.6.15, the hdaps driver is included in the Linux kernel. Grab the latest kernel for the sake of simplicity. For ease of development and administration, this article was developed on Fedora Core V5. The following instructions used to set up the kernel build environment are specific to Fedora Core, but the general principles are applicable to any Linux distribution.
Kernel development setup
Kernel configuration, compilation, and testing
To modify the kernel, follow the instructions from the excellent release notes. Open a Web browser and start following the instructions in section 8.6: "Preparing for Kernel Development." When you get to section 2, you may have a problem with the second command: su -c 'yumdownloader --source kernel' . If the command does not pull down the kernel-2.6.15-1.2054_FC5.src.rpm package for you, use wget to get it with the command: wget ftp://ftp.linux.ncsu.edu/pub/fedora/linux/core/5/source/SRPMS/kernel-2.6.15-1.2054_FC5.src.rpm .
When you get to step 5, select the basic i686 default config with the command: cp configs/kernel-2.6.15-i686.config .config . Make sure to change the EXTRAVERSION section in the makefile from -prep to -1.2054_FC5 . Update the build configuration with make oldconfig . Install the kernel development modules, as well with the command su -c "yum install kernel-devel" . This will be used for compilation of our panic instigation module.
We have now completed the relevant portions of the kernel configuration from the Fedora Core V5 Release Notes document. The remaining steps are standard to any kernel build process. I recommend building and installing your new kernel, modules, and RAM disk setup now to make sure things are working as expected. You can skip the following steps and head right to the kernel modification section if you are confident about your new kernel configuration.
Build your new kernel with the make command. When the kernel is built successfully, copy it to the /boot directory with the command su -c "cp ./arch/i386/boot/bzImage /boot/vmlinuz-2.6.15hdaps" . You'll need to build your modules with the command su -c "make modules_install" . The final build step is to create a RAM disk for the hdaps kernel with the command su -c "/sbin/mkinitrd hdapsInitrd.img 2.6.15-1.2054_FC5" . Copy this new RAM disk to the boot area with the command su -c "cp hdapsInitrd.img /boot/" . As root, update the grub.conf file with the following lines:
Listing 1. grub configuration
title Fedora Core (2.6.15hdaps)
root (hd0,0)
kernel /vmlinuz-2.6.15hdaps ro root=/dev/VolGroup00/LogVol00 rhgb quiet
initrd /hdapsInitrd.img
|
Modification of panic.c and hdaps.c
Now you are ready to start some shake-and-bake kernel hacking. Make sure you are in the ~/rpmbuild/BUILD/kernel-2.6.15/linux-2.6.15.i686 directory. The first priority is to include the hdaps module as a built-in component of the kernel, so it will be ready to provide for shake detection anywhere in the machine's run modes.
Use the command make menuconfig and select Device Drivers > Hardware Monitoring Support. Type a Y to include the Hardware Monitoring Support module since the hdaps module is dependent on it. Scroll to the bottom of the list and place another Y next to the IBM Hard Drive Active Protection System (hdaps) entry. Back out of the menus and save your configuration.
Open the file drivers/hwmon/hdaps.c and add the following text to the includes section: #include <linux/reboot.h> . Also add the new subroutine below directly after the hdaps_read_pair subroutine:
Listing 2. Full panicShake subroutine from hdaps.c
/*
* panicShake - reboot the machine if shaken
*/
extern void panicShake(void)
{
int ret, x, y; // return value and x,y from hdaps
int int baseX = -5000; // off scale default values
int baseY = -5000;
int totalDev = 0; // running total of deviations from rest (shaking total)
int devThreshold = 4000; // larger threshold for more shaking
int dimShiftX = 150; // in case your users shake more in a certain dimension
int dimShiftY = 150;
while(1)
{
ret = hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y);
if (!ret)
{
if( x != 0 && y != 0 )
{
// if its a successful read and not a zero read
if( baseX == -5000 )
{
baseX = x;
baseY = y;
}
if( abs(baseX - x) > dimShiftX || abs(baseY - y) > dimShiftY )
{
totalDev += abs(baseX - x);
totalDev += abs(baseY - y);
}
if( totalDev > devThreshold )
{
printk(KERN_EMERG "ok, ok! you're shaking my substrate - restarting");
emergency_restart();
}
}//if not a zero value
}//if successful read of hdaps data
}//infinite while
}//panicShake
|
With our shake-detection routine in place, we need to call it upon system panic. Open the kernel/panic.c file and place a call to the panicShake(); subroutine directly before the panicBlink conditional section. Issue a make command. Let's review the shake-detection code while your kernel is being rebuilt. First, we set up some variables:
Listing 3. panicShake variables
int ret, x, y; // return value and x,y from hdaps
int baseX = -5000; // off scale default values
int baseY = -5000;
int totalDev = 0; // running total of deviations from rest (shaking total)
int devThreshold = 4000; // larger threshold for more shaking
int dimShiftX = 150; // in case your users shake more in a certain dimension
int dimShiftY = 150;
|
Of particular note is the deviation threshold parameter and the dimensional shift parameters. These may require tuning based on the unique characteristics of the motion you are trying to detect. For example, if you feel the urge to shake your computer like you were performing a basketball pass, try decreasing the dimShiftX parameter to more easily detect motions perpendicular to the screen of the computer. Conversely, if your shaking impulse instigates motions of a sawing-timber nature, consider decreasing the dimShiftY parameter to quickly pick up on shaking moments of frustration parallel to the screen and reset your computer before further damage can occur.
The selections of 150 for dimensional parameters and 4000 for the total deviation are designed to acquire the typical shaking motions of an average user. For immediate response to inputs, try decreasing the dimensional shift parameters to 10 or less and a total deviation parameter of 10 or less. These values will cause other types of inputs to be immediately recognized, such as bashing the keyboard in frustration or smacking the display case in abject disgust.
Next, we consider the infinite loop statement and conditionals.
Listing 4. panicShake hdaps read and base setup
while(1)
{
ret = hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y);
if (!ret)
{
if( x != 0 && y != 0 )
{
// if its a successful read and not a zero read
if( baseX == -5000 )
{
baseX = x;
baseY = y;
}
|
The code works as follows: For the rest of time, read the current accelerometer readings from the hdaps sensor. Frequently, the read will be unsuccessful or both values will be equal to 0,0, which is unusable data. We need to avoid these spurious 0,0 readings, as about one out of every 10 readings at any orientation of the sensor will be 0,0 -- invalid data, indeed. If it's our first "successful" read, set the base parameters equal to the first x and y values. This will allow us to more robustly detect shaking or other movements if the panic occurred while the machine was on a nonflat surface, such as a person's knees.
The remainder of the subroutine is the implementation of the simple shake-detection algorithm.
Listing 5. panicShake shake detection
if( abs(baseX - x) > dimShiftX || abs(baseY - y) > dimShiftY )
{
totalDev += abs(baseX - x);
totalDev += abs(baseY - y);
baseX = x;
baseY = y;
}
if( totalDev > devThreshold )
{
printk(KERN_EMERG "ok, ok! you're shaking my substrate - restarting");
emergency_restart();
}
}//if not a zero value
}//if successful read of hdaps data
}//infinite while
|
If the dimensional shift in either direction is greater than our previously set threshold, increment the total deviation by the amount moved in both directions. Then set the current base to the existing level of acceleration. This repeated reinitialization of the base values will require the user to continuously exceed the dimensional shift values to increment the total deviation detected. This is useful for allowing the user to move and store the ThinkPad in panic mode as they track down the systems administrator. Remove the reinitialization assignments if you want simply setting the ThinkPad on its side or tilting it and holding it there to trigger a restart.
Testing your panicShake() kernel
To initiate a panic, we need to call the panic subroutine in the kernel. Create the following makefile: obj-m := panicCall.o , which will be used by the program panicCall.c on compilation:
Listing 6. panicCall.c kernel module source
/*
* panicCall.c - Instigate a kernel panic
*/
#include <linux/module.h> /* Needed by all modules */
#lincude <linux/kernel.h> /* Needed for KERN_INFO */
static char *pMesgStr = "PANIC SHAKE AND BAKE";
int init_module(void)
{
printk(KERN_INFO,"panicCall module loaded\n");
panic(pMesgStr);
return(0);
}
void cleanup_module(void)
{
printk(KERN_INFO,"panicCall module unloaded, beyond possible");
}
|
As root, compile the panicCall module with the command make -C /lib/modules/$(uname -r)/build SUBDIRS=$PWD modules . You now have a module you can call to trigger a panic with the command insmod panicCall.ko . Reboot if you haven't already (to activate your hdaps panic shake-enabled kernel) and run insmod panicCall.ko . You should see something similar to the following:
Listing 7. kernel panic stack strace
panicCall: module license 'unspecified' taints kernel.
Kernel panic - not syncing: PANIC SHAKE AND BAKE ACTIVE
[<c011a32e>] panic+0x3e/0x174 [<f8a97017>] init_module+0xb/0xc [panicCall]
[<c013050a>] sys_init_module+0x1382/0x1514 [<c0152413>] do_sync_read+0xb8/0xf3
[<c012a17f>] autoremove_wake_function+0x0/0x2d [<c01c0672>]
_atomic_dec_and_lock+0x22/0x2c
[<c0169c32>] mntput_no_expire+0x11/0x6d [<c0102bc1>] syscall_call+0x7/0xb
|
Now pick up your computer and give it a good shake, and it will print out the "shaking substrate" message and perform a restart. If you're the sort who would rather not shake a potentially active disk drive, issue the following commands as root:
Listing 8. RAM disk creation, module copying
mkdir /tmp/ramdisk0
mke2fs /dev/ram0
mount /dev/ram0 /tmp/ramdisk0/
cp /root/panicCall.ko /tmp/ramdisk0/
cp /sbin/insmod /tmp/ramdisk0/
|
You now have the two files you need to insert a module into the kernel located on a RAM disk. Update the /etc/init.d/halt script with the following section, directly below the fsck check section and directly above the halt execute section:
Listing 9. modification of /etc/init.d/halt
echo "disks now mounted in readonly mode, spin down in 5 seconds";
/sbin/hdparm -S 1 /dev/hda
echo "spin down hda called, waiting 10 seconds";
sleep 10
echo "calling panic from ramdisk location";
/tmp/ramdisk0/insmod /tmp/ramdisk0/panicCall.ko
|
Execute the command init 0 as root to send the machine into shutdown mode. Prior to the call for power off, the machine will load the panic instigator module into the kernel, and the shake detection routine will be called. If you listen to the hard disk as the system is shutting down, you can hear the distinct meatier-than-usual click, followed by the Doppler disk decrease as the arm is placed in the rest "stowage" position and the platters spun down. After about five more seconds, the panic module will be executed from RAM disk while the physical disk heads are still parked. Now you can shake your ThinkPad to your heart's content without concern for your disk's health.
User space shutdown and motion detection
Many an IT administrator has yearned for the ability to know the physical history of hardware. With the same simple shake-detection algorithm above, a Perl script, and a monitoring policy, administrators will be better able to track the status of their hardware. For example, we will use the Perl script below to shut down the machine gracefully when shaken by the user. Modifications can be made easily to send an e-mail, flash the "ThinkLight," or play a sound file based on the user's manipulation of the ThinkPad.
Listing 10. Perl script for shake detection, Part 1
#!/usr/bin/perl -w
# shakeShutdown.pl - shutdown (or other command) when the computer is shaken
use strict;
my $file = "/sys/devices/platform/hdaps/position";
my $baseX = -5000;
my $baseY = -5000;
my $totalDev = 0;
if( @ARGV != 1 ){ die "specify a threshold value" }
my $devThreshold = $ARGV[0];
my $dimShiftX = 150;
my $dimShiftY = 150;
while(1)
{
open(HD,"$file") or die "can't open file";
my $line = <HD>;
chomp($line);
$line =~ s/\(//g;
$line =~ s/\)//g;
$line =~ s/\,/ /g;
my( $x, $y ) = split " ", $line;
|
As you can see, the initial program setup is nearly identical to the hdaps kernel code. The regular expressions and split commands simply change the x and y values from (5,4) to 5 and 4. The remainder of the program is also virtually identical:
Listing 11. Perl script for shake detection, Part 2
if( $x != 0 >> $y != 0 )
{
if( $baseX == -5000 )
{
$baseX = $x;
$baseY = $y;
}
if( abs($baseX - $x) > $dimShiftX || abs($baseY - $y) > $dimShiftY )
{
$totalDev += abs($baseX -$x);
$totalDev += abs($baseY -$y);
$baseX = $x;
$baseY = $y;
}
if( $totalDev > $devThreshold )
{
print "threshold passed $totalDev\n";
my $res=`/sbin/shutdown -h 1`;
}
}
close(HD);
}
|
Note the shutdown -h 1 command. This will give the user 60 seconds to change his ways and issue a shutdown abort. Change this command to run your favorite mailer, and you can let your systems administrator know when a user is abusing equipment. Log messages to the system log or sound the PC speaker for instant user feedback about getting physical. Run the script with the command perl shakeShutdown.lp 1000 . The much-smaller deviation threshold is due to the decreased number of reads-per-time interval of the accelerometer compared with the number of reads-per-time interval in kernel space.
Modifying the dimensional shift parameters and the deviation threshold can provide for additional useful monitoring of physical activities outside kernel space. For example, to acquire a "walking" behavior, set the dimensional shift parameters to around 20 and set the deviation threshold to around 5000. This will pick up about 63 dual-axis dimensional shifts, consistent with the laptop being running while in an over-the-shoulder typical laptop bag. After this long walk is detected (as opposed to a short walk between cube and conference), the machine will enter shutdown to prevent overheating while the airflow is confined in the carrying case. Modify the dimensional shift parameters to be highly sensitive, and every significant bump, drop, or shake can be recorded.
Conclusion
With these simple algorithms for user space and kernel-level code, you now have the ability to detect, log, and respond to the full range of physical input from the user. With these code examples, you can do everything from modifying hard-drive performance parameters based on computed altitude from continuous acceleration to tracking the number of steps from your cubicle to the conference room and mail it to your space planner.
Resources Learn
Get products and technologies
-
The Fedora Core V5 distribution includes hdaps in the kernel.
-
Innovate your next open source development project with
IBM trial software, available for download or on DVD.
Discuss
About the author | | | Nathan Harrington is a programmer at IBM currently working with Linux and resource-locating technologies. |
Rate this page
|