1063 lines
26 KiB
C
1063 lines
26 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* arch_timer_edge_cases.c - Tests the aarch64 timer IRQ functionality.
|
|
*
|
|
* The test validates some edge cases related to the arch-timer:
|
|
* - timers above the max TVAL value.
|
|
* - timers in the past
|
|
* - moving counters ahead and behind pending timers.
|
|
* - reprograming timers.
|
|
* - timers fired multiple times.
|
|
* - masking/unmasking using the timer control mask.
|
|
*
|
|
* Copyright (c) 2021, Google LLC.
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
|
|
#include <pthread.h>
|
|
#include <sys/sysinfo.h>
|
|
|
|
#include "arch_timer.h"
|
|
#include "gic.h"
|
|
#include "vgic.h"
|
|
|
|
static const uint64_t CVAL_MAX = ~0ULL;
|
|
/* tval is a signed 32-bit int. */
|
|
static const int32_t TVAL_MAX = INT32_MAX;
|
|
static const int32_t TVAL_MIN = INT32_MIN;
|
|
|
|
/* After how much time we say there is no IRQ. */
|
|
static const uint32_t TIMEOUT_NO_IRQ_US = 50000;
|
|
|
|
/* A nice counter value to use as the starting one for most tests. */
|
|
static const uint64_t DEF_CNT = (CVAL_MAX / 2);
|
|
|
|
/* Number of runs. */
|
|
static const uint32_t NR_TEST_ITERS_DEF = 5;
|
|
|
|
/* Default wait test time in ms. */
|
|
static const uint32_t WAIT_TEST_MS = 10;
|
|
|
|
/* Default "long" wait test time in ms. */
|
|
static const uint32_t LONG_WAIT_TEST_MS = 100;
|
|
|
|
/* Shared with IRQ handler. */
|
|
struct test_vcpu_shared_data {
|
|
atomic_t handled;
|
|
atomic_t spurious;
|
|
} shared_data;
|
|
|
|
struct test_args {
|
|
/* Virtual or physical timer and counter tests. */
|
|
enum arch_timer timer;
|
|
/* Delay used for most timer tests. */
|
|
uint64_t wait_ms;
|
|
/* Delay used in the test_long_timer_delays test. */
|
|
uint64_t long_wait_ms;
|
|
/* Number of iterations. */
|
|
int iterations;
|
|
/* Whether to test the physical timer. */
|
|
bool test_physical;
|
|
/* Whether to test the virtual timer. */
|
|
bool test_virtual;
|
|
};
|
|
|
|
struct test_args test_args = {
|
|
.wait_ms = WAIT_TEST_MS,
|
|
.long_wait_ms = LONG_WAIT_TEST_MS,
|
|
.iterations = NR_TEST_ITERS_DEF,
|
|
.test_physical = true,
|
|
.test_virtual = true,
|
|
};
|
|
|
|
static int vtimer_irq, ptimer_irq;
|
|
|
|
enum sync_cmd {
|
|
SET_COUNTER_VALUE,
|
|
USERSPACE_USLEEP,
|
|
USERSPACE_SCHED_YIELD,
|
|
USERSPACE_MIGRATE_SELF,
|
|
NO_USERSPACE_CMD,
|
|
};
|
|
|
|
typedef void (*sleep_method_t)(enum arch_timer timer, uint64_t usec);
|
|
|
|
static void sleep_poll(enum arch_timer timer, uint64_t usec);
|
|
static void sleep_sched_poll(enum arch_timer timer, uint64_t usec);
|
|
static void sleep_in_userspace(enum arch_timer timer, uint64_t usec);
|
|
static void sleep_migrate(enum arch_timer timer, uint64_t usec);
|
|
|
|
sleep_method_t sleep_method[] = {
|
|
sleep_poll,
|
|
sleep_sched_poll,
|
|
sleep_migrate,
|
|
sleep_in_userspace,
|
|
};
|
|
|
|
typedef void (*irq_wait_method_t)(void);
|
|
|
|
static void wait_for_non_spurious_irq(void);
|
|
static void wait_poll_for_irq(void);
|
|
static void wait_sched_poll_for_irq(void);
|
|
static void wait_migrate_poll_for_irq(void);
|
|
|
|
irq_wait_method_t irq_wait_method[] = {
|
|
wait_for_non_spurious_irq,
|
|
wait_poll_for_irq,
|
|
wait_sched_poll_for_irq,
|
|
wait_migrate_poll_for_irq,
|
|
};
|
|
|
|
enum timer_view {
|
|
TIMER_CVAL,
|
|
TIMER_TVAL,
|
|
};
|
|
|
|
static void assert_irqs_handled(uint32_t n)
|
|
{
|
|
int h = atomic_read(&shared_data.handled);
|
|
|
|
__GUEST_ASSERT(h == n, "Handled %d IRQS but expected %d", h, n);
|
|
}
|
|
|
|
static void userspace_cmd(uint64_t cmd)
|
|
{
|
|
GUEST_SYNC_ARGS(cmd, 0, 0, 0, 0);
|
|
}
|
|
|
|
static void userspace_migrate_vcpu(void)
|
|
{
|
|
userspace_cmd(USERSPACE_MIGRATE_SELF);
|
|
}
|
|
|
|
static void userspace_sleep(uint64_t usecs)
|
|
{
|
|
GUEST_SYNC_ARGS(USERSPACE_USLEEP, usecs, 0, 0, 0);
|
|
}
|
|
|
|
static void set_counter(enum arch_timer timer, uint64_t counter)
|
|
{
|
|
GUEST_SYNC_ARGS(SET_COUNTER_VALUE, counter, timer, 0, 0);
|
|
}
|
|
|
|
static void guest_irq_handler(struct ex_regs *regs)
|
|
{
|
|
unsigned int intid = gic_get_and_ack_irq();
|
|
enum arch_timer timer;
|
|
uint64_t cnt, cval;
|
|
uint32_t ctl;
|
|
bool timer_condition, istatus;
|
|
|
|
if (intid == IAR_SPURIOUS) {
|
|
atomic_inc(&shared_data.spurious);
|
|
goto out;
|
|
}
|
|
|
|
if (intid == ptimer_irq)
|
|
timer = PHYSICAL;
|
|
else if (intid == vtimer_irq)
|
|
timer = VIRTUAL;
|
|
else
|
|
goto out;
|
|
|
|
ctl = timer_get_ctl(timer);
|
|
cval = timer_get_cval(timer);
|
|
cnt = timer_get_cntct(timer);
|
|
timer_condition = cnt >= cval;
|
|
istatus = (ctl & CTL_ISTATUS) && (ctl & CTL_ENABLE);
|
|
GUEST_ASSERT_EQ(timer_condition, istatus);
|
|
|
|
/* Disable and mask the timer. */
|
|
timer_set_ctl(timer, CTL_IMASK);
|
|
|
|
atomic_inc(&shared_data.handled);
|
|
|
|
out:
|
|
gic_set_eoi(intid);
|
|
}
|
|
|
|
static void set_cval_irq(enum arch_timer timer, uint64_t cval_cycles,
|
|
uint32_t ctl)
|
|
{
|
|
atomic_set(&shared_data.handled, 0);
|
|
atomic_set(&shared_data.spurious, 0);
|
|
timer_set_cval(timer, cval_cycles);
|
|
timer_set_ctl(timer, ctl);
|
|
}
|
|
|
|
static void set_tval_irq(enum arch_timer timer, uint64_t tval_cycles,
|
|
uint32_t ctl)
|
|
{
|
|
atomic_set(&shared_data.handled, 0);
|
|
atomic_set(&shared_data.spurious, 0);
|
|
timer_set_ctl(timer, ctl);
|
|
timer_set_tval(timer, tval_cycles);
|
|
}
|
|
|
|
static void set_xval_irq(enum arch_timer timer, uint64_t xval, uint32_t ctl,
|
|
enum timer_view tv)
|
|
{
|
|
switch (tv) {
|
|
case TIMER_CVAL:
|
|
set_cval_irq(timer, xval, ctl);
|
|
break;
|
|
case TIMER_TVAL:
|
|
set_tval_irq(timer, xval, ctl);
|
|
break;
|
|
default:
|
|
GUEST_FAIL("Could not get timer %d", timer);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Note that this can theoretically hang forever, so we rely on having
|
|
* a timeout mechanism in the "runner", like:
|
|
* tools/testing/selftests/kselftest/runner.sh.
|
|
*/
|
|
static void wait_for_non_spurious_irq(void)
|
|
{
|
|
int h;
|
|
|
|
local_irq_disable();
|
|
|
|
for (h = atomic_read(&shared_data.handled); h == atomic_read(&shared_data.handled);) {
|
|
wfi();
|
|
local_irq_enable();
|
|
isb(); /* handle IRQ */
|
|
local_irq_disable();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Wait for an non-spurious IRQ by polling in the guest or in
|
|
* userspace (e.g. userspace_cmd=USERSPACE_SCHED_YIELD).
|
|
*
|
|
* Note that this can theoretically hang forever, so we rely on having
|
|
* a timeout mechanism in the "runner", like:
|
|
* tools/testing/selftests/kselftest/runner.sh.
|
|
*/
|
|
static void poll_for_non_spurious_irq(enum sync_cmd usp_cmd)
|
|
{
|
|
int h;
|
|
|
|
local_irq_disable();
|
|
|
|
h = atomic_read(&shared_data.handled);
|
|
|
|
local_irq_enable();
|
|
while (h == atomic_read(&shared_data.handled)) {
|
|
if (usp_cmd == NO_USERSPACE_CMD)
|
|
cpu_relax();
|
|
else
|
|
userspace_cmd(usp_cmd);
|
|
}
|
|
local_irq_disable();
|
|
}
|
|
|
|
static void wait_poll_for_irq(void)
|
|
{
|
|
poll_for_non_spurious_irq(NO_USERSPACE_CMD);
|
|
}
|
|
|
|
static void wait_sched_poll_for_irq(void)
|
|
{
|
|
poll_for_non_spurious_irq(USERSPACE_SCHED_YIELD);
|
|
}
|
|
|
|
static void wait_migrate_poll_for_irq(void)
|
|
{
|
|
poll_for_non_spurious_irq(USERSPACE_MIGRATE_SELF);
|
|
}
|
|
|
|
/*
|
|
* Sleep for usec microseconds by polling in the guest or in
|
|
* userspace (e.g. userspace_cmd=USERSPACE_SCHEDULE).
|
|
*/
|
|
static void guest_poll(enum arch_timer test_timer, uint64_t usec,
|
|
enum sync_cmd usp_cmd)
|
|
{
|
|
uint64_t cycles = usec_to_cycles(usec);
|
|
/* Whichever timer we are testing with, sleep with the other. */
|
|
enum arch_timer sleep_timer = 1 - test_timer;
|
|
uint64_t start = timer_get_cntct(sleep_timer);
|
|
|
|
while ((timer_get_cntct(sleep_timer) - start) < cycles) {
|
|
if (usp_cmd == NO_USERSPACE_CMD)
|
|
cpu_relax();
|
|
else
|
|
userspace_cmd(usp_cmd);
|
|
}
|
|
}
|
|
|
|
static void sleep_poll(enum arch_timer timer, uint64_t usec)
|
|
{
|
|
guest_poll(timer, usec, NO_USERSPACE_CMD);
|
|
}
|
|
|
|
static void sleep_sched_poll(enum arch_timer timer, uint64_t usec)
|
|
{
|
|
guest_poll(timer, usec, USERSPACE_SCHED_YIELD);
|
|
}
|
|
|
|
static void sleep_migrate(enum arch_timer timer, uint64_t usec)
|
|
{
|
|
guest_poll(timer, usec, USERSPACE_MIGRATE_SELF);
|
|
}
|
|
|
|
static void sleep_in_userspace(enum arch_timer timer, uint64_t usec)
|
|
{
|
|
userspace_sleep(usec);
|
|
}
|
|
|
|
/*
|
|
* Reset the timer state to some nice values like the counter not being close
|
|
* to the edge, and the control register masked and disabled.
|
|
*/
|
|
static void reset_timer_state(enum arch_timer timer, uint64_t cnt)
|
|
{
|
|
set_counter(timer, cnt);
|
|
timer_set_ctl(timer, CTL_IMASK);
|
|
}
|
|
|
|
static void test_timer_xval(enum arch_timer timer, uint64_t xval,
|
|
enum timer_view tv, irq_wait_method_t wm, bool reset_state,
|
|
uint64_t reset_cnt)
|
|
{
|
|
local_irq_disable();
|
|
|
|
if (reset_state)
|
|
reset_timer_state(timer, reset_cnt);
|
|
|
|
set_xval_irq(timer, xval, CTL_ENABLE, tv);
|
|
|
|
/* This method re-enables IRQs to handle the one we're looking for. */
|
|
wm();
|
|
|
|
assert_irqs_handled(1);
|
|
local_irq_enable();
|
|
}
|
|
|
|
/*
|
|
* The test_timer_* functions will program the timer, wait for it, and assert
|
|
* the firing of the correct IRQ.
|
|
*
|
|
* These functions don't have a timeout and return as soon as they receive an
|
|
* IRQ. They can hang (forever), so we rely on having a timeout mechanism in
|
|
* the "runner", like: tools/testing/selftests/kselftest/runner.sh.
|
|
*/
|
|
|
|
static void test_timer_cval(enum arch_timer timer, uint64_t cval,
|
|
irq_wait_method_t wm, bool reset_state,
|
|
uint64_t reset_cnt)
|
|
{
|
|
test_timer_xval(timer, cval, TIMER_CVAL, wm, reset_state, reset_cnt);
|
|
}
|
|
|
|
static void test_timer_tval(enum arch_timer timer, int32_t tval,
|
|
irq_wait_method_t wm, bool reset_state,
|
|
uint64_t reset_cnt)
|
|
{
|
|
test_timer_xval(timer, (uint64_t) tval, TIMER_TVAL, wm, reset_state,
|
|
reset_cnt);
|
|
}
|
|
|
|
static void test_xval_check_no_irq(enum arch_timer timer, uint64_t xval,
|
|
uint64_t usec, enum timer_view timer_view,
|
|
sleep_method_t guest_sleep)
|
|
{
|
|
local_irq_disable();
|
|
|
|
set_xval_irq(timer, xval, CTL_ENABLE | CTL_IMASK, timer_view);
|
|
guest_sleep(timer, usec);
|
|
|
|
local_irq_enable();
|
|
isb();
|
|
|
|
/* Assume success (no IRQ) after waiting usec microseconds */
|
|
assert_irqs_handled(0);
|
|
}
|
|
|
|
static void test_cval_no_irq(enum arch_timer timer, uint64_t cval,
|
|
uint64_t usec, sleep_method_t wm)
|
|
{
|
|
test_xval_check_no_irq(timer, cval, usec, TIMER_CVAL, wm);
|
|
}
|
|
|
|
static void test_tval_no_irq(enum arch_timer timer, int32_t tval, uint64_t usec,
|
|
sleep_method_t wm)
|
|
{
|
|
/* tval will be cast to an int32_t in test_xval_check_no_irq */
|
|
test_xval_check_no_irq(timer, (uint64_t) tval, usec, TIMER_TVAL, wm);
|
|
}
|
|
|
|
/* Test masking/unmasking a timer using the timer mask (not the IRQ mask). */
|
|
static void test_timer_control_mask_then_unmask(enum arch_timer timer)
|
|
{
|
|
reset_timer_state(timer, DEF_CNT);
|
|
set_tval_irq(timer, -1, CTL_ENABLE | CTL_IMASK);
|
|
|
|
/* Unmask the timer, and then get an IRQ. */
|
|
local_irq_disable();
|
|
timer_set_ctl(timer, CTL_ENABLE);
|
|
/* This method re-enables IRQs to handle the one we're looking for. */
|
|
wait_for_non_spurious_irq();
|
|
|
|
assert_irqs_handled(1);
|
|
local_irq_enable();
|
|
}
|
|
|
|
/* Check that timer control masks actually mask a timer being fired. */
|
|
static void test_timer_control_masks(enum arch_timer timer)
|
|
{
|
|
reset_timer_state(timer, DEF_CNT);
|
|
|
|
/* Local IRQs are not masked at this point. */
|
|
|
|
set_tval_irq(timer, -1, CTL_ENABLE | CTL_IMASK);
|
|
|
|
/* Assume no IRQ after waiting TIMEOUT_NO_IRQ_US microseconds */
|
|
sleep_poll(timer, TIMEOUT_NO_IRQ_US);
|
|
|
|
assert_irqs_handled(0);
|
|
timer_set_ctl(timer, CTL_IMASK);
|
|
}
|
|
|
|
static void test_fire_a_timer_multiple_times(enum arch_timer timer,
|
|
irq_wait_method_t wm, int num)
|
|
{
|
|
int i;
|
|
|
|
local_irq_disable();
|
|
reset_timer_state(timer, DEF_CNT);
|
|
|
|
set_tval_irq(timer, 0, CTL_ENABLE);
|
|
|
|
for (i = 1; i <= num; i++) {
|
|
/* This method re-enables IRQs to handle the one we're looking for. */
|
|
wm();
|
|
|
|
/* The IRQ handler masked and disabled the timer.
|
|
* Enable and unmmask it again.
|
|
*/
|
|
timer_set_ctl(timer, CTL_ENABLE);
|
|
|
|
assert_irqs_handled(i);
|
|
}
|
|
|
|
local_irq_enable();
|
|
}
|
|
|
|
static void test_timers_fired_multiple_times(enum arch_timer timer)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(irq_wait_method); i++)
|
|
test_fire_a_timer_multiple_times(timer, irq_wait_method[i], 10);
|
|
}
|
|
|
|
/*
|
|
* Set a timer for tval=delta_1_ms then reprogram it to
|
|
* tval=delta_2_ms. Check that we get the timer fired. There is no
|
|
* timeout for the wait: we use the wfi instruction.
|
|
*/
|
|
static void test_reprogramming_timer(enum arch_timer timer, irq_wait_method_t wm,
|
|
int32_t delta_1_ms, int32_t delta_2_ms)
|
|
{
|
|
local_irq_disable();
|
|
reset_timer_state(timer, DEF_CNT);
|
|
|
|
/* Program the timer to DEF_CNT + delta_1_ms. */
|
|
set_tval_irq(timer, msec_to_cycles(delta_1_ms), CTL_ENABLE);
|
|
|
|
/* Reprogram the timer to DEF_CNT + delta_2_ms. */
|
|
timer_set_tval(timer, msec_to_cycles(delta_2_ms));
|
|
|
|
/* This method re-enables IRQs to handle the one we're looking for. */
|
|
wm();
|
|
|
|
/* The IRQ should arrive at DEF_CNT + delta_2_ms (or after). */
|
|
GUEST_ASSERT(timer_get_cntct(timer) >=
|
|
DEF_CNT + msec_to_cycles(delta_2_ms));
|
|
|
|
local_irq_enable();
|
|
assert_irqs_handled(1);
|
|
};
|
|
|
|
static void test_reprogram_timers(enum arch_timer timer)
|
|
{
|
|
int i;
|
|
uint64_t base_wait = test_args.wait_ms;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(irq_wait_method); i++) {
|
|
/*
|
|
* Ensure reprogramming works whether going from a
|
|
* longer time to a shorter or vice versa.
|
|
*/
|
|
test_reprogramming_timer(timer, irq_wait_method[i], 2 * base_wait,
|
|
base_wait);
|
|
test_reprogramming_timer(timer, irq_wait_method[i], base_wait,
|
|
2 * base_wait);
|
|
}
|
|
}
|
|
|
|
static void test_basic_functionality(enum arch_timer timer)
|
|
{
|
|
int32_t tval = (int32_t) msec_to_cycles(test_args.wait_ms);
|
|
uint64_t cval = DEF_CNT + msec_to_cycles(test_args.wait_ms);
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(irq_wait_method); i++) {
|
|
irq_wait_method_t wm = irq_wait_method[i];
|
|
|
|
test_timer_cval(timer, cval, wm, true, DEF_CNT);
|
|
test_timer_tval(timer, tval, wm, true, DEF_CNT);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This test checks basic timer behavior without actually firing timers, things
|
|
* like: the relationship between cval and tval, tval down-counting.
|
|
*/
|
|
static void timers_sanity_checks(enum arch_timer timer, bool use_sched)
|
|
{
|
|
reset_timer_state(timer, DEF_CNT);
|
|
|
|
local_irq_disable();
|
|
|
|
/* cval in the past */
|
|
timer_set_cval(timer,
|
|
timer_get_cntct(timer) -
|
|
msec_to_cycles(test_args.wait_ms));
|
|
if (use_sched)
|
|
userspace_migrate_vcpu();
|
|
GUEST_ASSERT(timer_get_tval(timer) < 0);
|
|
|
|
/* tval in the past */
|
|
timer_set_tval(timer, -1);
|
|
if (use_sched)
|
|
userspace_migrate_vcpu();
|
|
GUEST_ASSERT(timer_get_cval(timer) < timer_get_cntct(timer));
|
|
|
|
/* tval larger than TVAL_MAX. This requires programming with
|
|
* timer_set_cval instead so the value is expressible
|
|
*/
|
|
timer_set_cval(timer,
|
|
timer_get_cntct(timer) + TVAL_MAX +
|
|
msec_to_cycles(test_args.wait_ms));
|
|
if (use_sched)
|
|
userspace_migrate_vcpu();
|
|
GUEST_ASSERT(timer_get_tval(timer) <= 0);
|
|
|
|
/*
|
|
* tval larger than 2 * TVAL_MAX.
|
|
* Twice the TVAL_MAX completely loops around the TVAL.
|
|
*/
|
|
timer_set_cval(timer,
|
|
timer_get_cntct(timer) + 2ULL * TVAL_MAX +
|
|
msec_to_cycles(test_args.wait_ms));
|
|
if (use_sched)
|
|
userspace_migrate_vcpu();
|
|
GUEST_ASSERT(timer_get_tval(timer) <=
|
|
msec_to_cycles(test_args.wait_ms));
|
|
|
|
/* negative tval that rollovers from 0. */
|
|
set_counter(timer, msec_to_cycles(1));
|
|
timer_set_tval(timer, -1 * msec_to_cycles(test_args.wait_ms));
|
|
if (use_sched)
|
|
userspace_migrate_vcpu();
|
|
GUEST_ASSERT(timer_get_cval(timer) >= (CVAL_MAX - msec_to_cycles(test_args.wait_ms)));
|
|
|
|
/* tval should keep down-counting from 0 to -1. */
|
|
timer_set_tval(timer, 0);
|
|
sleep_poll(timer, 1);
|
|
GUEST_ASSERT(timer_get_tval(timer) < 0);
|
|
|
|
local_irq_enable();
|
|
|
|
/* Mask and disable any pending timer. */
|
|
timer_set_ctl(timer, CTL_IMASK);
|
|
}
|
|
|
|
static void test_timers_sanity_checks(enum arch_timer timer)
|
|
{
|
|
timers_sanity_checks(timer, false);
|
|
/* Check how KVM saves/restores these edge-case values. */
|
|
timers_sanity_checks(timer, true);
|
|
}
|
|
|
|
static void test_set_cnt_after_tval_max(enum arch_timer timer, irq_wait_method_t wm)
|
|
{
|
|
local_irq_disable();
|
|
reset_timer_state(timer, DEF_CNT);
|
|
|
|
set_cval_irq(timer,
|
|
(uint64_t) TVAL_MAX +
|
|
msec_to_cycles(test_args.wait_ms) / 2, CTL_ENABLE);
|
|
|
|
set_counter(timer, TVAL_MAX);
|
|
|
|
/* This method re-enables IRQs to handle the one we're looking for. */
|
|
wm();
|
|
|
|
assert_irqs_handled(1);
|
|
local_irq_enable();
|
|
}
|
|
|
|
/* Test timers set for: cval = now + TVAL_MAX + wait_ms / 2 */
|
|
static void test_timers_above_tval_max(enum arch_timer timer)
|
|
{
|
|
uint64_t cval;
|
|
int i;
|
|
|
|
/*
|
|
* Test that the system is not implementing cval in terms of
|
|
* tval. If that was the case, setting a cval to "cval = now
|
|
* + TVAL_MAX + wait_ms" would wrap to "cval = now +
|
|
* wait_ms", and the timer would fire immediately. Test that it
|
|
* doesn't.
|
|
*/
|
|
for (i = 0; i < ARRAY_SIZE(sleep_method); i++) {
|
|
reset_timer_state(timer, DEF_CNT);
|
|
cval = timer_get_cntct(timer) + TVAL_MAX +
|
|
msec_to_cycles(test_args.wait_ms);
|
|
test_cval_no_irq(timer, cval,
|
|
msecs_to_usecs(test_args.wait_ms) +
|
|
TIMEOUT_NO_IRQ_US, sleep_method[i]);
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(irq_wait_method); i++) {
|
|
/* Get the IRQ by moving the counter forward. */
|
|
test_set_cnt_after_tval_max(timer, irq_wait_method[i]);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Template function to be used by the test_move_counter_ahead_* tests. It
|
|
* sets the counter to cnt_1, the [c|t]val, the counter to cnt_2, and
|
|
* then waits for an IRQ.
|
|
*/
|
|
static void test_set_cnt_after_xval(enum arch_timer timer, uint64_t cnt_1,
|
|
uint64_t xval, uint64_t cnt_2,
|
|
irq_wait_method_t wm, enum timer_view tv)
|
|
{
|
|
local_irq_disable();
|
|
|
|
set_counter(timer, cnt_1);
|
|
timer_set_ctl(timer, CTL_IMASK);
|
|
|
|
set_xval_irq(timer, xval, CTL_ENABLE, tv);
|
|
set_counter(timer, cnt_2);
|
|
/* This method re-enables IRQs to handle the one we're looking for. */
|
|
wm();
|
|
|
|
assert_irqs_handled(1);
|
|
local_irq_enable();
|
|
}
|
|
|
|
/*
|
|
* Template function to be used by the test_move_counter_ahead_* tests. It
|
|
* sets the counter to cnt_1, the [c|t]val, the counter to cnt_2, and
|
|
* then waits for an IRQ.
|
|
*/
|
|
static void test_set_cnt_after_xval_no_irq(enum arch_timer timer,
|
|
uint64_t cnt_1, uint64_t xval,
|
|
uint64_t cnt_2,
|
|
sleep_method_t guest_sleep,
|
|
enum timer_view tv)
|
|
{
|
|
local_irq_disable();
|
|
|
|
set_counter(timer, cnt_1);
|
|
timer_set_ctl(timer, CTL_IMASK);
|
|
|
|
set_xval_irq(timer, xval, CTL_ENABLE, tv);
|
|
set_counter(timer, cnt_2);
|
|
guest_sleep(timer, TIMEOUT_NO_IRQ_US);
|
|
|
|
local_irq_enable();
|
|
isb();
|
|
|
|
/* Assume no IRQ after waiting TIMEOUT_NO_IRQ_US microseconds */
|
|
assert_irqs_handled(0);
|
|
timer_set_ctl(timer, CTL_IMASK);
|
|
}
|
|
|
|
static void test_set_cnt_after_tval(enum arch_timer timer, uint64_t cnt_1,
|
|
int32_t tval, uint64_t cnt_2,
|
|
irq_wait_method_t wm)
|
|
{
|
|
test_set_cnt_after_xval(timer, cnt_1, tval, cnt_2, wm, TIMER_TVAL);
|
|
}
|
|
|
|
static void test_set_cnt_after_cval(enum arch_timer timer, uint64_t cnt_1,
|
|
uint64_t cval, uint64_t cnt_2,
|
|
irq_wait_method_t wm)
|
|
{
|
|
test_set_cnt_after_xval(timer, cnt_1, cval, cnt_2, wm, TIMER_CVAL);
|
|
}
|
|
|
|
static void test_set_cnt_after_tval_no_irq(enum arch_timer timer,
|
|
uint64_t cnt_1, int32_t tval,
|
|
uint64_t cnt_2, sleep_method_t wm)
|
|
{
|
|
test_set_cnt_after_xval_no_irq(timer, cnt_1, tval, cnt_2, wm,
|
|
TIMER_TVAL);
|
|
}
|
|
|
|
static void test_set_cnt_after_cval_no_irq(enum arch_timer timer,
|
|
uint64_t cnt_1, uint64_t cval,
|
|
uint64_t cnt_2, sleep_method_t wm)
|
|
{
|
|
test_set_cnt_after_xval_no_irq(timer, cnt_1, cval, cnt_2, wm,
|
|
TIMER_CVAL);
|
|
}
|
|
|
|
/* Set a timer and then move the counter ahead of it. */
|
|
static void test_move_counters_ahead_of_timers(enum arch_timer timer)
|
|
{
|
|
int i;
|
|
int32_t tval;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(irq_wait_method); i++) {
|
|
irq_wait_method_t wm = irq_wait_method[i];
|
|
|
|
test_set_cnt_after_cval(timer, 0, DEF_CNT, DEF_CNT + 1, wm);
|
|
test_set_cnt_after_cval(timer, CVAL_MAX, 1, 2, wm);
|
|
|
|
/* Move counter ahead of negative tval. */
|
|
test_set_cnt_after_tval(timer, 0, -1, DEF_CNT + 1, wm);
|
|
test_set_cnt_after_tval(timer, 0, -1, TVAL_MAX, wm);
|
|
tval = TVAL_MAX;
|
|
test_set_cnt_after_tval(timer, 0, tval, (uint64_t) tval + 1,
|
|
wm);
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(sleep_method); i++) {
|
|
sleep_method_t sm = sleep_method[i];
|
|
|
|
test_set_cnt_after_cval_no_irq(timer, 0, DEF_CNT, CVAL_MAX, sm);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Program a timer, mask it, and then change the tval or counter to cancel it.
|
|
* Unmask it and check that nothing fires.
|
|
*/
|
|
static void test_move_counters_behind_timers(enum arch_timer timer)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(sleep_method); i++) {
|
|
sleep_method_t sm = sleep_method[i];
|
|
|
|
test_set_cnt_after_cval_no_irq(timer, DEF_CNT, DEF_CNT - 1, 0,
|
|
sm);
|
|
test_set_cnt_after_tval_no_irq(timer, DEF_CNT, -1, 0, sm);
|
|
}
|
|
}
|
|
|
|
static void test_timers_in_the_past(enum arch_timer timer)
|
|
{
|
|
int32_t tval = -1 * (int32_t) msec_to_cycles(test_args.wait_ms);
|
|
uint64_t cval;
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(irq_wait_method); i++) {
|
|
irq_wait_method_t wm = irq_wait_method[i];
|
|
|
|
/* set a timer wait_ms the past. */
|
|
cval = DEF_CNT - msec_to_cycles(test_args.wait_ms);
|
|
test_timer_cval(timer, cval, wm, true, DEF_CNT);
|
|
test_timer_tval(timer, tval, wm, true, DEF_CNT);
|
|
|
|
/* Set a timer to counter=0 (in the past) */
|
|
test_timer_cval(timer, 0, wm, true, DEF_CNT);
|
|
|
|
/* Set a time for tval=0 (now) */
|
|
test_timer_tval(timer, 0, wm, true, DEF_CNT);
|
|
|
|
/* Set a timer to as far in the past as possible */
|
|
test_timer_tval(timer, TVAL_MIN, wm, true, DEF_CNT);
|
|
}
|
|
|
|
/*
|
|
* Set the counter to wait_ms, and a tval to -wait_ms. There should be no
|
|
* IRQ as that tval means cval=CVAL_MAX-wait_ms.
|
|
*/
|
|
for (i = 0; i < ARRAY_SIZE(sleep_method); i++) {
|
|
sleep_method_t sm = sleep_method[i];
|
|
|
|
set_counter(timer, msec_to_cycles(test_args.wait_ms));
|
|
test_tval_no_irq(timer, tval, TIMEOUT_NO_IRQ_US, sm);
|
|
}
|
|
}
|
|
|
|
static void test_long_timer_delays(enum arch_timer timer)
|
|
{
|
|
int32_t tval = (int32_t) msec_to_cycles(test_args.long_wait_ms);
|
|
uint64_t cval = DEF_CNT + msec_to_cycles(test_args.long_wait_ms);
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(irq_wait_method); i++) {
|
|
irq_wait_method_t wm = irq_wait_method[i];
|
|
|
|
test_timer_cval(timer, cval, wm, true, DEF_CNT);
|
|
test_timer_tval(timer, tval, wm, true, DEF_CNT);
|
|
}
|
|
}
|
|
|
|
static void guest_run_iteration(enum arch_timer timer)
|
|
{
|
|
test_basic_functionality(timer);
|
|
test_timers_sanity_checks(timer);
|
|
|
|
test_timers_above_tval_max(timer);
|
|
test_timers_in_the_past(timer);
|
|
|
|
test_move_counters_ahead_of_timers(timer);
|
|
test_move_counters_behind_timers(timer);
|
|
test_reprogram_timers(timer);
|
|
|
|
test_timers_fired_multiple_times(timer);
|
|
|
|
test_timer_control_mask_then_unmask(timer);
|
|
test_timer_control_masks(timer);
|
|
}
|
|
|
|
static void guest_code(enum arch_timer timer)
|
|
{
|
|
int i;
|
|
|
|
local_irq_disable();
|
|
|
|
gic_init(GIC_V3, 1);
|
|
|
|
timer_set_ctl(VIRTUAL, CTL_IMASK);
|
|
timer_set_ctl(PHYSICAL, CTL_IMASK);
|
|
|
|
gic_irq_enable(vtimer_irq);
|
|
gic_irq_enable(ptimer_irq);
|
|
local_irq_enable();
|
|
|
|
for (i = 0; i < test_args.iterations; i++) {
|
|
GUEST_SYNC(i);
|
|
guest_run_iteration(timer);
|
|
}
|
|
|
|
test_long_timer_delays(timer);
|
|
GUEST_DONE();
|
|
}
|
|
|
|
static uint32_t next_pcpu(void)
|
|
{
|
|
uint32_t max = get_nprocs();
|
|
uint32_t cur = sched_getcpu();
|
|
uint32_t next = cur;
|
|
cpu_set_t cpuset;
|
|
|
|
TEST_ASSERT(max > 1, "Need at least two physical cpus");
|
|
|
|
sched_getaffinity(0, sizeof(cpuset), &cpuset);
|
|
|
|
do {
|
|
next = (next + 1) % CPU_SETSIZE;
|
|
} while (!CPU_ISSET(next, &cpuset));
|
|
|
|
return next;
|
|
}
|
|
|
|
static void migrate_self(uint32_t new_pcpu)
|
|
{
|
|
int ret;
|
|
cpu_set_t cpuset;
|
|
pthread_t thread;
|
|
|
|
thread = pthread_self();
|
|
|
|
CPU_ZERO(&cpuset);
|
|
CPU_SET(new_pcpu, &cpuset);
|
|
|
|
pr_debug("Migrating from %u to %u\n", sched_getcpu(), new_pcpu);
|
|
|
|
ret = pthread_setaffinity_np(thread, sizeof(cpuset), &cpuset);
|
|
|
|
TEST_ASSERT(ret == 0, "Failed to migrate to pCPU: %u; ret: %d\n",
|
|
new_pcpu, ret);
|
|
}
|
|
|
|
static void kvm_set_cntxct(struct kvm_vcpu *vcpu, uint64_t cnt,
|
|
enum arch_timer timer)
|
|
{
|
|
if (timer == PHYSICAL)
|
|
vcpu_set_reg(vcpu, KVM_REG_ARM_PTIMER_CNT, cnt);
|
|
else
|
|
vcpu_set_reg(vcpu, KVM_REG_ARM_TIMER_CNT, cnt);
|
|
}
|
|
|
|
static void handle_sync(struct kvm_vcpu *vcpu, struct ucall *uc)
|
|
{
|
|
enum sync_cmd cmd = uc->args[1];
|
|
uint64_t val = uc->args[2];
|
|
enum arch_timer timer = uc->args[3];
|
|
|
|
switch (cmd) {
|
|
case SET_COUNTER_VALUE:
|
|
kvm_set_cntxct(vcpu, val, timer);
|
|
break;
|
|
case USERSPACE_USLEEP:
|
|
usleep(val);
|
|
break;
|
|
case USERSPACE_SCHED_YIELD:
|
|
sched_yield();
|
|
break;
|
|
case USERSPACE_MIGRATE_SELF:
|
|
migrate_self(next_pcpu());
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void test_run(struct kvm_vm *vm, struct kvm_vcpu *vcpu)
|
|
{
|
|
struct ucall uc;
|
|
|
|
/* Start on CPU 0 */
|
|
migrate_self(0);
|
|
|
|
while (true) {
|
|
vcpu_run(vcpu);
|
|
switch (get_ucall(vcpu, &uc)) {
|
|
case UCALL_SYNC:
|
|
handle_sync(vcpu, &uc);
|
|
break;
|
|
case UCALL_DONE:
|
|
goto out;
|
|
case UCALL_ABORT:
|
|
REPORT_GUEST_ASSERT(uc);
|
|
goto out;
|
|
default:
|
|
TEST_FAIL("Unexpected guest exit\n");
|
|
}
|
|
}
|
|
|
|
out:
|
|
return;
|
|
}
|
|
|
|
static void test_init_timer_irq(struct kvm_vm *vm, struct kvm_vcpu *vcpu)
|
|
{
|
|
vcpu_device_attr_get(vcpu, KVM_ARM_VCPU_TIMER_CTRL,
|
|
KVM_ARM_VCPU_TIMER_IRQ_PTIMER, &ptimer_irq);
|
|
vcpu_device_attr_get(vcpu, KVM_ARM_VCPU_TIMER_CTRL,
|
|
KVM_ARM_VCPU_TIMER_IRQ_VTIMER, &vtimer_irq);
|
|
|
|
sync_global_to_guest(vm, ptimer_irq);
|
|
sync_global_to_guest(vm, vtimer_irq);
|
|
|
|
pr_debug("ptimer_irq: %d; vtimer_irq: %d\n", ptimer_irq, vtimer_irq);
|
|
}
|
|
|
|
static void test_vm_create(struct kvm_vm **vm, struct kvm_vcpu **vcpu,
|
|
enum arch_timer timer)
|
|
{
|
|
*vm = vm_create_with_one_vcpu(vcpu, guest_code);
|
|
TEST_ASSERT(*vm, "Failed to create the test VM\n");
|
|
|
|
vm_init_descriptor_tables(*vm);
|
|
vm_install_exception_handler(*vm, VECTOR_IRQ_CURRENT,
|
|
guest_irq_handler);
|
|
|
|
vcpu_init_descriptor_tables(*vcpu);
|
|
vcpu_args_set(*vcpu, 1, timer);
|
|
|
|
test_init_timer_irq(*vm, *vcpu);
|
|
vgic_v3_setup(*vm, 1, 64);
|
|
sync_global_to_guest(*vm, test_args);
|
|
}
|
|
|
|
static void test_print_help(char *name)
|
|
{
|
|
pr_info("Usage: %s [-h] [-b] [-i iterations] [-l long_wait_ms] [-p] [-v]\n"
|
|
, name);
|
|
pr_info("\t-i: Number of iterations (default: %u)\n",
|
|
NR_TEST_ITERS_DEF);
|
|
pr_info("\t-b: Test both physical and virtual timers (default: true)\n");
|
|
pr_info("\t-l: Delta (in ms) used for long wait time test (default: %u)\n",
|
|
LONG_WAIT_TEST_MS);
|
|
pr_info("\t-l: Delta (in ms) used for wait times (default: %u)\n",
|
|
WAIT_TEST_MS);
|
|
pr_info("\t-p: Test physical timer (default: true)\n");
|
|
pr_info("\t-v: Test virtual timer (default: true)\n");
|
|
pr_info("\t-h: Print this help message\n");
|
|
}
|
|
|
|
static bool parse_args(int argc, char *argv[])
|
|
{
|
|
int opt;
|
|
|
|
while ((opt = getopt(argc, argv, "bhi:l:pvw:")) != -1) {
|
|
switch (opt) {
|
|
case 'b':
|
|
test_args.test_physical = true;
|
|
test_args.test_virtual = true;
|
|
break;
|
|
case 'i':
|
|
test_args.iterations =
|
|
atoi_positive("Number of iterations", optarg);
|
|
break;
|
|
case 'l':
|
|
test_args.long_wait_ms =
|
|
atoi_positive("Long wait time", optarg);
|
|
break;
|
|
case 'p':
|
|
test_args.test_physical = true;
|
|
test_args.test_virtual = false;
|
|
break;
|
|
case 'v':
|
|
test_args.test_virtual = true;
|
|
test_args.test_physical = false;
|
|
break;
|
|
case 'w':
|
|
test_args.wait_ms = atoi_positive("Wait time", optarg);
|
|
break;
|
|
case 'h':
|
|
default:
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
|
|
err:
|
|
test_print_help(argv[0]);
|
|
return false;
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
struct kvm_vcpu *vcpu;
|
|
struct kvm_vm *vm;
|
|
|
|
/* Tell stdout not to buffer its content */
|
|
setbuf(stdout, NULL);
|
|
|
|
if (!parse_args(argc, argv))
|
|
exit(KSFT_SKIP);
|
|
|
|
if (test_args.test_virtual) {
|
|
test_vm_create(&vm, &vcpu, VIRTUAL);
|
|
test_run(vm, vcpu);
|
|
kvm_vm_free(vm);
|
|
}
|
|
|
|
if (test_args.test_physical) {
|
|
test_vm_create(&vm, &vcpu, PHYSICAL);
|
|
test_run(vm, vcpu);
|
|
kvm_vm_free(vm);
|
|
}
|
|
|
|
return 0;
|
|
}
|