Add timer test case based on common arch_timer code, one-shot mode
is tested with timer interrupt.
Signed-off-by: Bibo Mao <maobibo@loongson.cn>
---
tools/testing/selftests/kvm/Makefile.kvm | 10 +-
.../kvm/include/loongarch/arch_timer.h | 79 +++++++++++++++
.../kvm/include/loongarch/processor.h | 10 ++
.../selftests/kvm/lib/loongarch/processor.c | 4 +-
.../selftests/kvm/loongarch/arch_timer.c | 98 +++++++++++++++++++
5 files changed, 196 insertions(+), 5 deletions(-)
create mode 100644 tools/testing/selftests/kvm/include/loongarch/arch_timer.h
create mode 100644 tools/testing/selftests/kvm/loongarch/arch_timer.c
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 148d427ff24b..662adf8f309b 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -183,6 +183,8 @@ TEST_GEN_PROGS_arm64 += memslot_perf_test
TEST_GEN_PROGS_arm64 += mmu_stress_test
TEST_GEN_PROGS_arm64 += rseq_test
TEST_GEN_PROGS_arm64 += steal_time
+SPLIT_TESTS_arm64 += arch_timer
+SPLIT_TESTS_arm64 += get-reg-list
TEST_GEN_PROGS_s390 = $(TEST_GEN_PROGS_COMMON)
TEST_GEN_PROGS_s390 += s390/memop
@@ -209,6 +211,8 @@ TEST_GEN_PROGS_riscv += memslot_perf_test
TEST_GEN_PROGS_riscv += mmu_stress_test
TEST_GEN_PROGS_riscv += rseq_test
TEST_GEN_PROGS_riscv += steal_time
+SPLIT_TESTS_riscv += arch_timer
+SPLIT_TESTS_riscv += get-reg-list
TEST_GEN_PROGS_loongarch += coalesced_io_test
TEST_GEN_PROGS_loongarch += demand_paging_test
@@ -222,10 +226,10 @@ TEST_GEN_PROGS_loongarch += kvm_page_table_test
TEST_GEN_PROGS_loongarch += memslot_modification_stress_test
TEST_GEN_PROGS_loongarch += memslot_perf_test
TEST_GEN_PROGS_loongarch += set_memory_region_test
+TEST_GEN_PROGS_loongarch += arch_timer
+SPLIT_TESTS_loongarch = arch_timer
-SPLIT_TESTS += arch_timer
-SPLIT_TESTS += get-reg-list
-
+SPLIT_TESTS += $(SPLIT_TESTS_$(ARCH))
TEST_PROGS += $(TEST_PROGS_$(ARCH))
TEST_GEN_PROGS += $(TEST_GEN_PROGS_$(ARCH))
TEST_GEN_PROGS_EXTENDED += $(TEST_GEN_PROGS_EXTENDED_$(ARCH))
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
index 000000000000..94b1cba2744d
--- /dev/null
+++ b/tools/testing/selftests/kvm/include/loongarch/arch_timer.h
@@ -0,0 +1,79 @@
+/* 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 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 unsigned long timer_get_val(void)
+{
+ return csr_read(LOONGARCH_CSR_TVAL);
+}
+
+static inline unsigned long timer_get_cfg(void)
+{
+ return csr_read(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 __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 */
diff --git a/tools/testing/selftests/kvm/include/loongarch/processor.h b/tools/testing/selftests/kvm/include/loongarch/processor.h
index b027f8f4dac7..61f6e215046b 100644
--- a/tools/testing/selftests/kvm/include/loongarch/processor.h
+++ b/tools/testing/selftests/kvm/include/loongarch/processor.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
diff --git a/tools/testing/selftests/kvm/lib/loongarch/processor.c b/tools/testing/selftests/kvm/lib/loongarch/processor.c
index 20ba476ccb72..436990258068 100644
--- a/tools/testing/selftests/kvm/lib/loongarch/processor.c
+++ b/tools/testing/selftests/kvm/lib/loongarch/processor.c
@@ -271,8 +271,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
index 000000000000..2a2cebcf3885
--- /dev/null
+++ b/tools/testing/selftests/kvm/loongarch/arch_timer.c
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ *
+ * The test validates 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)
+{
+ uint64_t xcnt, val, cfg, xcnt_diff_us;
+ unsigned int intid;
+ uint32_t cpu = guest_get_vcpuid();
+ 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();
+
+ /*
+ * On physical 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_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_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);
+}
--
2.39.3
Hi, Bibo,
On Tue, Nov 4, 2025 at 7:37 PM Bibo Mao <maobibo@loongson.cn> wrote:
>
> Add timer test case based on common arch_timer code, one-shot mode
> is tested with timer interrupt.
>
> Signed-off-by: Bibo Mao <maobibo@loongson.cn>
> ---
> tools/testing/selftests/kvm/Makefile.kvm | 10 +-
> .../kvm/include/loongarch/arch_timer.h | 79 +++++++++++++++
> .../kvm/include/loongarch/processor.h | 10 ++
> .../selftests/kvm/lib/loongarch/processor.c | 4 +-
> .../selftests/kvm/loongarch/arch_timer.c | 98 +++++++++++++++++++
> 5 files changed, 196 insertions(+), 5 deletions(-)
> create mode 100644 tools/testing/selftests/kvm/include/loongarch/arch_timer.h
> create mode 100644 tools/testing/selftests/kvm/loongarch/arch_timer.c
>
> diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
> index 148d427ff24b..662adf8f309b 100644
> --- a/tools/testing/selftests/kvm/Makefile.kvm
> +++ b/tools/testing/selftests/kvm/Makefile.kvm
> @@ -183,6 +183,8 @@ TEST_GEN_PROGS_arm64 += memslot_perf_test
> TEST_GEN_PROGS_arm64 += mmu_stress_test
> TEST_GEN_PROGS_arm64 += rseq_test
> TEST_GEN_PROGS_arm64 += steal_time
> +SPLIT_TESTS_arm64 += arch_timer
> +SPLIT_TESTS_arm64 += get-reg-list
>
> TEST_GEN_PROGS_s390 = $(TEST_GEN_PROGS_COMMON)
> TEST_GEN_PROGS_s390 += s390/memop
> @@ -209,6 +211,8 @@ TEST_GEN_PROGS_riscv += memslot_perf_test
> TEST_GEN_PROGS_riscv += mmu_stress_test
> TEST_GEN_PROGS_riscv += rseq_test
> TEST_GEN_PROGS_riscv += steal_time
> +SPLIT_TESTS_riscv += arch_timer
> +SPLIT_TESTS_riscv += get-reg-list
>
> TEST_GEN_PROGS_loongarch += coalesced_io_test
> TEST_GEN_PROGS_loongarch += demand_paging_test
> @@ -222,10 +226,10 @@ TEST_GEN_PROGS_loongarch += kvm_page_table_test
> TEST_GEN_PROGS_loongarch += memslot_modification_stress_test
> TEST_GEN_PROGS_loongarch += memslot_perf_test
> TEST_GEN_PROGS_loongarch += set_memory_region_test
> +TEST_GEN_PROGS_loongarch += arch_timer
These tests should be in alpha-betical order.
Huacai
> +SPLIT_TESTS_loongarch = arch_timer
>
> -SPLIT_TESTS += arch_timer
> -SPLIT_TESTS += get-reg-list
> -
> +SPLIT_TESTS += $(SPLIT_TESTS_$(ARCH))
> TEST_PROGS += $(TEST_PROGS_$(ARCH))
> TEST_GEN_PROGS += $(TEST_GEN_PROGS_$(ARCH))
> TEST_GEN_PROGS_EXTENDED += $(TEST_GEN_PROGS_EXTENDED_$(ARCH))
> 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
> index 000000000000..94b1cba2744d
> --- /dev/null
> +++ b/tools/testing/selftests/kvm/include/loongarch/arch_timer.h
> @@ -0,0 +1,79 @@
> +/* 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 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 unsigned long timer_get_val(void)
> +{
> + return csr_read(LOONGARCH_CSR_TVAL);
> +}
> +
> +static inline unsigned long timer_get_cfg(void)
> +{
> + return csr_read(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 __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 */
> diff --git a/tools/testing/selftests/kvm/include/loongarch/processor.h b/tools/testing/selftests/kvm/include/loongarch/processor.h
> index b027f8f4dac7..61f6e215046b 100644
> --- a/tools/testing/selftests/kvm/include/loongarch/processor.h
> +++ b/tools/testing/selftests/kvm/include/loongarch/processor.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
> diff --git a/tools/testing/selftests/kvm/lib/loongarch/processor.c b/tools/testing/selftests/kvm/lib/loongarch/processor.c
> index 20ba476ccb72..436990258068 100644
> --- a/tools/testing/selftests/kvm/lib/loongarch/processor.c
> +++ b/tools/testing/selftests/kvm/lib/loongarch/processor.c
> @@ -271,8 +271,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
> index 000000000000..2a2cebcf3885
> --- /dev/null
> +++ b/tools/testing/selftests/kvm/loongarch/arch_timer.c
> @@ -0,0 +1,98 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + *
> + * The test validates 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)
> +{
> + uint64_t xcnt, val, cfg, xcnt_diff_us;
> + unsigned int intid;
> + uint32_t cpu = guest_get_vcpuid();
> + 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();
> +
> + /*
> + * On physical 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_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_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);
> +}
> --
> 2.39.3
>
On 2025/11/15 下午12:10, Huacai Chen wrote:
> Hi, Bibo,
>
> On Tue, Nov 4, 2025 at 7:37 PM Bibo Mao <maobibo@loongson.cn> wrote:
>>
>> Add timer test case based on common arch_timer code, one-shot mode
>> is tested with timer interrupt.
>>
>> Signed-off-by: Bibo Mao <maobibo@loongson.cn>
>> ---
>> tools/testing/selftests/kvm/Makefile.kvm | 10 +-
>> .../kvm/include/loongarch/arch_timer.h | 79 +++++++++++++++
>> .../kvm/include/loongarch/processor.h | 10 ++
>> .../selftests/kvm/lib/loongarch/processor.c | 4 +-
>> .../selftests/kvm/loongarch/arch_timer.c | 98 +++++++++++++++++++
>> 5 files changed, 196 insertions(+), 5 deletions(-)
>> create mode 100644 tools/testing/selftests/kvm/include/loongarch/arch_timer.h
>> create mode 100644 tools/testing/selftests/kvm/loongarch/arch_timer.c
>>
>> diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
>> index 148d427ff24b..662adf8f309b 100644
>> --- a/tools/testing/selftests/kvm/Makefile.kvm
>> +++ b/tools/testing/selftests/kvm/Makefile.kvm
>> @@ -183,6 +183,8 @@ TEST_GEN_PROGS_arm64 += memslot_perf_test
>> TEST_GEN_PROGS_arm64 += mmu_stress_test
>> TEST_GEN_PROGS_arm64 += rseq_test
>> TEST_GEN_PROGS_arm64 += steal_time
>> +SPLIT_TESTS_arm64 += arch_timer
>> +SPLIT_TESTS_arm64 += get-reg-list
>>
>> TEST_GEN_PROGS_s390 = $(TEST_GEN_PROGS_COMMON)
>> TEST_GEN_PROGS_s390 += s390/memop
>> @@ -209,6 +211,8 @@ TEST_GEN_PROGS_riscv += memslot_perf_test
>> TEST_GEN_PROGS_riscv += mmu_stress_test
>> TEST_GEN_PROGS_riscv += rseq_test
>> TEST_GEN_PROGS_riscv += steal_time
>> +SPLIT_TESTS_riscv += arch_timer
>> +SPLIT_TESTS_riscv += get-reg-list
>>
>> TEST_GEN_PROGS_loongarch += coalesced_io_test
>> TEST_GEN_PROGS_loongarch += demand_paging_test
>> @@ -222,10 +226,10 @@ TEST_GEN_PROGS_loongarch += kvm_page_table_test
>> TEST_GEN_PROGS_loongarch += memslot_modification_stress_test
>> TEST_GEN_PROGS_loongarch += memslot_perf_test
>> TEST_GEN_PROGS_loongarch += set_memory_region_test
>> +TEST_GEN_PROGS_loongarch += arch_timer
> These tests should be in alpha-betical order.
Sure, will do in next version.
Regards
Bibo Mao
>
> Huacai
>
>> +SPLIT_TESTS_loongarch = arch_timer
>>
>> -SPLIT_TESTS += arch_timer
>> -SPLIT_TESTS += get-reg-list
>> -
>> +SPLIT_TESTS += $(SPLIT_TESTS_$(ARCH))
>> TEST_PROGS += $(TEST_PROGS_$(ARCH))
>> TEST_GEN_PROGS += $(TEST_GEN_PROGS_$(ARCH))
>> TEST_GEN_PROGS_EXTENDED += $(TEST_GEN_PROGS_EXTENDED_$(ARCH))
>> 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
>> index 000000000000..94b1cba2744d
>> --- /dev/null
>> +++ b/tools/testing/selftests/kvm/include/loongarch/arch_timer.h
>> @@ -0,0 +1,79 @@
>> +/* 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 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 unsigned long timer_get_val(void)
>> +{
>> + return csr_read(LOONGARCH_CSR_TVAL);
>> +}
>> +
>> +static inline unsigned long timer_get_cfg(void)
>> +{
>> + return csr_read(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 __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 */
>> diff --git a/tools/testing/selftests/kvm/include/loongarch/processor.h b/tools/testing/selftests/kvm/include/loongarch/processor.h
>> index b027f8f4dac7..61f6e215046b 100644
>> --- a/tools/testing/selftests/kvm/include/loongarch/processor.h
>> +++ b/tools/testing/selftests/kvm/include/loongarch/processor.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
>> diff --git a/tools/testing/selftests/kvm/lib/loongarch/processor.c b/tools/testing/selftests/kvm/lib/loongarch/processor.c
>> index 20ba476ccb72..436990258068 100644
>> --- a/tools/testing/selftests/kvm/lib/loongarch/processor.c
>> +++ b/tools/testing/selftests/kvm/lib/loongarch/processor.c
>> @@ -271,8 +271,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
>> index 000000000000..2a2cebcf3885
>> --- /dev/null
>> +++ b/tools/testing/selftests/kvm/loongarch/arch_timer.c
>> @@ -0,0 +1,98 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + *
>> + * The test validates 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)
>> +{
>> + uint64_t xcnt, val, cfg, xcnt_diff_us;
>> + unsigned int intid;
>> + uint32_t cpu = guest_get_vcpuid();
>> + 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();
>> +
>> + /*
>> + * On physical 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_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_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);
>> +}
>> --
>> 2.39.3
>>
© 2016 - 2025 Red Hat, Inc.