Fan control (NAS540)

From NAS-Central Zyxel Wiki
Jump to: navigation, search

The fan in the NAS540 is software controlled, by the daemon /usr/sbin/app_wd. It does so by using 2 external tools, /sbin/i2cget and /firmware/sbin/mmiotool.


This tool is used to read values through an i2c bus.

~$ i2cget
Usage: i2cget [-f] [-y] I2CBUS CHIP-ADDRESS [DATA-ADDRESS [MODE]]
  I2CBUS is an integer or an I2C bus name
  ADDRESS is an integer (0x03 - 0x77)
  MODE is one of:
    b (read byte data, default)
    w (read word data)
    c (write byte/read byte)
    Append p for SMBus PEC

On the '540 you can read the CPU temperature, and the fan speed.

Read CPU temperature

For cpu temperature this command is used:

i2cget -y 0x0 0x0a 0x07

The output is the temperature in degrees Celcius (hexadecimal)

Read fan speed

For fan speed:

i2cget -y 0x0 0x0a 0x08

The output is the time in msec of one rotation (hexadecimal), or in other words, rpm = 60000 / value. Zero means the fan is not running.


I think this means 'memory mapped I/O tool'. It is used to read or write values on a certain memory address.

~$ mmiotool
Usage: mmiotool [-r PHYSADDR] [-w PHYSADDR VALUE] [-R PHYSADDR RANGE] [-m SLEEP] -h
          -r, --read PHYSADDR        : Read 32 bits from physical memory address PHYSADDR 
          -w, --write PHYSADDR VALUE : Write VALUE at physical memory address PHYSADDR 
          -R, --mread PHYSADDR RANGE : Read RANGE bytes starting from physical address PHYSADDR 
          -W, --mwrite PHYSADDR VALUE RANGE INCR : Write VALUE at physical address PHYSADDR over RANGE bytes, incrementing VALUE by INCR at each step 
          -m, --msleep SLEEP         : Sleep for SLEEP milliseconds 
          -h, --help                 : print this help message. 

Set fan speed

Fan speed is set by the command

mmiotool -w 0x9045802C <value>

Here value seems to be a value in the range ~500 - ~5000, which causes a fanspeed ~1360 - ~250 rpm. For my 540 I found this lookup table:

value resulting fan speed
512 1363
768 1333
1024 1304
1280 1276
1536 1224
1792 1176
2048 1132
2304 1071
2560 1016
2816 967
3072 895
3328 833
3584 750
3840 674
4096 582
4352 483
4608 387
4864 265

As I don't see an easy formula for this, I don't know how reproducable this is on other hardware.

User controlled fan speed

To control the fan speed yourself (maybe because you want a lower set temperature, maybe you want to use the disk thermo sensor, maybe you want it because you can), the are 3 obvious options:

Kill app_wd

As app_wd is setting the fan speed each 5 seconds, it makes no sense to just write your own values. You can kill app_wd, but it has more duties. It is also responsible for (re-)starting other daemons. So maybe it's not a good idea to kill it.

Hook /sbin/i2cget

/sbin/ is on a ramdrive (the initramfs) so you can simply hook it. Rename /sbin/i2cget to /sbin/i2cget.old. and create a new script /sbin/i2cget:


exec $0.old "$@"

Set the executable bit, and now your script will be invoked instead of the original i2cget. The only command passes the execution to the original i2cget, so nothing gained (yet) To control the fan speed this way you can feed app_wd with artificial temperatures. To do so you have to identify the caller, when CPU temperature is asked:


if [ "$4" = "0x07" ] ; then
    # CPU Temperature requested. Find caller
    pp=$( grep PPid /proc/${pp}/status | awk '{ print $2 }' )
    pp=$( grep PPid /proc/${pp}/status | awk '{ print $2 }' ) # Two times. The first one gives /bin/sh
    pp=$( readlink /proc/${pp}/exe )

    if [ "$pp" = "/usr/sbin/app_wd" ] ; then
        echo $my_fake_value
        exit 0
# In all other cases, pass it through
exec $0.old "$@"

The fake value is hexadecimal, e.g. 0x25 for 37°C

Hook /firmware/sbin/mmiotool

This executable is on a ramdrive either, so the same trick can be used. As far a I could find no other daemon uses this tool, so you don't even have to filter for the caller. Just provide a dummy output:

echo Wrote to  $2: $3
exit 0

Now you can call mmiotool.old in your own script to set the fan speed.