Auxiliary clocks behave differently from regular ones. Add a testcase to validate their functionality.
Signed-off-by: Thomas Weißschuh thomas.weissschuh@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