I have a HP 4510s laptop. The fan control works ok under Windows. Does not work under any tried Linux distro/version. Under Linux/Ubuntu, the fan runs at some very low speed. This is not sufficient to keep CPUs cool enough under higher load.
I tried most of the hints from the internet regarding the Linux laptop fancontrol, none worked. To list, I tried lm_sensors, pwmconfig, acpi_os= setting and numerous other things to no avail. And I've updated BIOS and there's nothing except "Fan always on on AC" and it does not do the trick.
It is possible to get the system temps using lm_sensors. Furthermore, there are fan control points at
/sys/devices/virtual/thermal/cooling_device*/cur_state (* from 0-4 are active)
I ended up taking the famous tp-fancontrol (for IBM ThinkPads) script from
http://thinkwiki.org/wiki/ACPI_fan_control_script
and botched it up as hp-fancontrol. I don't remember enough from my automation studies (it was 20 years ago) so it turned out very simple. But it seems to work so far. There are lots of left-over code still and it's ugly as ****. I installed it as /usr/bin/hp_fancontrol and added trigger to /etc/rc.local like this
# Start hp-fancontrol on multiuser
/usr/bin/hp-fancontrol -l -d
exit 0
I'd be very grateful for anything better, even if it is just the control_loop part, so please comment. Also, be gentle and read the disclaimers (ugly as... above and code itself). The QUIET seems not to work, it breaks DAEMONIZE.
#!/bin/bash
# hp-fancontrol 0.1
# Based on tp-fancontrol (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 HP laptop models
# (ProBook 4510s) according to user-defined temperature thresholds. It
# implements its own decision algorithm, overriding the any embedded
# controller.
#
# Run 'hp-fancontrol --help' for options.
#
# For optimal fan behavior during suspend and resume, invoke
# "hp-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 core:
# (min temperature: when to step up from 0-th fan level,
# max temperature: when to step up to maximum fan level)
THRESHOLDS=( # Core
# min max # ----
43 65 # 0
43 65 # 1
)
# LEVELS=0 - 31 (5 bits) # Fan speed levels
ANTIPULSE=1 # Prevent fan pulsing noise
# (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
MAX_THRESH_SHIFT=0 # increase max thresholds by this much
MIN_WAIT=180 # minimum time (seconds) to spend in a given level before
# stepping down
HP_ACPI=/sys/devices/virtual/thermal/cooling_device
PID_FILE=/var/run/hp-fancontrol.pid
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
SEP=',' # Separator char for display
WATCHDOG_DELAY=$(( 3 * INTERVAL ))
HAVE_WATCHDOG=false
HAVE_LEVELCMD=true
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 the min temperature thresholds by N degrees
(positive for quieter, negative for cooler).
Max temperature thresholds are not affected.
-S N Shift up the max temperature thresholds by N degrees
(positive for quieter, negative for cooler). DANGEROUS.
-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:S:qtdlp:kuh' OPT; do
case "$OPT" in
s) # shift thresholds
MIN_THRESH_SHIFT="$OPTARG"
;;
S) # shift thresholds
MAX_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
# 2 basic temperatures from CPU cores:
CPU0=`/usr/bin/sensors | /usr/bin/awk '/Core 0/ { print $3 }' | sed -e s/[^0-9]//g`
CPU1=`/usr/bin/sensors | /usr/bin/awk '/Core 1/ { print $3 }' | sed -e s/[^0-9]//g`
echo -n "$CPU0 $CPU1 ";
return 0
}
speedometer() { # output fan speed RPM
read F0 < /sys/devices/virtual/thermal/cooling_device0/cur_state
read F1 < /sys/devices/virtual/thermal/cooling_device1/cur_state
read F2 < /sys/devices/virtual/thermal/cooling_device2/cur_state
read F3 < /sys/devices/virtual/thermal/cooling_device3/cur_state
read F4 < /sys/devices/virtual/thermal/cooling_device4/cur_state
FAN=$(($F4+$F3*2+$F2*4+$F1*8+$F0*16))
echo -n $FAN;
return 0
}
setlevel() { # set fan speed level
local LEVEL=$1
if ! $DRY_RUN; then
if $HAVE_LEVELCMD; then
BLEVEL=$(echo "obase=2;$LEVEL" | bc)
LEN=$(echo ${#BLEVEL})
BLEVEL5=`echo "0000"$BLEVEL`
B5=${BLEVEL5:(-5)}
echo ${B5:0:1} > /sys/devices/virtual/thermal/cooling_device0/cur_state
echo ${B5:1:1} > /sys/devices/virtual/thermal/cooling_device1/cur_state
echo ${B5:2:1} > /sys/devices/virtual/thermal/cooling_device2/cur_state
echo ${B5:3:1} > /sys/devices/virtual/thermal/cooling_device3/cur_state
echo ${B5:4:1} > /sys/devices/virtual/thermal/cooling_device4/cur_state
else
case "$LEVEL" in
(auto) LEVEL=0x80 ;;
(disengaged) LEVEL=0x40 ;;
esac
#echo 0x2F $LEVEL > $IBM_ACPI/ecdump
fi
fi
}
getlevel() { # get fan speed level
# perl -e 'm/^EC 0x20: .* .(..)$/ and print $1 and exit 0 while <>; exit 1' < $IBM_ACPI/ecdump
read F0 < /sys/devices/virtual/thermal/cooling_device0/cur_state
read F1 < /sys/devices/virtual/thermal/cooling_device1/cur_state
read F2 < /sys/devices/virtual/thermal/cooling_device2/cur_state
read F3 < /sys/devices/virtual/thermal/cooling_device3/cur_state
read F4 < /sys/devices/virtual/thermal/cooling_device4/cur_state
FAN=$(($F4+$F3*2+$F2*4+$F1*8+$F0*16))
echo -n $FAN;
}
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, fan turned up"
if ! $DRY_RUN; then
if $HAVE_LEVELCMD; then
setlevel 31
fi
fi
}
floor_div() {
echo $(( (($1)+1000*($2))/($2) - 1000 ))
}
set_priority() {
! $DRY_RUN && renice -10 -p $$
}
init_state() {
IDX=0
NEW_IDX=0
START_TIME=0
MAX_IDX=31
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
# Get readouts
TEMPS=`thermometer`
$QUIET || SPEED=`speedometer`
$QUIET || ECLEVEL=`getlevel`
NOW=`date +%s`
if echo "$TEMPS" | grep -q "[^ 0-9$SEP\n-]"; then
echo "Invalid character in temperatures: $TEMPS" >&2; exit 1;
fi
OLDLEVEL=$ECLEVEL
NEWLEVEL=$OLDLEVEL
CHANGE=0
for TEMP in $TEMPS; do
if [ $TEMP -gt 430 ] ; then
if [ $OLDLEVEL -lt 31 ] ; then
CHANGE=$(($CHANGE+1)) ;
# NEWLEVEL=$(($OLDLEVEL+1)) ;
fi
elif [ $TEMP -gt 410 ] ; then
# No change between 41-43C
CHANGE=$(($CHANGE)) ;
else
if [ $OLDLEVEL -gt 0 ] ; then
CHANGE=$(($CHANGE-1)) ;
# NEWLEVEL=$(($OLDLEVEL-1)) ;
fi
fi
NEWLEVEL=$(($OLDLEVEL+$CHANGE)) ;
if [ $NEWLEVEL -gt 31 ] ; then
NEWLEVEL=31 ;
fi
if [ $NEWLEVEL -lt 0 ] ; then
NEWLEVEL=0 ;
fi
OLDLEVEL=$NEWLEVEL ;
done
# Interrupted by a signal?
if $RESTART; then
init_state
log "Resetting state"
continue
fi
# Transition
if [ "$ECLEVEL" != "$NEWLEVEL" ]; then
START_TIME=$NOW
log "Changing fan level: $ECLEVEL->$NEWLEVEL (temps: $TEMPS)"
fi
setlevel $NEWLEVEL
sleep $INTERVAL
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
set_priority
AM_DAEMON=true QUIET=false control_fan 0<&- 1>&- 2>&- &
echo "$0: Starting as daemon"
echo $! > "$PID_FILE"
exit 0
fi
else
[ -e "$PID_FILE" ] && echo "WARNING: daemon already running"
set_priority
control_fan
fi