Introduction
I got a Fujitsu-Siemens Amilo Xi 1554. A good machine, but terribly hot, with an amazingly bad design of its heat-sink. I live in a tropical area so the air temperature is often more than 30°C. This and the fact that Fujitsu does not know how to design performing cooling solutions lead to my laptop producing a lot of heat, to the point that I have to use an external keyboard because it’s too warm.
Under Windows XP, I used the NHC (Notebook Hardware Control) application available for free at http://www.pbus-167.com. It worked pretty well. In the past, I modified an existing class to make it work with my Amilo 1554. It forced the fan to spin by writing fake temperature value to a defined ACPI field in my DSDT table (\_SB.PCI0.LPCB.EC0.XHPP), say 90°C for example, and did this several times per second using a timer so that it overrides the real temperature. This worked well.
Then I migrated to Windows 7 x64, but no luck, there is no released NHC version for 64 bits systems (as of today april 15, 2011) although there should have one soon. As the result, my laptop was damn hot even with the proper energy management policy defined in Windows 7.
Then I tried Debian Lenny and got addicted to it. But I was still out of luck : there is no way to control my fan using ACPI out-of-the-box. No /proc/acpi/thermal_zone/fan. Hot laptop.
Then I upgraded to Squeeze and liked it. Still can’t control my fan however.
How to force the fan to spin
How to force this fan to spin? Well NHC guided me to the right way. I have to write a fake temperature over the \_SB.PCI0.LPCB.EC0.XHPP field! How to do it under Linux? Just follow my instructions and see if it works for you.
First, you will need a way to use a custom DSDT. Depending on your distribution, there are one to several ways to do it. Here are the main ones:
- The initrd way. In some Linux distributions, such as Ubuntu, you can just copy your DSDT.aml file in /boot/ and the DSDT file will override the one who is loaded normally. This will only work if your kernel has been compiled with the “ACPI_CUSTOM_DSDT_INITRD” option.
- The kernel recompilation way. By including your DSDT.hex file in the include directory of your Linux source files and setting some parameters in your .config file, your custom DSDT would be loaded. This was the only way available for me as far as I know, as support for the ACPI_CUSTOM_DSDT_INITRD kernel config parameter was dropped in kernels released after the 2.6.25 version.
Second, you will need a way to query and execute methods on your DSDT. I used the acpi_call kernel module available at https://github.com/mkottman/acpi_call.
Third, you have to retrieve and disassemble your DSDT to analyze it. It’s basically a piece of AML code that is used for ACPI management. Each laptop has a different DSDT and it will change even with a different amount of RAM and different hardware devices.
Under Debian Squeeze, I installed iasl (“apt-get install iasl”) and followed the instructions described at http://www.lesswatts.org/projects/acpi/overridingDSDT.php. Below is the original AML code for my thermal zone:
Scope (_TZ) { Name (THPP, Zero) ThermalZone (THRM) { Method (KELV, 1, NotSerialized) { If (LGreater (Arg0, 0x7F)) { XOr (Arg0, 0xFF, Local0) Add (Local0, One, Local0) Multiply (Local0, 0x0A, Local0) Subtract (0x0AAC, Local0, Local1) } Else { Multiply (Arg0, 0x0A, Local0) Add (Local0, 0x0AAC, Local1) } Return (Local1) } Method (_TMP, 0, NotSerialized) { If (LEqual (THPP, 0x69)) { Return (KELV (THPP)) } If (LEqual (\_SB.PCI0.LPCB.EC0.FGEC, Zero)) { Return (KELV (Zero)) } Else { Multiply (\_SB.PCI0.LPCB.EC0.XHPP, 0x02, THPP) ShiftRight (THPP, One, THPP) Return (KELV (THPP)) } } Method (_CRT, 0, NotSerialized) { Return (KELV (0x64)) } } }
As you can see, there is nothing to set the fan speeds. There are 3 methods in my thermal zone:
- KELV(Arg0) converts a Celsius temperature to Kelvin degrees x 10. For example if Arg0 is 60°C, it will return 3332 °K
- _TMP() returns the temperature in Kelvin degrees x 10
- _CRT() returns the critical temperature in Kelvin degrees x10 (100°C or 3732°K here) which define the temperature at which your laptop will automatically shut down for security reasons.
The interesting method is the _TMP method. Especially the following code:
Multiply (\_SB.PCI0.LPCB.EC0.XHPP, 0x02, THPP) ShiftRight (THPP, One, THPP) Return (KELV (THPP))
I explain this code line by line below:
Multiply (\_SB.PCI0.LPCB.EC0.XHPP, 0x02, THPP)
This line multiplies the value contained in \_SB.PCI0.LPCB.EC0.XHPP by 2 and stores the result in the THPP named variable. For example 60 x 2 = 120.
ShiftRight (THPP, One, THPP)
This line shifts the bits of the value of THPP one step to the right. For example, 120 >> 1 = 60. So basically we get back our previous value (don’t ask me for the usefulness of those two lines, I don’t know too).
Return (KELV (THPP))
This will return the value in THPP converted in Kelvin degrees x 10. For example 60 x 10 + 2732 = 3332.
The interesting line is the one beginning with Multiply. We can guess that \_SB.PCI0.LPCB.EC0.XHPP contains the current temperature in Celsius degrees. That’s this value that we have to override!
Since the acpi_call kernel module does not allow us to write to ACPI field directly (or does it?), I added my own method to my DSDT thermal zone, right after the KELV method:
/* Write an artificial temperature to XHPP */ Method (DEFT, 1, NotSerialized) { Store (Arg0, \_SB.PCI0.LPCB.EC0.XHPP) Return (\_SB.PCI0.LPCB.EC0.XHPP) }
This method writes the value of its first argument to the \_SB.PCI0.LPCB.EC0.XHPP field, and returns the new value of this field. I named it “DEFT” for DEFine Temperature.
I recompiled my modified DSDT (“iasl -tc new.dsl”) and corrected the few errors of the original one (I will not cover this in this tutorial). No errors.
Now I just have to get a kernel which allow me to load my custom DSDT. You can find my custom DSDT here but remember it is good for my computer only as I did other modifications (for example, my computer was recognized as an Amilo Xi 1526 after I changed the graphic card, so I corrected that in the DSDT).
I followed these instructions, also available in this PDF document, to compile my own kernel. After I had generated my .config file using make menuconfig, I had to edit the generated .config file and add/modify the following lines:
CONFIG_ACPI_CUSTOM_DSDT_FILE="DSDT.hex" CONFIG_ACPI_CUSTOM_DSDT=y CONFIG_STANDALONE=y
Then I copied my DSDT.hex file in the include directory of my Linux kernel sources and launched the compilation. This took about 1 hour on an Intel Core 2 Duo T7200 (don’t forget to set the CONCURRENCY_LEVEL environment variable to the number of cores of your processor to compile faster).
Then installed the new kernel (explained in the previous PDF), rebooted successfully and ran a dmesg command. I was informed that the new kernel loaded my custom DSDT by the presence of the following lines:
[ 0.000000] ACPI: RSDP 00000000000f77b0 00014 (v00 FSC ) [ 0.000000] ACPI: RSDT 000000007fe943e2 00048 (v01 PTLTD PC 06040000 LTP 00000000) [ 0.000000] ACPI: FACP 000000007fe9ae20 00074 (v01 INTEL CALISTGA 06040000 LOHR 0000005A) [ 0.000000] ACPI: Override [DSDT-F35_____], this is unsafe: tainting kernel [ 0.000000] Disabling lock debugging due to kernel taint [ 0.000000] ACPI: DSDT @ 0x000000007fe95ade Table override, replaced with: [ 0.000000] ACPI: DSDT ffffffff81493970 05362 (v01 UW____ F35_____ 06040000 INTL 20100528) [ 0.000000] ACPI: FACS 000000007fe9bfc0 00040 [ 0.000000] ACPI: APIC 000000007fe9ae94 00068 (v01 INTEL CALISTGA 06040000 LOHR 0000005A) [ 0.000000] ACPI: HPET 000000007fe9aefc 00038 (v01 INTEL CALISTGA 06040000 LOHR 0000005A) [ 0.000000] ACPI: MCFG 000000007fe9af34 0003C (v01 INTEL CALISTGA 06040000 LOHR 0000005A) [ 0.000000] ACPI: APIC 000000007fe9af70 00068 (v01 FSC PC 06040000 LTP 00000000) [ 0.000000] ACPI: BOOT 000000007fe9afd8 00028 (v01 FSC PC 06040000 LTP 00000001) [ 0.000000] ACPI: SSDT 000000007fe9548f 0064F (v01 SataRe SataPri 00001000 INTL 20050624) [ 0.000000] ACPI: SSDT 000000007fe94dfd 00692 (v01 SataRe SataSec 00001000 INTL 20050624) [ 0.000000] ACPI: SSDT 000000007fe9442a 004F6 (v01 PmRef CpuPm 00003000 INTL 20050624)
So far, so good. Now is time to test the acpi_call kernel module. So extract its sources, and do the following:
# cd acpi_call_source_directory # make # make load
There should have an error about rmmod but that is totally normal, it is caused by the Makefile action trying to remove the module before installing the new compiled one. If you don’t have any error following the insmod command, then you are ready to make ACPI calls!
Basically, all you have to do is write your queries to the /proc/acpi/call file and the acpi_call module will execute it, and will return the result in the /proc/acpi/call file.
I tried a few commands in bash scripts in order to read /proc/acpi/call right after I wrote to it. For example:
#!/bin/bash echo '\_TZ.THPP' > /proc/acpi/call cat /proc/acpi/call
will return the current temperature (“0x3c” for example). I then tried to call my own method:
#!/bin/bash echo '\_TZ.THRM.DEFT 90' > /proc/acpi/call cat /proc/acpi/call
As expected, it returned 90 (0x5a). So I executed this several times, quickly. I heard my fan spinning up and the reported temperature was now 90°C!
Script to control the fan
We are now almost done. We now need a little script to control the behavior of the fan. I decided that I wanted the fan to spin whenever the temperature exceed 50°C, and let it do whatever it wants below. I also wanted the script to be an infinite loop that can be stopped by writing the value “STOP” to a file. I placed this script in the same directory than the acpi_call module. You will find this script, fan_cooler, below:
#!/bin/bash if ! lsmod | grep -q acpi_call; then cd $(dirname "$0") /sbin/insmod acpi_call.ko fi if ! lsmod | grep -q acpi_call; then echo "Error: acpi_call module not loaded" exit fi coolingtimer=0.05 coolingtemperature=50 coolingiterations=1000 coolingcheckduration=2 coolingidleduration=10 user=gabriel coolingstatusfile=/home/$user/FAN_COOLING_STATUS gksu -u $user -k "echo 'Started on $(date)' > $coolingstatusfile" FAN_COOLING_STATUS=$(cat $coolingstatusfile) until [ "$FAN_COOLING_STATUS" = "STOP" ]; do rawtemp=$(cat /proc/acpi/thermal_zone/THRM/temperature) set -- $rawtemp temperature=$2 echo "Temperature is" $temperature if [ "$temperature" -ge "$coolingtemperature" ]; then echo "Forces the fan to spin for $coolingiterations iterations!" for i in $(seq 0 $coolingiterations) do echo "\_TZ.THRM.DEFT 90" > /proc/acpi/call if [ "$1" = "debug" ]; then cat /proc/acpi/call echo "--end--" fi FAN_COOLING_STATUS=$(cat $coolingstatusfile) if [ "$FAN_COOLING_STATUS" = "STOP" ]; then echo "Exit forced, script terminated." FAN_COOLING_STATUS="EXITED $(date)" gksu -u $user -k "echo $FAN_COOLING_STATUS > $coolingstatusfile" exit fi sleep $coolingtimer done sleep $coolingcheckduration else echo "Nothing to do, waiting for $coolingidleduration seconds" sleep $coolingidleduration fi done echo "Exit forced, script terminated." FAN_COOLING_STATUS="EXITED $(date)" gksu -u $user -k "echo $FAN_COOLING_STATUS > $coolingstatusfile"
The script is also available here if you want to download it.
Run this script as root (or modify it with gksu), and you’re done!
The fan is now magically forced to spin whenever the temperature reach 50°C, and stops after a while, continuing to poll the temperature.
If you want to make things more user-friendly, create a launcher with the command “gksu /path/to/fan_cooler” and launch it where you want! Note that using a launcher will not display any window or terminal.
You can also make the following script (stop_fan_cooler) and make a launcher of it to stop the other script whenever you want:
#!/bin/bash echo "STOP" > /home/gabriel/FAN_COOLING_STATUS
Do not run this last script as root if you want the file FAN_COOLING_STATUS to be writable by your user.
You can modify this script as you want, I release it under the ISC license. For example, you could add several temperature threshold, leading to different fan speeds.
I believe that this tutorial is valid for the following laptop models because they share the same BIOS and DSDT table:
- Amilo Xi 1554
- Amilo Xi 1547
- Amilo Xi 1546
- Amilo Xi 1526
- Amilo Pi 1556
- Amilo Pi1 536
- Amilo Xi 14xx
And probably others.
This tutorial is now over, I hope you appreciated it and that it gave you some ideas to control your fan!
Nice work man!
I’ve also an Xi156..very hot machine..
Btw, I’ve read your dsdt and you probabily suffer to this particulary bug:
https://bugs.launchpad.net/ubuntu/+source/linux/+bug/100110
You will found my solution at comment #10.
The solution cause 2 second of sleep (originaly was 18), but in my dsdt I’ve set a sleep to 0x96 (150 ms), so the total sleep is 300 ms without any boot problem.
Enjoy! and thank you for this tutorial!
I just recompiled my kernel with the DSDT patch you told me about, and the boot is much faster now 🙂 That’s incredible that the computer was doing nothing during those seconds. Thanks again!
Thanks! I will check about this bug. I didn’t noticed that my computer take a long time to start, but if there is some seconds to win at each boot, it’s always good to take 🙂
Thanks for the articleit will make my machine good
hello,
i have a amilo pi2530
and my fan doesn’t work -> overheats
i’m using ubuntu12
and i’m a newbie and need a step by step tutorial how i can handel this problem (includes everything what to do on laptop)
the thinkfan this realted scripts i found doesn’t work becuase i can’t install the acpi-thinkpad
i just need a simple program to switch my cooler on
i found some parameters in sys/device/virtual/thermal/
but all the values ar to high and i can’t change it
please help me
or i put the laptop in trash and have to buy a new one
especialy designed for linux
Hi,
my script does not repair broken fans. However you can open your laptop and verify if the fan is well plugged and working. If not, the you should replace the fan.
hello
thanks for answer
my fan isn’t broken
but it looks like a conflict between graphic-drivers on this laptop
i deinstalled it with synaptic and fan works (cooling down to 50-60°)
but no 3d support
i try to find the right driver that works also with fan.
i’m looking forward to fix
Hi, great job, can u try do the same with win x64 ?
Very thanks for your sharing.
I used your approach on Lenovo N500. Unfortunately fan speeding-up didn’t solve problem, Seems it’s hardware design problem.
Anyway your tutorial was great and informative.