Auxiliary clocks behave differently from regular ones.
Add a testcase to validate their functionality.

Signed-off-by: Thomas Weißschuh <thomas.weisssc...@linutronix.de>
---
 tools/testing/selftests/timers/.gitignore |   1 +
 tools/testing/selftests/timers/Makefile   |   2 +-
 tools/testing/selftests/timers/auxclock.c | 319 ++++++++++++++++++++++++++++++
 3 files changed, 321 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/timers/.gitignore 
b/tools/testing/selftests/timers/.gitignore
index 
bb5326ff900b8edc3aa2d8d596599973593fbaf0..dcee43b3ecd9351c9bb0483088d712ccd7b57367
 100644
--- a/tools/testing/selftests/timers/.gitignore
+++ b/tools/testing/selftests/timers/.gitignore
@@ -20,3 +20,4 @@ valid-adjtimex
 adjtick
 set-tz
 freq-step
+auxclock
diff --git a/tools/testing/selftests/timers/Makefile 
b/tools/testing/selftests/timers/Makefile
index 
32203593c62e1e0cdfd3de6f567ea1e82913f2ef..3a8833b3fb7449495c66a92c4d82e35a6755b5e8
 100644
--- a/tools/testing/selftests/timers/Makefile
+++ b/tools/testing/selftests/timers/Makefile
@@ -5,7 +5,7 @@ LDLIBS += -lrt -lpthread -lm
 # these are all "safe" tests that don't modify
 # system time or require escalated privileges
 TEST_GEN_PROGS = posix_timers nanosleep nsleep-lat set-timer-lat mqueue-lat \
-            inconsistency-check raw_skew threadtest rtcpie
+            inconsistency-check raw_skew threadtest rtcpie auxclock
 
 DESTRUCTIVE_TESTS = alarmtimer-suspend valid-adjtimex adjtick change_skew \
                      skew_consistency clocksource-switch freq-step leap-a-day \
diff --git a/tools/testing/selftests/timers/auxclock.c 
b/tools/testing/selftests/timers/auxclock.c
new file mode 100644
index 
0000000000000000000000000000000000000000..0ba2f9996114ade3147f0f3aec49904556a23cd4
--- /dev/null
+++ b/tools/testing/selftests/timers/auxclock.c
@@ -0,0 +1,319 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Work around type conflicts between libc and the UAPI headers */
+#define _SYS_TIME_H
+#define __timeval_defined
+#define _GNU_SOURCE
+
+#include <fcntl.h>
+#include <linux/types.h>
+#include <linux/timex.h>
+#include <sched.h>
+#include <stdio.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+#include "../kselftest_harness.h"
+
+#ifndef CLOCK_AUX
+#define        CLOCK_AUX       16
+#endif
+
+#ifndef NSEC_PER_SEC
+#define NSEC_PER_SEC 1000000000ULL
+#endif
+
+#define AUXCLOCK_SELFTEST_TIMENS_OFFSET 10000
+
+static int configure_auxclock(__kernel_clockid_t clockid, bool enable)
+{
+       char path[100];
+       int fd, ret;
+
+       ret = snprintf(path, sizeof(path),
+                      "/sys/kernel/time/aux_clocks/%d/aux_clock_enable",
+                      (int)clockid - CLOCK_AUX);
+       if (ret >= sizeof(path))
+               return -ENOSPC;
+
+       fd = open(path, O_WRONLY);
+       if (fd == -1)
+               return -errno;
+
+       /* Always disable to reset */
+       ret = dprintf(fd, "0\n");
+       if (enable)
+               ret = dprintf(fd, "1\n");
+       close(fd);
+
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+
+/* Everything is done in terms of 64bit time values to keep the code readable 
*/
+
+static inline void timespec_to_kernel_timespec(const struct timespec *ts,
+                                              struct __kernel_timespec *kts)
+{
+       if (!kts)
+               return;
+
+       kts->tv_sec = ts->tv_sec;
+       kts->tv_nsec = ts->tv_nsec;
+}
+
+static inline void kernel_timespec_to_timespec(const struct __kernel_timespec 
*kts,
+                                              struct timespec *ts)
+{
+       if (!kts)
+               return;
+
+       ts->tv_sec = kts->tv_sec;
+       ts->tv_nsec = kts->tv_nsec;
+}
+
+static int sys_clock_getres_time64(__kernel_clockid_t clockid, struct 
__kernel_timespec *ts)
+{
+#if defined(__NR_clock_getres_time64)
+       return syscall(__NR_clock_getres_time64, clockid, ts);
+#elif defined(__NR_clock_getres)
+       struct timespec _ts;
+       int ret;
+
+       ret = syscall(__NR_clock_getres, clockid, &_ts);
+       if (!ret)
+               timespec_to_kernel_timespec(&_ts, ts);
+       return ret;
+#else
+#error "No clock_getres() support"
+#endif
+}
+
+static int sys_clock_gettime64(__kernel_clockid_t clockid, struct 
__kernel_timespec *ts)
+{
+#if defined(__NR_clock_gettime64)
+       return syscall(__NR_clock_gettime64, clockid, ts);
+#elif defined(__NR_clock_gettime)
+       struct timespec _ts;
+       int ret;
+
+       ret = syscall(__NR_clock_gettime, clockid, &_ts);
+       if (!ret)
+               timespec_to_kernel_timespec(&_ts, ts);
+       return ret;
+#else
+#error "No clock_gettime() support"
+#endif
+}
+
+static int sys_clock_settime64(__kernel_clockid_t clockid, const struct 
__kernel_timespec *ts)
+{
+#if defined(__NR_clock_settime64)
+       return syscall(__NR_clock_settime64, clockid, ts);
+#elif defined(__NR_clock_settime)
+       struct timespec _ts;
+
+       kernel_timespec_to_timespec(ts, &_ts);
+       return syscall(__NR_clock_settime, clockid, &_ts);
+#else
+#error "No clock_settime() support"
+#endif
+}
+
+static int sys_clock_adjtime64(__kernel_clockid_t clockid, struct 
__kernel_timex *tx)
+{
+#if defined(__NR_clock_adjtime64)
+       return syscall(__NR_clock_adjtime64, clockid, tx);
+#elif __LONG_WIDTH__ == 64 && defined(__NR_clock_adjtime)
+       return syscall(__NR_clock_adjtime, clockid, tx);
+#else
+#error "No clock_adjtime() support"
+#endif
+}
+
+FIXTURE(auxclock) {};
+
+FIXTURE_VARIANT(auxclock) {
+       __kernel_clockid_t clock;
+       bool clock_enabled;
+       bool use_timens;
+};
+
+FIXTURE_VARIANT_ADD(auxclock, default) {
+       .clock          = CLOCK_AUX,
+       .clock_enabled  = true,
+       .use_timens     = false,
+};
+
+FIXTURE_VARIANT_ADD(auxclock, timens) {
+       .clock          = CLOCK_AUX,
+       .clock_enabled  = true,
+       .use_timens     = true,
+};
+
+FIXTURE_VARIANT_ADD(auxclock, disabled) {
+       .clock          = CLOCK_AUX,
+       .clock_enabled  = false,
+       .use_timens     = false,
+};
+
+/* No timens_disabled to keep the testmatrix smaller. */
+
+static void enter_timens(struct __test_metadata *_metadata)
+{
+       int ret, fd;
+       char buf[100];
+
+       ret = unshare(CLONE_NEWTIME);
+       if (ret != 0 && errno == EPERM)
+               SKIP(return, "no permissions for unshare(CLONE_NEWTIME)");
+       if (ret != 0 && errno == EINVAL)
+               SKIP(return, "time namespaces not available");
+       ASSERT_EQ(0, ret) TH_LOG("unshare(CLONE_NEWTIME) failed: %s", 
strerror(errno));
+       fd = open("/proc/self/timens_offsets", O_WRONLY);
+       if (fd == -1 && errno == ENOENT)
+               SKIP(return, "no support for time namespaces");
+       ASSERT_NE(-1, fd);
+       /* Fiddle with the namespace to make the tests more meaningful */
+       ret = snprintf(buf, sizeof(buf), "monotonic %d 0\nboottime %d 0\n",
+                      AUXCLOCK_SELFTEST_TIMENS_OFFSET, 
AUXCLOCK_SELFTEST_TIMENS_OFFSET);
+       ASSERT_TRUE(ret > 0 && ret < sizeof(buf));
+       ret = write(fd, buf, ret);
+       ASSERT_NE(-1, ret);
+       close(fd);
+       fd = open("/proc/self/ns/time_for_children", O_RDONLY);
+       ASSERT_NE(-1, fd);
+       ret = setns(fd, CLONE_NEWTIME);
+       close(fd);
+       ASSERT_EQ(0, ret);
+}
+
+FIXTURE_SETUP(auxclock) {
+       int ret;
+
+       ret = configure_auxclock(variant->clock, variant->clock_enabled);
+       if (ret == -ENOENT)
+               SKIP(return, "auxclocks not enabled");
+       ASSERT_EQ(0, ret);
+
+       if (variant->use_timens)
+               enter_timens(_metadata);
+}
+
+FIXTURE_TEARDOWN(auxclock) {
+       int ret;
+
+       ret = configure_auxclock(variant->clock, false);
+       ASSERT_EQ(0, ret);
+}
+
+TEST_F(auxclock, sys_clock_getres) {
+       struct __kernel_timespec ts;
+       int ret;
+
+       /* clock_getres() is always expected to work */
+       ret = sys_clock_getres_time64(variant->clock, &ts);
+       ASSERT_EQ(0, ret);
+       ASSERT_EQ(0, ts.tv_sec);
+       ASSERT_EQ(1, ts.tv_nsec);
+}
+
+TEST_F(auxclock, sys_clock_gettime) {
+       struct __kernel_timespec ts;
+       int ret;
+
+       ret = sys_clock_gettime64(variant->clock, &ts);
+       if (variant->clock_enabled) {
+               ASSERT_EQ(0, ret);
+       } else {
+               ASSERT_EQ(-1, ret);
+               ASSERT_EQ(ENODEV, errno);
+       }
+}
+
+static void auxclock_validate_progression(struct __test_metadata *_metadata,
+                                         const struct __kernel_timespec *a,
+                                         const struct __kernel_timespec *b)
+{
+       int64_t diff;
+
+       diff = (b->tv_sec - a->tv_sec) * NSEC_PER_SEC;
+       diff += b->tv_nsec - a->tv_nsec;
+
+       /* Arbitrary values */
+       ASSERT_LT(1, diff);
+       ASSERT_GT(1 * NSEC_PER_SEC, diff);
+}
+
+TEST_F(auxclock, sys_clock_settime) {
+       struct __kernel_timespec a, b = {};
+       int ret;
+
+       a.tv_sec = 1234;
+       a.tv_nsec = 5678;
+
+       ret = sys_clock_settime64(variant->clock, &a);
+       if (!variant->clock_enabled) {
+               ASSERT_EQ(-1, ret);
+               ASSERT_EQ(ENODEV, errno);
+               return;
+       }
+
+       ASSERT_EQ(0, ret);
+
+       ret = sys_clock_gettime64(variant->clock, &b);
+       ASSERT_EQ(0, ret);
+
+       auxclock_validate_progression(_metadata, &a, &b);
+}
+
+TEST_F(auxclock, sys_clock_adjtime) {
+       struct __kernel_timex tx;
+       int ret, realtime_freq;
+
+       memset(&tx, 0, sizeof(tx));
+       tx.modes = ADJ_FREQUENCY;
+       ret = sys_clock_adjtime64(CLOCK_REALTIME, &tx);
+       ASSERT_NE(-1, ret);
+       ASSERT_TRUE(tx.modes & ADJ_FREQUENCY);
+       realtime_freq = tx.freq;
+
+       ret = sys_clock_adjtime64(variant->clock, &tx);
+       if (variant->clock_enabled) {
+               ASSERT_NE(-1, ret);
+               ASSERT_EQ(realtime_freq, tx.freq);
+       } else {
+               ASSERT_EQ(-1, ret);
+               ASSERT_EQ(ENODEV, errno);
+       }
+}
+
+TEST_F(auxclock, progression) {
+       struct __kernel_timespec a, b;
+       int ret;
+
+       if (!variant->clock_enabled) {
+               TH_LOG("no progression on disabled clocks");
+               return;
+       }
+
+       /* set up reference */
+       ret = sys_clock_gettime64(variant->clock, &a);
+       ASSERT_EQ(0, ret);
+
+       for (int i = 0; i < 100; i++) {
+               memset(&b, 0, sizeof(b));
+               ret = sys_clock_gettime64(variant->clock, &b);
+               ASSERT_EQ(0, ret);
+               auxclock_validate_progression(_metadata, &a, &b);
+
+               memset(&a, 0, sizeof(a));
+               ret = sys_clock_gettime64(variant->clock, &a);
+               ASSERT_EQ(0, ret);
+               auxclock_validate_progression(_metadata, &b, &a);
+       }
+}
+
+TEST_HARNESS_MAIN

-- 
2.50.0


Reply via email to