[LTP] [PATCH v4 14/31] testcases: sysfs: sys_hwmon01
Cyril Hrubis
chrubis@suse.cz
Thu Aug 27 13:21:40 CEST 2026
Add test for /sys/class/hwmon/*/* files.
Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
include/tst_path_defs.h | 1 +
runtest/sysfs | 1 +
testcases/kernel/sysfs/class/hwmon/.gitignore | 1 +
testcases/kernel/sysfs/class/hwmon/Makefile | 7 +
.../kernel/sysfs/class/hwmon/sys_hwmon01.c | 173 ++++++++++++++++++
5 files changed, 183 insertions(+)
create mode 100644 testcases/kernel/sysfs/class/hwmon/.gitignore
create mode 100644 testcases/kernel/sysfs/class/hwmon/Makefile
create mode 100644 testcases/kernel/sysfs/class/hwmon/sys_hwmon01.c
diff --git a/include/tst_path_defs.h b/include/tst_path_defs.h
index f3fa3efd6..7e9180ffe 100644
--- a/include/tst_path_defs.h
+++ b/include/tst_path_defs.h
@@ -95,6 +95,7 @@
/* SYSFS CLASS */
#define PATH_CLASS_ATA_DEVICE "/sys/class/ata_device"
#define PATH_CLASS_BDI "/sys/class/bdi"
+#define PATH_CLASS_HWMON "/sys/class/hwmon"
/* SYSFS DEVICES */
#define PATH_SYS_CLOCKEVENTS "/sys/devices/system/clockevents"
diff --git a/runtest/sysfs b/runtest/sysfs
index c0983a5be..23c7adc7a 100644
--- a/runtest/sysfs
+++ b/runtest/sysfs
@@ -10,3 +10,4 @@ sys_cpu_cache01 sys_cpu_cache01
sys_clockevents01 sys_clockevents01
sys_ata01 sys_ata01
sys_bdi01 sys_bdi01
+sys_hwmon01 sys_hwmon01
diff --git a/testcases/kernel/sysfs/class/hwmon/.gitignore b/testcases/kernel/sysfs/class/hwmon/.gitignore
new file mode 100644
index 000000000..f619181d7
--- /dev/null
+++ b/testcases/kernel/sysfs/class/hwmon/.gitignore
@@ -0,0 +1 @@
+/sys_hwmon01
diff --git a/testcases/kernel/sysfs/class/hwmon/Makefile b/testcases/kernel/sysfs/class/hwmon/Makefile
new file mode 100644
index 000000000..034038061
--- /dev/null
+++ b/testcases/kernel/sysfs/class/hwmon/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+
+top_srcdir ?= ../../../../..
+
+include $(top_srcdir)/include/mk/testcases.mk
+include $(top_srcdir)/include/mk/generic_leaf_target.mk
diff --git a/testcases/kernel/sysfs/class/hwmon/sys_hwmon01.c b/testcases/kernel/sysfs/class/hwmon/sys_hwmon01.c
new file mode 100644
index 000000000..8e2e569ae
--- /dev/null
+++ b/testcases/kernel/sysfs/class/hwmon/sys_hwmon01.c
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+/*\
+ * Walks all hardware monitoring devices under /sys/class/hwmon/ and performs
+ * sanity and cross-checks on the sensor channels exported per the stable
+ * hwmon sysfs ABI (Documentation/hwmon/sysfs-interface.rst).
+ *
+ * For every discovered channel it verifies that:
+ *
+ * - tempN_input is within a plausible range (-55000 to 200000 millidegree
+ * Celsius, i.e. within the extended-industrial silicon sensor rating floor
+ * and below a generously high bound)
+ * - tempN_alarm, tempN_crit_alarm, tempN_min_alarm, tempN_max_alarm and
+ * tempN_fault are booleans, when present
+ * - fanN_input is within a plausible range (0 to 100000 RPM, comfortably
+ * above even exotic small high-speed blower fans)
+ * - fanN_alarm, fanN_fault, fanN_min_alarm and fanN_max_alarm are booleans,
+ * when present
+ * - inN_input is within a plausible range (-100000 to 100000 millivolt,
+ * allowing for negative supply rails, not just positive ones, inN
+ * channels are numbered from 0, unlike the other channel types which
+ * start at 1)
+ * - inN_alarm and related alarm attributes are booleans, when present
+ * - currN_input is within a plausible range (-500000 to 500000 milliampere,
+ * allowing for negative values on bidirectional/battery charge-discharge
+ * current sensors)
+ * - powerN_input is within a plausible range (0 to 2000000000 microwatt,
+ * i.e. 2 kW, chosen to stay within LONG_MAX on 32bit architectures as well)
+ * - pwmN is in the documented range [0, 255]
+ * - update_interval, when present, is within a plausible range (1 to
+ * 3600000 millisecond, i.e. up to an hour)
+ *
+ * All checks skip silently when a particular channel or attribute is not
+ * present, since the set of exposed sensors varies wildly between chips and
+ * platforms and up to MAX_CHANNELS indices are probed for every channel type
+ * regardless of how many are actually implemented by a given chip.
+ *
+ * Note: per Documentation/ABI/testing/sysfs-class-hwmon, tempN/inN/currN
+ * min/max/crit/lcrit are plain independent RW attributes. The kernel does
+ * not cross-validate them against each other (only tempN_crit/tempN_lcrit
+ * even mention a ``typically greater/lower than`` relation to max/min, and
+ * that is explicitly not a guarantee), so this test deliberately does not
+ * assert any ordering between them, e.g. a firmware default of minN > maxN
+ * is not a kernel or driver bug.
+ */
+
+#include <string.h>
+#include <dirent.h>
+
+#include "tst_test.h"
+#include "tst_sysfs_assert.h"
+#include "tst_path_defs.h"
+
+#define MAX_CHANNELS 32
+
+static void check_temp_channel(const char *hwmon, int idx)
+{
+ TST_SYSFS_ASSERT_RANGELL_SILENT(-55000, 200000,
+ PATH_CLASS_HWMON "/%s/temp%d_input", hwmon, idx);
+
+ TST_SYSFS_ASSERT_BOOL_SILENT(PATH_CLASS_HWMON "/%s/temp%d_alarm", hwmon, idx);
+ TST_SYSFS_ASSERT_BOOL_SILENT(PATH_CLASS_HWMON "/%s/temp%d_crit_alarm", hwmon, idx);
+ TST_SYSFS_ASSERT_BOOL_SILENT(PATH_CLASS_HWMON "/%s/temp%d_min_alarm", hwmon, idx);
+ TST_SYSFS_ASSERT_BOOL_SILENT(PATH_CLASS_HWMON "/%s/temp%d_max_alarm", hwmon, idx);
+ TST_SYSFS_ASSERT_BOOL_SILENT(PATH_CLASS_HWMON "/%s/temp%d_fault", hwmon, idx);
+}
+
+static void check_fan_channel(const char *hwmon, int idx)
+{
+ TST_SYSFS_ASSERT_RANGELL_SILENT(0, 100000,
+ PATH_CLASS_HWMON "/%s/fan%d_input", hwmon, idx);
+
+ TST_SYSFS_ASSERT_BOOL_SILENT(PATH_CLASS_HWMON "/%s/fan%d_alarm", hwmon, idx);
+ TST_SYSFS_ASSERT_BOOL_SILENT(PATH_CLASS_HWMON "/%s/fan%d_min_alarm", hwmon, idx);
+ TST_SYSFS_ASSERT_BOOL_SILENT(PATH_CLASS_HWMON "/%s/fan%d_max_alarm", hwmon, idx);
+ TST_SYSFS_ASSERT_BOOL_SILENT(PATH_CLASS_HWMON "/%s/fan%d_fault", hwmon, idx);
+}
+
+static void check_in_channel(const char *hwmon, int idx)
+{
+ TST_SYSFS_ASSERT_RANGELL_SILENT(-100000, 100000,
+ PATH_CLASS_HWMON "/%s/in%d_input", hwmon, idx);
+
+ TST_SYSFS_ASSERT_BOOL_SILENT(PATH_CLASS_HWMON "/%s/in%d_alarm", hwmon, idx);
+ TST_SYSFS_ASSERT_BOOL_SILENT(PATH_CLASS_HWMON "/%s/in%d_min_alarm", hwmon, idx);
+ TST_SYSFS_ASSERT_BOOL_SILENT(PATH_CLASS_HWMON "/%s/in%d_max_alarm", hwmon, idx);
+ TST_SYSFS_ASSERT_BOOL_SILENT(PATH_CLASS_HWMON "/%s/in%d_crit_alarm", hwmon, idx);
+ TST_SYSFS_ASSERT_BOOL_SILENT(PATH_CLASS_HWMON "/%s/in%d_lcrit_alarm", hwmon, idx);
+}
+
+static void check_curr_channel(const char *hwmon, int idx)
+{
+ /*
+ * Unlike fan/power, current can be negative on bidirectional sensors,
+ * e.g. a battery charge/discharge current monitor.
+ */
+ TST_SYSFS_ASSERT_RANGELL_SILENT(-500000, 500000,
+ PATH_CLASS_HWMON "/%s/curr%d_input", hwmon, idx);
+
+ TST_SYSFS_ASSERT_BOOL_SILENT(PATH_CLASS_HWMON "/%s/curr%d_alarm", hwmon, idx);
+}
+
+static void check_power_channel(const char *hwmon, int idx)
+{
+ /*
+ * 2000000000 uW (2 kW) comfortably covers any single-component power
+ * sensor (CPU, GPU, PSU rail, ...) while still fitting within
+ * LONG_MAX on 32bit architectures.
+ */
+ TST_SYSFS_ASSERT_RANGELL_SILENT(0, 2000000000,
+ PATH_CLASS_HWMON "/%s/power%d_input", hwmon, idx);
+}
+
+static void check_pwm_channel(const char *hwmon, int idx)
+{
+ TST_SYSFS_ASSERT_RANGELL_SILENT(0, 255, PATH_CLASS_HWMON "/%s/pwm%d", hwmon, idx);
+}
+
+static void check_hwmon_device(const char *hwmon)
+{
+ char name[64];
+ int i;
+
+ if (TST_SYSFS_READ_STR(name, sizeof(name), PATH_CLASS_HWMON "/%s/name", hwmon))
+ tst_res(TINFO, "%s: name = '%s'", hwmon, name);
+
+ TST_SYSFS_ASSERT_RANGELL_SILENT(1, 3600000,
+ PATH_CLASS_HWMON "/%s/update_interval", hwmon);
+
+ /* inN channels are numbered from 0, per the hwmon ABI */
+ for (i = 0; i <= MAX_CHANNELS; i++)
+ check_in_channel(hwmon, i);
+
+ for (i = 1; i <= MAX_CHANNELS; i++) {
+ check_temp_channel(hwmon, i);
+ check_fan_channel(hwmon, i);
+ check_curr_channel(hwmon, i);
+ check_power_channel(hwmon, i);
+ check_pwm_channel(hwmon, i);
+ }
+}
+
+static void do_test(void)
+{
+ DIR *d;
+ struct dirent *ent;
+ int found = 0;
+
+ if (!tst_sysfs_exists(PATH_CLASS_HWMON))
+ tst_brk(TCONF, PATH_CLASS_HWMON ": not present");
+
+ d = SAFE_OPENDIR(PATH_CLASS_HWMON);
+
+ while ((ent = SAFE_READDIR(d))) {
+ if (strncmp(ent->d_name, "hwmon", 5))
+ continue;
+
+ found = 1;
+ check_hwmon_device(ent->d_name);
+ }
+
+ SAFE_CLOSEDIR(d);
+
+ if (!found)
+ tst_res(TCONF, "No hwmon device found");
+}
+
+static struct tst_test test = {
+ .test_all = do_test,
+};
--
2.54.0
More information about the ltp
mailing list