// 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