blob: a8d2c5b38542d2e238daebfe69cc14e92b048662 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) 2020 Linaro Limited. All rights reserved.
* Author: Viresh Kumar<viresh.kumar@linaro.org>
*/
/*\
* [Description]
*
* Check time difference between successive readings and report a bug if
* difference found to be over 5 ms.
*
* This test reports a s390x BUG which has been fixed in:
*
* commit 5b43bd184530af6b868d8273b0a743a138d37ee8
* Author: Heiko Carstens <hca@linux.ibm.com>
* Date: Wed Mar 24 20:23:55 2021 +0100
*
* s390/vdso: fix initializing and updating of vdso_data
*/
#include "config.h"
#include "parse_vdso.h"
#include "time64_variants.h"
#include "tst_timer.h"
#include "tst_safe_clocks.h"
clockid_t clks[] = {
CLOCK_REALTIME,
CLOCK_REALTIME_COARSE,
CLOCK_MONOTONIC,
CLOCK_MONOTONIC_COARSE,
CLOCK_MONOTONIC_RAW,
CLOCK_BOOTTIME,
};
static gettime_t ptr_vdso_gettime, ptr_vdso_gettime64;
static long long delta = 5;
static inline int do_vdso_gettime(gettime_t vdso, clockid_t clk_id, void *ts)
{
if (!vdso) {
errno = ENOSYS;
return -1;
}
return vdso(clk_id, ts);
}
static inline int vdso_gettime(clockid_t clk_id, void *ts)
{
return do_vdso_gettime(ptr_vdso_gettime, clk_id, ts);
}
static inline int vdso_gettime64(clockid_t clk_id, void *ts)
{
return do_vdso_gettime(ptr_vdso_gettime64, clk_id, ts);
}
static inline int my_gettimeofday(clockid_t clk_id, void *ts)
{
struct timeval tval;
if (clk_id != CLOCK_REALTIME)
tst_brk(TBROK, "%s: Invalid clk_id, exiting", tst_clock_name(clk_id));
if (gettimeofday(&tval, NULL) < 0)
tst_brk(TBROK | TERRNO, "gettimeofday() failed");
/*
* The array defines the type to TST_LIBC_TIMESPEC and so we can cast
* this into struct timespec.
*/
*((struct timespec *)ts) = tst_timespec_from_us(tst_timeval_to_us(tval));
return 0;
}
static struct time64_variants variants[] = {
{ .clock_gettime = libc_clock_gettime, .ts_type = TST_LIBC_TIMESPEC, .desc = "vDSO or syscall with libc spec"},
#if (__NR_clock_gettime != __LTP__NR_INVALID_SYSCALL)
{ .clock_gettime = sys_clock_gettime, .ts_type = TST_KERN_OLD_TIMESPEC, .desc = "syscall with old kernel spec"},
{ .clock_gettime = vdso_gettime, .ts_type = TST_KERN_OLD_TIMESPEC, .desc = "vDSO with old kernel spec"},
#endif
#if (__NR_clock_gettime64 != __LTP__NR_INVALID_SYSCALL)
{ .clock_gettime = sys_clock_gettime64, .ts_type = TST_KERN_TIMESPEC, .desc = "syscall time64 with kernel spec"},
{ .clock_gettime = vdso_gettime64, .ts_type = TST_KERN_TIMESPEC, .desc = "vDSO time64 with kernel spec"},
#endif
{ .clock_gettime = my_gettimeofday, .ts_type = TST_LIBC_TIMESPEC, .desc = "gettimeofday"},
};
static void setup(void)
{
if (tst_is_virt(VIRT_ANY)) {
tst_res(TINFO, "Running in a virtual machine, multiply the delta by 10.");
delta *= 10;
}
find_clock_gettime_vdso(&ptr_vdso_gettime, &ptr_vdso_gettime64);
}
static void run(unsigned int i)
{
struct tst_ts ts;
long long start, end = 0, diff, slack;
struct time64_variants *tv;
int count = 10000, ret;
unsigned int j;
do {
for (j = 0; j < ARRAY_SIZE(variants); j++) {
/* Refresh time in start */
start = end;
tv = &variants[j];
ts.type = tv->ts_type;
/* Do gettimeofday() test only for CLOCK_REALTIME */
if (tv->clock_gettime == my_gettimeofday && clks[i] != CLOCK_REALTIME)
continue;
ret = tv->clock_gettime(clks[i], tst_ts_get(&ts));
if (ret) {
/*
* _vdso_gettime() sets error to ENOSYS if vdso
* isn't available.
*/
if (errno == ENOSYS)
continue;
tst_res(TFAIL | TERRNO, "%s: clock_gettime() failed (%d)",
tst_clock_name(clks[i]), j);
return;
}
end = tst_ts_to_ns(ts);
/* Skip comparison on first traversal */
if (count == 10000 && !j)
continue;
/*
* gettimeofday() doesn't capture time less than 1 us,
* add 999 to it.
*/
if (tv->clock_gettime == my_gettimeofday)
slack = 999;
else
slack = 0;
diff = end + slack - start;
if (diff < 0) {
tst_res(TFAIL, "%s: Time travelled backwards (%d): %lld ns",
tst_clock_name(clks[i]), j, diff);
return;
}
diff /= 1000000;
if (diff >= delta) {
tst_res(TFAIL, "%s(%s): Difference between successive readings greater than %lld ms (%d): %lld",
tst_clock_name(clks[i]), tv->desc, delta, j, diff);
return;
}
}
} while (--count);
tst_res(TPASS, "%s: Difference between successive readings is reasonable for following variants:",
tst_clock_name(clks[i]));
for (j = 0; j < ARRAY_SIZE(variants); j++) {
if (variants[j].clock_gettime == my_gettimeofday && clks[i] != CLOCK_REALTIME)
continue;
tst_res(TINFO, "\t- %s", variants[j].desc);
}
}
static struct tst_test test = {
.test = run,
.setup = setup,
.tcnt = ARRAY_SIZE(clks),
.tags = (const struct tst_tag[]) {
{"linux-git", "5b43bd184530"},
{}
}
};