259 lines
6.7 KiB
C
259 lines
6.7 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (c) 2019 Linaro Limited. All rights reserved.
|
|
* Author: Rafael David Tinoco <rafael.tinoco@linaro.org>
|
|
*/
|
|
|
|
/*
|
|
* 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,
|
|
};
|