Stable device path for Linux hwmon interfaces
The problem
/sys/class/hwmon/hwmon2/temp1_input
reports your CPU temperature.
But the next time you reboot, the path might be /sys/class/hwmon/hwmon1/temp1_input
.
=> The path is not stable, so you cannot easily refer to it.
Example use case where you might need stable paths is use in i3status (it’s the status bar in the animation on top of this article).
The solution
Let’s write a udev rule to make a stable device path to the CPU temperature.
Identify hwmon interface (for this boot)
Identify the hwmon interface number that (currently) reports your CPU temperature.
There usually exist multiple instances (one for CPU, GPU, laptop battery etc.):
$ tree /sys/class/hwmon
/sys/class/hwmon
βββ hwmon0
βββ hwmon1
βββ hwmon2
βββ hwmon3
Each hwmon
interface has a name assigned to it. Check their names:
$ cat /sys/class/hwmon/hwmon{0,1,2,3}/name
hidpp_battery_0
atk0110
k10temp
fam15h_power
In both my AMD CPU systems k10temp is the driver for reporting temperature of the CPU.
=> hwmon2
is our interface during this boot.
Usually the driver exposes multiple measurements (identified by temp<NUMBER>_input
files):
$ ls /sys/class/hwmon/hwmon2/temp*_input
/sys/class/hwmon/hwmon2/temp1_input
/sys/class/hwmon/hwmon2/temp2_input
If there are multiple measurements, there might be (but not always!) temp<NUMBER>_label
files
help you distinguish what precisely it measures:
$ cat /sys/class/hwmon/hwmon2/temp*_label
Tdie
Tctl
Many people suggest that die temperature is the one you should pay attention to.
=> /sys/class/hwmon/hwmon2/temp1_input
is our target.
Find out details for udev rule
We need to write a udev rule to give our CPU temperature reading a path that does not change.
Now that we know the hwmon interface is at /sys/class/hwmon/hwmon2
(for this boot), we can interrogate its
udev attributes, so we can write a rule that matches the specific device:
$ udevadm info --attribute-walk --path=/sys/class/hwmon/hwmon2
looking at device '/devices/pci0000:00/0000:00:18.3/hwmon/hwmon2':
KERNEL=="hwmon2"
SUBSYSTEM=="hwmon" <--
DRIVER==""
ATTR{temp1_label}=="Tdie"
ATTR{temp2_label}=="Tctl"
ATTR{temp1_input}=="45875"
ATTR{temp2_input}=="45875"
ATTR{name}=="k10temp"
ATTR{temp1_max}=="70000"
looking at parent device '/devices/pci0000:00/0000:00:18.3':
KERNELS=="0000:00:18.3"
SUBSYSTEMS=="pci"
DRIVERS=="k10temp"
ATTRS{subsystem_device}=="0x0000"
ATTRS{local_cpus}=="000000ff"
ATTRS{class}=="0x060000"
ATTRS{d3cold_allowed}=="0"
ATTRS{subsystem_vendor}=="0x0000"
ATTRS{numa_node}=="-1"
ATTRS{driver_override}=="(null)"
ATTRS{irq}=="0"
ATTRS{device}=="0x15eb" <--
ATTRS{enable}=="0"
ATTRS{dma_mask_bits}=="32"
ATTRS{revision}=="0x00"
ATTRS{local_cpulist}=="0-7"
ATTRS{msi_bus}=="1"
ATTRS{ari_enabled}=="0"
ATTRS{consistent_dma_mask_bits}=="32"
ATTRS{broken_parity_status}=="0"
ATTRS{vendor}=="0x1022" <--
NOTE: I added arrows to show which keys we’re interested in.
The (0x1022, 0x15eb) combo identifies an exact PCI device (in this case AMD + “Raven/Raven2 Device 24: Function 3”). You can look up PCI vendor/device codes online.
Write udev rule
Now make /etc/udev/rules.d/cpu-temp-stable-path.rules
:
# Give CPU temp a stable device path
# AMD Ryzen 2400G (Raven/Raven2 Device 24: Function 3)
ACTION=="add", SUBSYSTEM=="hwmon", ATTRS{vendor}=="0x1022", ATTRS{device}=="0x15eb", RUN+="/bin/sh -c 'ln -s /sys$devpath/temp1_input /dev/cpu_temp'"
It’s safe on each boot to make a new symlink, because /dev
is a non-persistent (= RAM-based) filesystem.
Test the rule
You can test the rule matches by running:
$ udevadm test /sys/class/hwmon/hwmon2
<other output>
run: '/bin/sh -c 'ln -s /sys/devices/pci0000:00/0000:00:18.3/hwmon/hwmon2/temp1_input /dev/cpu_temp''
Unload module index
Unloaded link configuration context.
If the output includes the run: <make symlink>
line, the rule matched and you’re good.
Sidenote: udev native symlinks don’t seem to work for hwmon
There exists a native symlink facility (SYMLINK+=
) in udev, but we need to “run” a command to make a symlink,
because for some unexplained reason the symlink facility doesn’t seem to work for hwmon
interfaces.
Maybe it’s because hwmon natively doesn’t live under /dev
?
Conclusion
We wrote a udev rule that makes sure that /dev/cpu_temp
should always report your CPU temperature.
Good job!