Fan control (NAS540)

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. = /sbin/i2cget = 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. = /firmware/sbin/mmiotool = 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 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: 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=$$ 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 fi fi exec $0.old "$@" The fake value is hexadecimal, e.g. 0x25 for 37°C
 * 1) !/bin/sh
 * 1) !/bin/sh
 * 1) In all other cases, pass it through

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.
 * 1) !/bin/sh