* [PATCH v2 1/4] KVM: arm64: selftests: Add GPR save/restore functions for NV
2026-04-12 14:22 [PATCH v2 0/4] KVM: arm64: selftests: Basic nested guest support Wei-Lin Chang
@ 2026-04-12 14:22 ` Wei-Lin Chang
2026-04-12 14:22 ` [PATCH v2 2/4] KVM: arm64: sefltests: Add helpers for guest hypervisors Wei-Lin Chang
` (2 subsequent siblings)
3 siblings, 0 replies; 6+ messages in thread
From: Wei-Lin Chang @ 2026-04-12 14:22 UTC (permalink / raw)
To: linux-arm-kernel, kvmarm, kvm, linux-kselftest, linux-kernel
Cc: Marc Zyngier, Oliver Upton, Joey Gouly, Suzuki K Poulose,
Zenghui Yu, Catalin Marinas, Will Deacon, Paolo Bonzini,
Shuah Khan, Wei-Lin Chang
Adapt entry.S and hyp-entry.S from arch/arm64/kvm/hyp so that guest
hypervisors can save and restore GPRs, and provide exception handlers
to regain control after the nested guest exits. Other system register
save/restore will be added later on demand.
Signed-off-by: Wei-Lin Chang <weilin.chang@arm.com>
---
tools/testing/selftests/kvm/Makefile.kvm | 3 +
.../selftests/kvm/include/arm64/nested.h | 45 ++++++
tools/testing/selftests/kvm/lib/arm64/entry.S | 132 ++++++++++++++++++
.../selftests/kvm/lib/arm64/hyp-entry.S | 77 ++++++++++
.../testing/selftests/kvm/lib/arm64/nested.c | 12 ++
5 files changed, 269 insertions(+)
create mode 100644 tools/testing/selftests/kvm/include/arm64/nested.h
create mode 100644 tools/testing/selftests/kvm/lib/arm64/entry.S
create mode 100644 tools/testing/selftests/kvm/lib/arm64/hyp-entry.S
create mode 100644 tools/testing/selftests/kvm/lib/arm64/nested.c
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 98da9fa4b8b7..3dc3e39f7025 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -30,10 +30,13 @@ LIBKVM_x86 += lib/x86/svm.c
LIBKVM_x86 += lib/x86/ucall.c
LIBKVM_x86 += lib/x86/vmx.c
+LIBKVM_arm64 += lib/arm64/entry.S
LIBKVM_arm64 += lib/arm64/gic.c
LIBKVM_arm64 += lib/arm64/gic_v3.c
LIBKVM_arm64 += lib/arm64/gic_v3_its.c
LIBKVM_arm64 += lib/arm64/handlers.S
+LIBKVM_arm64 += lib/arm64/hyp-entry.S
+LIBKVM_arm64 += lib/arm64/nested.c
LIBKVM_arm64 += lib/arm64/processor.c
LIBKVM_arm64 += lib/arm64/spinlock.c
LIBKVM_arm64 += lib/arm64/ucall.c
diff --git a/tools/testing/selftests/kvm/include/arm64/nested.h b/tools/testing/selftests/kvm/include/arm64/nested.h
new file mode 100644
index 000000000000..86d931facacb
--- /dev/null
+++ b/tools/testing/selftests/kvm/include/arm64/nested.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * ARM64 Nested virtualization defines
+ */
+
+#ifndef SELFTEST_KVM_NESTED_H
+#define SELFTEST_KVM_NESTED_H
+
+#define ARM_EXCEPTION_IRQ 0
+#define ARM_EXCEPTION_EL1_SERROR 1
+#define ARM_EXCEPTION_TRAP 2
+#define ARM_EXCEPTION_IL 3
+#define ARM_EXCEPTION_EL2_IRQ 4
+#define ARM_EXCEPTION_EL2_SERROR 5
+#define ARM_EXCEPTION_EL2_TRAP 6
+
+#ifndef __ASSEMBLER__
+
+#include <asm/ptrace.h>
+#include "kvm_util.h"
+
+extern char hyp_vectors[];
+
+struct cpu_context {
+ struct user_pt_regs regs; /* sp = sp_el0 */
+};
+
+struct vcpu {
+ struct cpu_context context;
+};
+
+/*
+ * KVM has host_data and hyp_context, combine them because we're only doing
+ * hyp context.
+ */
+struct hyp_data {
+ struct cpu_context hyp_context;
+};
+
+u64 __guest_enter(struct vcpu *vcpu, struct cpu_context *hyp_context);
+void __hyp_exception(u64 type);
+
+#endif /* !__ASSEMBLER__ */
+
+#endif /* SELFTEST_KVM_NESTED_H */
diff --git a/tools/testing/selftests/kvm/lib/arm64/entry.S b/tools/testing/selftests/kvm/lib/arm64/entry.S
new file mode 100644
index 000000000000..33bedf5e7fb2
--- /dev/null
+++ b/tools/testing/selftests/kvm/lib/arm64/entry.S
@@ -0,0 +1,132 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * adapted from arch/arm64/kvm/hyp/entry.S
+ */
+
+/*
+ * Manually define these for now
+ */
+// offsetof(struct vcpu, context)
+#define CPU_CONTEXT 0
+// offsetof(struct cpu_context, regs)
+#define CPU_USER_PT_REGS 0
+
+#define CPU_XREG_OFFSET(x) (CPU_USER_PT_REGS + 8*x)
+#define CPU_LR_OFFSET CPU_XREG_OFFSET(30)
+#define CPU_SP_EL0_OFFSET (CPU_LR_OFFSET + 8)
+
+.macro save_callee_saved_regs ctxt
+ str x18, [\ctxt, #CPU_XREG_OFFSET(18)]
+ stp x19, x20, [\ctxt, #CPU_XREG_OFFSET(19)]
+ stp x21, x22, [\ctxt, #CPU_XREG_OFFSET(21)]
+ stp x23, x24, [\ctxt, #CPU_XREG_OFFSET(23)]
+ stp x25, x26, [\ctxt, #CPU_XREG_OFFSET(25)]
+ stp x27, x28, [\ctxt, #CPU_XREG_OFFSET(27)]
+ stp x29, lr, [\ctxt, #CPU_XREG_OFFSET(29)]
+.endm
+
+.macro restore_callee_saved_regs ctxt
+ ldr x18, [\ctxt, #CPU_XREG_OFFSET(18)]
+ ldp x19, x20, [\ctxt, #CPU_XREG_OFFSET(19)]
+ ldp x21, x22, [\ctxt, #CPU_XREG_OFFSET(21)]
+ ldp x23, x24, [\ctxt, #CPU_XREG_OFFSET(23)]
+ ldp x25, x26, [\ctxt, #CPU_XREG_OFFSET(25)]
+ ldp x27, x28, [\ctxt, #CPU_XREG_OFFSET(27)]
+ ldp x29, lr, [\ctxt, #CPU_XREG_OFFSET(29)]
+.endm
+
+.macro save_sp_el0 ctxt, tmp
+ mrs \tmp, sp_el0
+ str \tmp, [\ctxt, #CPU_SP_EL0_OFFSET]
+.endm
+
+.macro restore_sp_el0 ctxt, tmp
+ ldr \tmp, [\ctxt, #CPU_SP_EL0_OFFSET]
+ msr sp_el0, \tmp
+.endm
+
+/*
+ * u64 __guest_enter(struct vcpu *vcpu, struct cpu_context *hyp_context);
+ */
+.globl __guest_enter
+__guest_enter:
+ // x0: vcpu
+ // x1: hyp context
+
+ // Store vcpu and hyp context pointer on the stack
+ stp x0, x1, [sp, #-16]!
+
+ // Store the hyp regs
+ save_callee_saved_regs x1
+
+ // Save hyp's sp_el0
+ save_sp_el0 x1, x2
+
+ // x29 = vCPU user pt regs
+ add x29, x0, #CPU_CONTEXT
+
+ // Restore the guest's sp_el0
+ restore_sp_el0 x29, x0
+
+ // Restore guest regs x0-x17
+ ldp x0, x1, [x29, #CPU_XREG_OFFSET(0)]
+ ldp x2, x3, [x29, #CPU_XREG_OFFSET(2)]
+ ldp x4, x5, [x29, #CPU_XREG_OFFSET(4)]
+ ldp x6, x7, [x29, #CPU_XREG_OFFSET(6)]
+ ldp x8, x9, [x29, #CPU_XREG_OFFSET(8)]
+ ldp x10, x11, [x29, #CPU_XREG_OFFSET(10)]
+ ldp x12, x13, [x29, #CPU_XREG_OFFSET(12)]
+ ldp x14, x15, [x29, #CPU_XREG_OFFSET(14)]
+ ldp x16, x17, [x29, #CPU_XREG_OFFSET(16)]
+
+ // Restore guest regs x18-x29, lr
+ restore_callee_saved_regs x29
+
+ // Do not touch any register after this!
+ eret
+
+.globl __guest_exit
+__guest_exit:
+ // x0: return code
+ // x1: vcpu
+ // x2-x29,lr: vcpu regs
+ // vcpu x0-x1 on the stack
+
+ add x1, x1, #CPU_CONTEXT
+
+ // Store the guest regs x2 and x3
+ stp x2, x3, [x1, #CPU_XREG_OFFSET(2)]
+
+ // Retrieve the guest regs x0-x1 from the stack
+ ldp x2, x3, [sp], #16 // x0, x1
+
+ // Store the guest regs x0-x1 and x4-x17
+ stp x2, x3, [x1, #CPU_XREG_OFFSET(0)]
+ stp x4, x5, [x1, #CPU_XREG_OFFSET(4)]
+ stp x6, x7, [x1, #CPU_XREG_OFFSET(6)]
+ stp x8, x9, [x1, #CPU_XREG_OFFSET(8)]
+ stp x10, x11, [x1, #CPU_XREG_OFFSET(10)]
+ stp x12, x13, [x1, #CPU_XREG_OFFSET(12)]
+ stp x14, x15, [x1, #CPU_XREG_OFFSET(14)]
+ stp x16, x17, [x1, #CPU_XREG_OFFSET(16)]
+
+ // Store the guest regs x18-x29, lr
+ save_callee_saved_regs x1
+
+ // Store the guest's sp_el0
+ save_sp_el0 x1, x2
+
+ // At this point x0 and x1 on the stack is popped, so next is vCPU
+ // pointer, then hyp_context pointer
+ // *sp == vCPU, *(sp + 8) == hyp_context
+ // load x2 = hyp_context, x3 is just for ldp and popping sp
+ ldp x3, x2, [sp], #16
+
+ // Restore hyp's sp_el0
+ restore_sp_el0 x2, x3
+
+ // Now restore the hyp regs
+ restore_callee_saved_regs x2
+
+ dsb sy // Synchronize against in-flight ld/st
+ ret
diff --git a/tools/testing/selftests/kvm/lib/arm64/hyp-entry.S b/tools/testing/selftests/kvm/lib/arm64/hyp-entry.S
new file mode 100644
index 000000000000..6341f6e05c90
--- /dev/null
+++ b/tools/testing/selftests/kvm/lib/arm64/hyp-entry.S
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * adapted from arch/arm64/kvm/hyp/hyp-entry.S
+ */
+
+#include "nested.h"
+
+// skip over x0, x1 saved on entry, must be used only before the stack is modified
+.macro get_vcpu_ptr vcpu
+ ldr \vcpu, [sp, #16]
+.endm
+
+ .text
+
+el1_sync: // Guest trapped into EL2
+
+ get_vcpu_ptr x1
+ mov x0, #ARM_EXCEPTION_TRAP
+ b __guest_exit
+
+el1_irq:
+el1_fiq:
+ get_vcpu_ptr x1
+ mov x0, #ARM_EXCEPTION_IRQ
+ b __guest_exit
+
+el1_error:
+ get_vcpu_ptr x1
+ mov x0, #ARM_EXCEPTION_EL1_SERROR
+ b __guest_exit
+
+el2_sync:
+ mov x0, #ARM_EXCEPTION_EL2_TRAP
+ b __hyp_exception
+
+el2_irq:
+el2_fiq:
+ mov x0, #ARM_EXCEPTION_EL2_IRQ
+ b __hyp_exception
+
+el2_error:
+ mov x0, #ARM_EXCEPTION_EL2_SERROR
+ b __hyp_exception
+
+
+ .ltorg
+
+ .align 11
+
+.globl hyp_vectors
+hyp_vectors:
+
+.macro exception_vector target
+ .align 7
+ stp x0, x1, [sp, #-16]!
+ b \target
+.endm
+
+ exception_vector el2_sync // Synchronous EL2t
+ exception_vector el2_irq // IRQ EL2t
+ exception_vector el2_fiq // FIQ EL2t
+ exception_vector el2_error // Error EL2t
+
+ exception_vector el2_sync // Synchronous EL2h
+ exception_vector el2_irq // IRQ EL2h
+ exception_vector el2_fiq // FIQ EL2h
+ exception_vector el2_error // Error EL2h
+
+ exception_vector el1_sync // Synchronous 64-bit EL1
+ exception_vector el1_irq // IRQ 64-bit EL1
+ exception_vector el1_fiq // FIQ 64-bit EL1
+ exception_vector el1_error // Error 64-bit EL1
+
+ exception_vector el1_sync // Synchronous 32-bit EL1
+ exception_vector el1_irq // IRQ 32-bit EL1
+ exception_vector el1_fiq // FIQ 32-bit EL1
+ exception_vector el1_error // Error 32-bit EL1
diff --git a/tools/testing/selftests/kvm/lib/arm64/nested.c b/tools/testing/selftests/kvm/lib/arm64/nested.c
new file mode 100644
index 000000000000..06ddaab2436f
--- /dev/null
+++ b/tools/testing/selftests/kvm/lib/arm64/nested.c
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ARM64 Nested virtualization helpers
+ */
+
+#include "nested.h"
+#include "test_util.h"
+
+void __hyp_exception(u64 type)
+{
+ GUEST_FAIL("Unexpected hyp exception! type: %lx\n", type);
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v2 2/4] KVM: arm64: sefltests: Add helpers for guest hypervisors
2026-04-12 14:22 [PATCH v2 0/4] KVM: arm64: selftests: Basic nested guest support Wei-Lin Chang
2026-04-12 14:22 ` [PATCH v2 1/4] KVM: arm64: selftests: Add GPR save/restore functions for NV Wei-Lin Chang
@ 2026-04-12 14:22 ` Wei-Lin Chang
2026-04-12 14:22 ` [PATCH v2 3/4] KVM: arm64: sefltests: Add basic NV selftest Wei-Lin Chang
2026-04-12 14:22 ` [PATCH v2 4/4] KVM: arm64: selftests: Enhance hello_nested test Wei-Lin Chang
3 siblings, 0 replies; 6+ messages in thread
From: Wei-Lin Chang @ 2026-04-12 14:22 UTC (permalink / raw)
To: linux-arm-kernel, kvmarm, kvm, linux-kselftest, linux-kernel
Cc: Marc Zyngier, Oliver Upton, Joey Gouly, Suzuki K Poulose,
Zenghui Yu, Catalin Marinas, Will Deacon, Paolo Bonzini,
Shuah Khan, Wei-Lin Chang
Add helpers so that guest hypervisors can run nested guests. SP_EL1
save/restore is added to allow nested guests to use a stack.
Signed-off-by: Wei-Lin Chang <weilin.chang@arm.com>
---
.../selftests/kvm/include/arm64/nested.h | 17 +++++++
tools/testing/selftests/kvm/lib/arm64/entry.S | 5 ++
.../testing/selftests/kvm/lib/arm64/nested.c | 46 +++++++++++++++++++
3 files changed, 68 insertions(+)
diff --git a/tools/testing/selftests/kvm/include/arm64/nested.h b/tools/testing/selftests/kvm/include/arm64/nested.h
index 86d931facacb..7928ef89494a 100644
--- a/tools/testing/selftests/kvm/include/arm64/nested.h
+++ b/tools/testing/selftests/kvm/include/arm64/nested.h
@@ -21,8 +21,17 @@
extern char hyp_vectors[];
+enum vcpu_sysreg {
+ __INVALID_SYSREG__, /* 0 is reserved as an invalid value */
+
+ SP_EL1,
+
+ NR_SYS_REGS
+};
+
struct cpu_context {
struct user_pt_regs regs; /* sp = sp_el0 */
+ u64 sys_regs[NR_SYS_REGS];
};
struct vcpu {
@@ -37,9 +46,17 @@ struct hyp_data {
struct cpu_context hyp_context;
};
+void prepare_hyp(void);
+void init_vcpu(struct vcpu *vcpu, vm_paddr_t l2_pc, vm_paddr_t l2_stack_top);
+int run_l2(struct vcpu *vcpu, struct hyp_data *hyp_data);
+
+void do_hvc(void);
u64 __guest_enter(struct vcpu *vcpu, struct cpu_context *hyp_context);
void __hyp_exception(u64 type);
+void __sysreg_save_el1_state(struct cpu_context *ctxt);
+void __sysreg_restore_el1_state(struct cpu_context *ctxt);
+
#endif /* !__ASSEMBLER__ */
#endif /* SELFTEST_KVM_NESTED_H */
diff --git a/tools/testing/selftests/kvm/lib/arm64/entry.S b/tools/testing/selftests/kvm/lib/arm64/entry.S
index 33bedf5e7fb2..df3af3463c6c 100644
--- a/tools/testing/selftests/kvm/lib/arm64/entry.S
+++ b/tools/testing/selftests/kvm/lib/arm64/entry.S
@@ -3,6 +3,11 @@
* adapted from arch/arm64/kvm/hyp/entry.S
*/
+ .globl do_hvc
+ do_hvc:
+ hvc #0
+ ret
+
/*
* Manually define these for now
*/
diff --git a/tools/testing/selftests/kvm/lib/arm64/nested.c b/tools/testing/selftests/kvm/lib/arm64/nested.c
index 06ddaab2436f..b30d20b101c4 100644
--- a/tools/testing/selftests/kvm/lib/arm64/nested.c
+++ b/tools/testing/selftests/kvm/lib/arm64/nested.c
@@ -4,7 +4,53 @@
*/
#include "nested.h"
+#include "processor.h"
#include "test_util.h"
+#include <asm/sysreg.h>
+
+void prepare_hyp(void)
+{
+ write_sysreg(HCR_EL2_E2H | HCR_EL2_RW, hcr_el2);
+ write_sysreg(hyp_vectors, vbar_el2);
+ isb();
+}
+
+void init_vcpu(struct vcpu *vcpu, vm_paddr_t l2_pc, vm_paddr_t l2_stack_top)
+{
+ memset(vcpu, 0, sizeof(*vcpu));
+ vcpu->context.regs.pc = l2_pc;
+ vcpu->context.regs.pstate = PSR_MODE_EL1h | PSR_D_BIT | PSR_A_BIT | PSR_I_BIT | PSR_F_BIT;
+ vcpu->context.sys_regs[SP_EL1] = l2_stack_top;
+}
+
+void __sysreg_save_el1_state(struct cpu_context *ctxt)
+{
+ ctxt->sys_regs[SP_EL1] = read_sysreg(sp_el1);
+}
+
+void __sysreg_restore_el1_state(struct cpu_context *ctxt)
+{
+ write_sysreg(ctxt->sys_regs[SP_EL1], sp_el1);
+}
+
+int run_l2(struct vcpu *vcpu, struct hyp_data *hyp_data)
+{
+ u64 ret;
+
+ __sysreg_restore_el1_state(&vcpu->context);
+
+ write_sysreg(vcpu->context.regs.pstate, spsr_el2);
+ write_sysreg(vcpu->context.regs.pc, elr_el2);
+
+ ret = __guest_enter(vcpu, &hyp_data->hyp_context);
+
+ vcpu->context.regs.pc = read_sysreg(elr_el2);
+ vcpu->context.regs.pstate = read_sysreg(spsr_el2);
+
+ __sysreg_save_el1_state(&vcpu->context);
+
+ return ret;
+}
void __hyp_exception(u64 type)
{
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v2 3/4] KVM: arm64: sefltests: Add basic NV selftest
2026-04-12 14:22 [PATCH v2 0/4] KVM: arm64: selftests: Basic nested guest support Wei-Lin Chang
2026-04-12 14:22 ` [PATCH v2 1/4] KVM: arm64: selftests: Add GPR save/restore functions for NV Wei-Lin Chang
2026-04-12 14:22 ` [PATCH v2 2/4] KVM: arm64: sefltests: Add helpers for guest hypervisors Wei-Lin Chang
@ 2026-04-12 14:22 ` Wei-Lin Chang
2026-04-12 23:19 ` Itaru Kitayama
2026-04-12 14:22 ` [PATCH v2 4/4] KVM: arm64: selftests: Enhance hello_nested test Wei-Lin Chang
3 siblings, 1 reply; 6+ messages in thread
From: Wei-Lin Chang @ 2026-04-12 14:22 UTC (permalink / raw)
To: linux-arm-kernel, kvmarm, kvm, linux-kselftest, linux-kernel
Cc: Marc Zyngier, Oliver Upton, Joey Gouly, Suzuki K Poulose,
Zenghui Yu, Catalin Marinas, Will Deacon, Paolo Bonzini,
Shuah Khan, Wei-Lin Chang
This selftest simply starts an L1, which starts its own guest (L2). L2
runs without stage-1 and 2 translations, it calls an HVC to jump back
to L1.
Signed-off-by: Wei-Lin Chang <weilin.chang@arm.com>
---
tools/testing/selftests/kvm/Makefile.kvm | 1 +
.../selftests/kvm/arm64/hello_nested.c | 103 ++++++++++++++++++
2 files changed, 104 insertions(+)
create mode 100644 tools/testing/selftests/kvm/arm64/hello_nested.c
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 3dc3e39f7025..e8c108e0c487 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -168,6 +168,7 @@ TEST_GEN_PROGS_arm64 += arm64/arch_timer_edge_cases
TEST_GEN_PROGS_arm64 += arm64/at
TEST_GEN_PROGS_arm64 += arm64/debug-exceptions
TEST_GEN_PROGS_arm64 += arm64/hello_el2
+TEST_GEN_PROGS_arm64 += arm64/hello_nested
TEST_GEN_PROGS_arm64 += arm64/host_sve
TEST_GEN_PROGS_arm64 += arm64/hypercalls
TEST_GEN_PROGS_arm64 += arm64/external_aborts
diff --git a/tools/testing/selftests/kvm/arm64/hello_nested.c b/tools/testing/selftests/kvm/arm64/hello_nested.c
new file mode 100644
index 000000000000..97387e4697b3
--- /dev/null
+++ b/tools/testing/selftests/kvm/arm64/hello_nested.c
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * hello_nested - Go from vEL2 to EL1 then back
+ */
+
+#include "nested.h"
+#include "processor.h"
+#include "test_util.h"
+#include "ucall.h"
+
+#define XLATE2GPA (0xABCD)
+#define L2STACKSZ (0x100)
+
+/*
+ * TPIDR_EL2 is used to store vcpu id, so save and restore it.
+ */
+static vm_paddr_t ucall_translate_to_gpa(void *gva)
+{
+ vm_paddr_t gpa;
+ u64 vcpu_id = read_sysreg(tpidr_el2);
+
+ GUEST_SYNC2(XLATE2GPA, gva);
+
+ /* get the result from userspace */
+ gpa = read_sysreg(tpidr_el2);
+
+ write_sysreg(vcpu_id, tpidr_el2);
+
+ return gpa;
+}
+
+static void l2_guest_code(void)
+{
+ do_hvc();
+}
+
+static void guest_code(void)
+{
+ struct vcpu vcpu;
+ struct hyp_data hyp_data;
+ int ret;
+ vm_paddr_t l2_pc, l2_stack_top;
+ /* force 16-byte alignment for the stack pointer */
+ u8 l2_stack[L2STACKSZ] __attribute__((aligned(16)));
+
+ GUEST_ASSERT_EQ(get_current_el(), 2);
+ GUEST_PRINTF("vEL2 entry\n");
+
+ l2_pc = ucall_translate_to_gpa(l2_guest_code);
+ l2_stack_top = ucall_translate_to_gpa(&l2_stack[L2STACKSZ]);
+
+ init_vcpu(&vcpu, l2_pc, l2_stack_top);
+ prepare_hyp();
+
+ ret = run_l2(&vcpu, &hyp_data);
+ GUEST_ASSERT_EQ(ret, ARM_EXCEPTION_TRAP);
+ GUEST_DONE();
+}
+
+int main(void)
+{
+ struct kvm_vcpu_init init;
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+ struct ucall uc;
+ vm_paddr_t gpa;
+
+ TEST_REQUIRE(kvm_check_cap(KVM_CAP_ARM_EL2));
+ vm = vm_create(1);
+
+ kvm_get_default_vcpu_target(vm, &init);
+ init.features[0] |= BIT(KVM_ARM_VCPU_HAS_EL2);
+ vcpu = aarch64_vcpu_add(vm, 0, &init, guest_code);
+ kvm_arch_vm_finalize_vcpus(vm);
+
+ while (true) {
+ vcpu_run(vcpu);
+
+ switch (get_ucall(vcpu, &uc)) {
+ case UCALL_SYNC:
+ if (uc.args[0] == XLATE2GPA) {
+ gpa = addr_gva2gpa(vm, (vm_vaddr_t)uc.args[1]);
+ vcpu_set_reg(vcpu, KVM_ARM64_SYS_REG(SYS_TPIDR_EL2), gpa);
+ }
+ break;
+ case UCALL_PRINTF:
+ pr_info("%s", uc.buffer);
+ break;
+ case UCALL_DONE:
+ pr_info("DONE!\n");
+ goto end;
+ case UCALL_ABORT:
+ REPORT_GUEST_ASSERT(uc);
+ fallthrough;
+ default:
+ TEST_FAIL("Unhandled ucall: %ld\n", uc.cmd);
+ }
+ }
+
+end:
+ kvm_vm_free(vm);
+ return 0;
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v2 4/4] KVM: arm64: selftests: Enhance hello_nested test
2026-04-12 14:22 [PATCH v2 0/4] KVM: arm64: selftests: Basic nested guest support Wei-Lin Chang
` (2 preceding siblings ...)
2026-04-12 14:22 ` [PATCH v2 3/4] KVM: arm64: sefltests: Add basic NV selftest Wei-Lin Chang
@ 2026-04-12 14:22 ` Wei-Lin Chang
3 siblings, 0 replies; 6+ messages in thread
From: Wei-Lin Chang @ 2026-04-12 14:22 UTC (permalink / raw)
To: linux-arm-kernel, kvmarm, kvm, linux-kselftest, linux-kernel
Cc: Marc Zyngier, Oliver Upton, Joey Gouly, Suzuki K Poulose,
Zenghui Yu, Catalin Marinas, Will Deacon, Paolo Bonzini,
Shuah Khan, Wei-Lin Chang
Handle an "add" hypercall in L1 to add 2 numbers passed by L2, and
return the result. This better tests our save/restore functionality.
Signed-off-by: Wei-Lin Chang <weilin.chang@arm.com>
---
.../selftests/kvm/arm64/hello_nested.c | 31 ++++++++++++++++++-
.../selftests/kvm/include/arm64/nested.h | 2 +-
2 files changed, 31 insertions(+), 2 deletions(-)
diff --git a/tools/testing/selftests/kvm/arm64/hello_nested.c b/tools/testing/selftests/kvm/arm64/hello_nested.c
index 97387e4697b3..69f4d8e750e2 100644
--- a/tools/testing/selftests/kvm/arm64/hello_nested.c
+++ b/tools/testing/selftests/kvm/arm64/hello_nested.c
@@ -11,6 +11,10 @@
#define XLATE2GPA (0xABCD)
#define L2STACKSZ (0x100)
+#define L2SUCCESS (0x0)
+#define L2FAILED (0x1)
+#define L2ADD (0x2)
+
/*
* TPIDR_EL2 is used to store vcpu id, so save and restore it.
*/
@@ -31,7 +35,14 @@ static vm_paddr_t ucall_translate_to_gpa(void *gva)
static void l2_guest_code(void)
{
- do_hvc();
+ int ans = 0;
+
+ ans = do_hvc(L2ADD, 2, 3);
+
+ if (ans == 5)
+ do_hvc(L2SUCCESS, 0, 0);
+ else
+ do_hvc(L2FAILED, 0, 0);
}
static void guest_code(void)
@@ -42,6 +53,7 @@ static void guest_code(void)
vm_paddr_t l2_pc, l2_stack_top;
/* force 16-byte alignment for the stack pointer */
u8 l2_stack[L2STACKSZ] __attribute__((aligned(16)));
+ u64 arg1, arg2;
GUEST_ASSERT_EQ(get_current_el(), 2);
GUEST_PRINTF("vEL2 entry\n");
@@ -54,6 +66,23 @@ static void guest_code(void)
ret = run_l2(&vcpu, &hyp_data);
GUEST_ASSERT_EQ(ret, ARM_EXCEPTION_TRAP);
+
+ if (vcpu.context.regs.regs[0] == L2ADD) {
+ arg1 = vcpu.context.regs.regs[1];
+ arg2 = vcpu.context.regs.regs[2];
+ GUEST_PRINTF("L2 add request, arg1: %lx, arg2: %lx\n", arg1, arg2);
+ vcpu.context.regs.regs[0] = arg1 + arg2;
+ } else {
+ GUEST_FAIL("Unexpected hvc action\n");
+ }
+
+ ret = run_l2(&vcpu, &hyp_data);
+ GUEST_ASSERT_EQ(ret, ARM_EXCEPTION_TRAP);
+
+ if (vcpu.context.regs.regs[0] != L2SUCCESS)
+ GUEST_FAIL("L2 failed\n");
+
+ GUEST_PRINTF("L2 success!\n");
GUEST_DONE();
}
diff --git a/tools/testing/selftests/kvm/include/arm64/nested.h b/tools/testing/selftests/kvm/include/arm64/nested.h
index 7928ef89494a..b16a72488858 100644
--- a/tools/testing/selftests/kvm/include/arm64/nested.h
+++ b/tools/testing/selftests/kvm/include/arm64/nested.h
@@ -50,7 +50,7 @@ void prepare_hyp(void);
void init_vcpu(struct vcpu *vcpu, vm_paddr_t l2_pc, vm_paddr_t l2_stack_top);
int run_l2(struct vcpu *vcpu, struct hyp_data *hyp_data);
-void do_hvc(void);
+u64 do_hvc(u64 action, u64 arg1, u64 arg2);
u64 __guest_enter(struct vcpu *vcpu, struct cpu_context *hyp_context);
void __hyp_exception(u64 type);
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread