mirror of
https://github.com/torvalds/linux.git
synced 2026-05-27 16:44:58 +02:00
KVM: LoongArch: selftests: Add timer interrupt test case
Add timer test case based on common arch_timer code, timer interrupt with one-shot and period mode is tested. Signed-off-by: Bibo Mao <maobibo@loongson.cn> Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
This commit is contained in:
parent
d84fe2f30b
commit
df41742343
|
|
@ -210,6 +210,7 @@ TEST_GEN_PROGS_riscv += mmu_stress_test
|
|||
TEST_GEN_PROGS_riscv += rseq_test
|
||||
TEST_GEN_PROGS_riscv += steal_time
|
||||
|
||||
TEST_GEN_PROGS_loongarch = arch_timer
|
||||
TEST_GEN_PROGS_loongarch += coalesced_io_test
|
||||
TEST_GEN_PROGS_loongarch += demand_paging_test
|
||||
TEST_GEN_PROGS_loongarch += dirty_log_perf_test
|
||||
|
|
|
|||
85
tools/testing/selftests/kvm/include/loongarch/arch_timer.h
Normal file
85
tools/testing/selftests/kvm/include/loongarch/arch_timer.h
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* LoongArch Constant Timer specific interface
|
||||
*/
|
||||
#ifndef SELFTEST_KVM_ARCH_TIMER_H
|
||||
#define SELFTEST_KVM_ARCH_TIMER_H
|
||||
|
||||
#include "processor.h"
|
||||
|
||||
/* LoongArch timer frequency is constant 100MHZ */
|
||||
#define TIMER_FREQ (100UL << 20)
|
||||
#define msec_to_cycles(msec) (TIMER_FREQ * (unsigned long)(msec) / 1000)
|
||||
#define usec_to_cycles(usec) (TIMER_FREQ * (unsigned long)(usec) / 1000000)
|
||||
#define cycles_to_usec(cycles) ((unsigned long)(cycles) * 1000000 / TIMER_FREQ)
|
||||
|
||||
static inline unsigned long timer_get_cycles(void)
|
||||
{
|
||||
unsigned long val = 0;
|
||||
|
||||
__asm__ __volatile__(
|
||||
"rdtime.d %0, $zero\n\t"
|
||||
: "=r"(val)
|
||||
:
|
||||
);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static inline unsigned long timer_get_cfg(void)
|
||||
{
|
||||
return csr_read(LOONGARCH_CSR_TCFG);
|
||||
}
|
||||
|
||||
static inline unsigned long timer_get_val(void)
|
||||
{
|
||||
return csr_read(LOONGARCH_CSR_TVAL);
|
||||
}
|
||||
|
||||
static inline void disable_timer(void)
|
||||
{
|
||||
csr_write(0, LOONGARCH_CSR_TCFG);
|
||||
}
|
||||
|
||||
static inline void timer_irq_enable(void)
|
||||
{
|
||||
unsigned long val;
|
||||
|
||||
val = csr_read(LOONGARCH_CSR_ECFG);
|
||||
val |= ECFGF_TIMER;
|
||||
csr_write(val, LOONGARCH_CSR_ECFG);
|
||||
}
|
||||
|
||||
static inline void timer_irq_disable(void)
|
||||
{
|
||||
unsigned long val;
|
||||
|
||||
val = csr_read(LOONGARCH_CSR_ECFG);
|
||||
val &= ~ECFGF_TIMER;
|
||||
csr_write(val, LOONGARCH_CSR_ECFG);
|
||||
}
|
||||
|
||||
static inline void timer_set_next_cmp_ms(unsigned int msec, bool period)
|
||||
{
|
||||
unsigned long val;
|
||||
|
||||
val = msec_to_cycles(msec) & CSR_TCFG_VAL;
|
||||
val |= CSR_TCFG_EN;
|
||||
if (period)
|
||||
val |= CSR_TCFG_PERIOD;
|
||||
csr_write(val, LOONGARCH_CSR_TCFG);
|
||||
}
|
||||
|
||||
static inline void __delay(uint64_t cycles)
|
||||
{
|
||||
uint64_t start = timer_get_cycles();
|
||||
|
||||
while ((timer_get_cycles() - start) < cycles)
|
||||
cpu_relax();
|
||||
}
|
||||
|
||||
static inline void udelay(unsigned long usec)
|
||||
{
|
||||
__delay(usec_to_cycles(usec));
|
||||
}
|
||||
#endif /* SELFTEST_KVM_ARCH_TIMER_H */
|
||||
|
|
@ -83,6 +83,8 @@
|
|||
#define LOONGARCH_CSR_PRMD 0x1
|
||||
#define LOONGARCH_CSR_EUEN 0x2
|
||||
#define LOONGARCH_CSR_ECFG 0x4
|
||||
#define ECFGB_TIMER 11
|
||||
#define ECFGF_TIMER (BIT_ULL(ECFGB_TIMER))
|
||||
#define LOONGARCH_CSR_ESTAT 0x5 /* Exception status */
|
||||
#define CSR_ESTAT_EXC_SHIFT 16
|
||||
#define CSR_ESTAT_EXC_WIDTH 6
|
||||
|
|
@ -111,6 +113,14 @@
|
|||
#define LOONGARCH_CSR_KS1 0x31
|
||||
#define LOONGARCH_CSR_TMID 0x40
|
||||
#define LOONGARCH_CSR_TCFG 0x41
|
||||
#define CSR_TCFG_VAL (BIT_ULL(48) - BIT_ULL(2))
|
||||
#define CSR_TCFG_PERIOD_SHIFT 1
|
||||
#define CSR_TCFG_PERIOD (0x1UL << CSR_TCFG_PERIOD_SHIFT)
|
||||
#define CSR_TCFG_EN (0x1UL)
|
||||
#define LOONGARCH_CSR_TVAL 0x42
|
||||
#define LOONGARCH_CSR_TINTCLR 0x44 /* Timer interrupt clear */
|
||||
#define CSR_TINTCLR_TI_SHIFT 0
|
||||
#define CSR_TINTCLR_TI (1 << CSR_TINTCLR_TI_SHIFT)
|
||||
/* TLB refill exception entry */
|
||||
#define LOONGARCH_CSR_TLBRENTRY 0x88
|
||||
#define LOONGARCH_CSR_TLBRSAVE 0x8b
|
||||
|
|
|
|||
|
|
@ -276,8 +276,8 @@ static void loongarch_vcpu_setup(struct kvm_vcpu *vcpu)
|
|||
TEST_FAIL("Unknown guest mode, mode: 0x%x", vm->mode);
|
||||
}
|
||||
|
||||
/* user mode and page enable mode */
|
||||
val = PLV_USER | CSR_CRMD_PG;
|
||||
/* kernel mode and page enable mode */
|
||||
val = PLV_KERN | CSR_CRMD_PG;
|
||||
loongarch_set_csr(vcpu, LOONGARCH_CSR_CRMD, val);
|
||||
loongarch_set_csr(vcpu, LOONGARCH_CSR_PRMD, val);
|
||||
loongarch_set_csr(vcpu, LOONGARCH_CSR_EUEN, 1);
|
||||
|
|
|
|||
130
tools/testing/selftests/kvm/loongarch/arch_timer.c
Normal file
130
tools/testing/selftests/kvm/loongarch/arch_timer.c
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
// 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 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_code(void)
|
||||
{
|
||||
uint32_t cpu = guest_get_vcpuid();
|
||||
|
||||
timer_irq_enable();
|
||||
local_irq_enable();
|
||||
guest_test_period_timer(cpu);
|
||||
guest_test_oneshot_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);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user