#!/sbin/runscript # Copyright 1999-2004 Gentoo Technologies, Inc. # Distributed under the terms of the GNU General Public License v2 # $Header: # start or stop laptop_mode, best run by a power management daemon when # ac gets connected/disconnected from a laptop # # Contributors to this script: # Kiko Piris # Bart Samwel # Micha Feigin # Andrew Morton # Herve Eychenne # Dax Kelson # # Original Linux 2.4 version by: Jens Axboe # # Adapted to Gentoo Linux by Johannes Ballé # Updated by Dennis Nienhüser depend() { need localmount # make sure standby settings by hdparm init script are overridden use hdparm } setDefaults() { # Don't raise an error if the config file is incomplete # set defaults instead: # Maximum time, in seconds, of hard drive spindown time that you are # confortable with. Worst case, it's possible that you could lose this # amount of work if your battery fails you while in laptop mode. MAX_AGE=${MAX_AGE:-'600'} # Read-ahead, in kilobytes READAHEAD=${READAHEAD:-'4096'} # Shall we remount journaled fs. with appropiate commit interval? (1=yes) DO_REMOUNTS=${DO_REMOUNTS:-'1'} # And shall we add the "noatime" option to that as well? (1=yes) DO_REMOUNT_NOATIME=${DO_REMOUNT_NOATIME:-'1'} # Shall we adjust the idle timeout on a hard drive? DO_HD=${DO_HD:-'1'} # Adjust idle timeout on which hard drive? HD="${HD:-'/dev/hda'}" # spindown time for HD (hdparm -S values) AC_HD=${AC_HD:-'244'} BATT_HD=${BATT_HD:-'4'} # Dirty synchronous ratio. At this percentage of dirty pages the process which # calls write() does its own writeback DIRTY_RATIO=${DIRTY_RATIO:-'40'} # cpu frequency scaling # See Documentation/cpu-freq/user-guide.txt for more info DO_CPU=${CPU_MANAGE:-'0'} CPU_MAXFREQ=${CPU_MAXFREQ:-'slowest'} # # Allowed dirty background ratio, in percent. Once DIRTY_RATIO has been # exceeded, the kernel will wake pdflush which will then reduce the amount # of dirty memory to dirty_background_ratio. Set this nice and low, so once # some writeout has commenced, we do a lot of it. # DIRTY_BACKGROUND_RATIO=${DIRTY_BACKGROUND_RATIO:-'5'} # kernel default dirty buffer age DEF_AGE=${DEF_AGE:-'30'} DEF_UPDATE=${DEF_UPDATE:-'5'} DEF_DIRTY_BACKGROUND_RATIO=${DEF_DIRTY_BACKGROUND_RATIO:-'10'} DEF_DIRTY_RATIO=${DEF_DIRTY_RATIO:-'40'} DEF_XFS_AGE_BUFFER=${DEF_XFS_AGE_BUFFER:-'15'} DEF_XFS_SYNC_INTERVAL=${DEF_XFS_SYNC_INTERVAL:-'30'} DEF_XFS_BUFD_INTERVAL=${DEF_XFS_BUFD_INTERVAL:-'1'} # This must be adjusted manually to the value of HZ in the running kernel # on 2.4, until the XFS people change their 2.4 external interfaces to work in # centisecs. This can be automated, but it's a work in progress that still needs # some fixes. On 2.6 kernels, XFS uses USER_HZ instead of HZ for external # interfaces, and that is currently always set to 100. So you don't need to # change this on 2.6. XFS_HZ=${XFS_HZ:-'100'} if [ $DO_REMOUNT_NOATIME -eq 1 ] ; then NOATIME_OPT=",noatime" fi } checkconfig() { KLEVEL="$(uname -r | { IFS='.' read a b c echo $a.$b } )" case "$KLEVEL" in "2.4"|"2.6") ;; *) echo "Unhandled kernel version: $KLEVEL ('uname -r' = '$(uname -r)')" >&2 exit 1 ;; esac if [ ! -e /proc/sys/vm/laptop_mode ] ; then echo "Kernel is not patched with laptop_mode patch." >&2 exit 1 fi if [ ! -w /proc/sys/vm/laptop_mode ] ; then echo "You do not have enough privileges to enable laptop_mode." >&2 exit 1 fi } # Remove an option (the first parameter) of the form option= from # a mount options string (the rest of the parameters). parse_mount_opts () { OPT="$1" shift echo ",$*," | sed \ -e 's/,'"$OPT"'=[0-9]*,/,/g' \ -e 's/,,*/,/g' \ -e 's/^,//' \ -e 's/,$//' } # 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 \ -e 's/,'"$OPT"',/,/g' \ -e 's/,,*/,/g' \ -e 's/^,//' \ -e 's/,$//' } # 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" OPT="$2" DEF_OPT="$3" shift 3 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="$(awk '$1 == "'$L_DEV'" { print $4 }' /etc/fstab)" if echo "$FSTAB_OPTS" | grep "$OPT" > /dev/null ; then # option specified in fstab: extract the value and use it if echo "$FSTAB_OPTS" | grep "no$OPT" > /dev/null ; then echo "$PARSEDOPTS1,no$OPT" else # no$OPT not found -- so we must have $OPT. echo "$PARSEDOPTS1,$OPT" fi else # option not specified in fstab -- choose the default. echo "$PARSEDOPTS1,$DEF_OPT" 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" OPT="$2" shift 2 L_OPTS="$*" PARSEDOPTS1="$(parse_mount_opts $OPT $L_OPTS)" # Watch for a default commit in fstab FSTAB_OPTS="$(awk '$1 == "'$L_DEV'" { print $4 }' /etc/fstab)" if echo "$FSTAB_OPTS" | grep "$OPT=" > /dev/null ; then # option specified in fstab: extract the value, and use it echo -n "$PARSEDOPTS1,$OPT=" echo ",$FSTAB_OPTS," | sed \ -e 's/.*,'"$OPT"'=//' \ -e 's/,.*//' else # option not specified in fstab: set it to 0 echo "$PARSEDOPTS1,$OPT=0" fi } deduce_fstype () { MP="$1" # My root filesystem unfortunately has # type "unknown" in /etc/mtab. If we encounter # "unknown", we try to get the type from fstab. cat /etc/fstab | grep -v '^#' | while read FSTAB_DEV FSTAB_MP FSTAB_FST FSTAB_OPTS FSTAB_DUMP FSTAB_DUMP ; do if [ "$FSTAB_MP" = "$MP" ]; then echo $FSTAB_FST exit 0 fi done } start() { ebegin "Starting laptop_mode" setDefaults checkconfig || return 1 AGE=$((100*$MAX_AGE)) XFS_AGE=$(($XFS_HZ*$MAX_AGE)) echo -n "Starting laptop_mode" if [ -d /proc/sys/vm/pagebuf ] ; then # (For 2.4 and early 2.6.) # 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 # (A couple of early 2.6 laptop mode patches had these.) # 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 # (2.6.6) # 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 elif [ -f /proc/sys/fs/xfs/age_buffer_centisecs ] ; then # (2.6.7 upwards) # And not for these either. These are in centisecs, # not USER_HZ, so we have to use $AGE, not $XFS_AGE. echo $AGE > /proc/sys/fs/xfs/age_buffer_centisecs echo $AGE > /proc/sys/fs/xfs/xfssyncd_centisecs echo 3000 > /proc/sys/fs/xfs/xfsbufd_centisecs fi case "$KLEVEL" in "2.4") echo 1 > /proc/sys/vm/laptop_mode echo "30 500 0 0 $AGE $AGE 60 20 0" > /proc/sys/vm/bdflush ;; "2.6") echo 5 > /proc/sys/vm/laptop_mode echo "$AGE" > /proc/sys/vm/dirty_writeback_centisecs echo "$AGE" > /proc/sys/vm/dirty_expire_centisecs echo "$DIRTY_RATIO" > /proc/sys/vm/dirty_ratio echo "$DIRTY_BACKGROUND_RATIO" > /proc/sys/vm/dirty_background_ratio ;; esac if [ $DO_REMOUNTS -eq 1 ]; then cat /etc/mtab | while read DEV MP FST OPTS DUMP PASS ; do PARSEDOPTS="$(parse_mount_opts "$OPTS")" if [ "$FST" = 'unknown' ]; then FST=$(deduce_fstype $MP) fi case "$FST" in "ext3"|"reiserfs") PARSEDOPTS="$(parse_mount_opts commit "$OPTS")" mount $DEV -t $FST $MP -o remount,$PARSEDOPTS,commit=$MAX_AGE$NOATIME_OPT ;; "xfs") mount $DEV -t $FST $MP -o remount,$OPTS$NOATIME_OPT ;; esac if [ -b $DEV ] ; then blockdev --setra $(($READAHEAD * 2)) $DEV fi done fi if [ $DO_HD -eq 1 ] ; then for THISHD in $HD ; do /sbin/hdparm -S $BATT_HD $THISHD > /dev/null 2>&1 /sbin/hdparm -B 1 $THISHD > /dev/null 2>&1 done fi if [ $DO_CPU -eq 1 -a -e /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq ]; then if [ $CPU_MAXFREQ = 'slowest' ]; then CPU_MAXFREQ=`cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq` fi echo $CPU_MAXFREQ > /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq fi eend 0 } stop() { ebegin "Stopping laptop mode" setDefaults 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 -a ! -f /proc/sys/fs/xfs/lm_age_buffer ] ; then # These need to be restored, 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 elif [ -f /proc/sys/fs/xfs/age_buffer_centisecs ] ; then # These need to be restored as well. echo $((100*$DEF_XFS_AGE_BUFFER)) > /proc/sys/fs/xfs/age_buffer_centisecs echo $((100*$DEF_XFS_SYNC_INTERVAL)) > /proc/sys/fs/xfs/xfssyncd_centisecs echo $((100*$DEF_XFS_BUFD_INTERVAL)) > /proc/sys/fs/xfs/xfsbufd_centisecs fi case "$KLEVEL" in "2.4") echo "30 500 0 0 $U_AGE $B_AGE 60 20 0" > /proc/sys/vm/bdflush ;; "2.6") 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 echo "$DEF_DIRTY_BACKGROUND_RATIO" > /proc/sys/vm/dirty_background_ratio ;; esac if [ $DO_REMOUNTS -eq 1 ] ; then cat /etc/mtab | while read DEV MP FST OPTS DUMP PASS ; do # Reset commit and atime options to defaults. if [ "$FST" = 'unknown' ]; then FST=$(deduce_fstype $MP) fi case "$FST" in "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 if [ -b $DEV ] ; then blockdev --setra 256 $DEV fi done fi if [ $DO_HD -eq 1 ] ; then for THISHD in $HD ; do /sbin/hdparm -S $AC_HD $THISHD > /dev/null 2>&1 /sbin/hdparm -B 255 $THISHD > /dev/null 2>&1 done fi if [ $DO_CPU -eq 1 -a -e /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq ]; then echo `cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq` > /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq fi eend 0 }