KVM: arm64: selftests: Introduce arch_timer selftest
v2 cover KVM: arm64: selftests: Introduce arch_timer selftest
Hello,
The patch series adds a KVM selftest to validate the behavior of ARM's generic timer (patch-9). The test programs the timer IRQs periodically, and for each interrupt, it validates the behaviour against the architecture specifications. The test further provides a command-line interface to configure the number of vCPUs, the period of the timer, and the number of iterations that the test has to run for.
Since the test heavily depends on interrupts, the patch series also adds a basic support for ARM Generic Interrupt Controller v3 (GICv3) to the KVM selftest framework (patch-8).
Furthermore, additional processor utilities such as accessing the MMIO (via readl/writel), read/write to assembler unsupported registers, basic delay generation, enable/disable local IRQs, and so on, are also introduced that the test/GICv3 takes advantage of (patches 1 through 7).
The patch series, specifically the library support, is derived from the kvm-unit-tests and the kernel itself.
Regards,
Raghavendra
v1 -> v2:
Addressed comments from Zenghui in include/aarch64/arch_timer.h:
- Correct the header description
- Remove unnecessary inclusion of linux/sizes.h
- Re-arrange CTL_ defines in ascending order
- Remove inappropriate 'return' from timer_set_* functions, which returns 'void'.
Raghavendra Rao Ananta (10):
KVM: arm64: selftests: Add MMIO readl/writel support
KVM: arm64: selftests: Add write_sysreg_s and read_sysreg_s
KVM: arm64: selftests: Add support for cpu_relax
KVM: arm64: selftests: Add basic support for arch_timers
KVM: arm64: selftests: Add basic support to generate delays
KVM: arm64: selftests: Add support to disable and enable local IRQs
KVM: arm64: selftests: Add support to get the vcpuid from MPIDR_EL1
KVM: arm64: selftests: Add light-weight spinlock support
KVM: arm64: selftests: Add basic GICv3 support
KVM: arm64: selftests: Add arch_timer test
tools/testing/selftests/kvm/.gitignore | 1 + tools/testing/selftests/kvm/Makefile | 3 +- .../selftests/kvm/aarch64/arch_timer.c | 382 ++++++++++++++++++ .../kvm/include/aarch64/arch_timer.h | 142 +++++++ .../selftests/kvm/include/aarch64/delay.h | 25 ++ .../selftests/kvm/include/aarch64/gic.h | 21 + .../selftests/kvm/include/aarch64/processor.h | 140 ++++++- .../selftests/kvm/include/aarch64/spinlock.h | 13 + tools/testing/selftests/kvm/lib/aarch64/gic.c | 93 +++++ .../selftests/kvm/lib/aarch64/gic_private.h | 21 + .../selftests/kvm/lib/aarch64/gic_v3.c | 240 +++++++++++ .../selftests/kvm/lib/aarch64/gic_v3.h | 70 ++++ .../selftests/kvm/lib/aarch64/spinlock.c | 27 ++ 13 files changed, 1176 insertions(+), 2 deletions(-) create mode 100644 tools/testing/selftests/kvm/aarch64/arch_timer.c create mode 100644 tools/testing/selftests/kvm/include/aarch64/arch_timer.h create mode 100644 tools/testing/selftests/kvm/include/aarch64/delay.h create mode 100644 tools/testing/selftests/kvm/include/aarch64/gic.h create mode 100644 tools/testing/selftests/kvm/include/aarch64/spinlock.h create mode 100644 tools/testing/selftests/kvm/lib/aarch64/gic.c create mode 100644 tools/testing/selftests/kvm/lib/aarch64/gic_private.h create mode 100644 tools/testing/selftests/kvm/lib/aarch64/gic_v3.c create mode 100644 tools/testing/selftests/kvm/lib/aarch64/gic_v3.h create mode 100644 tools/testing/selftests/kvm/lib/aarch64/spinlock.c
Signature · 1 line
2.33.0.rc1.237.g0d66db33f3-goog
PATCH v2 1/10 KVM: arm64: selftests: Add MMIO readl/writel support
Define the readl() and writel() functions for the guests to access (4-byte) the MMIO region.
The routines, and their dependents, are inspired from the kernel's arch/arm64/include/asm/io.h and arch/arm64/include/asm/barrier.h.
--- .../selftests/kvm/include/aarch64/processor.h | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-)
tools/testing/selftests/kvm/include/aarch64/processor.h +44 -1
@@ -127,6 +127,49 @@ void vm_install_sync_handler(struct kvm_vm *vm, val; \ }) -#define isb () asm volatile("isb" : : : "memory") +#define isb () asm volatile("isb" : : : "memory") +#define dsb (opt) asm volatile("dsb " #opt : : : "memory") +#define dmb (opt) asm volatile("dmb " #opt : : : "memory") + +#define dma_wmb () dmb(oshst) +#define __iowmb () dma_wmb() + +#define dma_rmb () dmb(oshld) + +#define __iormb (v) \ +({ \ + unsigned long tmp; \ + \ + dma_rmb(); \ + \ +/* \ +* Courtesy of arch/arm64/include/asm/io.h: \ +* Create a dummy control dependency from the IO read to any \ +* later instructions. This ensures that a subsequent call \ +* to udelay() will be ordered due to the ISB in __delay(). \ +*/ \ + asmvolatile ("eor %0, %1, %1\n" \ +"cbnz %0, ." \ + :"=r" (tmp) :"r" ((unsigned long )(v)) \ + :"memory" ); \ +}) + +static __always_inline void __raw_writel(u32 val, volatile void *addr) +{ + asmvolatile ("str %w0, [%1]" : :"rZ" (val),"r" (addr)); +} + +static __always_inline u32 __raw_readl(const volatile void *addr) +{ +u32 val; + asmvolatile ("ldr %w0, [%1]" :"=r" (val) :"r" (addr)); +return val; +} + +#define writel_relaxed (v,c) ((void)__raw_writel((__force u32)cpu_to_le32(v),(c))) +#define readl_relaxed (c) ({ u32 __r = le32_to_cpu((__force __le32)__raw_readl(c)); __r; }) + +#define writel (v,c) ({ __iowmb(); writel_relaxed((v),(c));}) +#define readl (c) ({ u32 __v = readl_relaxed(c); __iormb(__v); __v; })#endif /* SELFTEST_KVM_PROCESSOR_H */
Signature · 1 line
2.33.0.rc1.237.g0d66db33f3-goog
PATCH v2 2/10 KVM: arm64: selftests: Add write_sysreg_s and read_sysreg_s
In reply to Raghavendra Rao AnantaFor register names that are unsupported by the assembler or the ones without architectural names, add the macros write_sysreg_s and read_sysreg_s to support them.
The functionality is derived from kvm-unit-tests and kernel's arch/arm64/include/asm/sysreg.h.
--- .../selftests/kvm/include/aarch64/processor.h | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+)
tools/testing/selftests/kvm/include/aarch64/processor.h +61 -0
@@ -115,6 +115,67 @@ void vm_install_exception_handler(struct kvm_vm *vm,void vm_install_sync_handler (struct kvm_vm *vm,int vector,int ec,handler_fn handler); +/* +* ARMv8 ARM reserves the following encoding for system registers: +* (Ref: ARMv8 ARM, Section: "System instruction class encoding overview", +* C5.2, version:ARM DDI 0487A.f) +* [20-19] : Op0 +* [18-16] : Op1 +* [15-12] : CRn +* [11-8] : CRm +* [7-5] : Op2 +*/ +#define Op0_shift 19 +#define Op0_mask 0x3 +#define Op1_shift 16 +#define Op1_mask 0x7 +#define CRn_shift 12 +#define CRn_mask 0xf +#define CRm_shift 8 +#define CRm_mask 0xf +#define Op2_shift 5 +#define Op2_mask 0x7 + +/* +* When accessed from guests, the ARM64_SYS_REG() doesn't work since it +* generates a different encoding for additional KVM processing, and is +* only suitable for userspace to access the register via ioctls. +* Hence, define a 'pure' sys_reg() here to generate the encodings as per spec. +*/ +#define sys_reg (op0, op1, crn, crm, op2) \ + (((op0) << Op0_shift) | ((op1) << Op1_shift) | \ + ((crn) << CRn_shift) | ((crm) << CRm_shift) | \ + ((op2) << Op2_shift)) + +asm( +" .irp num,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30\n" +" .equ .L__reg_num_x\\num, \\num\n" +" .endr\n" +" .equ .L__reg_num_xzr, 31\n" +"\n" +" .macro mrs_s, rt, sreg\n" +" .inst 0xd5200000|(\\sreg)|(.L__reg_num_\\rt)\n" +" .endm\n" +"\n" +" .macro msr_s, sreg, rt\n" +" .inst 0xd5000000|(\\sreg)|(.L__reg_num_\\rt)\n" +" .endm\n" +); + +/* +* read_sysreg_s() and write_sysreg_s()'s 'reg' has to be encoded via sys_reg() +*/ +#define read_sysreg_s (reg) ({ \ + u64 __val; \ + asm volatile("mrs_s %0, "__stringify(reg) : "=r" (__val)); \ + __val; \ +}) + +#define write_sysreg_s (reg, val) do { \ + u64 __val = (u64)val; \ + asm volatile("msr_s "__stringify(reg) ", %x0" : : "rZ" (__val));\ +} while (0) +#define write_sysreg (reg, val) \ ({ \ u64 __val = (u64)(val); \
Signature · 1 line
2.33.0.rc1.237.g0d66db33f3-goog
PATCH v2 3/10 KVM: arm64: selftests: Add support for cpu_relax
In reply to Raghavendra Rao AnantaImplement the guest helper routine, cpu_relax(), to yield the processor to other tasks.
The function was derived fromarch/arm64/include/asm/vdso/processor.h.
--- tools/testing/selftests/kvm/include/aarch64/processor.h | 5 +++++ 1 file changed, 5 insertions(+)
tools/testing/selftests/kvm/include/aarch64/processor.h +5 -0
@@ -188,6 +188,11 @@ asm( val; \ }) +static inline void cpu_relax (void ) +{ + asmvolatile ("yield" :::"memory" ); +} +#define isb () asm volatile("isb" : : : "memory")#define dsb (opt) asm volatile("dsb " #opt : : : "memory")#define dmb (opt) asm volatile("dmb " #opt : : : "memory")
Signature · 1 line
2.33.0.rc1.237.g0d66db33f3-goog
PATCH v2 4/10 KVM: arm64: selftests: Add basic support for arch_timers
In reply to Raghavendra Rao AnantaAdd a minimalistic library support to access the virtual timers, that can be used for simple timing functionalities, such as introducing delays in the guest.
--- .../kvm/include/aarch64/arch_timer.h | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 tools/testing/selftests/kvm/include/aarch64/arch_timer.h
tools/testing/selftests/kvm/include/aarch64/arch_timer.h new file +142 -0
@@ -0,0 +1,142 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* +* ARM Generic Timer specific interface +*/ + +#ifndef SELFTEST_KVM_ARCH_TIMER_H +#define SELFTEST_KVM_ARCH_TIMER_H + +#include "processor.h" + +enum arch_timer { +VIRTUAL , +PHYSICAL , +}; + +#define CTL_ENABLE (1 << 0) +#define CTL_IMASK (1 << 1) +#define CTL_ISTATUS (1 << 2) + +#define msec_to_cycles (msec) \ + (timer_get_cntfrq() * (uint64_t)(msec) / 1000) + +#define usec_to_cycles (usec) \ + (timer_get_cntfrq() * (uint64_t)(usec) / 1000000) + +#define cycles_to_usec (cycles) \ + ((uint64_t)(cycles) * 1000000 / timer_get_cntfrq()) + +static inline uint32_t timer_get_cntfrq (void ) +{ +return read_sysreg (cntfrq_el0); +} + +static inline uint64_t timer_get_cntct (enum arch_timer timer) +{ +isb (); + +switch (timer) { +case VIRTUAL : +return read_sysreg (cntvct_el0); +case PHYSICAL : +return read_sysreg (cntpct_el0); +default : +GUEST_ASSERT_1 (0 , timer); + } + +/* We should not reach here */ +return 0 ; +} + +static inline void timer_set_cval (enum arch_timer timer,uint64_t cval) +{ +switch (timer) { +case VIRTUAL : +write_sysreg (cntv_cval_el0, cval); +break ; +case PHYSICAL : +write_sysreg (cntp_cval_el0, cval); +break ; +default : +GUEST_ASSERT_1 (0 , timer); + } + +isb (); +} + +static inline uint64_t timer_get_cval (enum arch_timer timer) +{ +switch (timer) { +case VIRTUAL : +return read_sysreg (cntv_cval_el0); +case PHYSICAL : +return read_sysreg (cntp_cval_el0); +default : +GUEST_ASSERT_1 (0 , timer); + } + +/* We should not reach here */ +return 0 ; +} + +static inline void timer_set_tval (enum arch_timer timer,uint32_t tval) +{ +switch (timer) { +case VIRTUAL : +write_sysreg (cntv_tval_el0, tval); +break ; +case PHYSICAL : +write_sysreg (cntp_tval_el0, tval); +break ; +default : +GUEST_ASSERT_1 (0 , timer); + } + +isb (); +} + +static inline void timer_set_ctl (enum arch_timer timer,uint32_t ctl) +{ +switch (timer) { +case VIRTUAL : +write_sysreg (cntv_ctl_el0, ctl); +break ; +case PHYSICAL : +write_sysreg (cntp_ctl_el0, ctl); +break ; +default : +GUEST_ASSERT_1 (0 , timer); + } + +isb (); +} + +static inline uint32_t timer_get_ctl (enum arch_timer timer) +{ +switch (timer) { +case VIRTUAL : +return read_sysreg (cntv_ctl_el0); +case PHYSICAL : +return read_sysreg (cntp_ctl_el0); +default : +GUEST_ASSERT_1 (0 , timer); + } + +/* We should not reach here */ +return 0 ; +} + +static inline void timer_set_next_cval_ms (enum arch_timer timer,uint32_t msec) +{ +uint64_t now_ct =timer_get_cntct (timer); +uint64_t next_ct = now_ct +msec_to_cycles (msec); + +timer_set_cval (timer, next_ct); +} + +static inline void timer_set_next_tval_ms (enum arch_timer timer,uint32_t msec) +{ +timer_set_tval (timer,msec_to_cycles (msec)); +} + +#endif /* SELFTEST_KVM_ARCH_TIMER_H */
Signature · 1 line
2.33.0.rc1.237.g0d66db33f3-goog
PATCH v2 5/10 KVM: arm64: selftests: Add basic support to generate delays
In reply to Raghavendra Rao AnantaAdd udelay() support to generate a delay in the guest.
The routines are derived and simplified from kernel's arch/arm64/lib/delay.c.
--- .../selftests/kvm/include/aarch64/delay.h | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 tools/testing/selftests/kvm/include/aarch64/delay.h
tools/testing/selftests/kvm/include/aarch64/delay.h new file +25 -0
@@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* +* ARM simple delay routines +*/ + +#ifndef SELFTEST_KVM_ARM_DELAY_H +#define SELFTEST_KVM_ARM_DELAY_H + +#include "arch_timer.h" + +static inline void __delay (uint64_t cycles) +{ +enum arch_timer timer =VIRTUAL ; +uint64_t start =timer_get_cntct (timer); + +while ((timer_get_cntct (timer) - start) < cycles) +cpu_relax (); +} + +static inline void udelay (unsigned long usec) +{ +__delay (usec_to_cycles (usec)); +} + +#endif /* SELFTEST_KVM_ARM_DELAY_H */
Signature · 1 line
2.33.0.rc1.237.g0d66db33f3-goog
PATCH v2 6/10 KVM: arm64: selftests: Add support to disable and enable local IRQs
In reply to Raghavendra Rao AnantaAdd functions local_irq_enable() and local_irq_disable() to enable and disable the IRQs from the guest, respectively.
--- .../testing/selftests/kvm/include/aarch64/processor.h | 10 ++++++++++ 1 file changed, 10 insertions(+)
tools/testing/selftests/kvm/include/aarch64/processor.h +10 -0
@@ -238,4 +238,14 @@ static __always_inline u32 __raw_readl(const volatile void *addr)#define writel (v,c) ({ __iowmb(); writel_relaxed((v),(c));})#define readl (c) ({ u32 __v = readl_relaxed(c); __iormb(__v); __v; }) +static inline void local_irq_enable (void ) +{ + asmvolatile ("msr daifclr, #3" : : :"memory" ); +} + +static inline void local_irq_disable (void ) +{ + asmvolatile ("msr daifset, #3" : : :"memory" ); +} +#endif /* SELFTEST_KVM_PROCESSOR_H */
Signature · 1 line
2.33.0.rc1.237.g0d66db33f3-goog
PATCH v2 7/10 KVM: arm64: selftests: Add support to get the vcpuid from MPIDR_EL1
In reply to Raghavendra Rao AnantaAt times, such as when in the interrupt handler, the guest wants to get the vCPU-id that it's running on. As a result, introduce get_vcpuid() that parses the MPIDR_EL1 and returns the vcpuid to the requested caller.
--- .../selftests/kvm/include/aarch64/processor.h | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+)
tools/testing/selftests/kvm/include/aarch64/processor.h +19 -0
@@ -248,4 +248,23 @@ static inline void local_irq_disable(void) asm volatile("msr daifset, #3" : : : "memory"); } +#define MPIDR_LEVEL_BITS 8 +#define MPIDR_LEVEL_SHIFT (level) (MPIDR_LEVEL_BITS * level) +#define MPIDR_LEVEL_MASK ((1 << MPIDR_LEVEL_BITS) - 1) +#define MPIDR_AFFINITY_LEVEL (mpidr, level) \ + ((mpidr >> MPIDR_LEVEL_SHIFT(level)) & MPIDR_LEVEL_MASK) + +static inline uint32_t get_vcpuid (void ) +{ +uint32_t vcpuid =0 ; +uint64_t mpidr =read_sysreg (mpidr_el1); + +/* KVM limits only 16 vCPUs at level 0 */ + vcpuid = mpidr &0x0f ; + vcpuid |=MPIDR_AFFINITY_LEVEL (mpidr,1 ) <<4 ; + vcpuid |=MPIDR_AFFINITY_LEVEL (mpidr,2 ) <<12 ; + +return vcpuid; +} +#endif /* SELFTEST_KVM_PROCESSOR_H */
Signature · 1 line
2.33.0.rc1.237.g0d66db33f3-goog
PATCH v2 8/10 KVM: arm64: selftests: Add light-weight spinlock support
In reply to Raghavendra Rao AnantaAdd a simpler version of spinlock support for ARM64 for the guests to use.
The implementation is loosely based on the spinlock implementation in kvm-unit-tests.
--- tools/testing/selftests/kvm/Makefile | 2 +- .../selftests/kvm/include/aarch64/spinlock.h | 13 +++++++++ .../selftests/kvm/lib/aarch64/spinlock.c | 27 +++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/kvm/include/aarch64/spinlock.h create mode 100644 tools/testing/selftests/kvm/lib/aarch64/spinlock.c
tools/testing/selftests/kvm/Makefile +1 -1
@@ -35,7 +35,7 @@ endif LIBKVM = lib/assert.c lib/elf.c lib/io.c lib/kvm_util.c lib/rbtree.c lib/sparsebit.c lib/test_util.c lib/guest_modes.c lib/perf_test_util.c LIBKVM_x86_64 = lib/x86_64/apic.c lib/x86_64/processor.c lib/x86_64/vmx.c lib/x86_64/svm.c lib/x86_64/ucall.c lib/x86_64/handlers.S -LIBKVM_aarch64 = lib/aarch64/processor.c lib/aarch64/ucall.c lib/aarch64/handlers.S +LIBKVM_aarch64 = lib/aarch64/processor.c lib/aarch64/ucall.c lib/aarch64/handlers.S lib/aarch64/spinlock.c LIBKVM_s390x = lib/s390x/processor.c lib/s390x/ucall.c lib/s390x/diag318_test_handler.c TEST_GEN_PROGS_x86_64 = x86_64/cr4_cpuid_sync_test
tools/testing/selftests/kvm/include/aarch64/spinlock.h new file +13 -0
@@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef SELFTEST_KVM_ARM64_SPINLOCK_H +#define SELFTEST_KVM_ARM64_SPINLOCK_H + +struct spinlock { +int v; +}; + +extern void spin_lock (struct spinlock *lock); +extern void spin_unlock (struct spinlock *lock); + +#endif /* SELFTEST_KVM_ARM64_SPINLOCK_H */
tools/testing/selftests/kvm/lib/aarch64/spinlock.c new file +27 -0
@@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0 +/* +* ARM64 Spinlock support +*/ +#include <stdint.h> + +#include "spinlock.h" + +void spin_lock (struct spinlock *lock) +{ +uint32_t val, res; + + asmvolatile ( +"1: ldaxr %w0, [%2]\n" +" cbnz %w0, 1b\n" +" mov %w0, #1\n" +" stxr %w1, %w0, [%2]\n" +" cbnz %w1, 1b\n" + :"=&r" (val),"=&r" (res) + :"r" (&lock->v) + :"memory" ); +} + +void spin_unlock (struct spinlock *lock) +{ + asmvolatile ("stlr wzr, [%0]\n" : :"r" (&lock->v) :"memory" ); +}
Signature · 1 line
2.33.0.rc1.237.g0d66db33f3-goog
PATCH v2 9/10 KVM: arm64: selftests: Add basic GICv3 support
In reply to Raghavendra Rao AnantaAdd basic support for ARM Generic Interrupt Controller v3. The support provides guests to setup interrupts.
The work is inspired from kvm-unit-tests and the kernel's GIC driver (drivers/irqchip/irq-gic-v3.c).
--- tools/testing/selftests/kvm/Makefile | 2 +- .../selftests/kvm/include/aarch64/gic.h | 21 ++ tools/testing/selftests/kvm/lib/aarch64/gic.c | 93 +++++++ .../selftests/kvm/lib/aarch64/gic_private.h | 21 ++ .../selftests/kvm/lib/aarch64/gic_v3.c | 240 ++++++++++++++++++ .../selftests/kvm/lib/aarch64/gic_v3.h | 70 +++++ 6 files changed, 446 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/kvm/include/aarch64/gic.h create mode 100644 tools/testing/selftests/kvm/lib/aarch64/gic.c create mode 100644 tools/testing/selftests/kvm/lib/aarch64/gic_private.h create mode 100644 tools/testing/selftests/kvm/lib/aarch64/gic_v3.c create mode 100644 tools/testing/selftests/kvm/lib/aarch64/gic_v3.h
tools/testing/selftests/kvm/Makefile +1 -1
@@ -35,7 +35,7 @@ endif LIBKVM = lib/assert.c lib/elf.c lib/io.c lib/kvm_util.c lib/rbtree.c lib/sparsebit.c lib/test_util.c lib/guest_modes.c lib/perf_test_util.c LIBKVM_x86_64 = lib/x86_64/apic.c lib/x86_64/processor.c lib/x86_64/vmx.c lib/x86_64/svm.c lib/x86_64/ucall.c lib/x86_64/handlers.S -LIBKVM_aarch64 = lib/aarch64/processor.c lib/aarch64/ucall.c lib/aarch64/handlers.S lib/aarch64/spinlock.c +LIBKVM_aarch64 = lib/aarch64/processor.c lib/aarch64/ucall.c lib/aarch64/handlers.S lib/aarch64/spinlock.c lib/aarch64/gic.c lib/aarch64/gic_v3.c LIBKVM_s390x = lib/s390x/processor.c lib/s390x/ucall.c lib/s390x/diag318_test_handler.c TEST_GEN_PROGS_x86_64 = x86_64/cr4_cpuid_sync_test
tools/testing/selftests/kvm/include/aarch64/gic.h new file +21 -0
@@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* +* ARM Generic Interrupt Controller (GIC) specific defines +*/ + +#ifndef SELFTEST_KVM_GIC_H +#define SELFTEST_KVM_GIC_H + +enum gic_type { +GIC_V3 , +GIC_TYPE_MAX , +}; + +void gic_init (enum gic_type type,unsigned int nr_cpus, +void *dist_base,void *redist_base); +void gic_irq_enable (unsigned int intid); +void gic_irq_disable (unsigned int intid); +unsigned int gic_get_and_ack_irq (void ); +void gic_set_eoi (unsigned int intid); + +#endif /* SELFTEST_KVM_GIC_H */
tools/testing/selftests/kvm/lib/aarch64/gic.c new file +93 -0
93 lines not on this page · raw message
tools/testing/selftests/kvm/lib/aarch64/gic_private.h new file +21 -0
@@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* +* ARM Generic Interrupt Controller (GIC) private defines that's only +* shared among the GIC library code. +*/ + +#ifndef SELFTEST_KVM_GIC_PRIVATE_H +#define SELFTEST_KVM_GIC_PRIVATE_H + +struct gic_common_ops { +void (*gic_init)(unsigned int nr_cpus,void *dist_base); +void (*gic_cpu_init)(unsigned int cpu,void *redist_base); +void (*gic_irq_enable)(unsigned int intid); +void (*gic_irq_disable)(unsigned int intid); +uint64_t (*gic_read_iar)(void ); +void (*gic_write_eoir)(uint32_t irq); +}; + +extern const struct gic_common_ops gicv3_ops; + +#endif /* SELFTEST_KVM_GIC_PRIVATE_H */
tools/testing/selftests/kvm/lib/aarch64/gic_v3.c new file +240 -0
240 lines not on this page · raw message
tools/testing/selftests/kvm/lib/aarch64/gic_v3.h new file +70 -0
70 lines not on this page · raw message
Signature · 1 line
2.33.0.rc1.237.g0d66db33f3-goog
PATCH v2 10/10 KVM: arm64: selftests: Add arch_timer test
In reply to Raghavendra Rao AnantaAdd a KVM selftest to validate the arch_timer functionality. Primarily, the test sets up periodic timer interrupts and validates the basic architectural expectations upon its receipt.
The test provides command-line options to configure the period of the timer, number of iterations, and number of vCPUs.
--- tools/testing/selftests/kvm/.gitignore | 1 + tools/testing/selftests/kvm/Makefile | 1 + .../selftests/kvm/aarch64/arch_timer.c | 382 ++++++++++++++++++ 3 files changed, 384 insertions(+) create mode 100644 tools/testing/selftests/kvm/aarch64/arch_timer.c
tools/testing/selftests/kvm/.gitignore +1 -0
@@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only +/aarch64/arch_timer /aarch64/debug-exceptions /aarch64/get-reg-list /aarch64/vgic_init
tools/testing/selftests/kvm/Makefile +1 -0
@@ -84,6 +84,7 @@ TEST_GEN_PROGS_x86_64 += set_memory_region_test TEST_GEN_PROGS_x86_64 += steal_time TEST_GEN_PROGS_x86_64 += kvm_binary_stats_test +TEST_GEN_PROGS_aarch64 += aarch64/arch_timer TEST_GEN_PROGS_aarch64 += aarch64/debug-exceptions TEST_GEN_PROGS_aarch64 += aarch64/get-reg-list TEST_GEN_PROGS_aarch64 += aarch64/vgic_init
tools/testing/selftests/kvm/aarch64/arch_timer.c new file +382 -0
382 lines not on this page · raw message
Signature · 1 line
2.33.0.rc1.237.g0d66db33f3-goog
Re: [PATCH v2 10/10] KVM: arm64: selftests: Add arch_timer test
Quoted 290 lines · Raghavendra Rao Ananta
Add a KVM selftest to validate the
arch_timerfunctionality. Primarily, the test sets up periodic timer interrupts and validates the basic architectural expectations upon its receipt.The test provides command-line options to configure the period of the timer, number of iterations, and number of vCPUs.
Signed-off-by Raghavendra Rao Ananta <[email protected]>--- tools/testing/selftests/kvm/.gitignore | 1 + tools/testing/selftests/kvm/Makefile | 1 + .../selftests/kvm/aarch64/arch_timer.c | 382 ++++++++++++++++++ 3 files changed, 384 insertions(+) create mode 100644 tools/testing/selftests/kvm/aarch64/arch_timer.ctools/testing/selftests/kvm/.gitignore +1
-0@@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only +/aarch64/arch_timer /aarch64/debug-exceptions /aarch64/get-reg-list /aarch64/vgic_inittools/testing/selftests/kvm/Makefile +1
-0@@ -84,6 +84,7 @@ TEST_GEN_PROGS_x86_64 += set_memory_region_test TEST_GEN_PROGS_x86_64 += steal_time TEST_GEN_PROGS_x86_64 += kvm_binary_stats_test +TEST_GEN_PROGS_aarch64 += aarch64/arch_timer TEST_GEN_PROGS_aarch64 += aarch64/debug-exceptions TEST_GEN_PROGS_aarch64 += aarch64/get-reg-list TEST_GEN_PROGS_aarch64 += aarch64/vgic_inittools/testing/selftests/kvm/aarch64/arch_timer.c new file +247
-0@@ -0,0 +1,382 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* +* arch_timer.c - Tests the aarch64 timer IRQ functionality +* +* The test validates both the virtual and physical timer IRQs using +* CVAL and TVAL registers. This consitutes the four stages in the test. +* The guest's main thread configures the timer interrupt for a stage +* and waits for it to fire, with a timeout equal to the timer period. +* It asserts that the timeout doesn't exceed the timer period. +* +* On the other hand, upon receipt of an interrupt, the guest's interrupt +* handler validates the interrupt by checking if the architectural state +* is in compliance with the specifications. +* +* The test provides command-line options to configure the timer's +* period (-p), number of vCPUs (-n), and iterations per stage (-i). +* +* Copyright (c) 2021, Google LLC. +*/ + +#define _GNU_SOURCE + +#include <stdlib.h> +#include <pthread.h> +#include <linux/kvm.h> +#include <linux/sizes.h> + +#include "kvm_util.h" +#include "processor.h" +#include "delay.h" +#include "arch_timer.h" +#include "gic.h" + +#define NR_VCPUS_DEF 4 +#define NR_TEST_ITERS_DEF 5 +#define TIMER_TEST_PERIOD_MS_DEF 10 +#define TIMER_TEST_ERR_MARGIN_US 100 + +struct test_args { +int nr_vcpus; +int nr_iter; +int timer_period_ms; +}; + +static struct test_args test_args = { + .nr_vcpus =NR_VCPUS_DEF , + .nr_iter =NR_TEST_ITERS_DEF , + .timer_period_ms =TIMER_TEST_PERIOD_MS_DEF , +}; + +#define msecs_to_usecs (msec) ((msec) * 1000LL) + +#define VTIMER_IRQ 27 +#define PTIMER_IRQ 30 + +#define REDIST_REGION_ATTR_ADDR (count, base, flags, index) \ + (((uint64_t)(count) << 52) | \ + ((uint64_t)((base) >> 16) << 16) | \ + ((uint64_t)(flags) << 12) | \ + index) + +#define GICD_BASE_GPA 0x8000000ULL +#define GICR_BASE_GPA 0x80A0000ULL + +enum guest_stage { +GUEST_STAGE_VTIMER_CVAL =1 , +GUEST_STAGE_VTIMER_TVAL , +GUEST_STAGE_PTIMER_CVAL , +GUEST_STAGE_PTIMER_TVAL , +GUEST_STAGE_MAX , +}; + +/* Sahred variables between host and guest */ +struct test_vcpu_shared_data { +int nr_iter; +enum guest_stage guest_stage; +uint64_t xcnt; +}; + +struct test_vcpu { +uint32_t vcpuid; +pthread_t pt_vcpu_run; +struct kvm_vm *vm; +}; + +static struct test_vcpu test_vcpu[KVM_MAX_VCPUS ]; +static struct test_vcpu_shared_data vcpu_shared_data[KVM_MAX_VCPUS ]; + +static void +guest_configure_timer_action (struct test_vcpu_shared_data *shared_data) +{ +switch (shared_data->guest_stage) { +case GUEST_STAGE_VTIMER_CVAL : +timer_set_next_cval_ms (VIRTUAL , test_args.timer_period_ms); + shared_data->xcnt =timer_get_cntct (VIRTUAL ); +timer_set_ctl (VIRTUAL ,CTL_ENABLE ); +break ; +case GUEST_STAGE_VTIMER_TVAL : +timer_set_next_tval_ms (VIRTUAL , test_args.timer_period_ms); + shared_data->xcnt =timer_get_cntct (VIRTUAL ); +timer_set_ctl (VIRTUAL ,CTL_ENABLE ); +break ; +case GUEST_STAGE_PTIMER_CVAL : +timer_set_next_cval_ms (PHYSICAL , test_args.timer_period_ms); + shared_data->xcnt =timer_get_cntct (PHYSICAL ); +timer_set_ctl (PHYSICAL ,CTL_ENABLE ); +break ; +case GUEST_STAGE_PTIMER_TVAL : +timer_set_next_tval_ms (PHYSICAL , test_args.timer_period_ms); + shared_data->xcnt =timer_get_cntct (PHYSICAL ); +timer_set_ctl (PHYSICAL ,CTL_ENABLE ); +break ; +default : +GUEST_ASSERT (0 ); + } +} + +static void guest_validate_irq (unsigned int intid, +struct test_vcpu_shared_data *shared_data) +{ +enum guest_stage stage = shared_data->guest_stage; +uint64_t xcnt =0 , xcnt_diff_us, cval =0 ; +unsigned long xctl =0 ; +unsigned int timer_irq =0 ; + +if (stage ==GUEST_STAGE_VTIMER_CVAL || + stage ==GUEST_STAGE_VTIMER_TVAL ) { + xctl =timer_get_ctl (VIRTUAL ); +timer_set_ctl (VIRTUAL ,CTL_IMASK ); + xcnt =timer_get_cntct (VIRTUAL ); + cval =timer_get_cval (VIRTUAL ); + timer_irq =VTIMER_IRQ ; + }else if (stage ==GUEST_STAGE_PTIMER_CVAL || + stage ==GUEST_STAGE_PTIMER_TVAL ) { + xctl =timer_get_ctl (PHYSICAL ); +timer_set_ctl (PHYSICAL ,CTL_IMASK ); + xcnt =timer_get_cntct (PHYSICAL ); + cval =timer_get_cval (PHYSICAL ); + timer_irq =PTIMER_IRQ ; + }else { +GUEST_ASSERT (0 ); + } + + xcnt_diff_us =cycles_to_usec (xcnt - shared_data->xcnt); + +/* Make sure we are dealing with the correct timer IRQ */ +GUEST_ASSERT_2 (intid == timer_irq, intid, timer_irq); + +/* Basic 'timer codition met' check */ +GUEST_ASSERT_3 (xcnt >= cval, xcnt, cval, xcnt_diff_us); +GUEST_ASSERT_1 (xctl &CTL_ISTATUS , xctl); +} + +static void guest_irq_handler (struct ex_regs *regs) +{ +unsigned int intid =gic_get_and_ack_irq (); +uint32_t cpu =get_vcpuid (); +struct test_vcpu_shared_data *shared_data = &vcpu_shared_data[cpu]; + +guest_validate_irq (intid, shared_data); + +WRITE_ONCE (shared_data->nr_iter, shared_data->nr_iter +1 ); + +gic_set_eoi (intid); +} + +static void guest_run_stage (struct test_vcpu_shared_data *shared_data, +enum guest_stage stage) +{ +uint32_t irq_iter, config_iter; + + shared_data->guest_stage = stage; + shared_data->nr_iter =0 ; + +for (config_iter =0 ; config_iter < test_args.nr_iter; config_iter++) { +/* Setup the next interrupt */ +guest_configure_timer_action (shared_data); + +/* Setup a timeout for the interrupt to arrive */ +udelay (msecs_to_usecs (test_args.timer_period_ms) + +TIMER_TEST_ERR_MARGIN_US ); + + irq_iter =READ_ONCE (shared_data->nr_iter); +GUEST_ASSERT_2 (config_iter +1 == irq_iter, + config_iter +1 , irq_iter); + }; +} + +static void guest_code (void ) +{ +uint32_t cpu =get_vcpuid (); +struct test_vcpu_shared_data *shared_data = &vcpu_shared_data[cpu]; + +local_irq_disable (); + +gic_init (GIC_V3 , test_args.nr_vcpus, + (void *)GICD_BASE_GPA , (void *)GICR_BASE_GPA ); + +timer_set_ctl (VIRTUAL ,CTL_IMASK ); +timer_set_ctl (PHYSICAL ,CTL_IMASK ); + +gic_irq_enable (VTIMER_IRQ ); +gic_irq_enable (PTIMER_IRQ ); +local_irq_enable (); + +guest_run_stage (shared_data,GUEST_STAGE_VTIMER_CVAL ); +guest_run_stage (shared_data,GUEST_STAGE_VTIMER_TVAL ); +guest_run_stage (shared_data,GUEST_STAGE_PTIMER_CVAL ); +guest_run_stage (shared_data,GUEST_STAGE_PTIMER_TVAL ); + +GUEST_DONE (); +} + +static void *test_vcpu_run (void *arg) +{ +struct ucall uc; +struct test_vcpu *vcpu = arg; +struct kvm_vm *vm = vcpu->vm; +uint32_t vcpuid = vcpu->vcpuid; +struct test_vcpu_shared_data *shared_data = &vcpu_shared_data[vcpuid]; + +vcpu_run (vm, vcpuid); + +switch (get_ucall (vm, vcpuid, &uc)) { +case UCALL_SYNC : +case UCALL_DONE : +break ; +case UCALL_ABORT : +sync_global_from_guest (vm, *shared_data); +TEST_ASSERT (false, +"%s at %s:%ld\n\tvalues: %lu, %lu; %lu, vcpu: %u; stage: %u; iter: %u" , + (const char *)uc.args[0 ], __FILE__, uc.args[1 ], + uc.args[2 ], uc.args[3 ], uc.args[4 ], vcpuid, + shared_data->guest_stage, shared_data->nr_iter); +break ; +default : +TEST_FAIL ("Unexpected guest exit\n" ); + } + +return NULL ; +} + +static void test_run (struct kvm_vm *vm) +{ +int i, ret; + +for (i =0 ; i < test_args.nr_vcpus; i++) {
tools/testing/selftests/kvm/aarch64/arch_timer.c line 257
Quoted 10 lines
tools/testing/selftests/kvm/aarch64/arch_timer.c new file +10
-0+ ret =pthread_create (&test_vcpu[i].pt_vcpu_run,NULL , + test_vcpu_run, &test_vcpu[i]); +TEST_ASSERT (!ret,"Failed to create vCPU-%d pthread\n" , i); + } + +for (i =0 ; i < test_args.nr_vcpus; i++) +pthread_join (test_vcpu[i].pt_vcpu_run,NULL ); +} + +static void test_vm_setup_gic (struct kvm_vm *vm,unsigned int n_gicr_pages)
I think it's nicer if this takes nr_vcpus instead of gicr_pages.
And, it possible, it would be doubly nice if it could be be made a library function (e.g., inside lib/aarch64/vgic.c?).
tools/testing/selftests/kvm/aarch64/arch_timer.c line 267
Quoted 10 lines
tools/testing/selftests/kvm/aarch64/arch_timer.c new file +10
-0+{ +uint64_t addr; +int gic_fd; + + gic_fd =kvm_create_device (vm,KVM_DEV_TYPE_ARM_VGIC_V3 , false); + + addr =GICD_BASE_GPA ; +kvm_device_access (gic_fd,KVM_DEV_ARM_VGIC_GRP_ADDR , +KVM_VGIC_V3_ADDR_TYPE_DIST , &addr, true); +virt_pg_map (vm,GICD_BASE_GPA ,GICD_BASE_GPA );
Not really a big deal at the moment, but if KVM implemented ESPIs, then a 4k page wouldn't be enough. Why not map 64Ks as there's space for it?
Quoted 9 lines
tools/testing/selftests/kvm/aarch64/arch_timer.c new file +9
-0+ + addr =REDIST_REGION_ATTR_ADDR (test_args.nr_vcpus,GICR_BASE_GPA ,0 ,0 ); +kvm_device_access (gic_fd,KVM_DEV_ARM_VGIC_GRP_ADDR , +KVM_VGIC_V3_ADDR_TYPE_REDIST_REGION , &addr, true); +virt_map (vm,GICR_BASE_GPA ,GICR_BASE_GPA , n_gicr_pages); + +kvm_device_access (gic_fd,KVM_DEV_ARM_VGIC_GRP_CTRL , +KVM_DEV_ARM_VGIC_CTRL_INIT ,NULL , true); +}
tools/testing/selftests/kvm/aarch64/arch_timer.c line 286
Quoted 10 lines
tools/testing/selftests/kvm/aarch64/arch_timer.c new file +10
-0+ +static struct kvm_vm *test_vm_create (void ) +{ +struct kvm_vm *vm; +unsigned int i, n_gicr_pages; +int nr_vcpus = test_args.nr_vcpus; + +/* Reserve additional pages for GICR MMIO based on nr_vcpus */ + n_gicr_pages =vm_calc_num_guest_pages (VM_MODE_DEFAULT , +2 *SZ_64K * nr_vcpus);
Might be better to use vm_get_page_size instead as it doesn't need the mode arg. Note that you would have to call it after creating the vm (vm_create..), but the nice thing is that that call uses the actual mode that the vm was created with. I propose moving this pages calculation to inside test_vm_setup_gic, which could take the number of vcpus as an arg (instead of nr_pages).
Quoted 99 lines
tools/testing/selftests/kvm/aarch64/arch_timer.c new file +96
-0+ + vm =vm_create_default_with_vcpus (nr_vcpus, n_gicr_pages,0 , + guest_code,NULL ); + +vm_init_descriptor_tables (vm); +vm_install_exception_handler (vm,VECTOR_IRQ_CURRENT , guest_irq_handler); + +for (i =0 ; i < nr_vcpus; i++) { +vcpu_init_descriptor_tables (vm, i); + + test_vcpu[i].vcpuid = i; + test_vcpu[i].vm = vm; + } + +ucall_init (vm,NULL ); +test_vm_setup_gic (vm, n_gicr_pages); + +/* Make all the test's cmdline args visible to the guest */ +sync_global_to_guest (vm, test_args); + +return vm; +} + +static void test_print_help (char *name) +{ +pr_info ("Usage: %s [-h] [-n nr_vcpus] [-i iterations] [-p timer_period_ms]\n" , + name); +pr_info ("\t-n: Number of vCPUs to configure (default: %u; max: %u)\n" , +NR_VCPUS_DEF ,KVM_MAX_VCPUS ); +pr_info ("\t-i: Number of iterations per stage (default: %u)\n" , +NR_TEST_ITERS_DEF ); +pr_info ("\t-p: Periodicity (in ms) of the guest timer (default: %u)\n" , +TIMER_TEST_PERIOD_MS_DEF ); +pr_info ("\t-h: print this help screen\n" ); +} + +static bool parse_args (int argc,char *argv[]) +{ +int opt; + +while ((opt =getopt (argc, argv,"hn:i:p:" )) !=-1 ) { +switch (opt) { +case 'n' : + test_args.nr_vcpus =atoi (optarg); +if (test_args.nr_vcpus <=0 ) { +pr_info ("Positive value needed for -n\n" ); + goto err; + }else if (test_args.nr_vcpus >KVM_MAX_VCPUS ) { +pr_info ("Max allowed vCPUs: %u\n" , +KVM_MAX_VCPUS ); + goto err; + } +break ; +case 'i' : + test_args.nr_iter =atoi (optarg); +if (test_args.nr_iter <=0 ) { +pr_info ("Positive value needed for -i\n" ); + goto err; + } +break ; +case 'p' : + test_args.timer_period_ms =atoi (optarg); +if (test_args.timer_period_ms <=0 ) { +pr_info ("Positive value needed for -p\n" ); + goto err; + } +break ; +case 'h' : +default : + goto err; + } + } + +return true; + +err: +test_print_help (argv[0 ]); +return false; +} + +int main (int argc,char *argv[]) +{ +struct kvm_vm *vm; + +/* Tell stdout not to buffer its content */ +setbuf (stdout,NULL ); + +if (!parse_args (argc, argv)) +exit (KSFT_SKIP ); + + vm =test_vm_create (); +test_run (vm); +kvm_vm_free (vm); + +return 0 ; +}Signature · 1 line
2.33.0.rc1.237.g0d66db33f3-goog
Thanks,
Ricardo
Re: [PATCH v2 10/10] KVM: arm64: selftests: Add arch_timer test
Quoted 305 lines · Ricardo Koller
Quoted 290 lines · Raghavendra Rao Ananta
Add a KVM selftest to validate the
arch_timerfunctionality. Primarily, the test sets up periodic timer interrupts and validates the basic architectural expectations upon its receipt.The test provides command-line options to configure the period of the timer, number of iterations, and number of vCPUs.
Signed-off-by Raghavendra Rao Ananta <[email protected]>--- tools/testing/selftests/kvm/.gitignore | 1 + tools/testing/selftests/kvm/Makefile | 1 + .../selftests/kvm/aarch64/arch_timer.c | 382 ++++++++++++++++++ 3 files changed, 384 insertions(+) create mode 100644 tools/testing/selftests/kvm/aarch64/arch_timer.ctools/testing/selftests/kvm/.gitignore +1
-0@@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only +/aarch64/arch_timer /aarch64/debug-exceptions /aarch64/get-reg-list /aarch64/vgic_inittools/testing/selftests/kvm/Makefile +1
-0@@ -84,6 +84,7 @@ TEST_GEN_PROGS_x86_64 += set_memory_region_test TEST_GEN_PROGS_x86_64 += steal_time TEST_GEN_PROGS_x86_64 += kvm_binary_stats_test +TEST_GEN_PROGS_aarch64 += aarch64/arch_timer TEST_GEN_PROGS_aarch64 += aarch64/debug-exceptions TEST_GEN_PROGS_aarch64 += aarch64/get-reg-list TEST_GEN_PROGS_aarch64 += aarch64/vgic_inittools/testing/selftests/kvm/aarch64/arch_timer.c new file +247
-0@@ -0,0 +1,382 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* +* arch_timer.c - Tests the aarch64 timer IRQ functionality +* +* The test validates both the virtual and physical timer IRQs using +* CVAL and TVAL registers. This consitutes the four stages in the test. +* The guest's main thread configures the timer interrupt for a stage +* and waits for it to fire, with a timeout equal to the timer period. +* It asserts that the timeout doesn't exceed the timer period. +* +* On the other hand, upon receipt of an interrupt, the guest's interrupt +* handler validates the interrupt by checking if the architectural state +* is in compliance with the specifications. +* +* The test provides command-line options to configure the timer's +* period (-p), number of vCPUs (-n), and iterations per stage (-i). +* +* Copyright (c) 2021, Google LLC. +*/ + +#define _GNU_SOURCE + +#include <stdlib.h> +#include <pthread.h> +#include <linux/kvm.h> +#include <linux/sizes.h> + +#include "kvm_util.h" +#include "processor.h" +#include "delay.h" +#include "arch_timer.h" +#include "gic.h" + +#define NR_VCPUS_DEF 4 +#define NR_TEST_ITERS_DEF 5 +#define TIMER_TEST_PERIOD_MS_DEF 10 +#define TIMER_TEST_ERR_MARGIN_US 100 + +struct test_args { +int nr_vcpus; +int nr_iter; +int timer_period_ms; +}; + +static struct test_args test_args = { + .nr_vcpus =NR_VCPUS_DEF , + .nr_iter =NR_TEST_ITERS_DEF , + .timer_period_ms =TIMER_TEST_PERIOD_MS_DEF , +}; + +#define msecs_to_usecs (msec) ((msec) * 1000LL) + +#define VTIMER_IRQ 27 +#define PTIMER_IRQ 30 + +#define REDIST_REGION_ATTR_ADDR (count, base, flags, index) \ + (((uint64_t)(count) << 52) | \ + ((uint64_t)((base) >> 16) << 16) | \ + ((uint64_t)(flags) << 12) | \ + index) + +#define GICD_BASE_GPA 0x8000000ULL +#define GICR_BASE_GPA 0x80A0000ULL + +enum guest_stage { +GUEST_STAGE_VTIMER_CVAL =1 , +GUEST_STAGE_VTIMER_TVAL , +GUEST_STAGE_PTIMER_CVAL , +GUEST_STAGE_PTIMER_TVAL , +GUEST_STAGE_MAX , +}; + +/* Sahred variables between host and guest */ +struct test_vcpu_shared_data { +int nr_iter; +enum guest_stage guest_stage; +uint64_t xcnt; +}; + +struct test_vcpu { +uint32_t vcpuid; +pthread_t pt_vcpu_run; +struct kvm_vm *vm; +}; + +static struct test_vcpu test_vcpu[KVM_MAX_VCPUS ]; +static struct test_vcpu_shared_data vcpu_shared_data[KVM_MAX_VCPUS ]; + +static void +guest_configure_timer_action (struct test_vcpu_shared_data *shared_data) +{ +switch (shared_data->guest_stage) { +case GUEST_STAGE_VTIMER_CVAL : +timer_set_next_cval_ms (VIRTUAL , test_args.timer_period_ms); + shared_data->xcnt =timer_get_cntct (VIRTUAL ); +timer_set_ctl (VIRTUAL ,CTL_ENABLE ); +break ; +case GUEST_STAGE_VTIMER_TVAL : +timer_set_next_tval_ms (VIRTUAL , test_args.timer_period_ms); + shared_data->xcnt =timer_get_cntct (VIRTUAL ); +timer_set_ctl (VIRTUAL ,CTL_ENABLE ); +break ; +case GUEST_STAGE_PTIMER_CVAL : +timer_set_next_cval_ms (PHYSICAL , test_args.timer_period_ms); + shared_data->xcnt =timer_get_cntct (PHYSICAL ); +timer_set_ctl (PHYSICAL ,CTL_ENABLE ); +break ; +case GUEST_STAGE_PTIMER_TVAL : +timer_set_next_tval_ms (PHYSICAL , test_args.timer_period_ms); + shared_data->xcnt =timer_get_cntct (PHYSICAL ); +timer_set_ctl (PHYSICAL ,CTL_ENABLE ); +break ; +default : +GUEST_ASSERT (0 ); + } +} + +static void guest_validate_irq (unsigned int intid, +struct test_vcpu_shared_data *shared_data) +{ +enum guest_stage stage = shared_data->guest_stage; +uint64_t xcnt =0 , xcnt_diff_us, cval =0 ; +unsigned long xctl =0 ; +unsigned int timer_irq =0 ; + +if (stage ==GUEST_STAGE_VTIMER_CVAL || + stage ==GUEST_STAGE_VTIMER_TVAL ) { + xctl =timer_get_ctl (VIRTUAL ); +timer_set_ctl (VIRTUAL ,CTL_IMASK ); + xcnt =timer_get_cntct (VIRTUAL ); + cval =timer_get_cval (VIRTUAL ); + timer_irq =VTIMER_IRQ ; + }else if (stage ==GUEST_STAGE_PTIMER_CVAL || + stage ==GUEST_STAGE_PTIMER_TVAL ) { + xctl =timer_get_ctl (PHYSICAL ); +timer_set_ctl (PHYSICAL ,CTL_IMASK ); + xcnt =timer_get_cntct (PHYSICAL ); + cval =timer_get_cval (PHYSICAL ); + timer_irq =PTIMER_IRQ ; + }else { +GUEST_ASSERT (0 ); + } + + xcnt_diff_us =cycles_to_usec (xcnt - shared_data->xcnt); + +/* Make sure we are dealing with the correct timer IRQ */ +GUEST_ASSERT_2 (intid == timer_irq, intid, timer_irq); + +/* Basic 'timer codition met' check */ +GUEST_ASSERT_3 (xcnt >= cval, xcnt, cval, xcnt_diff_us); +GUEST_ASSERT_1 (xctl &CTL_ISTATUS , xctl); +} + +static void guest_irq_handler (struct ex_regs *regs) +{ +unsigned int intid =gic_get_and_ack_irq (); +uint32_t cpu =get_vcpuid (); +struct test_vcpu_shared_data *shared_data = &vcpu_shared_data[cpu]; + +guest_validate_irq (intid, shared_data); + +WRITE_ONCE (shared_data->nr_iter, shared_data->nr_iter +1 ); + +gic_set_eoi (intid); +} + +static void guest_run_stage (struct test_vcpu_shared_data *shared_data, +enum guest_stage stage) +{ +uint32_t irq_iter, config_iter; + + shared_data->guest_stage = stage; + shared_data->nr_iter =0 ; + +for (config_iter =0 ; config_iter < test_args.nr_iter; config_iter++) { +/* Setup the next interrupt */ +guest_configure_timer_action (shared_data); + +/* Setup a timeout for the interrupt to arrive */ +udelay (msecs_to_usecs (test_args.timer_period_ms) + +TIMER_TEST_ERR_MARGIN_US ); + + irq_iter =READ_ONCE (shared_data->nr_iter); +GUEST_ASSERT_2 (config_iter +1 == irq_iter, + config_iter +1 , irq_iter); + }; +} + +static void guest_code (void ) +{ +uint32_t cpu =get_vcpuid (); +struct test_vcpu_shared_data *shared_data = &vcpu_shared_data[cpu]; + +local_irq_disable (); + +gic_init (GIC_V3 , test_args.nr_vcpus, + (void *)GICD_BASE_GPA , (void *)GICR_BASE_GPA ); + +timer_set_ctl (VIRTUAL ,CTL_IMASK ); +timer_set_ctl (PHYSICAL ,CTL_IMASK ); + +gic_irq_enable (VTIMER_IRQ ); +gic_irq_enable (PTIMER_IRQ ); +local_irq_enable (); + +guest_run_stage (shared_data,GUEST_STAGE_VTIMER_CVAL ); +guest_run_stage (shared_data,GUEST_STAGE_VTIMER_TVAL ); +guest_run_stage (shared_data,GUEST_STAGE_PTIMER_CVAL ); +guest_run_stage (shared_data,GUEST_STAGE_PTIMER_TVAL ); + +GUEST_DONE (); +} + +static void *test_vcpu_run (void *arg) +{ +struct ucall uc; +struct test_vcpu *vcpu = arg; +struct kvm_vm *vm = vcpu->vm; +uint32_t vcpuid = vcpu->vcpuid; +struct test_vcpu_shared_data *shared_data = &vcpu_shared_data[vcpuid]; + +vcpu_run (vm, vcpuid); + +switch (get_ucall (vm, vcpuid, &uc)) { +case UCALL_SYNC : +case UCALL_DONE : +break ; +case UCALL_ABORT : +sync_global_from_guest (vm, *shared_data); +TEST_ASSERT (false, +"%s at %s:%ld\n\tvalues: %lu, %lu; %lu, vcpu: %u; stage: %u; iter: %u" , + (const char *)uc.args[0 ], __FILE__, uc.args[1 ], + uc.args[2 ], uc.args[3 ], uc.args[4 ], vcpuid, + shared_data->guest_stage, shared_data->nr_iter); +break ; +default : +TEST_FAIL ("Unexpected guest exit\n" ); + } + +return NULL ; +} + +static void test_run (struct kvm_vm *vm) +{ +int i, ret; + +for (i =0 ; i < test_args.nr_vcpus; i++) {tools/testing/selftests/kvm/aarch64/arch_timer.c line 257
Quoted 10 lines
tools/testing/selftests/kvm/aarch64/arch_timer.c new file +10
-0+ ret =pthread_create (&test_vcpu[i].pt_vcpu_run,NULL , + test_vcpu_run, &test_vcpu[i]); +TEST_ASSERT (!ret,"Failed to create vCPU-%d pthread\n" , i); + } + +for (i =0 ; i < test_args.nr_vcpus; i++) +pthread_join (test_vcpu[i].pt_vcpu_run,NULL ); +} + +static void test_vm_setup_gic (struct kvm_vm *vm,unsigned int n_gicr_pages)I think it's nicer if this takes
nr_vcpusinstead ofgicr_pages.
Quoted 3 lines
And, it possible, it would be doubly nice if it could be be made a library function (e.g., inside
lib/aarch64/vgic.c?).
Yeah, great idea. I had the same suggestion from Reiji as well.
Quoted 5 lines
Quoted 5 lines
tools/testing/selftests/kvm/aarch64/arch_timer.c new file +5
-0+{ +uint64_t addr; +int gic_fd; + + gic_fd =kvm_create_device (vm,KVM_DEV_TYPE_ARM_VGIC_V3 , false);
Quoted 9 lines
tools/testing/selftests/kvm/aarch64/arch_timer.c line 267
Quoted 5 lines
tools/testing/selftests/kvm/aarch64/arch_timer.c new file +5
-0+ + addr =GICD_BASE_GPA ; +kvm_device_access (gic_fd,KVM_DEV_ARM_VGIC_GRP_ADDR , +KVM_VGIC_V3_ADDR_TYPE_DIST , &addr, true); +virt_pg_map (vm,GICD_BASE_GPA ,GICD_BASE_GPA );Not really a big deal at the moment, but if KVM implemented ESPIs, then a 4k page wouldn't be enough. Why not map 64Ks as there's space for it?
Let's fix it while we are adding a new lib function.
Quoted 18 lines
Quoted 18 lines
tools/testing/selftests/kvm/aarch64/arch_timer.c new file +18
-0+ + addr =REDIST_REGION_ATTR_ADDR (test_args.nr_vcpus,GICR_BASE_GPA ,0 ,0 ); +kvm_device_access (gic_fd,KVM_DEV_ARM_VGIC_GRP_ADDR , +KVM_VGIC_V3_ADDR_TYPE_REDIST_REGION , &addr, true); +virt_map (vm,GICR_BASE_GPA ,GICR_BASE_GPA , n_gicr_pages); + +kvm_device_access (gic_fd,KVM_DEV_ARM_VGIC_GRP_CTRL , +KVM_DEV_ARM_VGIC_CTRL_INIT ,NULL , true); +} + +static struct kvm_vm *test_vm_create (void ) +{ +struct kvm_vm *vm; +unsigned int i, n_gicr_pages; +int nr_vcpus = test_args.nr_vcpus; + +/* Reserve additional pages for GICR MMIO based on nr_vcpus */ + n_gicr_pages = vm_calc_num_guest_pages(VM_MODE_DEFAULT,
Quoted 9 lines
tools/testing/selftests/kvm/aarch64/arch_timer.c line 286
Quoted 1 line
tools/testing/selftests/kvm/aarch64/arch_timer.c new file +1
-0+ 2 * SZ_64K * nr_vcpus);Might be better to use
vm_get_page_sizeinstead as it doesn't need the mode arg. Note that you would have to call it after creating the vm (vm_create..), but the nice thing is that that call uses the actual mode that the vm was created with. I propose moving this pages calculation to insidetest_vm_setup_gic, which could take the number of vcpus as an arg (instead ofnr_pages).
My idea was to reserve all the pages that we need in one go and the fact that we know that we'd be creating a VM in a 'default' mode. But now that we are moving to a lib function, I'll move the logic there.
Thanks for the suggestions.
Regards,
Raghavendra
Quoted 102 lines
Quoted 97 lines
tools/testing/selftests/kvm/aarch64/arch_timer.c new file +96
-1+ + vm =vm_create_default_with_vcpus (nr_vcpus, n_gicr_pages,0 , + guest_code,NULL ); + +vm_init_descriptor_tables (vm); +vm_install_exception_handler (vm,VECTOR_IRQ_CURRENT , guest_irq_handler); + +for (i =0 ; i < nr_vcpus; i++) { +vcpu_init_descriptor_tables (vm, i); + + test_vcpu[i].vcpuid = i; + test_vcpu[i].vm = vm; + } + +ucall_init (vm,NULL ); +test_vm_setup_gic (vm, n_gicr_pages); + +/* Make all the test's cmdline args visible to the guest */ +sync_global_to_guest (vm, test_args); + +return vm; +} + +static void test_print_help (char *name) +{ +pr_info ("Usage: %s [-h] [-n nr_vcpus] [-i iterations] [-p timer_period_ms]\n" , + name); +pr_info ("\t-n: Number of vCPUs to configure (default: %u; max: %u)\n" , +NR_VCPUS_DEF ,KVM_MAX_VCPUS ); +pr_info ("\t-i: Number of iterations per stage (default: %u)\n" , +NR_TEST_ITERS_DEF ); +pr_info ("\t-p: Periodicity (in ms) of the guest timer (default: %u)\n" , +TIMER_TEST_PERIOD_MS_DEF ); +pr_info ("\t-h: print this help screen\n" ); +} + +static bool parse_args (int argc,char *argv[]) +{ +int opt; + +while ((opt =getopt (argc, argv,"hn:i:p:" )) !=-1 ) { +switch (opt) { +case 'n' : + test_args.nr_vcpus =atoi (optarg); +if (test_args.nr_vcpus <=0 ) { +pr_info ("Positive value needed for -n\n" ); + goto err; + }else if (test_args.nr_vcpus >KVM_MAX_VCPUS ) { +pr_info ("Max allowed vCPUs: %u\n" , +KVM_MAX_VCPUS ); + goto err; + } +break ; +case 'i' : + test_args.nr_iter =atoi (optarg); +if (test_args.nr_iter <=0 ) { +pr_info ("Positive value needed for -i\n" ); + goto err; + } +break ; +case 'p' : + test_args.timer_period_ms =atoi (optarg); +if (test_args.timer_period_ms <=0 ) { +pr_info ("Positive value needed for -p\n" ); + goto err; + } +break ; +case 'h' : +default : + goto err; + } + } + +return true; + +err: +test_print_help (argv[0 ]); +return false; +} + +int main (int argc,char *argv[]) +{ +struct kvm_vm *vm; + +/* Tell stdout not to buffer its content */ +setbuf (stdout,NULL ); + +if (!parse_args (argc, argv)) +exit (KSFT_SKIP ); + + vm =test_vm_create (); +test_run (vm); +kvm_vm_free (vm); + +return 0 ; +} --Quoted 2 lines
2.33.0.rc1.237.g0d66db33f3-goog
Thanks,
Ricardo