]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
KVM: LoongArch: selftests: Add timer interrupt test case
authorBibo Mao <maobibo@loongson.cn>
Fri, 28 Nov 2025 06:49:44 +0000 (14:49 +0800)
committerHuacai Chen <chenhuacai@loongson.cn>
Fri, 28 Nov 2025 06:49:44 +0000 (14:49 +0800)
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>
tools/testing/selftests/kvm/Makefile.kvm
tools/testing/selftests/kvm/include/loongarch/arch_timer.h [new file with mode: 0644]
tools/testing/selftests/kvm/include/loongarch/processor.h
tools/testing/selftests/kvm/lib/loongarch/processor.c
tools/testing/selftests/kvm/loongarch/arch_timer.c [new file with mode: 0644]

index 148d427ff24befa7e144d9214feb3e34018b7d10..9d01f4d0e3f97791fb5e886f1c13b6b49bc5da7e 100644 (file)
@@ -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
diff --git a/tools/testing/selftests/kvm/include/loongarch/arch_timer.h b/tools/testing/selftests/kvm/include/loongarch/arch_timer.h
new file mode 100644 (file)
index 0000000..2ed106b
--- /dev/null
@@ -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 */
index a1930f28e0448d7310d99d97216145ed2fa3c4ae..76840ddda57d1a9bb44bbbc9aaabc8d79311052b 100644 (file)
@@ -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
 #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
index b2a1fa7b18da19a8599779f6989db6c9bf39ed8f..a1b16140942bca1645555446bb68aaf125f9fd76 100644 (file)
@@ -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);
diff --git a/tools/testing/selftests/kvm/loongarch/arch_timer.c b/tools/testing/selftests/kvm/loongarch/arch_timer.c
new file mode 100644 (file)
index 0000000..6cc6716
--- /dev/null
@@ -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);
+}