/*
* test_timer: test Linux and ARM generic timer for monotonicity
* uses Perl's Test Anything Protocol (TAP), try "prove"
*
* Copyright (C) 2016 - 2018 Andre Przywara
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#define _GNU_SOURCE
#include
#include
#include
#include
#include
#include
#include
#include
#if defined __aarch64__
static void delay_tick(unsigned long r)
{
__asm__ volatile (
"1:subs %0, %0, #1\n\t"
"b.ne 1b\n"
: : "r" (r)
);
}
static uint64_t read_cntfrq(void)
{
uint64_t reg;
__asm__ volatile ("mrs %0, CNTFRQ_EL0\n" : "=r" (reg));
return reg;
}
static uint64_t read_counter(void)
{
uint64_t reg;
__asm__ volatile ("mrs %0, CNTVCT_EL0\n" :"=r" (reg));
return reg;
}
static uint64_t read_counter_sync(void)
{
uint64_t reg;
__asm__ volatile ("isb; mrs %0, CNTVCT_EL0\n" :"=r" (reg));
return reg;
}
#elif defined(__arm__)
static void delay_tick(unsigned long r)
{
__asm__ volatile (
"1:subs %0, %0, #1\n\t"
"bne 1b\n"
: : "r" (r)
);
}
static uint64_t read_cntfrq(void)
{
uint32_t reg;
__asm__ volatile ("mrc p15, 0, %0, c14, c0, 0\n" : "=r" (reg));
return reg;
}
static uint64_t read_counter(void)
{
uint32_t lo, hi;
__asm__ volatile ("mrrc p15, 1, %0, %1, c14\n" :"=r" (lo), "=r" (hi));
return ((uint64_t)hi << 32) | lo;
}
static uint64_t read_counter_sync(void)
{
uint32_t lo, hi;
__asm__ volatile (
"isb\n\t"
"mrrc p15, 1, %0, %1, c14\n"
: "=r" (lo), "=r" (hi)
);
return ((uint64_t)hi << 32) | lo;
}
#else
#error unsupported architecture
#endif
#define RESTORE_ONLY -1
#define ALL_CORES -2
static int pin_thread(pid_t pid, int core, bool restore)
{
static cpu_set_t oldmask;
cpu_set_t mask;
if (restore)
sched_setaffinity(pid, sizeof(cpu_set_t), &oldmask);
else
sched_getaffinity(pid, sizeof(cpu_set_t), &oldmask);
if (core == RESTORE_ONLY)
return 0;
CPU_ZERO(&mask);
if (core == ALL_CORES) {
int i;
int nr_cores = sysconf(_SC_NPROCESSORS_CONF);
for (i = 0; i < nr_cores; i++)
CPU_SET(i, &mask);
} else {
CPU_SET(core, &mask);
}
return sched_setaffinity(pid, sizeof(cpu_set_t), &mask);
}
static long nr_procs(void)
{
long online = sysconf(_SC_NPROCESSORS_ONLN);
long cpus = sysconf(_SC_NPROCESSORS_CONF);
if (cpus != online)
fprintf(stdout, "# %ld CPU%s offline\n",
cpus - online,
cpus - online > 1 ? "s" : "");
return cpus;
}
static int test_frequency(FILE *stream, int testnr, int nr_cores)
{
uint64_t freq = ~0, r;
int i;
bool equal = true;
for (i = 0; i < nr_cores; i++) {
if (pin_thread(0, i, false))
continue;
r = read_cntfrq();
if (freq == ~0)
freq = r;
else
equal = equal && (freq == r);
pin_thread(0, RESTORE_ONLY, true);
}
fprintf(stream, "%sok %d same timer frequency on all cores\n",
equal ? "" : "not ", testnr);
fprintf(stream, "# timer frequency is %"PRId64" Hz (%"PRId64" MHz)\n",
freq, freq / 1000000);
return 1;
}
static void offset_info(FILE *stream, int core)
{
uint64_t cnt, freq;
uint64_t diff1, diff2, diff3;
if (pin_thread(0, core, false))
return;
freq = read_cntfrq();
cnt = read_counter_sync();
diff1 = read_counter() - cnt;
cnt = read_counter_sync();
diff2 = read_counter_sync() - cnt;
cnt = read_counter_sync();
delay_tick(50);
diff3 = read_counter_sync() - cnt;
fprintf(stream, "# core %d: counter value: %"PRId64" => %"PRId64" sec\n",
core, cnt, cnt / freq);
fprintf(stream, "# core %d: offsets: back-to-back: %"PRId64", b-t-b synced: %"PRId64", b-t-b w/ delay: %"PRId64"\n",
core, diff1, diff2, diff3);
pin_thread(0, RESTORE_ONLY, true);
}
#define NSECS 1000000000U
#define MAX_ERRORS 16
static void test_monotonic(FILE *stream, int loops, int testnr)
{
uint64_t time1, time2;
int64_t diff, min = INT64_MAX, max = 0, sum = 0;
int errcnt = 0;
int i;
for (i = 0; i < loops; i++) {
time1 = read_counter_sync();
time2 = read_counter();
diff = time2 - time1;
if (diff < 0) {
errcnt++;
if (errcnt <= MAX_ERRORS)
fprintf(stream, "# time1: %"PRIx64", time2: %"PRIx64", diff: %"PRId64"\n",
time1, time2, diff);
if (errcnt == MAX_ERRORS + 1)
fprintf(stream, "# too many errors, stopping reports\n");
}
if (diff < min)
min = diff;
if (diff > max)
max = diff;
sum += diff;
}
fprintf(stream, "%sok %d native counter reads are monotonic # %d errors\n",
min >= 0 ? "" : "not ", testnr, errcnt);
fprintf(stream, "# min: %"PRId64", avg: %"PRId64", max: %"PRId64"\n",
min, sum / loops, max);
}
static void test_monotonic_linux(FILE *stream, int loops, int testnr)
{
struct timespec tp1, tp2;
int64_t diff, min = INT64_MAX, max = 0, sum = 0;
int errcnt = 0;
int i;
for (i = 0; i < loops; i++) {
clock_gettime(CLOCK_MONOTONIC_RAW, &tp1);
clock_gettime(CLOCK_MONOTONIC_RAW, &tp2);
diff = (tp2.tv_sec * NSECS + tp2.tv_nsec) -
(tp1.tv_sec * NSECS + tp1.tv_nsec);
if (diff < 0) {
if (errcnt == 0)
fprintf(stream, "# diffs: ");
errcnt++;
if (errcnt <= MAX_ERRORS)
fprintf(stream, "%s%"PRId64"",
errcnt == 1 ? "" : ", ", diff);
if (errcnt == MAX_ERRORS + 1)
fprintf(stream, "\n# too many errors, stopping reports");
}
if (diff < min)
min = diff;
if (diff > max)
max = diff;
sum += diff;
}
if (errcnt)
fprintf(stream, "\n");
fprintf(stream, "%sok %d Linux counter reads are monotonic # %d errors\n",
min >= 0 ? "" : "not ", testnr, errcnt);
fprintf(stream, "# min: %"PRId64", avg: %"PRId64", max: %"PRId64"\n",
min, sum / loops, max);
}
int main(int argc, char** argv)
{
int nr_cpus;
int i;
fprintf(stdout, "TAP version 13\n");
nr_cpus = nr_procs();
fprintf(stdout, "# number of cores: %d\n", nr_cpus);
test_frequency(stdout, 1, nr_cpus);
test_monotonic(stdout, 10000000, 2);
test_monotonic_linux(stdout, 10000000, 3);
for (i = 0; i < nr_cpus; i++)
offset_info(stdout, i);
fprintf(stdout, "1..3\n");
return 0;
}