Skip to content

Instantly share code, notes, and snippets.

@mortie
Last active February 4, 2022 01:49
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mortie/34c0165ce34175c833500d6aad9030b5 to your computer and use it in GitHub Desktop.
Save mortie/34c0165ce34175c833500d6aad9030b5 to your computer and use it in GitHub Desktop.
Better Dell fan speed control
# Copy fan-speed-control.sh to `/usr/local/bin/fan-speed-control.sh`,
# make sure it's executable with `sudo chmod +x /usr/local/bin/fan-speed-control.sh`.
# Then copy this file `/lib/systemd/system/fan-speed-control.service`,
# then run `sudo systemctl enable --now fan-speed-control`.
# Check that everything looks OK with `sudo journalctl -fe -u fan-speed-control`.
[Unit]
Description=Fan speed control
[Service]
ExecStart=/usr/local/bin/fan-speed-control.sh
[Install]
WantedBy=multi-user.target
#!/bin/sh
set -e
# This script automatically monitors temperature sensors,
# and sets fan speeds accordingly.
# It depends on lm_sensors and i8kfan (from https://aur.archlinux.org/packages/i8kutils/).
# It does a couple of things better than i8kutils' i8kmon:
#
# * i8kmon reads the actual fan speed every couple of seconds, which pauses the
# kernel for a bit (https://wiki.archlinux.org/index.php/Fan_speed_control#Dell_laptops),
# which causes audio clicks.
# This script just reads the fan speed very occasionally, to verify it's still correct.
# * Despite reading the fan speed so often, i8kmon doesn't seem to actually do
# anythig if the fans are running at the wrong speed. On my laptop, even with
# dell-bios-fan-control (https://github.com/TomFreudenberg/dell-bios-fan-control),
# the fans would sometimes get stuck at a high RPM after resuming from sleep
# regardless of temperature; this script will detect that and set the fan
# speeds to something sane right after you open the laptop lid.
#
# The fans will be off if the temperatures are below $cold degrees C.
# The fans will be running slowly if the temperatures are between $cold and $hot.
# The fans will be running quickly if the temperatures are above $hot.
# $delay: The number of seconds between each temperature check.
# $temp_sensor: The sensor which will be used.
# $slowdown_lag: The fans go from off to running slowly as soon as the temperature
# exceedes $old, but the temperature has to go down to
# $cold-$slowdown_lag before the fans turn off again.
# The same applies to going from running quickly to running slowly.
cold=50
hot=65
delay=1
temp_sensor="Package id 0"
slowdown_lag=5
script_pid=$$
# Install dell-smm-hwmon kernel module. Using 'force=1' because some
# dell laptops (like the 9575) aren't listed as supported, but still work.
if ! insmod_output=$(insmod "/lib/modules/$(uname -r)/kernel/drivers/hwmon/dell-smm-hwmon.ko.xz" force=1 2>&1); then
echo "$insmod_output"
if echo "$insmod_output" | grep ": File exists$" >/dev/null; then
echo "The error message 'File exists' usually means the module is already loaded, ignoring."
else
exit 1
fi
fi
# Clean up once the program exits by killing the acpi listener.
acpi_listener_pid=
cleanup() {
if ! [ -z "$acpi_listener_pid" ]; then
kill $acpi_listener_pid
fi
wait
exit 1
}
trap cleanup EXIT
# Set the fan speed with i8kfan.
lastfan=""
fancheck=0
frequentfancheck=0
setfan() {
# Sometimes, the bios takes control of our fan again, so we
# periodically check if the fan is still the desired value.
# We check them rarely because the kernel pauses for a bit every time
# i8kfan runs, but the acpi listener child process should notify us
# most of the times we need to immediately check.
if [ $frequentfancheck != 0 ]; then
frequentfancheck=$((frequentfancheck - 1))
fancheck=32
lastfan="$(i8kfan)"
echo "Double checked fan status: $lastfan"
elif [ $fancheck = 0 ]; then
fancheck=32
lastfan="$(i8kfan)"
echo "Double checked fan status: $lastfan"
fi
fancheck=$((fancheck - 1))
if [ "$lastfan" != "$*" ]; then
echo i8kfan "$1" "$2"
i8kfan "$1" "$2"
lastfan="$*"
fi
}
# Immediately check fan speeds when the lid opens.
onlidopen() {
frequentfancheck=3
}
trap onlidopen USR1
# Child process: Run acpi_listen, send USR1 when the lid opens.
acpi_listener() {
acpi_listen | grep --line-buffered "button/lid LID open" \
| while read -r; do
echo "Lid opened, checking fans"
kill -USR1 "$script_pid"
done
}
acpi_listener &
acpi_listener_pid=$!
# Find the temperature (as a whole number in celsius) of 'Package id 0'
temp() {
sensors \
| grep "$temp_sensor:" \
| grep -Po '\d+\.\d' | head -n 1 | cut -d'.' -f1
}
state=cold
while :; do
t=$(temp)
h=$hot
c=$cold
if [ $state = hot ]; then
h=$((h - slowdown_lag))
c=$((c - slowdown_lag))
elif [ $state = mid ]; then
c=$((c - slowdown_lag))
fi
if [ "$t" -le "$c" ]; then
state=cold
setfan 0 0
elif [ "$t" -le "$h" ]; then
state=mid
setfan 1 1
else
state=hot
setfan 2 2
fi
echo "${t}c $state"
sleep $delay
done
@eximoelle
Copy link

Hi! I did everything according to the instructions, but I had to disable the block of code that loads the Dell SMM module into the kernel (lines 38 to 47 in 'fan-speed-control.service':

# Install dell-smm-hwmon kernel module. Using 'force=1' because some
# dell laptops (like the 9575) aren't listed as supported, but still work.
if ! insmod_output=$(insmod "/lib/modules/$(uname -r)/kernel/drivers/hwmon/dell-smm-hwmon.ko.xz" force=1 2>&1); then
	echo "$insmod_output"
	if echo "$insmod_output" | grep ": File exists$" >/dev/null; then
		echo "The error message 'File exists' usually means the module is already loaded, ignoring."
	else
		exit 1
	fi
fi

Otherwise, when I tried to start the service, I got an error.

fan-speed-control.service - Fan speed control
     Loaded: loaded (/lib/systemd/system/fan-speed-control.service; enabled; vendor preset: enabled)
     Active: failed (Result: exit-code) since Sun 2021-08-22 19:49:31 +10; 39s ago
    Process: 92647 ExecStart=/usr/local/bin/fan-speed-control.sh (code=exited, status=1/FAILURE)
   Main PID: 92647 (code=exited, status=1/FAILURE)

авг 22 19:49:31 maxlion-Latitude-5400 systemd[1]: Started Fan speed control.
авг 22 19:49:31 maxlion-Latitude-5400 fan-speed-control.sh[92647]: insmod: ERROR: could not load module /lib/modules/5.11.0-31-generic/kernel/drivers/hwmon/d>
авг 22 19:49:31 maxlion-Latitude-5400 systemd[1]: fan-speed-control.service: Main process exited, code=exited, status=1/FAILURE
авг 22 19:49:31 maxlion-Latitude-5400 systemd[1]: fan-speed-control.service: Failed with result 'exit-code'.

After disabling the block, the script loads and works fine, but I would like to figure out why the error occurs and return the module check in the kernel.

My specs: Ubuntu 21.04, kernel 5.11.0-31-generic, Dell Latitude 5400.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment