mirror of
https://github.com/torvalds/linux.git
synced 2026-04-19 23:34:00 -04:00
With time counter test, it is to verify that time count starts from 0 and always grows up then. Signed-off-by: Bibo Mao <maobibo@loongson.cn> Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
201 lines
5.4 KiB
C
201 lines
5.4 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* The test validates periodic/one-shot constant timer IRQ using
|
|
* CSR.TCFG and CSR.TVAL registers.
|
|
*/
|
|
#include "arch_timer.h"
|
|
#include "kvm_util.h"
|
|
#include "processor.h"
|
|
#include "timer_test.h"
|
|
#include "ucall_common.h"
|
|
|
|
static void do_idle(void)
|
|
{
|
|
unsigned int intid;
|
|
unsigned long estat;
|
|
|
|
__asm__ __volatile__("idle 0" : : : "memory");
|
|
|
|
estat = csr_read(LOONGARCH_CSR_ESTAT);
|
|
intid = !!(estat & BIT(INT_TI));
|
|
|
|
/* Make sure pending timer IRQ arrived */
|
|
GUEST_ASSERT_EQ(intid, 1);
|
|
csr_write(CSR_TINTCLR_TI, LOONGARCH_CSR_TINTCLR);
|
|
}
|
|
|
|
static void guest_irq_handler(struct ex_regs *regs)
|
|
{
|
|
unsigned int intid;
|
|
uint32_t cpu = guest_get_vcpuid();
|
|
uint64_t xcnt, val, cfg, xcnt_diff_us;
|
|
struct test_vcpu_shared_data *shared_data = &vcpu_shared_data[cpu];
|
|
|
|
intid = !!(regs->estat & BIT(INT_TI));
|
|
|
|
/* Make sure we are dealing with the correct timer IRQ */
|
|
GUEST_ASSERT_EQ(intid, 1);
|
|
|
|
cfg = timer_get_cfg();
|
|
if (cfg & CSR_TCFG_PERIOD) {
|
|
WRITE_ONCE(shared_data->nr_iter, shared_data->nr_iter - 1);
|
|
if (shared_data->nr_iter == 0)
|
|
disable_timer();
|
|
csr_write(CSR_TINTCLR_TI, LOONGARCH_CSR_TINTCLR);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* On real machine, value of LOONGARCH_CSR_TVAL is BIT_ULL(48) - 1
|
|
* On virtual machine, its value counts down from BIT_ULL(48) - 1
|
|
*/
|
|
val = timer_get_val();
|
|
xcnt = timer_get_cycles();
|
|
xcnt_diff_us = cycles_to_usec(xcnt - shared_data->xcnt);
|
|
|
|
/* Basic 'timer condition met' check */
|
|
__GUEST_ASSERT(val > cfg,
|
|
"val = 0x%lx, cfg = 0x%lx, xcnt_diff_us = 0x%lx",
|
|
val, cfg, xcnt_diff_us);
|
|
|
|
csr_write(CSR_TINTCLR_TI, LOONGARCH_CSR_TINTCLR);
|
|
WRITE_ONCE(shared_data->nr_iter, shared_data->nr_iter + 1);
|
|
}
|
|
|
|
static void guest_test_period_timer(uint32_t cpu)
|
|
{
|
|
uint32_t irq_iter, config_iter;
|
|
uint64_t us;
|
|
struct test_vcpu_shared_data *shared_data = &vcpu_shared_data[cpu];
|
|
|
|
shared_data->nr_iter = test_args.nr_iter;
|
|
shared_data->xcnt = timer_get_cycles();
|
|
us = msecs_to_usecs(test_args.timer_period_ms) + test_args.timer_err_margin_us;
|
|
timer_set_next_cmp_ms(test_args.timer_period_ms, true);
|
|
|
|
for (config_iter = 0; config_iter < test_args.nr_iter; config_iter++) {
|
|
/* Setup a timeout for the interrupt to arrive */
|
|
udelay(us);
|
|
}
|
|
|
|
irq_iter = READ_ONCE(shared_data->nr_iter);
|
|
__GUEST_ASSERT(irq_iter == 0,
|
|
"irq_iter = 0x%x.\n"
|
|
" Guest period timer interrupt was not triggered within the specified\n"
|
|
" interval, try to increase the error margin by [-e] option.\n",
|
|
irq_iter);
|
|
}
|
|
|
|
static void guest_test_oneshot_timer(uint32_t cpu)
|
|
{
|
|
uint32_t irq_iter, config_iter;
|
|
uint64_t us;
|
|
struct test_vcpu_shared_data *shared_data = &vcpu_shared_data[cpu];
|
|
|
|
shared_data->nr_iter = 0;
|
|
shared_data->guest_stage = 0;
|
|
us = msecs_to_usecs(test_args.timer_period_ms) + test_args.timer_err_margin_us;
|
|
for (config_iter = 0; config_iter < test_args.nr_iter; config_iter++) {
|
|
shared_data->xcnt = timer_get_cycles();
|
|
|
|
/* Setup the next interrupt */
|
|
timer_set_next_cmp_ms(test_args.timer_period_ms, false);
|
|
/* Setup a timeout for the interrupt to arrive */
|
|
udelay(us);
|
|
|
|
irq_iter = READ_ONCE(shared_data->nr_iter);
|
|
__GUEST_ASSERT(config_iter + 1 == irq_iter,
|
|
"config_iter + 1 = 0x%x, irq_iter = 0x%x.\n"
|
|
" Guest timer interrupt was not triggered within the specified\n"
|
|
" interval, try to increase the error margin by [-e] option.\n",
|
|
config_iter + 1, irq_iter);
|
|
}
|
|
}
|
|
|
|
static void guest_test_emulate_timer(uint32_t cpu)
|
|
{
|
|
uint32_t config_iter;
|
|
uint64_t xcnt_diff_us, us;
|
|
struct test_vcpu_shared_data *shared_data = &vcpu_shared_data[cpu];
|
|
|
|
local_irq_disable();
|
|
shared_data->nr_iter = 0;
|
|
us = msecs_to_usecs(test_args.timer_period_ms);
|
|
for (config_iter = 0; config_iter < test_args.nr_iter; config_iter++) {
|
|
shared_data->xcnt = timer_get_cycles();
|
|
|
|
/* Setup the next interrupt */
|
|
timer_set_next_cmp_ms(test_args.timer_period_ms, false);
|
|
do_idle();
|
|
|
|
xcnt_diff_us = cycles_to_usec(timer_get_cycles() - shared_data->xcnt);
|
|
__GUEST_ASSERT(xcnt_diff_us >= us,
|
|
"xcnt_diff_us = 0x%lx, us = 0x%lx.\n",
|
|
xcnt_diff_us, us);
|
|
}
|
|
local_irq_enable();
|
|
}
|
|
|
|
static void guest_time_count_test(uint32_t cpu)
|
|
{
|
|
uint32_t config_iter;
|
|
unsigned long start, end, prev, us;
|
|
|
|
/* Assuming that test case starts to run in 1 second */
|
|
start = timer_get_cycles();
|
|
us = msec_to_cycles(1000);
|
|
__GUEST_ASSERT(start <= us,
|
|
"start = 0x%lx, us = 0x%lx.\n",
|
|
start, us);
|
|
|
|
us = msec_to_cycles(test_args.timer_period_ms);
|
|
for (config_iter = 0; config_iter < test_args.nr_iter; config_iter++) {
|
|
start = timer_get_cycles();
|
|
end = start + us;
|
|
/* test time count growing up always */
|
|
while (start < end) {
|
|
prev = start;
|
|
start = timer_get_cycles();
|
|
__GUEST_ASSERT(prev <= start,
|
|
"prev = 0x%lx, start = 0x%lx.\n",
|
|
prev, start);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void guest_code(void)
|
|
{
|
|
uint32_t cpu = guest_get_vcpuid();
|
|
|
|
/* must run at first */
|
|
guest_time_count_test(cpu);
|
|
|
|
timer_irq_enable();
|
|
local_irq_enable();
|
|
guest_test_period_timer(cpu);
|
|
guest_test_oneshot_timer(cpu);
|
|
guest_test_emulate_timer(cpu);
|
|
|
|
GUEST_DONE();
|
|
}
|
|
|
|
struct kvm_vm *test_vm_create(void)
|
|
{
|
|
struct kvm_vm *vm;
|
|
int nr_vcpus = test_args.nr_vcpus;
|
|
|
|
vm = vm_create_with_vcpus(nr_vcpus, guest_code, vcpus);
|
|
vm_init_descriptor_tables(vm);
|
|
vm_install_exception_handler(vm, EXCCODE_INT, guest_irq_handler);
|
|
|
|
/* Make all the test's cmdline args visible to the guest */
|
|
sync_global_to_guest(vm, test_args);
|
|
|
|
return vm;
|
|
}
|
|
|
|
void test_vm_cleanup(struct kvm_vm *vm)
|
|
{
|
|
kvm_vm_free(vm);
|
|
}
|