[PATCH v1 17/31] testcases: sysfs: Add sys_rtc01

Cyril Hrubis <[email protected]>
Newsgroups gmane.linux.ltp
Message-ID <[email protected]>
A test for /sys/class/rtc/*/* files.

Signed-off-by: Cyril Hrubis <[email protected]>
---
 runtest/sysfs                                |   1 +
 testcases/kernel/sysfs/class/rtc/.gitignore  |   1 +
 testcases/kernel/sysfs/class/rtc/Makefile    |   7 +
 testcases/kernel/sysfs/class/rtc/sys_rtc01.c | 200 +++++++++++++++++++
 4 files changed, 209 insertions(+)
 create mode 100644 testcases/kernel/sysfs/class/rtc/.gitignore
 create mode 100644 testcases/kernel/sysfs/class/rtc/Makefile
 create mode 100644 testcases/kernel/sysfs/class/rtc/sys_rtc01.c

diff --git a/runtest/sysfs b/runtest/sysfs
index b8d003228..de58499be 100644
--- a/runtest/sysfs
+++ b/runtest/sysfs
@@ -13,3 +13,4 @@ sys_bdi01 sys_bdi01
 sys_hwmon01 sys_hwmon01
 sys_leds01 sys_leds01
 sys_wakeup01 sys_wakeup01
+sys_rtc01 sys_rtc01
diff --git a/testcases/kernel/sysfs/class/rtc/.gitignore b/testcases/kernel/sysfs/class/rtc/.gitignore
new file mode 100644
index 000000000..0ce416b43
--- /dev/null
+++ b/testcases/kernel/sysfs/class/rtc/.gitignore
@@ -0,0 +1 @@
+/sys_rtc01
diff --git a/testcases/kernel/sysfs/class/rtc/Makefile b/testcases/kernel/sysfs/class/rtc/Makefile
new file mode 100644
index 000000000..034038061
--- /dev/null
+++ b/testcases/kernel/sysfs/class/rtc/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2026 Cyril Hrubis <[email protected]>
+
+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/rtc/sys_rtc01.c b/testcases/kernel/sysfs/class/rtc/sys_rtc01.c
new file mode 100644
index 000000000..6ae1b4465
--- /dev/null
+++ b/testcases/kernel/sysfs/class/rtc/sys_rtc01.c
@@ -0,0 +1,200 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <[email protected]>
+ */
+
+/*\
+ * Sanity checks for the RTC attributes exported under
+ * /sys/class/rtc/rtcN/.
+ *
+ * date and time report the RTC clock in UTC, while since_epoch reports the
+ * same value pre-converted to a Unix timestamp. For every RTC device the
+ * test verifies that:
+ *
+ * - hctosys is a boolean
+ * - max_user_freq is greater than zero
+ * - since_epoch matches date and time converted to a Unix timestamp
+ * - for the RTC device with hctosys == 1, i.e. the one that was actually
+ *   used by the kernel's own CONFIG_RTC_HCTOSYS code to set the system clock
+ *   at boot, since_epoch is additionally close to the current system time(2)
+ *   value. On a system with multiple RTC devices the other ones are not
+ *   guaranteed to be in sync with the system clock at all (e.g. a
+ *   peripheral's battery-backed RTC used only for its own wakeup alarm), so
+ *   this second check is skipped for them.
+ *
+ * The hctosys only reflects whether the kernel itself performed the sync; many
+ * setups runs "hwclock --hctosys" from a userspace init script instead sync
+ * the system clock from the RTC without the kernel ever knowing about it,
+ * leaving hctosys == 0 on every device despite the clock legitimately being in
+ * sync. To still get coverage on such systems, if no RTC has hctosys == 1 at
+ * all, rtc0 is treated as the presumed primary RTC and gets the system time
+ * check anyway.
+ *
+ * The date/time vs since_epoch cross-check allows a few seconds of slack
+ * since they are read from separate files and the wall clock keeps ticking
+ * in between reads.
+ *
+ * The since_epoch vs system time(2) cross-check allows extra slack that
+ * grows with system uptime: the RTC is a free-running hardware crystal
+ * oscillator that (absent some periodic resync mechanism, which is not
+ * guaranteed to be running) drifts independently of the system clock the
+ * whole time the system is up, not just across the couple of reads this
+ * test performs, so on a long-running system a purely fixed slack would
+ * eventually produce false failures even on perfectly healthy hardware.
+ *
+ * The check may still fail if the system was booted with RTC that is fairly
+ * off the current time and the system was later synced with NTP.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <limits.h>
+#include <time.h>
+#include <sys/sysinfo.h>
+#include "tst_test.h"
+#include "tst_sysfs_assert.h"
+
+#define RTC_CLASS "/sys/class/rtc"
+#define SLACK_SEC 10
+
+/*
+ * Generously high assumed worst-case RTC crystal drift, in parts per
+ * million, used to scale the system time slack with uptime. 200 ppm covers
+ * even fairly poor/uncompensated RTC crystals across a wide temperature
+ * range; a decent RTC typically drifts far less than this.
+ */
+#define MAX_RTC_DRIFT_PPM 200
+
+static long sys_time_slack_sec(void)
+{
+	struct sysinfo info;
+
+	SAFE_SYSINFO(&info);
+
+	return SLACK_SEC + (long)info.uptime * MAX_RTC_DRIFT_PPM / 1000000;
+}
+
+static long read_hctosys(const char *name)
+{
+	return TST_SYSFS_READ_LI(RTC_CLASS "/%s/hctosys", name);
+}
+
+static void check_rtc(const char *name, int any_hctosys)
+{
+	char date[32] = "", rtc_time[32] = "";
+	struct tm tm = { 0 };
+	time_t rtc_epoch, sys_epoch;
+	long  hctosys, slack;
+	time_t since_epoch;
+	int is_primary;
+
+	TST_SYSFS_ASSERT_BOOL(RTC_CLASS "/%s/hctosys", name);
+	TST_SYSFS_ASSERT_RANGELL(1, LONG_MAX, RTC_CLASS "/%s/max_user_freq", name);
+
+	hctosys = read_hctosys(name);
+
+	/*
+	 * If no RTC has hctosys == 1 anywhere (see the top comment), fall
+	 * back to treating rtc0 as the presumed primary RTC.
+	 */
+	is_primary = hctosys || (!any_hctosys && !strcmp(name, "rtc0"));
+
+	TST_SYSFS_READ_STR(date, sizeof(date), RTC_CLASS "/%s/date", name);
+	TST_SYSFS_READ_STR(rtc_time, sizeof(rtc_time), RTC_CLASS "/%s/time",
+			   name);
+
+	since_epoch = TST_SYSFS_READ_LI(RTC_CLASS "/%s/since_epoch", name);
+
+	sys_epoch = time(NULL);
+
+	if (sscanf(date, "%d-%d-%d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday) != 3 ||
+	    sscanf(rtc_time, "%d:%d:%d", &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 3) {
+		tst_res(TFAIL, "%s: failed to parse date '%s' / time '%s'",
+			name, date, rtc_time);
+		return;
+	}
+
+	tm.tm_year -= 1900;
+	tm.tm_mon -= 1;
+	rtc_epoch = timegm(&tm);
+
+	if (llabs((long long)(rtc_epoch - since_epoch)) <= SLACK_SEC) {
+		tst_res(TPASS,
+			"%s: since_epoch (%lld) matches date '%s' time '%s' (%lld)",
+			name, (long long)since_epoch, date, rtc_time,
+			(long long)rtc_epoch);
+	} else {
+		tst_res(TFAIL,
+			"%s: since_epoch (%lld) does not match date '%s' time '%s' (%lld)",
+			name, (long long)since_epoch, date, rtc_time,
+			(long long)rtc_epoch);
+	}
+
+	if (!is_primary) {
+		tst_res(TINFO,
+			"%s: not the (assumed) primary RTC, not checking since_epoch against system time",
+			name);
+		return;
+	}
+
+	slack = sys_time_slack_sec();
+	long long diff = llabs((long long)(since_epoch - sys_epoch));
+
+	if (diff <= slack) {
+		tst_res(TPASS,
+			"%s: since_epoch (%lld), system time (%lld), diff %llds <= slack %lds",
+			name, (long long)since_epoch, (long long)sys_epoch,
+			diff, slack);
+	} else {
+		tst_res(TFAIL,
+			"%s: since_epoch (%lld), system time (%lld), diff %llds > slack %lds",
+			name, (long long)since_epoch, (long long)sys_epoch,
+			diff, slack);
+	}
+}
+
+static void do_test(void)
+{
+	DIR *d;
+	struct dirent *ent;
+	int found = 0;
+	int any_hctosys = 0;
+
+	d = SAFE_OPENDIR(RTC_CLASS);
+
+	/* First pass: find out whether any RTC has hctosys == 1 already. */
+	while ((ent = SAFE_READDIR(d))) {
+		if (strncmp(ent->d_name, "rtc", 3))
+			continue;
+
+		found = 1;
+
+		if (read_hctosys(ent->d_name))
+			any_hctosys = 1;
+	}
+
+	if (!found) {
+		tst_res(TCONF, "No RTC device found");
+		SAFE_CLOSEDIR(d);
+		return;
+	}
+
+	rewinddir(d);
+
+	/* Second pass: run the actual checks. */
+	while ((ent = SAFE_READDIR(d))) {
+		if (strncmp(ent->d_name, "rtc", 3))
+			continue;
+
+		tst_res(TINFO, "Checking %s", ent->d_name);
+		check_rtc(ent->d_name, any_hctosys);
+	}
+
+	SAFE_CLOSEDIR(d);
+}
+
+static struct tst_test test = {
+	.test_all = do_test,
+};
-- 
2.54.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.