201 lines
5.1 KiB
C
201 lines
5.1 KiB
C
// 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, precise_delta, coarse_delta;
|
|
|
|
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)
|
|
{
|
|
struct timespec res;
|
|
|
|
clock_getres(CLOCK_REALTIME, &res);
|
|
precise_delta = 5 + res.tv_nsec / 1000000;
|
|
|
|
clock_getres(CLOCK_REALTIME_COARSE, &res);
|
|
coarse_delta = 5 + res.tv_nsec / 1000000;
|
|
|
|
if (tst_is_virt(VIRT_ANY)) {
|
|
tst_res(TINFO, "Running in a virtual machine, multiply the delta by 10.");
|
|
precise_delta *= 10;
|
|
coarse_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;
|
|
|
|
if (clks[i] == CLOCK_REALTIME_COARSE || clks[i] == CLOCK_MONOTONIC_COARSE)
|
|
delta = coarse_delta;
|
|
else
|
|
delta = precise_delta;
|
|
|
|
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"},
|
|
{}
|
|
}
|
|
};
|