1 diff -Nbaur linux-2.6.1/Documentation/laptop-mode.txt linux-2.6.1-withlaptopmode/Documentation/laptop-mode.txt
2 --- linux-2.6.1/Documentation/laptop-mode.txt 1970-01-01 01:00:00.000000000 +0100
3 +++ linux-2.6.1-withlaptopmode/Documentation/laptop-mode.txt 2004-01-10 10:47:14.000000000 +0100
5 +How to conserve battery power using laptop-mode
6 +-----------------------------------------------
8 +Document Author: Bart Samwel (bart@samwel.tk)
9 +Date created: January 2, 2004
14 +Laptopmode is used to minimize the time that the hard disk needs to be spun up,
15 +to conserve battery power on laptops. It has been reported to cause significant
34 +If you just want to use it, run the laptop_mode control script (which is included
35 +at the end of this document) as follows:
39 +Then set your harddisk spindown time to a relatively low value with hdparm:
43 +The value -S 4 means 20 seconds idle time before spindown. Your harddisk will
44 +now only spin up when a disk cache miss occurs, or at least once every 10
45 +minutes to write back any pending changes.
47 +To stop laptop_mode, remount your filesystems with regular commit intervals
48 +(e.g., 5 seconds), and run "laptop_mode stop".
54 +* The downside of laptop mode is that you have a chance of losing up
55 + to 10 minutes of work. If you cannot afford this, don't use it!
57 +* Most desktop hard drives have a very limited lifetime measured in spindown
58 + cycles, typically about 50.000 times (it's usually listed on the spec sheet).
59 + Check your drive's rating, and don't wear down your drive's lifetime if you
62 +* If you mount some of your ext3/reiserfs filesystems with the -n option, then
63 + the control script will not be able to remount them correctly. You must set
64 + DO_REMOUNTS=0 in the control script, otherwise it will remount them with the
65 + wrong options -- or it will fail because it cannot write to /etc/mtab.
67 +* If you have your filesystems listed as type "auto" in fstab, like I did, then
68 + the control script will not recognize them as filesystems that need remounting.
73 +Laptop-mode is controlled by the flag /proc/sys/vm/laptop_mode. When this
74 +flag is set, any physical disk read operation (that might have caused the
75 +hard disk to spin up) causes Linux to flush all dirty blocks. The result
76 +of this is that after a disk has spun down, it will not be spun up anymore
77 +to write dirty blocks, because those blocks had already been written
78 +immediately after the most recent read operation
80 +To increase the effectiveness of the laptop_mode strategy, the laptop_mode
81 +control script increases dirty_expire_centisecs and dirty_writeback_centisecs in
82 +/proc/sys/vm to about 10 minutes (by default), which means that pages that are
83 +dirtied are not forced to be written to disk as often. The control script also
84 +changes the dirty background ratio, so that background writeback of dirty pages
85 +is not done anymore. Combined with a higher commit value (also 10 minutes) for
86 +ext3 or ReiserFS filesystems (also done automatically by the control script),
87 +this results in concentration of disk activity in a small time interval which
88 +occurs only once every 10 minutes, or whenever the disk is forced to spin up by
89 +a cache miss. The disk can then be spun down in the periods of inactivity.
91 +If you want to find out which process caused the disk to spin up, you can
92 +gather information by setting the flag /proc/sys/vm/block_dump. When this flag
93 +is set, Linux reports all disk read and write operations that take place, and
94 +all block dirtyings done to files. This makes it possible to debug why a disk
95 +needs to spin up, and to increase battery life even more.
97 +If 10 minutes is too much or too little downtime for you, you can configure
98 +this downtime as follows. In the control script, set the MAX_AGE value to the
99 +maximum number of seconds of disk downtime that you would like. You should
100 +then set your filesystem's commit interval to the same value. The dirty ratio
101 +is also configurable from the control script.
103 +If you don't like the idea of the control script remounting your filesystems
104 +for you, you can change DO_REMOUNTS to 0 in the script.
106 +Thanks to Kiko Piris, the control script can be used to enable laptop mode on
107 +both the Linux 2.4 and 2.6 series.
113 +* Bartek Kania reports getting up to 50 minutes of extra battery life (on top
114 + of his regular 3 to 3.5 hours) using very aggressive power management (hdparm
115 + -B1) and a spindown time of 5 seconds (hdparm -S1).
117 +* You can spin down the disk while playing MP3, by setting the disk readahead
118 + to 8MB (hdparm -a 16384). Effectively, the disk will read a complete MP3 at
119 + once, and will then spin down while the MP3 is playing. (Thanks to Bartek
126 +Please note that this control script works for the Linux 2.4 and 2.6 series.
128 +--------------------CONTROL SCRIPT BEGIN------------------------------------------
131 +# start or stop laptop_mode, best run by a power management daemon when
132 +# ac gets connected/disconnected from a laptop
134 +# install as /sbin/laptop_mode
136 +# Contributors to this script: Kiko Piris
139 +# Original Linux 2.4 version by: Jens Axboe
141 +parse_mount_opts () {
143 + sed 's/.*/,&,/g' | \
144 + sed 's/,commit=[0-9]*,/,/g' | \
145 + sed 's/,,*/,/g' | \
151 +KLEVEL="$(uname -r | cut -c1-3)"
160 + echo "Unhandled kernel level: $KLEVEL ('uname -r' = '$(uname -r)')"
165 +# Shall we remount journaled fs. with appropiate commit interval? (1=yes)
168 +# age time, in seconds. should be put into a sysconfig file
171 +# Allowed dirty ratio, in pct. should be put into a sysconfig file as well.
174 +# kernel default dirty buffer age
177 +DEF_DIRTY_BACKGROUND_RATIO=10
181 +if [ ! -e /proc/sys/vm/laptop_mode ]; then
182 + echo "Kernel is not patched with laptop_mode patch."
186 +if [ ! -w /proc/sys/vm/laptop_mode ]; then
187 + echo "You do not have enough privileges to enable laptop_mode."
193 + AGE=$((100*$MAX_AGE))
194 + echo -n "Starting laptop_mode"
197 + echo "1" > /proc/sys/vm/laptop_mode
198 + echo "30 500 0 0 $AGE $AGE 60 20 0" > /proc/sys/vm/bdflush
201 + echo "1" > /proc/sys/vm/laptop_mode
202 + echo "$AGE" > /proc/sys/vm/dirty_writeback_centisecs
203 + echo "$AGE" > /proc/sys/vm/dirty_expire_centisecs
204 + echo "$DIRTY_RATIO" > /proc/sys/vm/dirty_ratio
205 + echo "$DIRTY_RATIO" > /proc/sys/vm/dirty_background_ratio
208 + if [ $DO_REMOUNTS -eq 1 ]; then
209 + cat /etc/mtab | while read DEV MP FST OPTS DUMP PASS ; do
210 + PARSEDOPTS="$(parse_mount_opts "$OPTS")"
212 + "ext3") mount $DEV -t $FST $MP -o remount,$PARSEDOPTS,commit=$MAX_AGE ;;
213 + "reiserfs") mount $DEV -t $FST $MP -o remount,$PARSEDOPTS,commit=$MAX_AGE ;;
214 + "xfs") mount $DEV -t $FST $MP -o remount,$PARSEDOPTS,commit=$MAX_AGE ;;
221 + U_AGE=$((100*$DEF_UPDATE))
222 + B_AGE=$((100*$DEF_AGE))
223 + echo -n "Stopping laptop_mode"
226 + echo "0" > /proc/sys/vm/laptop_mode
227 + echo "30 500 0 0 $U_AGE $B_AGE 60 20 0" > /proc/sys/vm/bdflush
230 + echo "0" > /proc/sys/vm/laptop_mode
231 + echo "$U_AGE" > /proc/sys/vm/dirty_writeback_centisecs
232 + echo "$B_AGE" > /proc/sys/vm/dirty_expire_centisecs
233 + echo "$DEF_DIRTY_RATIO" > /proc/sys/vm/dirty_ratio
234 + echo "$DEF_DIRTY_BACKGROUND_RATIO" > /proc/sys/vm/dirty_background_ratio
237 + if [ $DO_REMOUNTS -eq 1 ]; then
238 + cat /etc/mtab | while read DEV MP FST OPTS DUMP PASS ; do
239 + PARSEDOPTS="$(parse_mount_opts "$OPTS")"
241 + "ext3") mount $DEV -t $FST $MP -o remount,$PARSEDOPTS ;;
242 + "reiserfs") mount $DEV -t $FST $MP -o remount,$PARSEDOPTS ;;
243 + "xfs") mount $DEV -t $FST $MP -o remount,$PARSEDOPTS ;;
250 + echo "Usage: $0 {start|stop}"
256 +--------------------CONTROL SCRIPT END--------------------------------------------
262 +Dax Kelson submitted this so that the ACPI acpid daemon will
263 +kick off the laptop_mode script and run hdparm.
265 +---------------------------/etc/acpi/events/ac_adapter BEGIN-------------------------------------------
267 +action=/etc/acpi/actions/battery.sh
268 +---------------------------/etc/acpi/events/ac_adapter END-------------------------------------------
270 +---------------------------/etc/acpi/actions/battery.sh BEGIN-------------------------------------------
274 +# cat /proc/acpi/processor/CPU0/throttling for more info
278 +# spindown time for HD (man hdparm for valid values)
279 +# I prefer 2 hours for acad and 20 seconds for batt
283 +# ac/battery event handler
285 +status=`awk '/^state: / { print $2 }' /proc/acpi/ac_adapter/AC/state`
289 + echo "Setting HD spindown to 2 hours"
290 + /sbin/laptop-mode stop
291 + /sbin/hdparm -S $ACAD_HD /dev/hda > /dev/null 2>&1
292 + /sbin/hdparm -B 255 /dev/hda > /dev/null 2>&1
293 + #echo -n $ACAD_CPU:$ACAD_THR > /proc/acpi/processor/CPU0/limit
297 + echo "Setting HD spindown to 20 seconds"
298 + /sbin/laptop-mode start
299 + /sbin/hdparm -S $BATT_HD /dev/hda > /dev/null 2>&1
300 + /sbin/hdparm -B 1 /dev/hda > /dev/null 2>&1
301 + #echo -n $BATT_CPU:$BATT_THR > /proc/acpi/processor/CPU0/limit
305 +---------------------------/etc/acpi/actions/battery.sh END-------------------------------------------
310 +Bartek Kania submitted this, it can be used to measure how much time your disk
311 +spends spun up/down.
313 +---------------------------dslm.c BEGIN-------------------------------------------
315 + * Simple Disk SLeep Monitor
317 + * Licenced under the GPL
327 +#include <sys/ioctl.h>
328 +#include <linux/hdreg.h>
338 +/* Check if the disk is in powersave-mode
339 + * Most of the code is stolen from hdparm.
340 + * 1 = active, 0 = standby/sleep, -1 = unknown */
341 +int check_powermode(int fd)
343 + unsigned char args[4] = {WIN_CHECKPOWERMODE1,0,0,0};
346 + if (ioctl(fd, HDIO_DRIVE_CMD, &args)
347 + && (args[0] = WIN_CHECKPOWERMODE2) /* try again with 0x98 */
348 + && ioctl(fd, HDIO_DRIVE_CMD, &args)) {
349 + if (errno != EIO || args[0] != 0 || args[1] != 0) {
350 + state = -1; /* "unknown"; */
352 + state = 0; /* "sleeping"; */
354 + state = (args[2] == 255) ? 1 : 0;
356 + D(printf(" drive state is: %s\n", state));
361 +char *state_name(int i)
363 + if (i == -1) return "unknown";
364 + if (i == 0) return "sleeping";
365 + if (i == 1) return "active";
367 + return "internal error";
370 +char *myctime(time_t time)
372 + char *ts = ctime(&time);
373 + ts[strlen(ts) - 1] = 0;
378 +void measure(int fd)
384 + time_t curr_time = 0;
386 + time_t active_time = 0;
387 + time_t sleep_time = 0;
388 + time_t unknown_time = 0;
389 + time_t total_time = 0;
393 + printf("Starting measurements\n");
395 + last_state = check_powermode(fd);
396 + start_time = last_time = time(0);
397 + printf(" System is in state %s\n\n", state_name(last_state));
401 + curr_state = check_powermode(fd);
403 + if (curr_state != last_state || endit) {
405 + curr_time = time(0);
406 + time_diff = curr_time - last_time;
408 + if (last_state == 1) active_time += time_diff;
409 + else if (last_state == 0) sleep_time += time_diff;
410 + else unknown_time += time_diff;
412 + last_state = curr_state;
413 + last_time = curr_time;
415 + printf("%s: State-change to %s\n", myctime(curr_time),
416 + state_name(curr_state));
419 + changes--; /* Compensate for SIGINT */
421 + total_time = time(0) - start_time;
422 + printf("\nTotal running time: %lus\n", curr_time - start_time);
423 + printf(" State changed %d times\n", changes);
425 + tmp = (float)sleep_time / (float)total_time * 100;
426 + printf(" Time in sleep state: %lus (%.2f%%)\n", sleep_time, tmp);
427 + tmp = (float)active_time / (float)total_time * 100;
428 + printf(" Time in active state: %lus (%.2f%%)\n", active_time, tmp);
429 + tmp = (float)unknown_time / (float)total_time * 100;
430 + printf(" Time in unknown state: %lus (%.2f%%)\n", unknown_time, tmp);
440 + puts("usage: dslm [-w <time>] <disk>");
444 +int main(int ac, char **av)
448 + int settle_time = 60;
450 + /* Parse the simple command-line */
453 + else if (ac == 4) {
454 + settle_time = atoi(av[2]);
459 + if (!(fd = open(disk, O_RDONLY|O_NONBLOCK))) {
460 + printf("Can't open %s, because: %s\n", disk, strerror(errno));
465 + printf("Waiting %d seconds for the system to settle down to "
466 + "'normal'\n", settle_time);
467 + sleep(settle_time);
469 + puts("Not waiting for system to settle down");
471 + signal(SIGINT, ender);
479 +---------------------------dslm.c END---------------------------------------------
480 diff -Nbaur linux-2.6.1/drivers/block/ll_rw_blk.c linux-2.6.1-withlaptopmode/drivers/block/ll_rw_blk.c
481 --- linux-2.6.1/drivers/block/ll_rw_blk.c 2004-01-09 20:29:05.000000000 +0100
482 +++ linux-2.6.1-withlaptopmode/drivers/block/ll_rw_blk.c 2004-01-09 20:29:39.000000000 +0100
484 #include <linux/completion.h>
485 #include <linux/slab.h>
486 #include <linux/swap.h>
487 +#include <linux/writeback.h>
489 static void blk_unplug_work(void *data);
490 static void blk_unplug_timeout(unsigned long data);
491 @@ -2302,6 +2303,15 @@
492 mod_page_state(pgpgout, count);
494 mod_page_state(pgpgin, count);
496 + if (unlikely(block_dump)) {
497 + char b[BDEVNAME_SIZE];
498 + printk("%s(%d): %s block %Lu on %s\n",
499 + current->comm, current->pid,
500 + (rw & WRITE) ? "WRITE" : "READ",
501 + (unsigned long long)bio->bi_sector, bdevname(bio->bi_bdev,b));
504 generic_make_request(bio);
507 @@ -2593,6 +2603,12 @@
508 disk_stat_add(disk, write_ticks, duration);
512 + * schedule the writeout of pending dirty data when the disk is idle.
513 + * (postpone writeback until system is quiescent again.)
515 + if (unlikely(laptop_mode))
516 + disk_is_spun_up(1);
517 disk_stat_inc(disk, reads);
518 disk_stat_add(disk, read_ticks, duration);
520 diff -Nbaur linux-2.6.1/fs/buffer.c linux-2.6.1-withlaptopmode/fs/buffer.c
521 --- linux-2.6.1/fs/buffer.c 2004-01-09 20:29:10.000000000 +0100
522 +++ linux-2.6.1-withlaptopmode/fs/buffer.c 2004-01-09 20:29:45.000000000 +0100
523 @@ -855,10 +855,13 @@
524 struct buffer_head *bh = head;
527 - if (buffer_uptodate(bh))
528 + if (buffer_uptodate(bh)) {
529 set_buffer_dirty(bh);
531 + if (unlikely(block_dump))
532 + printk("%s(%d): dirtied buffer\n", current->comm, current->pid);
536 bh = bh->b_this_page;
537 } while (bh != head);
539 diff -Nbaur linux-2.6.1/include/linux/sysctl.h linux-2.6.1-withlaptopmode/include/linux/sysctl.h
540 --- linux-2.6.1/include/linux/sysctl.h 2004-01-09 20:29:10.000000000 +0100
541 +++ linux-2.6.1-withlaptopmode/include/linux/sysctl.h 2004-01-09 20:29:46.000000000 +0100
543 VM_SWAPPINESS=19, /* Tendency to steal mapped memory */
544 VM_LOWER_ZONE_PROTECTION=20,/* Amount of protection of lower zones */
545 VM_MIN_FREE_KBYTES=21, /* Minimum free kilobytes to maintain */
546 + VM_LAPTOP_MODE=22, /* vm laptop mode */
547 + VM_BLOCK_DUMP=23, /* block dump mode */
551 diff -Nbaur linux-2.6.1/include/linux/writeback.h linux-2.6.1-withlaptopmode/include/linux/writeback.h
552 --- linux-2.6.1/include/linux/writeback.h 2003-12-24 05:19:46.000000000 +0100
553 +++ linux-2.6.1-withlaptopmode/include/linux/writeback.h 2004-01-04 12:10:58.000000000 +0100
555 * mm/page-writeback.c
557 int wakeup_bdflush(long nr_pages);
558 +void disk_is_spun_up(int postpone_writeback);
560 -/* These 5 are exported to sysctl. */
561 +/* These are exported to sysctl. */
562 extern int dirty_background_ratio;
563 extern int vm_dirty_ratio;
564 extern int dirty_writeback_centisecs;
565 extern int dirty_expire_centisecs;
566 +extern int block_dump;
567 +extern int laptop_mode;
571 diff -Nbaur linux-2.6.1/kernel/sysctl.c linux-2.6.1-withlaptopmode/kernel/sysctl.c
572 --- linux-2.6.1/kernel/sysctl.c 2003-12-24 05:19:46.000000000 +0100
573 +++ linux-2.6.1-withlaptopmode/kernel/sysctl.c 2003-12-24 06:24:53.000000000 +0100
575 .strategy = &sysctl_intvec,
579 + .ctl_name = VM_LAPTOP_MODE,
580 + .procname = "laptop_mode",
581 + .data = &laptop_mode,
582 + .maxlen = sizeof(laptop_mode),
584 + .proc_handler = &proc_dointvec,
585 + .strategy = &sysctl_intvec,
589 + .ctl_name = VM_BLOCK_DUMP,
590 + .procname = "block_dump",
591 + .data = &block_dump,
592 + .maxlen = sizeof(block_dump),
594 + .proc_handler = &proc_dointvec,
595 + .strategy = &sysctl_intvec,
601 diff -Nbaur linux-2.6.1/mm/page-writeback.c linux-2.6.1-withlaptopmode/mm/page-writeback.c
602 --- linux-2.6.1/mm/page-writeback.c 2003-12-24 05:19:46.000000000 +0100
603 +++ linux-2.6.1-withlaptopmode/mm/page-writeback.c 2004-01-04 17:50:03.000000000 +0100
605 #include <linux/smp.h>
606 #include <linux/sysctl.h>
607 #include <linux/cpu.h>
608 +#include <linux/quotaops.h>
611 * The maximum number of pages to writeout in a single bdflush/kupdate
614 int dirty_expire_centisecs = 30 * 100;
617 + * Flag that makes the machine dump writes/reads and block dirtyings.
622 + * Flag that puts the machine in "laptop mode".
626 /* End of sysctl-exported parameters */
630 if (nr_reclaimable + ps.nr_writeback <= dirty_thresh)
633 - if (!writeback_in_progress(bdi) && nr_reclaimable > background_thresh)
634 + if (unlikely(laptop_mode) && pages_written > 0) {
636 + * Schedule full writeout to happen soon. We don't postpone
637 + * previously scheduled full writeouts, otherwise a writing
638 + * process throttled by balance_dirty_pages will be able to
639 + * postpone the full writeout indefinitely, keeping the disk
640 + * spun up as a result.
642 + disk_is_spun_up(0);
645 + if (!unlikely(laptop_mode) && !writeback_in_progress(bdi) && nr_reclaimable > background_thresh)
646 pdflush_operation(background_writeout, 0);
650 oldest_jif = jiffies - (dirty_expire_centisecs * HZ) / 100;
652 next_jif = start_jif + (dirty_writeback_centisecs * HZ) / 100;
654 + wbc.older_than_this = NULL;
655 nr_to_write = ps.nr_dirty + ps.nr_unstable +
656 (inodes_stat.nr_inodes - inodes_stat.nr_unused);
657 while (nr_to_write > 0) {
660 if (time_before(next_jif, jiffies + HZ))
661 next_jif = jiffies + HZ;
664 + sync_filesystems(0);
667 if (dirty_writeback_centisecs)
668 mod_timer(&wb_timer, next_jif);
674 +static struct timer_list laptop_mode_wb_timer;
676 +static void laptop_mode_wb_timer_fn(unsigned long unused)
678 + mod_timer(&wb_timer, jiffies);
682 + * We've spun up the disk and we're in laptop mode: schedule writeback
683 + * of all dirty data in 5 seconds.
685 + * Laptop mode writeback will be delayed if it has previously been
686 + * scheduled to occur within 5 seconds. That way, the writeback will
687 + * only be triggered if the system is truly quiet again.
689 +void disk_is_spun_up(int postpone_writeback)
691 + if (postpone_writeback || !timer_pending(&laptop_mode_wb_timer))
692 + mod_timer(&laptop_mode_wb_timer, jiffies + 5 * HZ);
696 static void wb_timer_fn(unsigned long unused)
698 if (pdflush_operation(wb_kupdate, 0) < 0)
701 wb_timer.function = wb_timer_fn;
702 add_timer(&wb_timer);
704 + init_timer(&laptop_mode_wb_timer);
705 + laptop_mode_wb_timer.data = 0;
706 + laptop_mode_wb_timer.function = laptop_mode_wb_timer_fn;
709 register_cpu_notifier(&ratelimit_nb);
712 __mark_inode_dirty(mapping->host,
715 + if (unlikely(block_dump))
716 + printk("%s(%d): dirtied page\n", current->comm, current->pid);