
From: Bart Samwel <bart@samwel.tk>

This patch contains the following updates to the laptop mode docs:

* Various improvements to the text, added a hint and a caveat.

* Control script changes to support XFS (in 2.4, 2.6 with lm_* sysctl
  values and 2.6 without lm_* sysctl values).
 
* Control script changes to reset the the "atime" mount option to the
  value listed in fstab when laptop mode is stopped.

* Bug fix in dslm.c.---



---

 25-akpm/Documentation/laptop-mode.txt |  204 ++++++++++++++++++++++++++++++----
 1 files changed, 180 insertions(+), 24 deletions(-)

diff -puN Documentation/laptop-mode.txt~laptop-mode-doc-update Documentation/laptop-mode.txt
--- 25/Documentation/laptop-mode.txt~laptop-mode-doc-update	2004-04-04 16:40:33.014162448 -0700
+++ 25-akpm/Documentation/laptop-mode.txt	2004-04-04 16:40:33.020161536 -0700
@@ -3,6 +3,7 @@ How to conserve battery power using lapt
 
 Document Author: Bart Samwel (bart@samwel.tk)
 Date created: January 2, 2004
+Last modified: April 3, 2004
 
 Introduction
 ------------
@@ -40,15 +41,20 @@ The value -S 4 means 20 seconds idle tim
 now only spin up when a disk cache miss occurs, or at least once every 10
 minutes to write back any pending changes.
 
-To stop laptop_mode, remount your filesystems with regular commit intervals
-(e.g., 5 seconds), and run "laptop_mode stop".
+To stop laptop_mode, run "laptop_mode stop".
 
 
 Caveats
 -------
 
 * The downside of laptop mode is that you have a chance of losing up
-  to 10 minutes of work. If you cannot afford this, don't use it!
+  to 10 minutes of work. If you cannot afford this, don't use it! It's
+  wise to turn OFF laptop mode when you're almost out of battery --
+  although this will make the battery run out faster, at least you'll
+  lose less work when it actually runs out. I'm still looking for someone
+  to submit instructions on how to turn off laptop mode when battery is low,
+  e.g., using ACPI events. I don't have a laptop myself, so if you do and
+  you care to contribute such instructions, please do.
 
 * Most desktop hard drives have a very limited lifetime measured in spindown
   cycles, typically about 50.000 times (it's usually listed on the spec sheet).
@@ -63,6 +69,14 @@ Caveats
 * If you have your filesystems listed as type "auto" in fstab, like I did, then
   the control script will not recognize them as filesystems that need remounting.
 
+* If you have XFS, make SURE that you set the XFS_HZ value in the control script
+  correctly, to the value of HZ of your running kernel. Laptop mode will not
+  work correctly if it is set too low, and you may lose data if it is set too
+  high. The reason for this problem is that XFS does not export its sysctl
+  variables in centisecs (like most other subsystems do) but in "jiffies",
+  which is an internal kernel measure. Once this is fixed things will get better.
+
+
 The details
 -----------
 
@@ -88,7 +102,11 @@ If you want to find out which process ca
 gather information by setting the flag /proc/sys/vm/block_dump. When this flag
 is set, Linux reports all disk read and write operations that take place, and
 all block dirtyings done to files. This makes it possible to debug why a disk
-needs to spin up, and to increase battery life even more.
+needs to spin up, and to increase battery life even more. The output of
+block_dump is written to the kernel output, and it can be retrieved using
+"dmesg". When you use block_dump, you may want to turn off klogd, otherwise
+the output of block_dump will be logged, causing disk activity that is not
+normally there.
 
 If 10 minutes is too much or too little downtime for you, you can configure
 this downtime as follows. In the control script, set the MAX_AGE value to the
@@ -132,6 +150,10 @@ Tips & Tricks
 
   The supplied script does this.
 
+* In syslog.conf, you can prefix entries with a dash ``-'' to omit syncing the
+  file after every logging. When you're using laptop-mode and your disk doesn't
+  spin down, this is a likely culprit.
+
 
 Control script
 --------------
@@ -139,7 +161,7 @@ Control script
 Please note that this control script works for the Linux 2.4 and 2.6 series.
 
 --------------------CONTROL SCRIPT BEGIN------------------------------------------
-#!/bin/sh
+#! /bin/sh
 
 # start or stop laptop_mode, best run by a power management daemon when
 # ac gets connected/disconnected from a laptop
@@ -148,28 +170,120 @@ Please note that this control script wor
 #
 # Contributors to this script:   Kiko Piris
 #				 Bart Samwel
+#				 Micha Feigin
+#				 Andrew Morton
 #				 Dax Kelson
+#
 # Original Linux 2.4 version by: Jens Axboe
 
+# Remove an option (the first parameter) of the form option=<number> from
+# a mount options string (the rest of the parameters).
 parse_mount_opts () {
+	OPT="$1"
+	shift
 	echo "$*"			| \
-	sed 's/commit=[0-9]*//g'	| \
+	sed 's/.*/,&,/'			| \
+	sed 's/,'"$OPT"'=[0-9]*,/,/g'	| \
 	sed 's/,,*/,/g'			| \
 	sed 's/^,//'			| \
 	sed 's/,$//'			| \
 	cat -
 }
 
+# Remove an option (the first parameter) without any arguments from
+# a mount option string (the rest of the parameters).
+parse_nonumber_mount_opts () {
+	OPT="$1"
+	shift
+	echo "$*" 			| \
+	sed 's/.*/,&,/'			| \
+	sed 's/,'"$OPT"',/,/g'		| \
+	sed 's/,,*/,/g'			| \
+	sed 's/^,//'			| \
+	sed 's/,$//'			| \
+	cat -
+}
+
+# Find out the state of a yes/no option (e.g. "atime"/"noatime") in
+# fstab for a given filesystem, and use this state to replace the
+# value of the option in another mount options string. The device
+# is the first argument, the option name the second, and the default
+# value the third. The remainder is the mount options string.
+#
+# Example:
+# parse_yesno_opts_wfstab /dev/hda1 atime atime defaults,noatime
+#
+# If fstab contains, say, "rw" for this filesystem, then the result
+# will be "defaults,atime".
+parse_yesno_opts_wfstab () {
+	L_DEV=$1
+	shift
+	OPT=$1
+	shift
+	DEF_OPT=$1
+	shift
+	L_OPTS="$*"
+	PARSEDOPTS1="$(parse_nonumber_mount_opts $OPT $L_OPTS)"
+	PARSEDOPTS1="$(parse_nonumber_mount_opts no$OPT $PARSEDOPTS1)"
+	# Watch for a default atime in fstab
+	FSTAB_OPTS="$(cat /etc/fstab | sed 's/  / /g' | grep ^\ *"$L_DEV " | awk '{ print $4 }')"
+	if [ -z "$(echo "$FSTAB_OPTS" | grep "$OPT")" ] ; then
+		# option not specified in fstab -- choose the default.
+		echo "$PARSEDOPTS1,$DEF_OPT"
+	else
+		# option specified in fstab: extract the value and use it
+		if [ -z "$(echo "$FSTAB_OPTS" | grep "no$OPT")" ] ; then
+			# no$OPT not found -- so we must have $OPT.
+			echo "$PARSEDOPTS1,$OPT"
+		else
+			echo "$PARSEDOPTS1,no$OPT"
+		fi
+	fi
+}
+
+# Find out the state of a numbered option (e.g. "commit=NNN") in
+# fstab for a given filesystem, and use this state to replace the
+# value of the option in another mount options string. The device
+# is the first argument, and the option name the second. The
+# remainder is the mount options string in which the replacement
+# must be done.
+#
+# Example:
+# parse_mount_opts_wfstab /dev/hda1 commit defaults,commit=7
+#
+# If fstab contains, say, "commit=3,rw" for this filesystem, then the
+# result will be "rw,commit=3".
+parse_mount_opts_wfstab () {
+	L_DEV=$1
+	shift
+	OPT=$1
+	shift
+	L_OPTS="$*"
+
+	PARSEDOPTS1="$(parse_mount_opts $OPT $L_OPTS)"
+	# Watch for a default commit in fstab
+	FSTAB_OPTS="$(cat /etc/fstab | sed 's/	/ /g' | grep ^\ *"$L_DEV " | awk '{ print $4 }')"
+	if [ -z "$(echo "$FSTAB_OPTS" | grep "$OPT=")" ] ; then
+		# option not specified in fstab: set it to 0
+		echo "$PARSEDOPTS1,$OPT=0"
+	else
+		# option specified in fstab: extract the value, and use it
+		echo -n "$PARSEDOPTS1,$OPT="
+		echo "$FSTAB_OPTS"	| \
+		sed 's/.*/,&,/'		| \
+		sed 's/.*,'"$OPT"'=//'	| \
+		sed 's/,.*//'		| \
+		cat -
+	fi
+}
+
 KLEVEL="$(uname -r | cut -c1-3)"
 case "$KLEVEL" in
-	"2.4")
-		true
-		;;
-	"2.6")
+	"2.4"|"2.6")
 		true
 		;;
 	*)
-		echo "Unhandled kernel level: $KLEVEL ('uname -r' = '$(uname -r)')"
+		echo "Unhandled kernel version: $KLEVEL ('uname -r' = '$(uname -r)')"
 		exit 1
 		;;
 esac
@@ -199,7 +313,13 @@ DEF_AGE=30
 DEF_UPDATE=5
 DEF_DIRTY_BACKGROUND_RATIO=10
 DEF_DIRTY_RATIO=40
+DEF_XFS_AGE_BUFFER=15
+DEF_XFS_SYNC_INTERVAL=30
 
+# This must be adjusted manually to the value of HZ in the running kernel,
+# until the XFS people change their external interfaces to work in centisecs
+# like the rest of the external world. Unfortunately this cannot be automated. :(
+XFS_HZ=1000
 
 if [ ! -e /proc/sys/vm/laptop_mode ]; then
 	echo "Kernel is not patched with laptop_mode patch."
@@ -214,7 +334,25 @@ fi
 case "$1" in
 	start)
 		AGE=$((100*$MAX_AGE))
+		XFS_AGE=$(($XFS_HZ*$MAX_AGE))
 		echo -n "Starting laptop_mode"
+
+		if [ -d /proc/sys/vm/pagebuf ] ; then
+			# This only needs to be set, not reset -- it is only used when
+			# laptop mode is enabled.
+			echo $XFS_AGE > /proc/sys/vm/pagebuf/lm_flush_age
+			echo $XFS_AGE > /proc/sys/fs/xfs/lm_sync_interval
+		elif [ -f /proc/sys/fs/xfs/lm_age_buffer ] ; then
+			# The same goes for these.
+			echo $XFS_AGE > /proc/sys/fs/xfs/lm_age_buffer
+			echo $XFS_AGE > /proc/sys/fs/xfs/lm_sync_interval
+		elif [ -f /proc/sys/fs/xfs/age_buffer ] ; then
+			# But not for these -- they are also used in normal
+			# operation.
+			echo $XFS_AGE > /proc/sys/fs/xfs/age_buffer
+			echo $XFS_AGE > /proc/sys/fs/xfs/sync_interval
+		fi
+
 		case "$KLEVEL" in
 			"2.4")
 				echo "1"				> /proc/sys/vm/laptop_mode
@@ -232,26 +370,36 @@ case "$1" in
 			cat /etc/mtab | while read DEV MP FST OPTS DUMP PASS ; do
 				PARSEDOPTS="$(parse_mount_opts "$OPTS")"
 				case "$FST" in
-					"ext3")		mount $DEV -t $FST $MP -o remount,$PARSEDOPTS,commit=$MAX_AGE,noatime ;;
-					"reiserfs")	mount $DEV -t $FST $MP -o remount,$PARSEDOPTS,commit=$MAX_AGE,noatime ;;
-					"xfs")		mount $DEV -t $FST $MP -o remount,$PARSEDOPTS,commit=$MAX_AGE,noatime ;;
+					"ext3"|"reiserfs")
+						PARSEDOPTS="$(parse_mount_opts commit "$OPTS")"
+						mount $DEV -t $FST $MP -o remount,$PARSEDOPTS,commit=$MAX_AGE,noatime
+						;;
+					"xfs")
+						mount $DEV -t $FST $MP -o remount,$OPTS,noatime
+						;;
 				esac
-				blockdev --setra $((READAHEAD * 2)) $DEV
 			done
 		fi
+		if [ -b $DEV ] ; then
+			blockdev --setra $(($READAHEAD * 2)) $DEV
+		fi
 		echo "."
 		;;
 	stop)
 		U_AGE=$((100*$DEF_UPDATE))
 		B_AGE=$((100*$DEF_AGE))
 		echo -n "Stopping laptop_mode"
+		echo "0" > /proc/sys/vm/laptop_mode
+		if [ -f /proc/sys/fs/xfs/age_buffer ] && [ ! -f /proc/sys/fs/xfs/lm_age_buffer ] ; then
+			# These need to be restored though, if there are no lm_*.
+			echo "$(($XFS_HZ*$DEF_XFS_AGE_BUFFER))" 	> /proc/sys/fs/xfs/age_buffer
+			echo "$(($XFS_HZ*$DEF_XFS_SYNC_INTERVAL))" 	> /proc/sys/fs/xfs/sync_interval
+		fi
 		case "$KLEVEL" in
 			"2.4")
-				echo "0"				> /proc/sys/vm/laptop_mode
 				echo "30 500 0 0 $U_AGE $B_AGE 60 20 0"	> /proc/sys/vm/bdflush
 				;;
 			"2.6")
-				echo "0"				> /proc/sys/vm/laptop_mode
 				echo "$U_AGE"				> /proc/sys/vm/dirty_writeback_centisecs
 				echo "$B_AGE"				> /proc/sys/vm/dirty_expire_centisecs
 				echo "$DEF_DIRTY_RATIO"			> /proc/sys/vm/dirty_ratio
@@ -260,19 +408,27 @@ case "$1" in
 		esac
 		if [ $DO_REMOUNTS -eq 1 ]; then
 			cat /etc/mtab | while read DEV MP FST OPTS DUMP PASS ; do
-				PARSEDOPTS="$(parse_mount_opts "$OPTS")"
+				# Reset commit and atime options to defaults.
 				case "$FST" in
-					"ext3")		mount $DEV -t $FST $MP -o remount,$PARSEDOPTS ;;
-					"reiserfs")	mount $DEV -t $FST $MP -o remount,$PARSEDOPTS ;;
-					"xfs")		mount $DEV -t $FST $MP -o remount,$PARSEDOPTS ;;
+					"ext3"|"reiserfs")
+						PARSEDOPTS="$(parse_mount_opts_wfstab $DEV commit $OPTS)"
+						PARSEDOPTS="$(parse_yesno_opts_wfstab $DEV atime atime $PARSEDOPTS)"
+						mount $DEV -t $FST $MP -o remount,$PARSEDOPTS
+						;;
+					"xfs")
+						PARSEDOPTS="$(parse_yesno_opts_wfstab $DEV atime atime $OPTS)"
+						mount $DEV -t $FST $MP -o remount,$PARSEDOPTS
+						;;
 				esac
-				blockdev --setra 256 $DEV
 			done
 		fi
+		if [ -b $DEV ] ; then
+			blockdev --setra 256 $DEV
+		fi
 		echo "."
 		;;
 	*)
-		echo "$0 {start|stop}"
+		echo "Usage: $0 {start|stop}"
 		;;
 
 esac
@@ -379,7 +535,7 @@ int check_powermode(int fd)
     } else {
 	state = (args[2] == 255) ? 1 : 0;
     }
-    D(printf(" drive state is:  %s\n", state));
+    D(printf(" drive state is:  %d\n", state));
 
     return state;
 }

_
