|
|
Line 33: |
Line 33: |
| This version also lets the EC read the RPM sensor every few minutes even when the anti-pulsing hack (which normally prevents this) is in use. | | This version also lets the EC read the RPM sensor every few minutes even when the anti-pulsing hack (which normally prevents this) is in use. |
| | | |
− | <pre> | + | <i><b>Script moved to the [[ACPI fan control script#Variable speed control scripts|article page]]</b></i>. |
− | #!/bin/bash
| |
− | | |
− | # tp-fancontrol 0.2.2 (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.
| |
− | #
| |
− | # 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 evidene, hunches and wishful thinking.
| |
− | # It is also model-specific.
| |
− | | |
− | # 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 T42 T43/p
| |
− | # min max # ---------- ------- --- ----
| |
− | 50 70 # EC 0x78 CPU CPU CPU
| |
− | 47 60 # EC 0x79 miniPCI ? HDAPS through EC (under center left of miniPCI)
| |
− | 43 55 # EC 0x7A HDD ? ?
| |
− | 49 68 # EC 0x7B GPU GPU GPU
| |
− | 37 52 # EC 0x7C BAT BAT BAT (front left)
| |
− | 45 55 # EC 0x7D n/a n/a n/a
| |
− | 34 45 # EC 0x7E BAT BAT BAT (rear right)
| |
− | 45 55 # EC 0x7F n/a n/a n/a
| |
− | 45 55 # EC 0xC0 ? n/a ?
| |
− | 48 60 # EC 0xC1 ? n/a Southbridge under miniPCI?
| |
− | 47 60 # EC 0xC2 ? n/a ?
| |
− | 47 60 # HDAPS HDAPS HDAPS HDAPS direct access (under center left of miniPCI)
| |
− | )
| |
− | | |
− | LEVELS=( 0 2 4 7) # Fan speed levels
| |
− | ANTIPULSE=( 0 1 0 0) # Prevent fan pulsing noise at this level
| |
− | # (reduces frequency of fan RPM updates)
| |
− | | |
− | OFF_THRESH_DELTA=2 # when gets this much cooler than 'min' above, may turn off fan
| |
− | MIN_THRESH_SHIFT=0 # increase min thresholds by this much
| |
− | MIN_WAIT=120 # 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
| |
− | SETTLE_TIME=6
| |
− | RESETTLE_TIME=300
| |
− | | |
− | PID_FILE=/var/run/tp-fancontrol.pid
| |
− | VERBOSE=true
| |
− | DRY_RUN=false
| |
− | DAEMONIZE=false
| |
− | AM_DAEMON=false
| |
− | KILL_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 daemon (ignores all but -p)
| |
− | -p pid file location for daemon mode, default: $PID_FILE
| |
− | "
| |
− | exit 1;
| |
− | }
| |
− | | |
− | while getopts 's:qtdlp:kh' OPT; do
| |
− | case "$OPT" in
| |
− | s) # shift thresholds
| |
− | MIN_THRESH_SHIFT="$OPTARG"
| |
− | ;;
| |
− | t) # test mode
| |
− | DRY_RUN=true
| |
− | ;;
| |
− | q) # quiet mode
| |
− | VERBOSE=false
| |
− | ;;
| |
− | 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
| |
− | ;;
| |
− | 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."
| |
− | VERBOSE=true
| |
− | 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 Y < $IBM_ACPI/thermal
| |
− | [ "$X" == "temperatures:" ] || { echo "$0: Bad temperatures: $X $Y" >&2; exit 1; }
| |
− | echo -n "$Y ";
| |
− | # Extended temperatures at EC offsets 0xC0 to 0xC2:
| |
− | [ -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
| |
− | # HDAPS temperature (optional):
| |
− | [ -r $HDAPS_TEMP ] && echo -n "`cat $HDAPS_TEMP` "
| |
− | 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
| |
− | }
| |
− | | |
− | cleanup() { # clean up after work
| |
− | $AM_DAEMON && rm -f "$PID_FILE" 2> /dev/null
| |
− | $SYSLOG && $LOGGER -t "`basename $0`[$$]" \
| |
− | "Shutting down, switching to automatic fan control"
| |
− | $DRY_RUN || echo enable > $IBM_ACPI/fan
| |
− | }
| |
− | | |
− | floor_div() {
| |
− | echo $(( (($1)+1000*($2))/($2) - 1000 ))
| |
− | }
| |
− | | |
− | 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
| |
− | | |
− | IDX=0
| |
− | START_TIME=0
| |
− | MAX_IDX=$(( ${#LEVELS[@]} - 1 ))
| |
− | SETTLE_LEFT=0
| |
− | RESETTLE_LEFT=0
| |
− | FIRST=true
| |
− | $SYSLOG && $LOGGER -t "`basename $0`[$$]" "Starting dynamic fan control"
| |
− | | |
− | # Control loop:
| |
− | while true; do
| |
− | TEMPS=`thermometer`
| |
− | $VERBOSE && SPEED=`speedometer`
| |
− | $VERBOSE && 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
| |
− | | |
− | # Transition
| |
− | $FIRST && OLDLEVEL='?' || OLDLEVEL=${LEVELS[$IDX]}
| |
− | NEWLEVEL=${LEVELS[$NEW_IDX]}
| |
− | $VERBOSE && echo "L=$OLDLEVEL->$NEWLEVEL EC=$ECLEVEL RPM=`printf %4s $SPEED` T=($TEMP_STR) Z=$Z_STR"
| |
− | if [ $OLDLEVEL != $NEWLEVEL ]; then
| |
− | START_TIME=$NOW
| |
− | $SYSLOG && $LOGGER -t "`basename $0`[$$]" "Changing fan level: $OLDLEVEL->$NEWLEVEL"
| |
− | 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 ; then
| |
− | if [ -f "$PID_FILE" ]; then
| |
− | set -e
| |
− | DPID="`cat \"$PID_FILE\"`"
| |
− | kill "$DPID"
| |
− | rm "$PID_FILE"
| |
− | $VERBOSE && echo "Killed process $DPID"
| |
− | else
| |
− | $VERBOSE && 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 VERBOSE=false control_fan 0<&- 1>&- 2>&- &
| |
− | echo $! > "$PID_FILE"
| |
− | exit 0
| |
− | fi
| |
− | else
| |
− | [ -e "$PID_FILE" ] && echo "WARNING: daemon already running"
| |
− | control_fan
| |
− | fi
| |
− | </pre>
| |
| | | |
| Feedback very much welcome. | | Feedback very much welcome. |
− |
| |
− | Spiney, I made the license more restrictive than prior versions (GPL+GFDL instead of public domains), is this OK with you?
| |
| | | |
| --[[User:Thinker|Thinker]] 23:08, 27 Nov 2005 (CET) | | --[[User:Thinker|Thinker]] 23:08, 27 Nov 2005 (CET) |
Wyrfel, are you sure the recent (19:54, 27 Oct 2005) cosmetic change was a good idea? The extensive chunks of code make it hard to grok the structure of the article in the absense of separator lines (which "===" doesn't have). --Thinker 22:10, 27 Oct 2005 (CEST)
We can discuss this. From my point of view, the chunks of code distinguish themselves from each other quite well, because they are each in one code block.
I do not like the = section level - and so far we avoided them on all pages - because
- it generates H1 headings, which is the same as the page heading,
- having more than one level with the hbars is confusing/less readable, because they are not very well distinguishable. This way i.e. the "Other" section looked like a separate empty secion.
I think the way it's now, the separator lines make it possible to easily distinguish the different main sections, while when you have both levels with separator lines, an additional task of distinguishing H1 and H2 separators is necessary.
However, i see your point as well and would like to hear more opinions/arguments.
Wyrfel 22:31, 27 Oct 2005 (CEST)
bash script with fine control over fan speed (for unpatched kernels)
Moved to the article page, after joint development by Spiney and Thinker.
Note that the fan levels, thresholds and anti-pulsing hacks are system-specific, so you may need to adjust them.
I think it'd probably be nice to have a table of the suggested values here. Those in the "unpatched kernels" script seems to work fine on my R52, but the other scripts all have different values.
--Micampe 08:19, 12 Nov 2005 (CET)
Sensor-specific variable-speed script
Here's a new variable-speed control script that lets you define the temperature range separately for each sensor. To keep things simple, it auto-computes the trip points (unlike the current script). Works well on a T43, and (just barely) keeps the fan off most of the time with CPU undervolting and fglrx set to maximum power saving. Feedback on other machines would be appreciated.
Any idea what are the sensors at EC offsets 0x79, 0x7A, 0xC0, 0xC1, 0xC2? On the T43, 0x79 seems to be the same as the HDAPS sensor (it never deviates by more than one degree from what the HDAPS sensor tells directly), but I don't know where it's located. Sensor 0x7A is uncorrelated with disk temperature and activity, so it can't be HDD like reported ofor R52. Sensor 0xC1 seems to be under the palm-rest (see discussion in Talk:Problem_with_fan_noise).
This version also lets the EC read the RPM sensor every few minutes even when the anti-pulsing hack (which normally prevents this) is in use.
Script moved to the article page.
Feedback very much welcome.
--Thinker 23:08, 27 Nov 2005 (CET)
Fan enable/disable scripts
We currently have two types of scripts -- the old ones which only enable/disable the fan, and the new ones which control the fan speed. Do the latter supercede the former, or do we know of models on which only enable/disable works? Maybe eventually the enable/disable scripts should be "archived" in the talk page to reduce clutter in the article?
--Thinker 00:08, 28 Nov 2005 (CET)