Commit df417423 authored by Bibo Mao's avatar Bibo Mao Committed by Huacai Chen
Browse files

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: default avatarBibo Mao <maobibo@loongson.cn>
Signed-off-by: default avatarHuacai Chen <chenhuacai@loongson.cn>
parent d84fe2f3
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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 −0
Original line number Diff line number Diff line
/* 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 */
+10 −0
Original line number Diff line number Diff line
@@ -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
+2 −2
Original line number Diff line number Diff line
@@ -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 −0
Original line number Diff line number Diff line
// 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);
}