public inbox for linux-arm-kernel@lists.infradead.org
 help / color / mirror / Atom feed
* [PATCH v2 0/4] KVM: arm64: selftests: Basic nested guest support
@ 2026-04-12 14:22 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
                   ` (3 more replies)
  0 siblings, 4 replies; 14+ 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

Hi,

This is v2 of adding basic support for running nested guests (L2) in
kselftest. After getting feedback from v1 [1], I mostly started over
from scratch. Therefore you won't lose any context if you start here.
Nonetheless, I still compiled the broad changes.

Patch 1 adds GPR save/restore code for guest, and vEL2 exception
vectors.

Patch 2 adds other hypervisor helpers.

Patch 3 adds the hello_nested selftest to jump from vEL2 -> EL1 -> vEL2.

Patch 4 enhances the hello_nested selftest so that vEL1 handles a
hypercall from EL1.

* Changes from v1 [1]:

  - Set HCR_EL2.E2H for the guest.

  - Pivoted from "userspace setting up everything" to "make L1 more like
    a proper hypervisor". Guest EL2 exception vectors, and GPR
    save/restore are added. There is also some infrastructure to
    save/restore system registers, right now only SP_EL1 is
    saved/restored to give L2 a stack. More system registers can be
    added in the future.

  - Removed the stage-2 page table generator. The stage-2 page table
    generator was bad, and the changes needed for the previous point
    alone is already making the series larger, so I decided to not add
    any guest stage-2 code in this iteration.

Thanks!

[1]: https://lore.kernel.org/kvmarm/20260325003620.2214766-1-weilin.chang@arm.com/

Wei-Lin Chang (4):
  KVM: arm64: selftests: Add GPR save/restore functions for NV
  KVM: arm64: sefltests: Add helpers for guest hypervisors
  KVM: arm64: sefltests: Add basic NV selftest
  KVM: arm64: selftests: Enhance hello_nested test

 tools/testing/selftests/kvm/Makefile.kvm      |   4 +
 .../selftests/kvm/arm64/hello_nested.c        | 132 +++++++++++++++++
 .../selftests/kvm/include/arm64/nested.h      |  62 ++++++++
 tools/testing/selftests/kvm/lib/arm64/entry.S | 137 ++++++++++++++++++
 .../selftests/kvm/lib/arm64/hyp-entry.S       |  77 ++++++++++
 .../testing/selftests/kvm/lib/arm64/nested.c  |  58 ++++++++
 6 files changed, 470 insertions(+)
 create mode 100644 tools/testing/selftests/kvm/arm64/hello_nested.c
 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

-- 
2.43.0



^ permalink raw reply	[flat|nested] 14+ messages in thread

* [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; 14+ 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] 14+ 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-14 22:14   ` Itaru Kitayama
  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, 1 reply; 14+ 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] 14+ 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; 14+ 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] 14+ 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; 14+ 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] 14+ messages in thread

* Re: [PATCH v2 3/4] KVM: arm64: sefltests: Add basic NV selftest
  2026-04-12 14:22 ` [PATCH v2 3/4] KVM: arm64: sefltests: Add basic NV selftest Wei-Lin Chang
@ 2026-04-12 23:19   ` Itaru Kitayama
  2026-04-13  9:18     ` Wei-Lin Chang
  0 siblings, 1 reply; 14+ messages in thread
From: Itaru Kitayama @ 2026-04-12 23:19 UTC (permalink / raw)
  To: Wei-Lin Chang
  Cc: linux-arm-kernel, kvmarm, kvm, linux-kselftest, linux-kernel,
	Marc Zyngier, Oliver Upton, Joey Gouly, Suzuki K Poulose,
	Zenghui Yu, Catalin Marinas, Will Deacon, Paolo Bonzini,
	Shuah Khan

On Sun, Apr 12, 2026 at 03:22:15PM +0100, Wei-Lin Chang wrote:
> 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.

How do you disable both the nested guest (L2)'s MMU and stage 2
translations?

Itaru.

> 
> 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	[flat|nested] 14+ messages in thread

* Re: [PATCH v2 3/4] KVM: arm64: sefltests: Add basic NV selftest
  2026-04-12 23:19   ` Itaru Kitayama
@ 2026-04-13  9:18     ` Wei-Lin Chang
  2026-04-13 21:31       ` Itaru Kitayama
  0 siblings, 1 reply; 14+ messages in thread
From: Wei-Lin Chang @ 2026-04-13  9:18 UTC (permalink / raw)
  To: Itaru Kitayama
  Cc: linux-arm-kernel, kvmarm, kvm, linux-kselftest, linux-kernel,
	Marc Zyngier, Oliver Upton, Joey Gouly, Suzuki K Poulose,
	Zenghui Yu, Catalin Marinas, Will Deacon, Paolo Bonzini,
	Shuah Khan

Hi Itaru,

On Mon, Apr 13, 2026 at 08:19:25AM +0900, Itaru Kitayama wrote:
> On Sun, Apr 12, 2026 at 03:22:15PM +0100, Wei-Lin Chang wrote:
> > 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.
> 
> How do you disable both the nested guest (L2)'s MMU and stage 2
> translations?

Guest stage-2 is disabled by not setting HCR_EL2.VM in prepare_hyp(),
and stage-1 is disabled by not writing to SCTLR_EL12 in init_vcpu(),
effectively using the default value set by L0. However since SCTLR_EL1
has many architecturally UNKNOWN bits (including SCTLR_EL1.M), it should
be better to write a value before running L2 I suppose...

Thanks,
Wei-Lin Chang

> 
> Itaru.
> 
> > 
> > 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	[flat|nested] 14+ messages in thread

* Re: [PATCH v2 3/4] KVM: arm64: sefltests: Add basic NV selftest
  2026-04-13  9:18     ` Wei-Lin Chang
@ 2026-04-13 21:31       ` Itaru Kitayama
  2026-04-14 10:16         ` Wei-Lin Chang
  0 siblings, 1 reply; 14+ messages in thread
From: Itaru Kitayama @ 2026-04-13 21:31 UTC (permalink / raw)
  To: Wei-Lin Chang
  Cc: linux-arm-kernel, kvmarm, kvm, linux-kselftest, linux-kernel,
	Marc Zyngier, Oliver Upton, Joey Gouly, Suzuki K Poulose,
	Zenghui Yu, Catalin Marinas, Will Deacon, Paolo Bonzini,
	Shuah Khan

On Mon, Apr 13, 2026 at 10:18:42AM +0100, Wei-Lin Chang wrote:
> Hi Itaru,
> 
> On Mon, Apr 13, 2026 at 08:19:25AM +0900, Itaru Kitayama wrote:
> > On Sun, Apr 12, 2026 at 03:22:15PM +0100, Wei-Lin Chang wrote:
> > > 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.
> > 
> > How do you disable both the nested guest (L2)'s MMU and stage 2
> > translations?
> 
> Guest stage-2 is disabled by not setting HCR_EL2.VM in prepare_hyp(),
> and stage-1 is disabled by not writing to SCTLR_EL12 in init_vcpu(),
> effectively using the default value set by L0. However since SCTLR_EL1
> has many architecturally UNKNOWN bits (including SCTLR_EL1.M), it should
> be better to write a value before running L2 I suppose...

Thanks. What do you think of using copy_el2_to_el1() macro in at.c, so we
can prepare in guest_code() to manipulate the SCTLR_EL12 System register 
with the sensible programmed values?

Itaru.

> 
> Thanks,
> Wei-Lin Chang
> 
> > 
> > Itaru.
> > 
> > > 
> > > 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	[flat|nested] 14+ messages in thread

* Re: [PATCH v2 3/4] KVM: arm64: sefltests: Add basic NV selftest
  2026-04-13 21:31       ` Itaru Kitayama
@ 2026-04-14 10:16         ` Wei-Lin Chang
  2026-04-14 22:05           ` Itaru Kitayama
  0 siblings, 1 reply; 14+ messages in thread
From: Wei-Lin Chang @ 2026-04-14 10:16 UTC (permalink / raw)
  To: Itaru Kitayama
  Cc: linux-arm-kernel, kvmarm, kvm, linux-kselftest, linux-kernel,
	Marc Zyngier, Oliver Upton, Joey Gouly, Suzuki K Poulose,
	Zenghui Yu, Catalin Marinas, Will Deacon, Paolo Bonzini,
	Shuah Khan

On Tue, Apr 14, 2026 at 06:31:22AM +0900, Itaru Kitayama wrote:
> On Mon, Apr 13, 2026 at 10:18:42AM +0100, Wei-Lin Chang wrote:
> > Hi Itaru,
> > 
> > On Mon, Apr 13, 2026 at 08:19:25AM +0900, Itaru Kitayama wrote:
> > > On Sun, Apr 12, 2026 at 03:22:15PM +0100, Wei-Lin Chang wrote:
> > > > 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.
> > > 
> > > How do you disable both the nested guest (L2)'s MMU and stage 2
> > > translations?
> > 
> > Guest stage-2 is disabled by not setting HCR_EL2.VM in prepare_hyp(),
> > and stage-1 is disabled by not writing to SCTLR_EL12 in init_vcpu(),
> > effectively using the default value set by L0. However since SCTLR_EL1
> > has many architecturally UNKNOWN bits (including SCTLR_EL1.M), it should
> > be better to write a value before running L2 I suppose...
> 
> Thanks. What do you think of using copy_el2_to_el1() macro in at.c, so we
> can prepare in guest_code() to manipulate the SCTLR_EL12 System register 
> with the sensible programmed values?

Yes, using copy_el2_to_el1() can give us an L2 stage-1 that is identical
to the L1's stage-1. But what I was considering was if guest stage-2 is
enabled (which we plan to implement), then those stage-1 page tables
will have to be mapped for L2, and its base address translated to L2IPA.
It's doable but seems like extra complexity when stage-1 is not so
interesting for KVM (except for AT?), it lets the guest do whatever it
likes and let the hardware do the translation.

Let me know if you have reasons to want stage-1 for L2, there could be
something I should consider but did not.

Thanks,
Wei-Lin Chang

> 
> Itaru.
> 
> > 
> > Thanks,
> > Wei-Lin Chang
> > 
> > > 
> > > Itaru.
> > > 
> > > > 
> > > > 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	[flat|nested] 14+ messages in thread

* Re: [PATCH v2 3/4] KVM: arm64: sefltests: Add basic NV selftest
  2026-04-14 10:16         ` Wei-Lin Chang
@ 2026-04-14 22:05           ` Itaru Kitayama
  2026-04-16 21:58             ` Wei-Lin Chang
  0 siblings, 1 reply; 14+ messages in thread
From: Itaru Kitayama @ 2026-04-14 22:05 UTC (permalink / raw)
  To: Wei-Lin Chang
  Cc: linux-arm-kernel, kvmarm, kvm, linux-kselftest, linux-kernel,
	Marc Zyngier, Oliver Upton, Joey Gouly, Suzuki K Poulose,
	Zenghui Yu, Catalin Marinas, Will Deacon, Paolo Bonzini,
	Shuah Khan

On Tue, Apr 14, 2026 at 11:16:47AM +0100, Wei-Lin Chang wrote:
> On Tue, Apr 14, 2026 at 06:31:22AM +0900, Itaru Kitayama wrote:
> > On Mon, Apr 13, 2026 at 10:18:42AM +0100, Wei-Lin Chang wrote:
> > > Hi Itaru,
> > > 
> > > On Mon, Apr 13, 2026 at 08:19:25AM +0900, Itaru Kitayama wrote:
> > > > On Sun, Apr 12, 2026 at 03:22:15PM +0100, Wei-Lin Chang wrote:
> > > > > 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.
> > > > 
> > > > How do you disable both the nested guest (L2)'s MMU and stage 2
> > > > translations?
> > > 
> > > Guest stage-2 is disabled by not setting HCR_EL2.VM in prepare_hyp(),
> > > and stage-1 is disabled by not writing to SCTLR_EL12 in init_vcpu(),
> > > effectively using the default value set by L0. However since SCTLR_EL1
> > > has many architecturally UNKNOWN bits (including SCTLR_EL1.M), it should
> > > be better to write a value before running L2 I suppose...
> > 
> > Thanks. What do you think of using copy_el2_to_el1() macro in at.c, so we
> > can prepare in guest_code() to manipulate the SCTLR_EL12 System register 
> > with the sensible programmed values?
> 
> Yes, using copy_el2_to_el1() can give us an L2 stage-1 that is identical
> to the L1's stage-1. But what I was considering was if guest stage-2 is
> enabled (which we plan to implement), then those stage-1 page tables
> will have to be mapped for L2, and its base address translated to L2IPA.
> It's doable but seems like extra complexity when stage-1 is not so
> interesting for KVM (except for AT?), it lets the guest do whatever it
> likes and let the hardware do the translation.
> 
> Let me know if you have reasons to want stage-1 for L2, there could be
> something I should consider but did not.

By keeping nested guest's MMU enabled, we can exercise the shadow stage
2 on the host. But I am fine with you starting nested guest's IPA and I
hope Marc and Oliver approve this seris and merge upstream.

Thanks,
Itaru.

> 
> Thanks,
> Wei-Lin Chang
> 
> > 
> > Itaru.
> > 
> > > 
> > > Thanks,
> > > Wei-Lin Chang
> > > 
> > > > 
> > > > Itaru.
> > > > 
> > > > > 
> > > > > 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	[flat|nested] 14+ messages in thread

* Re: [PATCH v2 2/4] KVM: arm64: sefltests: Add helpers for guest hypervisors
  2026-04-12 14:22 ` [PATCH v2 2/4] KVM: arm64: sefltests: Add helpers for guest hypervisors Wei-Lin Chang
@ 2026-04-14 22:14   ` Itaru Kitayama
  2026-04-16 22:15     ` Wei-Lin Chang
  0 siblings, 1 reply; 14+ messages in thread
From: Itaru Kitayama @ 2026-04-14 22:14 UTC (permalink / raw)
  To: Wei-Lin Chang
  Cc: linux-arm-kernel, kvmarm, kvm, linux-kselftest, linux-kernel,
	Marc Zyngier, Oliver Upton, Joey Gouly, Suzuki K Poulose,
	Zenghui Yu, Catalin Marinas, Will Deacon, Paolo Bonzini,
	Shuah Khan

On Sun, Apr 12, 2026 at 03:22:14PM +0100, Wei-Lin Chang wrote:
> 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;
>  };

I am not sure of these structs you introduced only for nested guest feature
testing, as the KVM arm64 code they are quite complex and involved, 
extracring part of those and add members as hello_nested or simliar
tests evolve, then add test cases to me seems fragile. 
But if you have strong reason to add these would you mind explaining a bit?

Thanks,
Itaru.

>  
> +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	[flat|nested] 14+ messages in thread

* Re: [PATCH v2 3/4] KVM: arm64: sefltests: Add basic NV selftest
  2026-04-14 22:05           ` Itaru Kitayama
@ 2026-04-16 21:58             ` Wei-Lin Chang
  0 siblings, 0 replies; 14+ messages in thread
From: Wei-Lin Chang @ 2026-04-16 21:58 UTC (permalink / raw)
  To: Itaru Kitayama
  Cc: linux-arm-kernel, kvmarm, kvm, linux-kselftest, linux-kernel,
	Marc Zyngier, Oliver Upton, Joey Gouly, Suzuki K Poulose,
	Zenghui Yu, Catalin Marinas, Will Deacon, Paolo Bonzini,
	Shuah Khan

On Wed, Apr 15, 2026 at 07:05:17AM +0900, Itaru Kitayama wrote:
> On Tue, Apr 14, 2026 at 11:16:47AM +0100, Wei-Lin Chang wrote:
> > On Tue, Apr 14, 2026 at 06:31:22AM +0900, Itaru Kitayama wrote:
> > > On Mon, Apr 13, 2026 at 10:18:42AM +0100, Wei-Lin Chang wrote:
> > > > Hi Itaru,
> > > > 
> > > > On Mon, Apr 13, 2026 at 08:19:25AM +0900, Itaru Kitayama wrote:
> > > > > On Sun, Apr 12, 2026 at 03:22:15PM +0100, Wei-Lin Chang wrote:
> > > > > > 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.
> > > > > 
> > > > > How do you disable both the nested guest (L2)'s MMU and stage 2
> > > > > translations?
> > > > 
> > > > Guest stage-2 is disabled by not setting HCR_EL2.VM in prepare_hyp(),
> > > > and stage-1 is disabled by not writing to SCTLR_EL12 in init_vcpu(),
> > > > effectively using the default value set by L0. However since SCTLR_EL1
> > > > has many architecturally UNKNOWN bits (including SCTLR_EL1.M), it should
> > > > be better to write a value before running L2 I suppose...
> > > 
> > > Thanks. What do you think of using copy_el2_to_el1() macro in at.c, so we
> > > can prepare in guest_code() to manipulate the SCTLR_EL12 System register 
> > > with the sensible programmed values?
> > 
> > Yes, using copy_el2_to_el1() can give us an L2 stage-1 that is identical
> > to the L1's stage-1. But what I was considering was if guest stage-2 is
> > enabled (which we plan to implement), then those stage-1 page tables
> > will have to be mapped for L2, and its base address translated to L2IPA.
> > It's doable but seems like extra complexity when stage-1 is not so
> > interesting for KVM (except for AT?), it lets the guest do whatever it
> > likes and let the hardware do the translation.
> > 
> > Let me know if you have reasons to want stage-1 for L2, there could be
> > something I should consider but did not.
> 
> By keeping nested guest's MMU enabled, we can exercise the shadow stage
> 2 on the host. But I am fine with you starting nested guest's IPA and I
> hope Marc and Oliver approve this seris and merge upstream.

I think you have guest stage-1 and guest stage-2 confused. Whether the
nested guest's stage-1 MMU is enabled or not does not affect what KVM is
doing with the shadow page tables. Stage-1 MMU translates L2VA -> L2IPA.
Shadow page tables store the combined translation of L2IPA -> L1IPA
(stage-2 PTs L1 built for L2) and L1IPA -> host PA (stage-2 PTs host
built for L1).

Additionally, stage-2 not enabled for L2 does not mean shadow stage-2 is
not exercised, there is still a distince shadow stage-2 for it doing the
work, albeit simple (the stored mapping is the same as the canonical
stage-2).

All in all, if we want to make the shadow page tables more interesting,
what we should do is build a stage-2 for L2, and enable it in L1, not
just turn on L2's stage-1 MMU.

Thanks,
Wei-Lin Chang

> 
> Thanks,
> Itaru.
> 
> > 
> > Thanks,
> > Wei-Lin Chang
> > 
> > > 
> > > Itaru.
> > > 
> > > > 
> > > > Thanks,
> > > > Wei-Lin Chang
> > > > 
> > > > > 
> > > > > Itaru.
> > > > > 
> > > > > > 
> > > > > > 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	[flat|nested] 14+ messages in thread

* Re: [PATCH v2 2/4] KVM: arm64: sefltests: Add helpers for guest hypervisors
  2026-04-14 22:14   ` Itaru Kitayama
@ 2026-04-16 22:15     ` Wei-Lin Chang
  2026-04-16 23:39       ` Itaru Kitayama
  0 siblings, 1 reply; 14+ messages in thread
From: Wei-Lin Chang @ 2026-04-16 22:15 UTC (permalink / raw)
  To: Itaru Kitayama
  Cc: linux-arm-kernel, kvmarm, kvm, linux-kselftest, linux-kernel,
	Marc Zyngier, Oliver Upton, Joey Gouly, Suzuki K Poulose,
	Zenghui Yu, Catalin Marinas, Will Deacon, Paolo Bonzini,
	Shuah Khan

On Wed, Apr 15, 2026 at 07:14:46AM +0900, Itaru Kitayama wrote:
> On Sun, Apr 12, 2026 at 03:22:14PM +0100, Wei-Lin Chang wrote:
> > 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;
> >  };
> 
> I am not sure of these structs you introduced only for nested guest feature
> testing, as the KVM arm64 code they are quite complex and involved, 
> extracring part of those and add members as hello_nested or simliar
> tests evolve, then add test cases to me seems fragile. 
> But if you have strong reason to add these would you mind explaining a bit?

Sorry, I don't quite get all of your points. I understand your argument
being evolving these structs as time goes is fragile. For this didn't
KVM itself evolve like this?

As for having these structs, how can we make L1 a small hypervisor
without them?

Thanks,
Wei-Lin Chang

> 
> Thanks,
> Itaru.
> 
> >  
> > +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	[flat|nested] 14+ messages in thread

* Re: [PATCH v2 2/4] KVM: arm64: sefltests: Add helpers for guest hypervisors
  2026-04-16 22:15     ` Wei-Lin Chang
@ 2026-04-16 23:39       ` Itaru Kitayama
  0 siblings, 0 replies; 14+ messages in thread
From: Itaru Kitayama @ 2026-04-16 23:39 UTC (permalink / raw)
  To: Wei-Lin Chang
  Cc: linux-arm-kernel, kvmarm, kvm, linux-kselftest, linux-kernel,
	Marc Zyngier, Oliver Upton, Joey Gouly, Suzuki K Poulose,
	Zenghui Yu, Catalin Marinas, Will Deacon, Paolo Bonzini,
	Shuah Khan

On Thu, Apr 16, 2026 at 11:15:57PM +0100, Wei-Lin Chang wrote:
> On Wed, Apr 15, 2026 at 07:14:46AM +0900, Itaru Kitayama wrote:
> > On Sun, Apr 12, 2026 at 03:22:14PM +0100, Wei-Lin Chang wrote:
> > > 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;
> > >  };
> > 
> > I am not sure of these structs you introduced only for nested guest feature
> > testing, as the KVM arm64 code they are quite complex and involved, 
> > extracring part of those and add members as hello_nested or simliar
> > tests evolve, then add test cases to me seems fragile. 
> > But if you have strong reason to add these would you mind explaining a bit?
> 
> Sorry, I don't quite get all of your points. I understand your argument
> being evolving these structs as time goes is fragile. For this didn't
> KVM itself evolve like this?
> 
> As for having these structs, how can we make L1 a small hypervisor
> without them?

You're correct and I was wrong. We will just have to change the structs for 
nested virtualization selftests as we add more test cases.

Itaru.

> 
> Thanks,
> Wei-Lin Chang
> 
> > 
> > Thanks,
> > Itaru.
> > 
> > >  
> > > +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	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2026-04-16 23:40 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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-14 22:14   ` Itaru Kitayama
2026-04-16 22:15     ` Wei-Lin Chang
2026-04-16 23:39       ` Itaru Kitayama
2026-04-12 14:22 ` [PATCH v2 3/4] KVM: arm64: sefltests: Add basic NV selftest Wei-Lin Chang
2026-04-12 23:19   ` Itaru Kitayama
2026-04-13  9:18     ` Wei-Lin Chang
2026-04-13 21:31       ` Itaru Kitayama
2026-04-14 10:16         ` Wei-Lin Chang
2026-04-14 22:05           ` Itaru Kitayama
2026-04-16 21:58             ` Wei-Lin Chang
2026-04-12 14:22 ` [PATCH v2 4/4] KVM: arm64: selftests: Enhance hello_nested test Wei-Lin Chang

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox