// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2019 Linaro Limited. All rights reserved. * Author: Rafael David Tinoco */ /* * clock_adjtime() syscall might have as execution path: * * 1) a regular POSIX clock (only REALTIME clock implements adjtime()) * - will behave exactly like adjtimex() system call. * - only one being tested here. * * 2) a dynamic POSIX clock (which ops are implemented by PTP clocks) * - will trigger the PTP clock driver function "adjtime()" * - different implementations from one PTP clock to another * - might return EOPNOTSUPP (like ptp_kvm_caps, for example) * - no entry point for clock_adjtime(), missing "CLOCK_PTP" model * * so it is sane to check possible adjustments: * * - ADJ_OFFSET - usec or nsec, kernel adjusts time gradually by offset * (-512000 < offset < 512000) * - ADJ_FREQUENCY - system clock frequency offset * - ADJ_MAXERROR - maximum error (usec) * - ADJ_ESTERROR - estimated time error in us * - ADJ_STATUS - clock command/status of ntp implementation * - ADJ_TIMECONST - PLL stiffness (jitter dependent) + poll int for PLL * - ADJ_TICK - us between clock ticks * (>= 900000/HZ, <= 1100000/HZ) * * and also the standalone ones (using .offset variable): * * - ADJ_OFFSET_SINGLESHOT - behave like adjtime() * - ADJ_OFFSET_SS_READ - ret remaining time for completion after SINGLESHOT * * For ADJ_STATUS, consider the following flags: * * rw STA_PLL - enable phase-locked loop updates (ADJ_OFFSET) * rw STA_PPSFREQ - enable PPS (pulse-per-second) freq discipline * rw STA_PPSTIME - enable PPS time discipline * rw STA_FLL - select freq-locked loop mode. * rw STA_INS - ins leap sec after the last sec of UTC day (all days) * rw STA_DEL - del leap sec at last sec of UTC day (all days) * rw STA_UNSYNC - clock unsynced * rw STA_FREQHOLD - hold freq. ADJ_OFFSET made w/out auto small adjs * ro STA_PPSSIGNAL - valid PPS (pulse-per-second) signal is present * ro STA_PPSJITTER - PPS signal jitter exceeded. * ro STA_PPSWANDER - PPS signal wander exceeded. * ro STA_PPSERROR - PPS signal calibration error. * ro STA_CLOKERR - clock HW fault. * ro STA_NANO - 0 = us, 1 = ns (set = ADJ_NANO, cl = ADJ_MICRO) * rw STA_MODE - mode: 0 = phased locked loop. 1 = freq locked loop * ro STA_CLK - clock source. unused. */ #include "clock_adjtime.h" static long hz; static struct tst_timex saved, ttxc; static int supported; struct test_case { unsigned int modes; long highlimit; long delta; }; struct test_case tc[] = { { .modes = ADJ_OFFSET_SINGLESHOT, }, { .modes = ADJ_OFFSET_SS_READ, }, { .modes = ADJ_ALL, }, { .modes = ADJ_OFFSET, .highlimit = 500000, .delta = 10000, }, { .modes = ADJ_FREQUENCY, .delta = 100, }, { .modes = ADJ_MAXERROR, .delta = 100, }, { .modes = ADJ_ESTERROR, .delta = 100, }, { .modes = ADJ_TIMECONST, .delta = 1, }, { .modes = ADJ_TICK, .highlimit = 1100000, .delta = 1000, }, }; static struct test_variants { int (*clock_adjtime)(clockid_t clk_id, void *timex); enum tst_timex_type type; char *desc; } variants[] = { #if (__NR_clock_adjtime != __LTP__NR_INVALID_SYSCALL) {.clock_adjtime = sys_clock_adjtime, .type = TST_KERN_OLD_TIMEX, .desc = "syscall with old kernel spec"}, #endif #if (__NR_clock_adjtime64 != __LTP__NR_INVALID_SYSCALL) {.clock_adjtime = sys_clock_adjtime64, .type = TST_KERN_TIMEX, .desc = "syscall time64 with kernel spec"}, #endif }; static void verify_clock_adjtime(unsigned int i) { struct test_variants *tv = &variants[tst_variant]; struct tst_timex verify; long long val; int rval; memset(&ttxc, 0, sizeof(ttxc)); memset(&verify, 0, sizeof(verify)); ttxc.type = verify.type = tv->type; rval = tv->clock_adjtime(CLOCK_REALTIME, tst_timex_get(&ttxc)); if (rval < 0) { tst_res(TFAIL | TERRNO, "clock_adjtime() failed %i", rval); return; } timex_show("GET", &ttxc); timex_set_field_uint(&ttxc, ADJ_MODES, tc[i].modes); if (tc[i].delta) { val = timex_get_field_long(&ttxc, tc[i].modes); val += tc[i].delta; /* fix limits, if existent, so no errors occur */ if (tc[i].highlimit && val >= tc[i].highlimit) val = tc[i].highlimit; timex_set_field_long(&ttxc, tc[i].modes, val); } rval = tv->clock_adjtime(CLOCK_REALTIME, tst_timex_get(&ttxc)); if (rval < 0) { tst_res(TFAIL | TERRNO, "clock_adjtime() failed %i", rval); return; } timex_show("SET", &ttxc); rval = tv->clock_adjtime(CLOCK_REALTIME, tst_timex_get(&verify)); if (rval < 0) { tst_res(TFAIL | TERRNO, "clock_adjtime() failed %i", rval); return; } timex_show("VERIFY", &verify); if (tc[i].delta && timex_get_field_long(&ttxc, tc[i].modes) != timex_get_field_long(&verify, tc[i].modes)) { tst_res(TFAIL, "clock_adjtime(): could not set value (mode=%x)", tc[i].modes); } tst_res(TPASS, "clock_adjtime(): success (mode=%x)", tc[i].modes); } static void setup(void) { struct test_variants *tv = &variants[tst_variant]; size_t i; int rval; tst_res(TINFO, "Testing variant: %s", tv->desc); saved.type = tv->type; rval = tv->clock_adjtime(CLOCK_REALTIME, tst_timex_get(&saved)); if (rval < 0) { tst_res(TFAIL | TERRNO, "clock_adjtime() failed %i", rval); return; } supported = 1; if (rval != TIME_OK && rval != TIME_ERROR) { timex_show("SAVE_STATUS", &saved); tst_brk(TBROK | TERRNO, "clock has on-going leap changes, " "returned: %i", rval); } hz = SAFE_SYSCONF(_SC_CLK_TCK); for (i = 0; i < ARRAY_SIZE(tc); i++) { /* fix high and low limits by dividing it per HZ value */ if (tc[i].modes == ADJ_TICK) tc[i].highlimit /= hz; /* fix usec as being test default resolution */ if (timex_get_field_uint(&saved, ADJ_MODES) & ADJ_NANO) { if (tc[i].modes == ADJ_OFFSET) { tc[i].highlimit *= 1000; tc[i].delta *= 1000; } } } } static void cleanup(void) { struct test_variants *tv = &variants[tst_variant]; unsigned int modes = ADJ_ALL; int rval; if (supported == 0) return; /* restore clock resolution based on original status flag */ if (timex_get_field_uint(&saved, ADJ_STATUS) & STA_NANO) modes |= ADJ_NANO; else modes |= ADJ_MICRO; timex_set_field_uint(&saved, ADJ_MODES, modes); /* restore original clock flags */ rval = tv->clock_adjtime(CLOCK_REALTIME, tst_timex_get(&saved)); if (rval < 0) { tst_res(TFAIL | TERRNO, "clock_adjtime() failed %i", rval); return; } } static struct tst_test test = { .test = verify_clock_adjtime, .setup = setup, .cleanup = cleanup, .tcnt = ARRAY_SIZE(tc), .test_variants = ARRAY_SIZE(variants), .needs_root = 1, .restore_wallclock = 1, };