My Odroid HC4 uses fancontrol to set the chassis fan speed. This is the /etc/fancontrol config file created using the interactive pwmconfig command:

# cat /etc/fancontrol

# Configuration file generated by pwmconfig, changes will be lost
INTERVAL=10
DEVPATH=hwmon0=devices/virtual/thermal/thermal_zone0 hwmon2=devices/platform/pwm-fan
DEVNAME=hwmon0=cpu_thermal hwmon2=pwmfan
FCTEMPS=hwmon2/pwm1=hwmon0/temp1_input
FCFANS=hwmon2/pwm1=hwmon2/fan1_input
MINTEMP=hwmon2/pwm1=20
MAXTEMP=hwmon2/pwm1=60
MINSTART=hwmon2/pwm1=76
MINSTOP=hwmon2/pwm1=75
MINPWM=hwmon2/pwm1=75
MAXPWM=hwmon2/pwm1=76

With this config, fancontrol was working fine, but only until I had to reboot the box. When attempting to start fancontrol after a reboot, I got the following error message:

Configuration appears to be outdated, please run pwmconfig again.

I ran pwmconfig again, then compared the old and the new config file and noticed that the hwmonX numbers have changed - e.g. hwmon2 in the old config file was now hwmon0.

After some more investigation (thank you, google) I found out that the random order of hwmon numbers after each reboot is caused by the related kernel modules that are being loaded in a non-deterministic order.

How to fix this

The solution is to use absolute paths in the /etc/fancontrol config file. The DEVPATH and DEVNAME variables can be commented-out (or removed) and the remaining variables have to be converted to use absolute paths to the appropriate files in /sys/devices/. The random numbers in the hwmon portion of the path will be replaced with a wildcard.

As an example, let's convert the FCTEMPS variable value step by step:

The old value is
FCTEMPS=hwmon2/pwm1=hwmon0/temp1_input.

So there are 4 values to identify and convert: hwmon2, pwm1, hwmon0 and temp1_input.

By checking the DEVPATH value in the old config above, we can see that hwmon2 was set to devices/platform/pwm-fan. This corresponds to this absolute path: /sys/devices/platform/pwm-fan.

pwm1 has to be found somewhere deeper in that directory structure. In my case, the full path is: /sys/devices/platform/pwm-fan/hwmon/hwmonX/pwm1, where X is the number that might be different after each reboot. We will replace this number with the [[:print:]]* wildcard later on (this wildcard means any printable character followed by zero or more printable characters).

We also know from the DEVPATH value that hwmon0 was set to devices/virtual/thermal/thermal_zone0. The absolute path in this case is: /sys/devices/virtual/thermal/thermal_zone0.

Again, temp1_input has to be found deeper in that directory structure. This time, the full path is: /sys/devices/virtual/thermal/thermal_zone0/hwmonX/temp1_input.

The intermediate FCTEMPS value then becomes
FCTEMPS=/sys/devices/platform/pwm-fan/hwmon/hwmonX/pwm1=/sys/devices/virtual/thermal/thermal_zone0/hwmonX/temp1_input.

Let's now replace the X with the wildcard to get this rather long final FCTEMPS value: FCTEMPS=/sys/devices/platform/pwm-fan/hwmon/hwmon[[:print:]]*/pwm1=/sys/devices/virtual/thermal/thermal_zone0/hwmon[[:print:]]*/temp1_input

The remaining variables need to be converted in a similar way.

My final config file where all variables are set using absolute paths looks like this:

# cat /etc/fancontrol

# Configuration file generated by pwmconfig, changes will be lost
INTERVAL=10
FCTEMPS=/sys/devices/platform/pwm-fan/hwmon/hwmon[[:print:]]*/pwm1=/sys/devices/virtual/thermal/thermal_zone0/hwmon[[:print:]]*/temp1_input
FCFANS=/sys/devices/platform/pwm-fan/hwmon/hwmon[[:print:]]*/pwm1=/sys/devices/platform/pwm-fan/hwmon/hwmon[[:print:]]*/fan1_input
MINTEMP=/sys/devices/platform/pwm-fan/hwmon/hwmon[[:print:]]*/pwm1=20
MAXTEMP=/sys/devices/platform/pwm-fan/hwmon/hwmon[[:print:]]*/pwm1=60
MINSTART=/sys/devices/platform/pwm-fan/hwmon/hwmon[[:print:]]*/pwm1=76
MINSTOP=/sys/devices/platform/pwm-fan/hwmon/hwmon[[:print:]]*/pwm1=75
MINPWM=/sys/devices/platform/pwm-fan/hwmon/hwmon[[:print:]]*/pwm1=75
MAXPWM=/sys/devices/platform/pwm-fan/hwmon/hwmon[[:print:]]*/pwm1=76

With this config, fancontrol works reliably across reboots.