Asus F3Jp fan control on Linux
Alexander Breckel, last update on 2008/09/08
I'm a more or less proud owner of an Asus F3Jp notebook, running a dualboot setup with Archlinux and Windows XP. Unfortunately on Linux the CPU fan is constantly running and over time the fan noise became so annoying that I tried to fix the problem. Here's a summary of what I did.
Caution: PLEASE do not try any of this at home without being FULLY aware of the consequences. I'm writing this solely for your information and I cannot be held responsible for ANY damage this probably will do to your notebook, your family or your country.
My setup consists of an Asus F3Jp running an up-to-date Archlinux 2008.06 with the version 2.6.26 of the Linux kernel. The CPU fan is constantly running; not on full speed, but still pretty loud. I tried to run lm_sensors using this manual, but `sensors` only reported the following:
~$ sensors acpitz-virtual-0 Adapter: Virtual device temp1: +54.0°C (crit = +105.0°C) coretemp-isa-0000 Adapter: ISA adapter Core 0: +59.0°C (high = +100.0°C, crit = +100.0°C) coretemp-isa-0001 Adapter: ISA adapter Core 1: +59.0°C (high = +100.0°C, crit = +100.0°C)
No fans are detected, because /proc/acpi/fan/ is empty. Normally there should be a subdirectory for each fan containing state information like RPM. This list indicates, that an "ACPI fan" should report the PnP Device ID PNP0C0B, but /sys/bus/acpi/devices/PNP0C0B:0x does not exist. So the problem is, that the CPU fan is either not ACPI compliant at all or just badly configured. I updated the BIOS to the current version 209 without noticing any changes. Interestingly on Windows XP SP2 the fan can be throttled with the Software Notebook Hardware Control (NHC) using its ACPI Control System. So it is possible to control the fan via ACPI, but somehow something on Linux prevents this from happening.
So lets take a look at how NHC manages to control the fan. Fortunately the relevant source code is available if you download and install the software. If you open up %INSTALLDIR%\acpi\ASUSTeK.cs, there is a beautiful piece of information I'll copy in here. Please contact me if you want this removed from here.
8333 // Fan control for Asus F3JP 8334 // User class by Martin Nössing 8335 // 8336 // The Fan speed will be controlled by the _SB.PCI0.SBRG.EC0.WMFN register in the Embedded Controller. 8337 // WMFN is a 8 bit register. The MSB (Bit 7) control the method of fan control: 8338 // manual = 0, automatic (default)= 1 8339 // The next 4 Bit (Bit 6 - 2) sets the fan speed (only when Bit 7 is 0): 8340 // value 0x0 to 0x5: fan is off 8341 // from value 0x5 to 0xF: fan speeds in ascending order 8342 // The only problem is that a maximum fan speed set in WMFN(0xF) is not the realy maximum speed of the fan. In automatic mode, 8343 // the fan can drive the motor more faster on higher tempertures! [...] 8353 //****NOTE!!:IF YOU TURN YOUR FAN OFF MANUALLY, OVERHEATING MAY OCCUR.!!****
In 2004 Alex Williamson wrote a Linux kernel patch that made ACPI objects and methods accessible in /sys/firmware/acpi/namespace/ACPI, but with Linux 2.6.21 the ACPI namespace was removed. If your kernel version is lower than that, you could try the approach described in here in some weird language. I found no way to get raw access to ACPI without modifying the kernel, which I really want to avoid. But if we cannot work with ACPI, maybe we could work like ACPI and emulate what it would have done if we had somehow modified _SB.PCI0.SBRG.EC0.WMFN.
So lets take a short look at what ACPI is and does. For deeper knowledge consider reading Wikipedia and some parts of the ACPI Specification, especially chapter 17 on ASL. The Advanced Configuration and Power Interface provides important information about your hardware and callable methods to control it. Altough it is technically capable of saving the human race, it is widely used to toggle suspend and monitor your battery status. The most interesting part for now is the Differentiated System Description Table (DSDT). Among other things it contains the definition of _SB.PCI0.SBRG.EC0.WMFN. The DSDT can be accessed at /proc/acpi/dsdt, but unfortunately it has to be disassembled first to be human readable. You probably need to install the Intel ACPI Source Language Compiler, which is part of the ACPICA release. On Archlinux the package community/iasl provides everything you need.
~$ sudo cp /proc/acpi/dsdt dsdt ~$ iasl -d dsdt Intel ACPI Component Architecture AML Disassembler version 20080729 [Aug 21 2008] Copyright (C) 2000 - 2008 Intel Corporation Supports ACPI Specification Revision 3.0a Loading Acpi table from file dsdt Acpi table [DSDT] successfully installed and loaded Pass 1 parse of [DSDT] Pass 2 parse of [DSDT] Parsing Deferred Opcodes (Methods/Buffers/Packages/Regions) ............................................................ Parsing completed Disassembly completed, written to "dsdt.dsl"
Now the file dsdt.dsl contains some code in ASL. I'll copy the relevant parts of my DSDT, but please keep in mind this is highly hardware- and BIOS-specific, so your version may very well differ from mine:
4521 Mutex (ASIO, 0x00)
[...]
5884 OperationRegion (KAID, SystemIO, 0x025C, 0x01)
5885 Field (KAID, ByteAcc, NoLock, Preserve)
5886 {
5887 AEID, 8
5888 }
5889
5890 OperationRegion (KAIC, SystemIO, 0x025D, 0x01)
5891 Field (KAIC, ByteAcc, NoLock, Preserve)
5892 {
5893 AEIC, 8
5894 }
5895
5896 Method (WEIE, 0, Serialized)
5897 {
5898 Store (0x4000, Local0)
5899 And (AEIC, 0x02, Local1)
5900 While (LAnd (LNotEqual (Local0, Zero), LEqual (Local1, 0x02)))
5901 {
5902 And (AEIC, 0x02, Local1)
5903 Decrement (Local0)
5904 }
5905 }
[...]
5982 Method (WMFN, 1, Serialized)
5983 {
5984 If (LEqual (Acquire (ASIO, 0xFFFF), 0x00))
5985 {
5986 WEIE ()
5987 Store (0x98, AEIC)
5988 WEIE ()
5989 Store (Arg0, AEID)
5990 WEIE ()
5991 Release (ASIO)
5992 }
5993 }
AEID and AEIC are the IO ports 0x025C and 0x025D. 'D' and 'C' sound much like Data and Control registers. WEIE is a method that waits for the seconds bit of AEIC to clear, probably some 'ready' status indicator. And WMFN is a method with one argument, that at first writes the byte 0x98 to the IO port AEIC (maybe a 'set fan speed' command) and then the argument to AEID. If you are looking for some mysterious ACPI magic ... there is none. All this code does can easily be achieved with a userspace C program like this:
Caution: Please do not execute this code, especially on anything else than an Asus F3Jp. It works for me, but probably won't work for you. It may even destroy your notebook as you can easily disable your CPU fan with this!
#include <stdio.h> // printf #include <stdlib.h> // atoi #include <stdint.h> // uint8_t, uint16_t #include <string.h> // strcmp #include <sys/io.h> // inb, outb // IO ports const uint16_t AEIC = 0x025D; // command register const uint16_t AEID = 0x025C; // data register // waits for the status bit to clear, max 0x4000 tries void WEIE() { uint16_t Local0 = 0x4000; uint8_t Local1 = inb(AEIC) & 0x02; while(Local0 != 0 && Local1 == 0x02) { Local1 = inb(AEIC) & 0x02; Local0--; } } // sets the fan speed void WMFN(uint8_t Arg0) { WEIE(); outb(0x98, AEIC); WEIE(); outb(Arg0, AEID); WEIE(); } int main(int argc, char ** argv) { if(argc != 2) { printf("usage: %s speed\n", argv[0]); printf("speed: `auto' or a value between 1 and 15\n"); printf("keep in mind that `auto' will be even faster than 15!\n"); return 1; } uint8_t speed = 0xFF; if(strcmp(argv[1], "auto") == 0) printf("setting speed to 'auto'\n"); else { int arg = atoi(argv[1]); if(arg < 1 || arg > 15) { printf("Error: the speed %d is not possible\n", arg); return 1; } printf("setting speed to %d\n", arg); speed = (arg << 3) | 0x07; } if(ioperm(AEID, 1, 1)) { printf("Error: could not gain access to IO port AEID (0x025C)\n"); return 1; } if(ioperm(AEIC, 1, 1)) { printf("Error: could not gain access to IO port AEIC (0x025D)\n"); return 1; } WMFN(speed); printf("done.\n"); return 0; }
The next step could be to write a script that polls the CPU temperature and sets the fan speed appropriatly. I still don't know how to read the fan RPM, so a customized DSDT to allow lm_sensors to manage the fan is still far away.
#!/bin/sh OLD_SPEED="0" OLD_TEMP="0" setSpeed () { if [ "$1" != "$OLD_SPEED" ]; then echo "new speed $1" OLD_SPEED="$1" ./fan "$1" > /dev/null fi } while [ 1 ]; do TEMP=`cat /proc/acpi/thermal_zone/THRM/temperature | awk '{print $2}'` if [ "$TEMP" != "$OLD_TEMP" ]; then echo "temperature $TEMP C" OLD_TEMP="$TEMP" fi if [ $TEMP -gt 65 ]; then setSpeed auto elif [ $TEMP -gt 60 ]; then setSpeed 13 elif [ $TEMP -gt 55 ]; then setSpeed 11 elif [ $TEMP -gt 50 ]; then setSpeed 9 else setSpeed 1 fi sleep 2 done
@Berion, I have to admin that after getting it to work this way I was too lazy to look further. But if anyone else wants to give it a try, I will happily post / link to the results here. Maybe the lm-sensors guys?
You saved me :)
It works so nice now.
I'm available to discuss and help to solve this problem!
thx a lot for your work,
mebitek
Thank you!!!
-ttturgut
People told me that these scripts and programs also work on ASUS F6A and Asus B50A, altough I cannot verify this. Asus, feel free to send me Notebooks ;-)