Difference between revisions of "Fan control scripts"

From ThinkWiki
Jump to: navigation, search
(init script for the comprehensive script)
(The comprehensive script: - moved to Code/tp-fancontrol)
Line 31: Line 31:
 
{{WARN|The list of temperature ranges used below is much more liberal than the rules used by the embedded controller firmware, and is derived mostly from anecdotal evidence, hunches and wishful thinking. It is also model-specific - see [[thermal sensors]].}}
 
{{WARN|The list of temperature ranges used below is much more liberal than the rules used by the embedded controller firmware, and is derived mostly from anecdotal evidence, hunches and wishful thinking. It is also model-specific - see [[thermal sensors]].}}
  
====The comprehensive script====
+
The script: {{CodeRef|tp-fancontrol}}
 
 
<pre>
 
#!/bin/bash
 
 
 
# tp-fancontrol 0.2.9 (http://thinkwiki.org/wiki/ACPI_fan_control_script)
 
# Provided under the GNU General Public License version 2 or later or
 
# the GNU Free Documentation License version 1.2 or later, at your option.
 
# See http://www.gnu.org/copyleft/gpl.html for the Warranty Disclaimer.
 
 
 
# This script dynamically controls fan speed on some ThinkPad models
 
# according to user-defined temperature thresholds.  It implements its
 
# own decision algorithm, overriding the ThinkPad embedded
 
# controller. It also implements a workaround for the fan noise pulse
 
# experienced every few seconds on some ThinkPads.
 
#
 
# Run 'tp-fancontrol --help' for options.
 
#
 
# For optimal fan behavior during suspend and resume, invoke
 
# "tp-fancontrol -u" during the suspend process.
 
#
 
# WARNING: This script relies on undocumented hardware features and
 
# overrides nominal hardware behavior. It may thus cause arbitrary
 
# damage to your laptop or data. Watch your temperatures!
 
#
 
# WARNING: The list of temperature ranges used below is much more liberal
 
# than the rules used by the embedded controller firmware, and is
 
# derived mostly from anecdotal evidence, hunches and wishful thinking.
 
# It is also model-specific (see http://thinkwiki.org/wiki/Thermal_sensors).
 
 
 
# Temperature ranges, per sensor:
 
# (min temperature: when to step up from 0-th fan level,
 
#  max temperature: when to step up to maximum fan level)
 
THRESHOLDS=( #  Sensor    ThinkPad model
 
            #            R51    T41/2  Z60t  43-26xx
 
# min  max  #  ---------- ------- -----  -----  ---------------------------
 
  50  70    #  EC 0x78    CPU    CPU    ?      CPU
 
  47  60    #  EC 0x79    miniPCI ?      ?      Between CPU and PCMCIA slot
 
  43  55    #  EC 0x7A    HDD    ?      ?      PCMCIA slot
 
  49  68    #  EC 0x7B    GPU    GPU    ?      GPU
 
  40  50    #  EC 0x7C    BAT    BAT    BAT    Sys BAT (front left of battery)
 
  45  55    #  EC 0x7D    n/a    n/a    n/a    UltraBay BAT
 
  37  47    #  EC 0x7E    BAT    BAT    BAT    Sys BAT (rear right of battery)
 
  45  55    #  EC 0x7F    n/a    n/a    n/a    UltraBay BAT
 
  45  60    #  EC 0xC0    ?      n/a    ?      Between northbridge and DRAM
 
  48  62    #  EC 0xC1    ?      n/a    ?      Southbridge (under miniPCI)
 
  50  65    #  EC 0xC2    ?      n/a    ?      Power circuitry (under CDC)
 
  47  60    #  HDAPS      HDAPS  HDAPS  HDAPS  HDAPS readout (same as EC 0x79)
 
)
 
 
 
 
 
LEVELS=(    0      2      4      7)  # Fan speed levels
 
ANTIPULSE=( 0      1      1      0)  # Prevent fan pulsing noise at this level
 
                                    # (reduces frequency of fan RPM updates)
 
 
 
OFF_THRESH_DELTA=3 # when gets this much cooler than 'min' above, may turn off fan
 
MIN_THRESH_SHIFT=0 # increase min thresholds by this much
 
MIN_WAIT=180 # minimum time (seconds) to spend in a given level before stepping down
 
 
 
IBM_ACPI=/proc/acpi/ibm
 
HDAPS_TEMP=/sys/bus/platform/drivers/hdaps/hdaps/temp1
 
LOGGER=/usr/bin/logger
 
INTERVAL=3        # sample+refresh interval
 
SETTLE_TIME=6    # wait this many seconds long before applying anti-pulsing
 
RESETTLE_TIME=600 # briefly disable anti-pulsing at every N seconds
 
SUSPEND_TIME=5    # seconds to sleep when receiving SIGUSR1
 
 
 
PID_FILE=/var/run/tp-fancontrol.pid
 
QUIET=false
 
DRY_RUN=false
 
DAEMONIZE=false
 
AM_DAEMON=false
 
KILL_DAEMON=false
 
SUSPEND_DAEMON=false
 
SYSLOG=false
 
 
 
usage() {
 
    echo "
 
Usage: $0 [OPTION]...
 
 
 
Available options:
 
  -s N  shift up temperature thresholds by N degrees
 
          (positive for quieter, negative for cooler)
 
  -t    test mode
 
  -q    quiet mode
 
  -d    daemon mode, go into background (implies -q)
 
  -l    log to syslog
 
  -k    kill already-running daemon
 
  -u    tell already-running daemon that the system is being suspended
 
  -p    pid file location for daemon mode, default: $PID_FILE
 
"
 
    exit 1;
 
}
 
 
 
while getopts 's:qtdlp:kuh' OPT; do
 
    case "$OPT" in
 
        s) # shift thresholds
 
            MIN_THRESH_SHIFT="$OPTARG"
 
            ;;
 
        t) # test mode
 
            DRY_RUN=true
 
            ;;
 
        q) # quiet mode
 
            QUIET=true
 
            ;;
 
        d) # go into background and daemonize
 
            DAEMONIZE=true
 
            ;;
 
        l) # log to syslog
 
            SYSLOG=true
 
            ;;
 
        p) # different pidfile
 
            PID_FILE="$OPTARG"
 
            ;;
 
        k) # kill daemon
 
            KILL_DAEMON=true
 
            ;;
 
        u) # suspend daemon
 
            SUSPEND_DAEMON=true
 
            ;;
 
        h) # short help
 
            usage
 
            ;;
 
        \?) # error
 
            usage
 
            ;;
 
    esac
 
done
 
[ $OPTIND -gt $# ] || usage  # no non-option args
 
 
 
# no logger found, no syslog capabilities
 
$SYSLOG && [ ! -x $LOGGER ] && SYSLOG=false || :
 
 
 
if $DRY_RUN; then
 
    echo "$0: Dry run, will not change fan state."
 
    QUIET=false
 
    DAEMONIZE=false
 
fi
 
 
 
thermometer() { # output list of temperatures
 
    # Base temperatures from ibm-acpi:
 
    [ -r $IBM_ACPI/thermal ] || { echo "$0: Cannot read $IBM_ACPI/thermal" 2>&1 ; exit 1; }
 
    read X Y1 Y2 Y3 Y4 Y5 Y6 Y7 Y8 Z < $IBM_ACPI/thermal
 
    Y="$Y1 $Y2 $Y3 $Y4 $Y5 $Y6 $Y7 $Y8"
 
    [ "$X" == "temperatures:" ] || { echo "$0: Bad temperatures: $X $Y $Z" >&2;  exit 1; }
 
    echo -n "$Y ";
 
    if [[ "$Z" == *\ *\ * ]]; then # ibm_acpi provided the 3 extra sensors from at EC offsets 0xC0 to 0xC2?
 
        echo -n "$Z "
 
    else
 
        [ -r $IBM_ACPI/ecdump ] || { echo "$0: Cannot read $IBM_ACPI/ecdump" 2>&1; exit 1; }
 
        perl -e 'm/^EC 0xc0: .(..) .(..) .(..) / and print hex($1)." ".hex($2)." ".hex($3)." " and exit 0 while <>; exit 1' < $IBM_ACPI/ecdump
 
    fi
 
    # HDAPS temperature (optional):
 
    if [ -r $HDAPS_TEMP ]; then
 
        Y="`cat $HDAPS_TEMP`"
 
        (( "$Y" > 100 )) || echo -n "$Y "  # the HDAPS readouts are nonsensical right after resume
 
    fi
 
    return 0
 
}
 
 
 
speedometer() { # output fan speed RPM
 
    sed -n 's/^speed:[ \t]*//p' $IBM_ACPI/fan
 
}
 
 
 
setlevel() { # set fan speed level
 
    $DRY_RUN || echo 0x2F $1 > $IBM_ACPI/ecdump
 
}
 
 
 
getlevel() { # get fan speed level
 
    perl -e 'm/^EC 0x20: .* .(..)$/ and print $1 and exit 0 while <>; exit 1' < $IBM_ACPI/ecdump
 
}
 
 
 
log() {
 
$QUIET || echo "> $*"
 
! $SYSLOG || $LOGGER -t "`basename $0`[$$]" "$*"
 
}
 
 
 
cleanup() { # clean up after work
 
    $AM_DAEMON && rm -f "$PID_FILE" 2> /dev/null
 
    log "Shutting down, switching to automatic fan control"
 
    $DRY_RUN || echo enable > $IBM_ACPI/fan
 
}
 
 
 
floor_div() {
 
    echo $(( (($1)+1000*($2))/($2) - 1000 ))
 
}
 
 
 
init_state() {
 
    IDX=0
 
    NEW_IDX=0
 
    START_TIME=0
 
    MAX_IDX=$(( ${#LEVELS[@]} - 1 ))
 
    SETTLE_LEFT=0
 
    RESETTLE_LEFT=0
 
    FIRST=true
 
    RESTART=false
 
}
 
 
 
control_fan() {
 
    # Enable the fan in default mode if anything goes wrong:
 
    set -e -E -u
 
    trap "cleanup; exit 2" HUP INT ABRT QUIT SEGV TERM
 
    trap "cleanup" EXIT
 
    trap "log 'Got SIGUSR1'; setlevel 0; RESTART=true; sleep $SUSPEND_TIME" USR1
 
 
 
    init_state
 
    log "Starting dynamic fan control"
 
 
 
    # Control loop:
 
    while true; do
 
        TEMPS=`thermometer`
 
        $QUIET || SPEED=`speedometer`
 
        $QUIET || ECLEVEL=`getlevel`
 
        NOW=`date +%s`
 
 
 
        # Calculate new level index by placing temperatures into Z-regions:
 
        # Z >= 2*I means "must be at index I or higher"
 
        # Z  = 2*I+1 is hysteresis: "don't step down if currently at I+1"
 
        # hence the Z-regions are, for d=(MAX-MIN)/(2*MAX_IDX-1) :
 
        #  Z=0:{-infty..MIN-d) Z=1:{MIN-d..MIN) Z=2:{MIN..MIN+d} Z=3:{MIN+d..MIN+2d} ... Z=2*MAX_IDX:{MAX-d, MAX}
 
 
 
        MAX_Z=$(( IDX>0 ? ( NOW>START_TIME+MIN_WAIT ? 2*(IDX-1) : 2*IDX ) : 0 ))
 
        SENSOR=0
 
        Z_STR="$MAX_Z+"
 
        TEMP_STR="";
 
        for TEMP in $TEMPS; do
 
            [ $((2*SENSOR+2)) -le ${#THRESHOLDS[@]} ] ||
 
                { echo "Too many sensors, not enough values in THRESHOLDS" 2>&1; exit 1; }
 
            if [ $TEMP == -128 ]; then
 
                Z='_'; TEMP='_' # inactive sensor
 
            else
 
                MIN=$((THRESHOLDS[SENSOR*2] + MIN_THRESH_SHIFT));
 
                MAX=$((THRESHOLDS[SENSOR*2+1]))
 
                if (( TEMP < MIN - OFF_THRESH_DELTA )); then
 
                    Z=0
 
                else
 
                    Z=$(( `floor_div $(( (TEMP-MIN)*(2*MAX_IDX-2) )) $((MAX-MIN))` + 2 ))
 
                fi
 
                [ $MAX_Z -gt $Z ] || MAX_Z=$Z
 
            fi
 
            Z_STR="${Z_STR}${Z}"
 
            TEMP_STR="${TEMP_STR}${TEMP} "
 
            (( ++SENSOR ))
 
        done
 
        [ $SENSOR -gt 0 ] || { echo "No temperatures read" >&2; exit 1; }
 
 
 
        (( (MAX_Z == 2*IDX-1) && ++MAX_Z )) # hysteresis
 
        NEW_IDX=$(( MAX_Z/2 ))
 
        [ $NEW_IDX -le $MAX_IDX ] || NEW_IDX=$MAX_IDX
 
 
 
# Interrupted by a signal?
 
if $RESTART; then
 
init_state
 
log "Resetting state"
 
continue
 
fi
 
 
 
        # Transition
 
        $FIRST && OLDLEVEL='?' || OLDLEVEL=${LEVELS[$IDX]}
 
        NEWLEVEL=${LEVELS[$NEW_IDX]}
 
        $QUIET || echo "L=$OLDLEVEL->$NEWLEVEL EC=$ECLEVEL RPM=`printf %4s $SPEED` T=($TEMP_STR) Z=$Z_STR"
 
        if [ "$OLDLEVEL" != "$NEWLEVEL" ]; then
 
            START_TIME=$NOW
 
            log "Changing fan level: $OLDLEVEL->$NEWLEVEL  (temps: $TEMP_STR)"
 
        fi
 
 
 
        setlevel $NEWLEVEL
 
 
 
        sleep $INTERVAL
 
 
 
        # If needed, apply anti-pulsing hack after a settle-down period (and occasionally re-settle):
 
        if [ ${ANTIPULSE[${NEW_IDX}]} == 1 ]; then
 
            if [ $NEWLEVEL != $OLDLEVEL -o $RESETTLE_LEFT -le 0 ]; then # start settling?
 
                SETTLE_LEFT=$SETTLE_TIME
 
                RESETTLE_LEFT=$RESETTLE_TIME
 
            fi
 
            if [ $SETTLE_LEFT -ge 0 ]; then
 
                SETTLE_LEFT=$((SETTLE_LEFT-INTERVAL))
 
            else
 
                setlevel 0x40 # disengage briefly to fool embedded controller
 
                sleep 0.5
 
                RESETTLE_LEFT=$((RESETTLE_LEFT-INTERVAL))
 
            fi
 
        fi
 
 
 
        IDX=$NEW_IDX
 
        FIRST=false
 
    done
 
}
 
 
 
if $KILL_DAEMON || $SUSPEND_DAEMON; then
 
    if [ -f "$PID_FILE" ]; then
 
set -e
 
DPID="`cat \"$PID_FILE\"`"
 
if $KILL_DAEMON; then
 
        kill "$DPID"
 
rm "$PID_FILE"
 
$QUIET || echo "Killed process $DPID"
 
else # SUSPEND_DAEMON
 
kill -USR1 "$DPID"
 
$QUIET || echo "Sent SIGUSR1 to $DPID"
 
fi
 
    else
 
        $QUIET || echo "Daemon not running."
 
        exit 1
 
    fi
 
elif $DAEMONIZE ; then
 
    if [ -e "$PID_FILE" ]; then
 
        echo "$0: File $PID_FILE already exists, refusing to run."
 
        exit 1
 
    else
 
AM_DAEMON=true QUIET=true control_fan 0<&- 1>&- 2>&- &
 
        echo $! > "$PID_FILE"
 
        exit 0
 
    fi
 
else
 
    [ -e "$PID_FILE" ] && echo "WARNING: daemon already running"
 
    control_fan
 
fi
 
</pre>
 
 
 
The authors of the script ([[User:Thinker|Thinker]] and [[User:Spiney|Spiney]]) disclaim all warranty for this script, and make it available under the terms of the [http://www.gnu.org/copyleft/gpl.html GPL] version 2 or later, or at your option, the [http://www.gnu.org/copyleft/fdl.html GFDL].
 
  
 
====init script for the comprehensive script====
 
====init script for the comprehensive script====

Revision as of 22:57, 26 July 2006

This page provides several scripts for controlling the ThinkPad's system fan according the its thermal sensors (overriding the embedded controller), in order to reduce fan noise and decrease power consumption.

ATTENTION!
These scripts rely on undocumented hardware features and override nominal hardware behavior. They may thus cause arbitrary damage to your laptop or data. Watch your temperatures!

Variable speed control scripts

The following scripts sets the fan speed according to the system's thermal sensors. In addition, they include a hack for preventing the annoying fan pulsing that occurs on some systems. Note that the fan levels, thresholds and anti-pulsing hacks are system-specific, so you may need to adjust them.

Comprehensive bash script with fine control over fan speed

The following requires only ibm-acpi 0.11 or higher (e.g., as found in kernel 2.6.14 and higher) with the experimental=1 module parameter. It supports (optional) daemon mode and logging to syslog.

This scripts uses a different temperature range for each thermal sensor, since they have different specs and thermal systems. For each sensor, a fan level is chosen based on the minimum and maximum temperatures configured for that sensor; then the actual fan level is set to the slowest that will satisfy all sensors. There are also some hysteresis features - see the script for the details. The method of controlling fan speed is documented here.

Current options:

Usage: ./tp-fancontrol [OPTION]...

Available options:
  -s N   shift up temperature thresholds by N degrees
         (positive for quieter, negative for cooler)
  -t     test mode
  -q     quiet mode
  -d     daemon mode, go into background (implies -q)
  -l     log to syslog
  -k     kill already-running daemon
  -u     tell already-running daemon that the system is being suspended
  -p     pid file location for daemon mode, default: $PID_FILE
ATTENTION!
The list of temperature ranges used below is much more liberal than the rules used by the embedded controller firmware, and is derived mostly from anecdotal evidence, hunches and wishful thinking. It is also model-specific - see thermal sensors.

The script: tp-fancontrol (download)

init script for the comprehensive script

The following init script (tested on Debian) allows you to configure some options using a config file. It requires to move the above tp-fancontrol script to /usr/bin/tp-fancontrol, but that can be changed easily. Written by Raigner.

#! /bin/sh
### BEGIN INIT INFO
# Provides:             tp-fancontrol
# Default-Start:        2 3 4 5
# Default-Stop:         S 0 1 6
### END INIT INFO
# Copyright (c) 2006 Ronald Aigner <ra3@os.inf.tu-dresden.de>

set -e

DAEMON=/usr/bin/tp-fancontrol
NAME=tp-fancontrol
DAEMONUSER=root
PIDDIR=/var/run
PIDFILE=$PIDDIR/tp-fancontrol.pid
DESC="Thinkpad CPU fan control"

test -x $DAEMON || exit 0

# source config file; edit that file to configure this script
TEMP_SHIFT=0
LOG_SYSLOG=1
QUIET=1
if [ -e /etc/tp-fancontrol.conf ]; then
  . /etc/tp-fancontrol.conf
fi
 
start_it()
{
  PARAMS=-d
  if [ -e $PIDFILE ]; then
    echo "$DESC already running. Stop first."
    exit 1
  fi
  if [ "$TEMP_SHIFT" != "0" ]; then
    PARAMS=$PARAMS -s $TEMP_SHIFT
  fi
  if [ "$LOG_SYSLOG" = "1" ]; then
    PARAMS=$PARAMS" -l"
  else
    if [ "$QUIET" = "1" ]; then
      PARAMS=$PARAMS" -q"
    fi
  fi
  PARAMS=$PARAMS" -p $PIDFILE"

  echo -n "Starting $DESC: $NAME ... "
  $DAEMON $PARAMS
  if [ "$?" = "0" ]; then
    echo "Success."
  else
    echo "Error while starting: $?"
  fi
}
 
stop_it()
{
  if [ -e $PIDFILE ]; then
    echo -n "Stopping $DESC ... "
    $DAEMON -k
    if [ "$?" = "0" ]; then
      echo "Success."
    else
      echo "Error while stopping: $?"
    fi
  else
    echo "$DESC not running."
  fi
}

restart_it()
{
  if [ -e $PIDFILE ]; then
    stop_it
    sleep 2
  fi
  start_it
}

case "$1" in
  start)
    start_it
  ;;
  stop)
    stop_it
  ;;
  reload|force-reload|restart)
    restart_it
  ;;
  *)
    echo "Usage: /etc/init.d/$NAME {start|stop|reload|restart|force-reload}" >&2
    exit 1
  ;;
esac

exit 0

The /etc/tp-fancontrol.conf config script could look like this:

# config file for tp-fancontrol

# 
# The temparature shift will shift the trip points for different fan speeds.
#
# A positive value means that the trip-point is at a higher temperature and
# thus the fan is less noisy. A negative shift lets the fan start earlier.
# The default value is 0.
#
#TEMP_SHIFT=10

#
# True if logging to syslog should be enabled. If zero no logging to syslog
# is enabled. All output will appear on console. This option implies QUIET=1.
# The default value is 1.
#
#LOG_SYSLOG=0

#
# If no logging to syslog is on, the output will be verbose. To be less
# verbose set QUIET to 1. The default value is 1.
#
#QUIET=1

#
# The file to store the pid of the currently running fan-control daemon in.
# The default value is /var/run/tp-fancontrol.pid
#
#PIDFILE=/var/run/tp-fancontrol.pid

Simple bash script with fine control over fan speed (requires kernel patch)

The following is a simpler patch (without extra features like daemon mode and logging). It requires the patch for controlling fan speed.

#!/bin/bash

# This script dynamically controls fan speed on some ThinkPad models
# according to user-defined temperature thresholds.  It implements its
# own decision algorithm, overriding the ThinkPad embedded
# controller. It also implements a workaround for the fan noise pulse
# experienced every few seconds on some ThinkPads.
#
# The script requires the ibm_acpi patch at 
# http://thinkwiki.org/wiki/Patch_for_controlling_fan_speed
#
# WARNING: This script relies on undocumented hardware features and
# overrides nominal hardware behavior. It may thus cause arbitrary
# damage to your laptop or data. Watch your temperatures!
#
# This file is placed in the public domain and may be freely distributed.

LEVELS=(    0      2      4      7)  # Fan speed levels
UP_TEMPS=(      52     60     68  )  # Speed increase trip points
DOWN_TEMPS=(  48     56     64    )  # Speed decrease trip points

ANTIPULSE=( 0      1      0      0)  # Prevent fan pulsing noise at this level
                                     #   (this also prevents fan speed updates)

IBM_ACPI=/proc/acpi/ibm
FAN=$IBM_ACPI/fan
INTERVAL=3
VERBOSE=true
DRY_RUN=false

[[ "$1" == "-t" ]] && { DRY_RUN=true; echo "$0: Dry run, will not change fan state."; }

# Enable the fan in default mode if anything goes wrong:
set -e -E -u
$DRY_RUN || trap "echo enable > $FAN; exit 0" EXIT HUP INT ABRT QUIT SEGV TERM


thermometer() { # output list of temperatures
    read X Y < $IBM_ACPI/thermal
    [[ "$X" == "temperatures:" ]] || { 
	echo "$0: Bad temperatures: $X $Y" >&2 
	exit 1
    }
    echo "$Y"; 
}

speedometer() { # output fan speed
    cat $FAN | sed '/^speed/!d; s/speed:[ \t]*//'
}

IDX=0
MAX_IDX=$(( ${#LEVELS[@]} - 1 ))
SETTLE=0

while true; do
    TEMPS=`thermometer`
    $VERBOSE && SPEED=`speedometer`

    # Calculate new level
    NEWIDX=$IDX
    DOWN=$(( IDX > 0 ))
    for TEMP in $TEMPS; do
        # Increase speed as much as needed
        while [[ $NEWIDX -lt $MAX_IDX ]] && 
              [[ $TEMP -ge ${UP_TEMPS[$NEWIDX]} ]]; do
            (( NEWIDX ++ ))
            DOWN=0
        done
        # Allow decrease (by one index)?
        if [[ $DOWN == 1 ]] && 
           [[ $TEMP -gt ${DOWN_TEMPS[$(( IDX - 1 ))]} ]]; then
            DOWN=0
        fi
    done
    if [[ $DOWN == 1 ]]; then
        NEWIDX=$(( IDX - 1 ))
    fi

    # Transition
    OLDLEVEL=${LEVELS[$IDX]}
    NEWLEVEL=${LEVELS[$NEWIDX]}
    $VERBOSE && echo "tpfan: Temps: $TEMPS   Fan: $SPEED   Level: $OLDLEVEL->$NEWLEVEL"
    $DRY_RUN || echo level $NEWLEVEL > $FAN

    sleep $INTERVAL

    # If needed, apply anti-pulsing hack after a settle-down period:
    if [[ ${ANTIPULSE[${NEWIDX}]} == 1 ]]; then
	if [[ $NEWLEVEL == $OLDLEVEL ]]; then
	    if [[ $SETTLE -ge 0 ]]; then
		(( SETTLE -= INTERVAL ))
	    else
		$DRY_RUN || echo level disengaged >> $FAN
		sleep 0.5
	    fi
	else
	    SETTLE=6
	fi
    fi

    IDX=$NEWIDX
done

The author of the script disclaims all warranty for this script, and releases it to the public domain (meaning you may use it and further distribute it under any terms you wish, including incorporating it into other software).

Fan enable/disable scripts

The following scripts were written before it was known how to control the fan speed, so they only toggle between fan disabled and default (noisy) fan behavior. In some models, they also do not monitor all available thermal sensors.

sh script example

#!/bin/sh

MAXTEMP=50

while [ 1 ];
do
       fan=no

       for temp in `sed s/temperatures:// < /proc/acpi/ibm/thermal`
       do
               test $temp -gt $MAXTEMP && fan=yes
       done

       command='disable'
       test "$fan" = "yes" && command='enable'
       echo $command > /proc/acpi/ibm/fan

       sleep 20
done


sh script with more features

#!/bin/sh

# fan control-script
#
# based upon ibm-acpi 0.11 (experimental=1 !)
#
# eliminates anoying "fan always on" in battery mode
# works with hysteresis (DELTA) so that always-turn-on/turn-off is avoided
# fan acivates at MAXTEMP and cools down CPU, GPU etc. to MAXTEMP-DELTA than the fan is turned off
# furthermore detects if AC is on and gives back fan control to default behaviour than
#
# one can change MAXTEMP and DELTA to individual values
# but take care of your THINKPAD don`t melt it!
#
# have fun!
# mk 05.05.05

MAXTEMP=51
DELTA=4

SWITCHTEMP=$MAXTEMP

#make sure the script doesn't leave the fan off on error
trap "echo enable > /proc/acpi/ibm/fan" EXIT

while [ 1 ];
do
  for ac in `sed s/state:// < /proc/acpi/ac_adapter/AC/state`
    do
     if [ "$ac" = "off-line" ]; then
         fan=no
         for temp in `sed s/temperatures:// < /proc/acpi/ibm/thermal`
           do
             test $temp -gt $SWITCHTEMP && fan=yes
           done

         if [ "$fan" = "yes" ]; then
           command='enable'
           SWITCHTEMP=`expr $MAXTEMP - $DELTA`
         else
           SWITCHTEMP=$MAXTEMP
           command='disable'
         fi

       else # ac-adapter on -> set fan control to standard behaviour
         command='enable'
       fi

       echo $command > /proc/acpi/ibm/fan
       sleep 15
     done 
  done


sh script with extra safety functionality

ibm_acpi usually works well. But to rely on it completely, this script provides some extra safety functionality:

  1. It catches various signals and turns the fan on before it quits.
  2. It turns off the fan under very strict conditions, leaving it on when unexpected errors occur.
#!/bin/sh

# july 2005 Erik Groeneveld, erik@cq2.nl
# It makes sure the fan is on in case of errors
# and only turns it off when all temps are ok.

IBM_ACPI=/proc/acpi/ibm
THERMOMETER=$IBM_ACPI/thermal
FAN=$IBM_ACPI/fan
MAXTRIPPOINT=65
MINTRIPPOINT=60
TRIPPOINT=$MINTRIPPOINT

echo fancontrol: Thermometer: $THERMOMETER, Fan: $FAN
echo fancontrol: Current `cat $THERMOMETER`
echo fancontrol: Controlling temperatures between $MINTRIPPOINT and $MAXTRIPPOINT degrees.

# Make sure the fan is turned on when the script crashes or is killed
trap "echo enable > $FAN; exit 0" HUP KILL INT ABRT STOP QUIT SEGV TERM

while [ 1 ];
do
       command=enable
       temperatures=`sed s/temperatures:// < $THERMOMETER`
       result=
       for temp in $temperatures
       do
               test $temp -le $TRIPPOINT && result=$result.Ok
       done
       if [ "$result" = ".Ok.Ok.Ok.Ok.Ok.Ok.Ok.Ok" ]; then
               command=disable
               TRIPPOINT=$MAXTRIPPOINT
       else
               command=enable
               TRIPPOINT=$MINTRIPPOINT
       fi
       echo $command > $FAN
       # Temperature ramps up quickly, so pick this not too large:
       sleep 5
done


Init scripts

Init script example

#! /bin/sh

N=/etc/init.d/fan

set -e

case "$1" in
 start)
       # make sure privileges don't persist across reboots
       if [ -d /var/run/fan ] && [ "x`ls /var/run/fan`" != x ]
       then
               touch -t 198501010000 /var/run/fan/*
       fi
       fan.sh &    # Script from above
       ;;
 stop|reload|restart|force-reload)
       killall fan.sh
       echo enable > /proc/acpi/ibm/fan
       ;;
 *)
       echo "Usage: $N {start|stop|restart|force-reload}" >&2
       exit 1
       ;;
esac

exit 0


Init script example for gentoo

Assume one of the above control scripts is /usr/sbin/ibm-fancontrold, for gentoo use the following init script in /etc/init.d/ibm-fancontrol. Copy the script to /etc/init.d/ibm-fancontrol, then do

# rc-update add ibm-fancontrol default

This will add the init script to the default runlevel.

#!/sbin/runscript
# 2005 Gilbert Tiefengruber
# Distributed under the terms of the GNU General Public License v2 
# IBM Fancontrol init script for IBM Thinkpad laptops (tested with R50)
# This init script was written for gentoo 2005.1, kernel 2.6.12
# You need the ibm_acpi kernel module version 0.11 or greater
# load the module with experimental=1 to enable the fan controls

depend() {
        need localmount
}
checkconfig() {
        if [ ! -e /proc/acpi/ibm/fan ]; then
                eerror "The ibm_acpi module must be loaded with (experimental=1)"
                return 1
        fi
} 
start() {
        checkconfig || return 1
        ebegin "Starting ibm-fancontrold"
        start-stop-daemon --quiet -p /var/run/ibm-fancontrold.pid -m -b --start -a /usr/sbin/ibm-fancontrold
        eend ${?}
} 
stop() {
        ebegin "Stopping ibm-fancontrold"
        start-stop-daemon --stop --quiet -p /var/run/ibm-fancontrold.pid
        eend ${?}
}


Other

fanctrld

fanctrld is a daemon (written in C) that controls the Thinkpad's fan. The basic approach is to monitor both temperature and fan speed. The fan is enabled when a certain temperature is exceeded, and disabled when the BIOS slows down the fan below a certain speed.

Ideas for improvement

  • Use the HDAPS sensor to automatically lower the temperature thresholds when the laptop is moving. Prolonged movement usually happens when the laptop is on the user's lap (so better keep temperatures down), or when in a moving vehicle where fan noise is typically overshadowed by vehicle noise.
  • Use a PID controller feedback loop instead of simple thresholds.

See also

  • How to control fan speed
  • Shimodax's ThinkPad fan control tool for a Windows offers functionality similar to these scripts; see the forum discussion at thinkpads.com.
  • Yury Polyanskiy has a kernel patch for automatic fan control in kernelspace (only enable/disable based on maximum temperature).