The Linux Kernel Mailing List
 help / color / mirror / Atom feed
* [PATCH v3 0/9] KVM: arm64: selftests: Basic nested guest support
@ 2026-05-16 18:29 Wei-Lin Chang
  2026-05-16 18:29 ` [PATCH v3 1/9] KVM: arm64: selftests: Add GPR save/restore functions for NV Wei-Lin Chang
                   ` (9 more replies)
  0 siblings, 10 replies; 11+ messages in thread
From: Wei-Lin Chang @ 2026-05-16 18:29 UTC (permalink / raw)
  To: linux-kernel, kvm, linux-kselftest, linux-arm-kernel, kvmarm
  Cc: Paolo Bonzini, Shuah Khan, Marc Zyngier, Oliver Upton, Joey Gouly,
	Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon,
	Itaru Kitayama, Wei-Lin Chang

Hi,

This is v3 of adding basic support for running nested guests (L2) in
kselftest. This time a framework for enabling stage-2 in the guest is
added, including the s2_mmu struct, s2 translation control setup, and
a stage-2 page table generator. Similar to L2 vCPU management, all
stage-2 work is done in the guest instead of userspace.

An additional shadow_stage2 test is added which leverages the framework
to run L2 with stage-2 enabled.

* Changes from v2 [1]:

  - Update vm_paddr_t to gpa_t, vm_vaddr_t to gva_t.

  - Added framework for enabling stage-2 in the guest.

  - Added shadow_stage2 test.

Thanks!

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

Wei-Lin Chang (9):
  KVM: arm64: selftests: Add GPR save/restore functions for NV
  KVM: arm64: selftests: Add helpers for guest hypervisors
  KVM: arm64: selftests: Add hello_nested basic NV selftest
  KVM: arm64: selftests: Enhance hello_nested test
  KVM: arm64: selftests: Add shadow_stage2 test
  KVM: arm64: selftests: shadow_stage2: Allocate L2 stack from dedicated
    pool
  KVM: arm64: selftests: shadow_stage2: Check supported stage-2 granule
    size
  KVM: arm64: selftests: Add infrastructure for using stage-2 in guest
  KVM: arm64: selftests: shadow_stage2: Turn on stage-2 translation for
    the nested guest

 tools/testing/selftests/kvm/Makefile.kvm      |   5 +
 .../selftests/kvm/arm64/hello_nested.c        | 134 +++++++++
 .../selftests/kvm/arm64/shadow_stage2.c       | 165 +++++++++++
 .../selftests/kvm/include/arm64/nested.h      |  85 ++++++
 tools/testing/selftests/kvm/lib/arm64/entry.S | 137 +++++++++
 .../selftests/kvm/lib/arm64/hyp-entry.S       |  77 +++++
 .../testing/selftests/kvm/lib/arm64/nested.c  | 264 ++++++++++++++++++
 7 files changed, 867 insertions(+)
 create mode 100644 tools/testing/selftests/kvm/arm64/hello_nested.c
 create mode 100644 tools/testing/selftests/kvm/arm64/shadow_stage2.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] 11+ messages in thread

* [PATCH v3 1/9] KVM: arm64: selftests: Add GPR save/restore functions for NV
  2026-05-16 18:29 [PATCH v3 0/9] KVM: arm64: selftests: Basic nested guest support Wei-Lin Chang
@ 2026-05-16 18:29 ` Wei-Lin Chang
  2026-05-16 18:29 ` [PATCH v3 2/9] KVM: arm64: selftests: Add helpers for guest hypervisors Wei-Lin Chang
                   ` (8 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Wei-Lin Chang @ 2026-05-16 18:29 UTC (permalink / raw)
  To: linux-kernel, kvm, linux-kselftest, linux-arm-kernel, kvmarm
  Cc: Paolo Bonzini, Shuah Khan, Marc Zyngier, Oliver Upton, Joey Gouly,
	Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon,
	Itaru Kitayama, 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] 11+ messages in thread

* [PATCH v3 2/9] KVM: arm64: selftests: Add helpers for guest hypervisors
  2026-05-16 18:29 [PATCH v3 0/9] KVM: arm64: selftests: Basic nested guest support Wei-Lin Chang
  2026-05-16 18:29 ` [PATCH v3 1/9] KVM: arm64: selftests: Add GPR save/restore functions for NV Wei-Lin Chang
@ 2026-05-16 18:29 ` Wei-Lin Chang
  2026-05-16 18:29 ` [PATCH v3 3/9] KVM: arm64: selftests: Add hello_nested basic NV selftest Wei-Lin Chang
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Wei-Lin Chang @ 2026-05-16 18:29 UTC (permalink / raw)
  To: linux-kernel, kvm, linux-kselftest, linux-arm-kernel, kvmarm
  Cc: Paolo Bonzini, Shuah Khan, Marc Zyngier, Oliver Upton, Joey Gouly,
	Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon,
	Itaru Kitayama, 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..30e626e427da 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, gpa_t l2_pc, gpa_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..f6c24beb01d0 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, gpa_t l2_pc, gpa_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] 11+ messages in thread

* [PATCH v3 3/9] KVM: arm64: selftests: Add hello_nested basic NV selftest
  2026-05-16 18:29 [PATCH v3 0/9] KVM: arm64: selftests: Basic nested guest support Wei-Lin Chang
  2026-05-16 18:29 ` [PATCH v3 1/9] KVM: arm64: selftests: Add GPR save/restore functions for NV Wei-Lin Chang
  2026-05-16 18:29 ` [PATCH v3 2/9] KVM: arm64: selftests: Add helpers for guest hypervisors Wei-Lin Chang
@ 2026-05-16 18:29 ` Wei-Lin Chang
  2026-05-16 18:29 ` [PATCH v3 4/9] KVM: arm64: selftests: Enhance hello_nested test Wei-Lin Chang
                   ` (6 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Wei-Lin Chang @ 2026-05-16 18:29 UTC (permalink / raw)
  To: linux-kernel, kvm, linux-kselftest, linux-arm-kernel, kvmarm
  Cc: Paolo Bonzini, Shuah Khan, Marc Zyngier, Oliver Upton, Joey Gouly,
	Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon,
	Itaru Kitayama, 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        | 104 ++++++++++++++++++
 2 files changed, 105 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..1cab56e4597b
--- /dev/null
+++ b/tools/testing/selftests/kvm/arm64/hello_nested.c
@@ -0,0 +1,104 @@
+// 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 gpa_t ucall_translate_to_gpa(void *gva)
+{
+	gpa_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;
+	gpa_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_ASSERT_EQ(ESR_ELx_EC(read_sysreg(esr_el2)), ESR_ELx_EC_HVC64);
+	GUEST_DONE();
+}
+
+int main(void)
+{
+	struct kvm_vcpu_init init;
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+	struct ucall uc;
+	gpa_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, (gva_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] 11+ messages in thread

* [PATCH v3 4/9] KVM: arm64: selftests: Enhance hello_nested test
  2026-05-16 18:29 [PATCH v3 0/9] KVM: arm64: selftests: Basic nested guest support Wei-Lin Chang
                   ` (2 preceding siblings ...)
  2026-05-16 18:29 ` [PATCH v3 3/9] KVM: arm64: selftests: Add hello_nested basic NV selftest Wei-Lin Chang
@ 2026-05-16 18:29 ` Wei-Lin Chang
  2026-05-16 18:29 ` [PATCH v3 5/9] KVM: arm64: selftests: Add shadow_stage2 test Wei-Lin Chang
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Wei-Lin Chang @ 2026-05-16 18:29 UTC (permalink / raw)
  To: linux-kernel, kvm, linux-kselftest, linux-arm-kernel, kvmarm
  Cc: Paolo Bonzini, Shuah Khan, Marc Zyngier, Oliver Upton, Joey Gouly,
	Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon,
	Itaru Kitayama, 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        | 32 ++++++++++++++++++-
 .../selftests/kvm/include/arm64/nested.h      |  2 +-
 2 files changed, 32 insertions(+), 2 deletions(-)

diff --git a/tools/testing/selftests/kvm/arm64/hello_nested.c b/tools/testing/selftests/kvm/arm64/hello_nested.c
index 1cab56e4597b..9ed5285f5f2d 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 gpa_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)
 	gpa_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");
@@ -55,6 +67,24 @@ static void guest_code(void)
 	ret = run_l2(&vcpu, &hyp_data);
 	GUEST_ASSERT_EQ(ret, ARM_EXCEPTION_TRAP);
 	GUEST_ASSERT_EQ(ESR_ELx_EC(read_sysreg(esr_el2)), ESR_ELx_EC_HVC64);
+
+	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);
+	GUEST_ASSERT_EQ(ESR_ELx_EC(read_sysreg(esr_el2)), ESR_ELx_EC_HVC64);
+
+	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 30e626e427da..c10ef4a85be7 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, gpa_t l2_pc, gpa_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] 11+ messages in thread

* [PATCH v3 5/9] KVM: arm64: selftests: Add shadow_stage2 test
  2026-05-16 18:29 [PATCH v3 0/9] KVM: arm64: selftests: Basic nested guest support Wei-Lin Chang
                   ` (3 preceding siblings ...)
  2026-05-16 18:29 ` [PATCH v3 4/9] KVM: arm64: selftests: Enhance hello_nested test Wei-Lin Chang
@ 2026-05-16 18:29 ` Wei-Lin Chang
  2026-05-16 18:30 ` [PATCH v3 6/9] KVM: arm64: selftests: shadow_stage2: Allocate L2 stack from dedicated pool Wei-Lin Chang
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Wei-Lin Chang @ 2026-05-16 18:29 UTC (permalink / raw)
  To: linux-kernel, kvm, linux-kselftest, linux-arm-kernel, kvmarm
  Cc: Paolo Bonzini, Shuah Khan, Marc Zyngier, Oliver Upton, Joey Gouly,
	Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon,
	Itaru Kitayama, Wei-Lin Chang

The shadow_stage2 test is aimed to exercise the shadow page table
management code in KVM. In this first patch a basic test similar to
hello_nested is created. Right now it doesn't turn on stage-2 for the
nested guest (L2) yet, therefore the shadow page table code in KVM will
only be triggered minimally now.

Signed-off-by: Wei-Lin Chang <weilin.chang@arm.com>
---
 tools/testing/selftests/kvm/Makefile.kvm      |   1 +
 .../selftests/kvm/arm64/shadow_stage2.c       | 128 ++++++++++++++++++
 2 files changed, 129 insertions(+)
 create mode 100644 tools/testing/selftests/kvm/arm64/shadow_stage2.c

diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index e8c108e0c487..c0fac2ba1339 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -176,6 +176,7 @@ TEST_GEN_PROGS_arm64 += arm64/page_fault_test
 TEST_GEN_PROGS_arm64 += arm64/psci_test
 TEST_GEN_PROGS_arm64 += arm64/sea_to_user
 TEST_GEN_PROGS_arm64 += arm64/set_id_regs
+TEST_GEN_PROGS_arm64 += arm64/shadow_stage2
 TEST_GEN_PROGS_arm64 += arm64/smccc_filter
 TEST_GEN_PROGS_arm64 += arm64/vcpu_width_config
 TEST_GEN_PROGS_arm64 += arm64/vgic_init
diff --git a/tools/testing/selftests/kvm/arm64/shadow_stage2.c b/tools/testing/selftests/kvm/arm64/shadow_stage2.c
new file mode 100644
index 000000000000..cf76a2b0582d
--- /dev/null
+++ b/tools/testing/selftests/kvm/arm64/shadow_stage2.c
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * shadow_stage2 - Test correctness of shadow stage 2
+ */
+
+#include "nested.h"
+#include "processor.h"
+#include "test_util.h"
+#include "ucall.h"
+
+#define XLATE2GPA	(0xABCD)
+#define L2STACKSZ	(0x100)
+
+#define L2SUCCESS	(0x0)
+#define L2FAILED	(0x1)
+#define L2SYNC		(0x2)
+
+/*
+ * TPIDR_EL2 is used to store vcpu id, so save and restore it.
+ */
+static gpa_t ucall_translate_to_gpa(void *gva)
+{
+	gpa_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(L2SYNC, 10, 0);
+	do_hvc(L2SYNC, 20, 0);
+	do_hvc(L2SYNC, 30, 0);
+
+	do_hvc(L2SUCCESS, 0, 0);
+}
+
+static void guest_code(void)
+{
+	struct vcpu vcpu;
+	struct hyp_data hyp_data;
+	int ret, i = 0;
+	gpa_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();
+
+	while (true) {
+		GUEST_PRINTF("L2 enter\n");
+		ret = run_l2(&vcpu, &hyp_data);
+		GUEST_PRINTF("L2 exit\n");
+		GUEST_ASSERT_EQ(ret, ARM_EXCEPTION_TRAP);
+		GUEST_ASSERT_EQ(ESR_ELx_EC(read_sysreg(esr_el2)), ESR_ELx_EC_HVC64);
+
+		if (vcpu.context.regs.regs[0] == L2SYNC)
+			GUEST_SYNC3(L2SYNC, i++, vcpu.context.regs.regs[1]);
+		else
+			break;
+	}
+
+	if (vcpu.context.regs.regs[0] != L2SUCCESS)
+		GUEST_FAIL("L2 failed\n");
+
+	GUEST_PRINTF("L2 success!\n");
+	GUEST_DONE();
+}
+
+int main(void)
+{
+	struct kvm_vcpu_init init;
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+	struct ucall uc;
+	gpa_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, (gva_t)uc.args[1]);
+				vcpu_set_reg(vcpu, KVM_ARM64_SYS_REG(SYS_TPIDR_EL2), gpa);
+			}
+			if (uc.args[0] == L2SYNC)
+				pr_info("L2SYNC, L1 info: %ld, L2 info: %ld\n", uc.args[1], uc.args[2]);
+			break;
+		case UCALL_PRINTF:
+			pr_info("[L1] %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] 11+ messages in thread

* [PATCH v3 6/9] KVM: arm64: selftests: shadow_stage2: Allocate L2 stack from dedicated pool
  2026-05-16 18:29 [PATCH v3 0/9] KVM: arm64: selftests: Basic nested guest support Wei-Lin Chang
                   ` (4 preceding siblings ...)
  2026-05-16 18:29 ` [PATCH v3 5/9] KVM: arm64: selftests: Add shadow_stage2 test Wei-Lin Chang
@ 2026-05-16 18:30 ` Wei-Lin Chang
  2026-05-16 18:30 ` [PATCH v3 7/9] KVM: arm64: selftests: shadow_stage2: Check supported stage-2 granule size Wei-Lin Chang
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Wei-Lin Chang @ 2026-05-16 18:30 UTC (permalink / raw)
  To: linux-kernel, kvm, linux-kselftest, linux-arm-kernel, kvmarm
  Cc: Paolo Bonzini, Shuah Khan, Marc Zyngier, Oliver Upton, Joey Gouly,
	Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon,
	Itaru Kitayama, Wei-Lin Chang

Instead of using L1's stack, create a simple page allocator and use that
to allocate L2 stack. The allocator will also be used later on when the
stage-2 page table generator builds stage-2 mappings for the nested
guest (L2).

Signed-off-by: Wei-Lin Chang <weilin.chang@arm.com>
---
 .../selftests/kvm/arm64/shadow_stage2.c       | 20 ++++++++---
 .../selftests/kvm/include/arm64/nested.h      |  9 +++++
 .../testing/selftests/kvm/lib/arm64/nested.c  | 33 +++++++++++++++++++
 3 files changed, 58 insertions(+), 4 deletions(-)

diff --git a/tools/testing/selftests/kvm/arm64/shadow_stage2.c b/tools/testing/selftests/kvm/arm64/shadow_stage2.c
index cf76a2b0582d..1ad510a38654 100644
--- a/tools/testing/selftests/kvm/arm64/shadow_stage2.c
+++ b/tools/testing/selftests/kvm/arm64/shadow_stage2.c
@@ -9,12 +9,16 @@
 #include "ucall.h"
 
 #define XLATE2GPA	(0xABCD)
-#define L2STACKSZ	(0x100)
 
 #define L2SUCCESS	(0x0)
 #define L2FAILED	(0x1)
 #define L2SYNC		(0x2)
 
+/* Used for L2 stack and guest S2 page tables. */
+#define L2_PAGE_POOL_ADDR	(0x80000000)
+#define L2_PAGE_POOL_NPAGES	(512)
+#define L2_PAGE_POOL_MEMSLOT	(0x2)
+
 /*
  * TPIDR_EL2 is used to store vcpu id, so save and restore it.
  */
@@ -48,14 +52,18 @@ static void guest_code(void)
 	struct hyp_data hyp_data;
 	int ret, i = 0;
 	gpa_t l2_pc, l2_stack_top;
-	/* force 16-byte alignment for the stack pointer */
-	u8 l2_stack[L2STACKSZ] __attribute__((aligned(16)));
+	struct page_pool pp;
 
 	GUEST_ASSERT_EQ(get_current_el(), 2);
 	GUEST_PRINTF("vEL2 entry\n");
 
+	pp.start = L2_PAGE_POOL_ADDR;
+	pp.npages = L2_PAGE_POOL_NPAGES;
+	pp.current = L2_PAGE_POOL_ADDR;
+	pp.page_size = get_page_size();
+
+	l2_stack_top = alloc_page(&pp) + pp.page_size;
 	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();
@@ -96,6 +104,10 @@ int main(void)
 	vcpu = aarch64_vcpu_add(vm, 0, &init, guest_code);
 	kvm_arch_vm_finalize_vcpus(vm);
 
+	vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS,
+				    L2_PAGE_POOL_ADDR, L2_PAGE_POOL_MEMSLOT,
+				    L2_PAGE_POOL_NPAGES, 0);
+
 	while (true) {
 		vcpu_run(vcpu);
 
diff --git a/tools/testing/selftests/kvm/include/arm64/nested.h b/tools/testing/selftests/kvm/include/arm64/nested.h
index c10ef4a85be7..8e7d7738b381 100644
--- a/tools/testing/selftests/kvm/include/arm64/nested.h
+++ b/tools/testing/selftests/kvm/include/arm64/nested.h
@@ -46,6 +46,15 @@ struct hyp_data {
 	struct cpu_context hyp_context;
 };
 
+struct page_pool {
+	gpa_t start;
+	gpa_t current;
+	size_t npages;
+	size_t page_size;
+};
+
+size_t get_page_size(void);
+gpa_t alloc_page(struct page_pool *pp);
 void prepare_hyp(void);
 void init_vcpu(struct vcpu *vcpu, gpa_t l2_pc, gpa_t l2_stack_top);
 int run_l2(struct vcpu *vcpu, struct hyp_data *hyp_data);
diff --git a/tools/testing/selftests/kvm/lib/arm64/nested.c b/tools/testing/selftests/kvm/lib/arm64/nested.c
index f6c24beb01d0..7f47e340f00d 100644
--- a/tools/testing/selftests/kvm/lib/arm64/nested.c
+++ b/tools/testing/selftests/kvm/lib/arm64/nested.c
@@ -7,6 +7,39 @@
 #include "processor.h"
 #include "test_util.h"
 #include <asm/sysreg.h>
+#include <linux/sizes.h>
+
+size_t get_page_size(void)
+{
+	u64 tcr_el1 = read_sysreg(tcr_el1);
+	u64 tg0 = SYS_FIELD_GET(TCR_EL1, TG0, tcr_el1);
+
+	switch (tg0) {
+	case TCR_EL1_TG0_4K:
+		return SZ_4K;
+	case TCR_EL1_TG0_16K:
+		return SZ_16K;
+	case TCR_EL1_TG0_64K:
+		return SZ_64K;
+	default:
+		GUEST_FAIL("Unexpected tg0 value!\n");
+		return 0;
+	}
+}
+
+gpa_t alloc_page(struct page_pool *pp)
+{
+	gpa_t page = pp->current;
+
+	pp->current += pp->page_size;
+
+	if ((pp->current - pp->start) / pp->page_size <= pp->npages) {
+		return page;
+	} else {
+		GUEST_FAIL("%s failed!\n", __func__);
+		return 0;
+	}
+}
 
 void prepare_hyp(void)
 {
-- 
2.43.0


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

* [PATCH v3 7/9] KVM: arm64: selftests: shadow_stage2: Check supported stage-2 granule size
  2026-05-16 18:29 [PATCH v3 0/9] KVM: arm64: selftests: Basic nested guest support Wei-Lin Chang
                   ` (5 preceding siblings ...)
  2026-05-16 18:30 ` [PATCH v3 6/9] KVM: arm64: selftests: shadow_stage2: Allocate L2 stack from dedicated pool Wei-Lin Chang
@ 2026-05-16 18:30 ` Wei-Lin Chang
  2026-05-16 18:30 ` [PATCH v3 8/9] KVM: arm64: selftests: Add infrastructure for using stage-2 in guest Wei-Lin Chang
                   ` (2 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Wei-Lin Chang @ 2026-05-16 18:30 UTC (permalink / raw)
  To: linux-kernel, kvm, linux-kselftest, linux-arm-kernel, kvmarm
  Cc: Paolo Bonzini, Shuah Khan, Marc Zyngier, Oliver Upton, Joey Gouly,
	Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon,
	Itaru Kitayama, Wei-Lin Chang

When selecting a granule size for stage-2, the supported stage-2 granule
size must be checked. Add the check. For simplicity, we check whether
the guest stage-1 granule size is supported for stage-2, and skip the
test otherwise.

Signed-off-by: Wei-Lin Chang <weilin.chang@arm.com>
---
 .../selftests/kvm/arm64/shadow_stage2.c       |  8 +++++
 .../selftests/kvm/include/arm64/nested.h      |  1 +
 .../testing/selftests/kvm/lib/arm64/nested.c  | 30 +++++++++++++++++++
 3 files changed, 39 insertions(+)

diff --git a/tools/testing/selftests/kvm/arm64/shadow_stage2.c b/tools/testing/selftests/kvm/arm64/shadow_stage2.c
index 1ad510a38654..c5332b8b5683 100644
--- a/tools/testing/selftests/kvm/arm64/shadow_stage2.c
+++ b/tools/testing/selftests/kvm/arm64/shadow_stage2.c
@@ -14,6 +14,8 @@
 #define L2FAILED	(0x1)
 #define L2SYNC		(0x2)
 
+#define TGRAN2NOSUP	(0x3)
+
 /* Used for L2 stack and guest S2 page tables. */
 #define L2_PAGE_POOL_ADDR	(0x80000000)
 #define L2_PAGE_POOL_NPAGES	(512)
@@ -53,6 +55,7 @@ static void guest_code(void)
 	int ret, i = 0;
 	gpa_t l2_pc, l2_stack_top;
 	struct page_pool pp;
+	u64 mmfr0 = read_sysreg(id_aa64mmfr0_el1);
 
 	GUEST_ASSERT_EQ(get_current_el(), 2);
 	GUEST_PRINTF("vEL2 entry\n");
@@ -62,6 +65,9 @@ static void guest_code(void)
 	pp.current = L2_PAGE_POOL_ADDR;
 	pp.page_size = get_page_size();
 
+	if (!has_tgran_2(mmfr0, pp.page_size))
+		GUEST_SYNC1(TGRAN2NOSUP);
+
 	l2_stack_top = alloc_page(&pp) + pp.page_size;
 	l2_pc = ucall_translate_to_gpa(l2_guest_code);
 
@@ -119,6 +125,8 @@ int main(void)
 			}
 			if (uc.args[0] == L2SYNC)
 				pr_info("L2SYNC, L1 info: %ld, L2 info: %ld\n", uc.args[1], uc.args[2]);
+			if (uc.args[0] == TGRAN2NOSUP)
+				ksft_exit_skip("Guest page size not supported as guest stage-2 page size!\n");
 			break;
 		case UCALL_PRINTF:
 			pr_info("[L1] %s", uc.buffer);
diff --git a/tools/testing/selftests/kvm/include/arm64/nested.h b/tools/testing/selftests/kvm/include/arm64/nested.h
index 8e7d7738b381..fc59fabff12d 100644
--- a/tools/testing/selftests/kvm/include/arm64/nested.h
+++ b/tools/testing/selftests/kvm/include/arm64/nested.h
@@ -55,6 +55,7 @@ struct page_pool {
 
 size_t get_page_size(void);
 gpa_t alloc_page(struct page_pool *pp);
+bool has_tgran_2(u64 mmfr0, size_t size);
 void prepare_hyp(void);
 void init_vcpu(struct vcpu *vcpu, gpa_t l2_pc, gpa_t l2_stack_top);
 int run_l2(struct vcpu *vcpu, struct hyp_data *hyp_data);
diff --git a/tools/testing/selftests/kvm/lib/arm64/nested.c b/tools/testing/selftests/kvm/lib/arm64/nested.c
index 7f47e340f00d..cda41f355263 100644
--- a/tools/testing/selftests/kvm/lib/arm64/nested.c
+++ b/tools/testing/selftests/kvm/lib/arm64/nested.c
@@ -9,6 +9,36 @@
 #include <asm/sysreg.h>
 #include <linux/sizes.h>
 
+#define _has_tgran_2(__r, __sz)						\
+	({								\
+		u64 _s1, _s2, _mmfr0 = __r;				\
+									\
+		_s2 = SYS_FIELD_GET(ID_AA64MMFR0_EL1,			\
+				    TGRAN##__sz##_2, _mmfr0);		\
+									\
+		_s1 = SYS_FIELD_GET(ID_AA64MMFR0_EL1,			\
+				    TGRAN##__sz, _mmfr0);		\
+									\
+		((_s2 != ID_AA64MMFR0_EL1_TGRAN##__sz##_2_NI &&		\
+		  _s2 != ID_AA64MMFR0_EL1_TGRAN##__sz##_2_TGRAN##__sz) || \
+		 (_s2 == ID_AA64MMFR0_EL1_TGRAN##__sz##_2_TGRAN##__sz && \
+		  _s1 != ID_AA64MMFR0_EL1_TGRAN##__sz##_NI));		\
+	})
+
+bool has_tgran_2(u64 mmfr0, size_t size)
+{
+	switch (size) {
+	case SZ_4K:
+		return _has_tgran_2(mmfr0, 4);
+	case SZ_16K:
+		return _has_tgran_2(mmfr0, 16);
+	case SZ_64K:
+		return _has_tgran_2(mmfr0, 64);
+	default:
+		return false;
+	}
+}
+
 size_t get_page_size(void)
 {
 	u64 tcr_el1 = read_sysreg(tcr_el1);
-- 
2.43.0


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

* [PATCH v3 8/9] KVM: arm64: selftests: Add infrastructure for using stage-2 in guest
  2026-05-16 18:29 [PATCH v3 0/9] KVM: arm64: selftests: Basic nested guest support Wei-Lin Chang
                   ` (6 preceding siblings ...)
  2026-05-16 18:30 ` [PATCH v3 7/9] KVM: arm64: selftests: shadow_stage2: Check supported stage-2 granule size Wei-Lin Chang
@ 2026-05-16 18:30 ` Wei-Lin Chang
  2026-05-16 18:30 ` [PATCH v3 9/9] KVM: arm64: selftests: shadow_stage2: Turn on stage-2 translation for the nested guest Wei-Lin Chang
  2026-05-19 21:31 ` [PATCH v3 0/9] KVM: arm64: selftests: Basic nested guest support Oliver Upton
  9 siblings, 0 replies; 11+ messages in thread
From: Wei-Lin Chang @ 2026-05-16 18:30 UTC (permalink / raw)
  To: linux-kernel, kvm, linux-kselftest, linux-arm-kernel, kvmarm
  Cc: Paolo Bonzini, Shuah Khan, Marc Zyngier, Oliver Upton, Joey Gouly,
	Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon,
	Itaru Kitayama, Wei-Lin Chang

Add a stage-2 page table generator, the s2_mmu structure, and vEL2
stage-2 preparation code for a guest hypervisor to turn on stage-2
translation for its nested guest.

Signed-off-by: Wei-Lin Chang <weilin.chang@arm.com>
---
 .../selftests/kvm/arm64/hello_nested.c        |   2 +-
 .../selftests/kvm/arm64/shadow_stage2.c       |   2 +-
 .../selftests/kvm/include/arm64/nested.h      |  15 +-
 .../testing/selftests/kvm/lib/arm64/nested.c  | 145 +++++++++++++++++-
 4 files changed, 160 insertions(+), 4 deletions(-)

diff --git a/tools/testing/selftests/kvm/arm64/hello_nested.c b/tools/testing/selftests/kvm/arm64/hello_nested.c
index 9ed5285f5f2d..b57e41c73214 100644
--- a/tools/testing/selftests/kvm/arm64/hello_nested.c
+++ b/tools/testing/selftests/kvm/arm64/hello_nested.c
@@ -62,7 +62,7 @@ static void guest_code(void)
 	l2_stack_top = ucall_translate_to_gpa(&l2_stack[L2STACKSZ]);
 
 	init_vcpu(&vcpu, l2_pc, l2_stack_top);
-	prepare_hyp();
+	prepare_hyp_no_s2();
 
 	ret = run_l2(&vcpu, &hyp_data);
 	GUEST_ASSERT_EQ(ret, ARM_EXCEPTION_TRAP);
diff --git a/tools/testing/selftests/kvm/arm64/shadow_stage2.c b/tools/testing/selftests/kvm/arm64/shadow_stage2.c
index c5332b8b5683..2b274b810dcf 100644
--- a/tools/testing/selftests/kvm/arm64/shadow_stage2.c
+++ b/tools/testing/selftests/kvm/arm64/shadow_stage2.c
@@ -72,7 +72,7 @@ static void guest_code(void)
 	l2_pc = ucall_translate_to_gpa(l2_guest_code);
 
 	init_vcpu(&vcpu, l2_pc, l2_stack_top);
-	prepare_hyp();
+	prepare_hyp_no_s2();
 
 	while (true) {
 		GUEST_PRINTF("L2 enter\n");
diff --git a/tools/testing/selftests/kvm/include/arm64/nested.h b/tools/testing/selftests/kvm/include/arm64/nested.h
index fc59fabff12d..1bcbb31b8d67 100644
--- a/tools/testing/selftests/kvm/include/arm64/nested.h
+++ b/tools/testing/selftests/kvm/include/arm64/nested.h
@@ -38,6 +38,14 @@ struct vcpu {
 	struct cpu_context context;
 };
 
+struct s2_mmu {
+	gpa_t pgd;
+	unsigned int vmid;
+	unsigned int page_size_shift;
+	u64 vtcr;
+	u64 ipa_bits;
+};
+
 /*
  * KVM has host_data and hyp_context, combine them because we're only doing
  * hyp context.
@@ -56,8 +64,13 @@ struct page_pool {
 size_t get_page_size(void);
 gpa_t alloc_page(struct page_pool *pp);
 bool has_tgran_2(u64 mmfr0, size_t size);
-void prepare_hyp(void);
+void prepare_hyp_no_s2(void);
+void prepare_hyp(struct s2_mmu *mmu);
 void init_vcpu(struct vcpu *vcpu, gpa_t l2_pc, gpa_t l2_stack_top);
+void create_s2_mapping(struct s2_mmu *mmu, u64 ipa, u64 pa, size_t size,
+		       struct page_pool *pp);
+void init_s2_mmu(struct s2_mmu *mmu, unsigned int vmid, gpa_t pgd,
+		 size_t page_size, u64 ipa_bits);
 int run_l2(struct vcpu *vcpu, struct hyp_data *hyp_data);
 
 u64 do_hvc(u64 action, u64 arg1, u64 arg2);
diff --git a/tools/testing/selftests/kvm/lib/arm64/nested.c b/tools/testing/selftests/kvm/lib/arm64/nested.c
index cda41f355263..9848d607ef64 100644
--- a/tools/testing/selftests/kvm/lib/arm64/nested.c
+++ b/tools/testing/selftests/kvm/lib/arm64/nested.c
@@ -71,13 +71,22 @@ gpa_t alloc_page(struct page_pool *pp)
 	}
 }
 
-void prepare_hyp(void)
+void prepare_hyp_no_s2(void)
 {
 	write_sysreg(HCR_EL2_E2H | HCR_EL2_RW, hcr_el2);
 	write_sysreg(hyp_vectors, vbar_el2);
 	isb();
 }
 
+void prepare_hyp(struct s2_mmu *mmu)
+{
+	write_sysreg(mmu->vtcr, vtcr_el2);
+	write_sysreg(mmu->pgd | ((u64)mmu->vmid << 48), vttbr_el2);
+	write_sysreg(HCR_EL2_E2H | HCR_EL2_RW | HCR_EL2_VM, hcr_el2);
+	write_sysreg(hyp_vectors, vbar_el2);
+	isb();
+}
+
 void init_vcpu(struct vcpu *vcpu, gpa_t l2_pc, gpa_t l2_stack_top)
 {
 	memset(vcpu, 0, sizeof(*vcpu));
@@ -86,6 +95,140 @@ void init_vcpu(struct vcpu *vcpu, gpa_t l2_pc, gpa_t l2_stack_top)
 	vcpu->context.sys_regs[SP_EL1] = l2_stack_top;
 }
 
+static int stage2_levels(unsigned int page_size_shift, u64 ipa_bits)
+{
+	/* taken from ARM64_HW_PGTABLE_LEVELS(ipa) in KVM */
+	return (ipa_bits - 4) / (page_size_shift - 3);
+}
+
+static u64 get_index(struct s2_mmu *mmu, u64 ipa, int level)
+{
+	int width = mmu->page_size_shift - 3;
+	int shift_amount = mmu->page_size_shift + (3 - level) * width;
+
+	return (ipa >> shift_amount) & GENMASK_ULL(width - 1, 0);
+}
+
+static u64 pte_gpa_to_gva(u64 gpa)
+{
+	/*
+	 * This depends on how the memory used for s2pt is mapped in GVA,
+	 * currently it is assumed they are idmapped.
+	 */
+	return gpa;
+}
+
+static u64 pte_to_pt_base(u64 pte)
+{
+	return pte & GENMASK_ULL(47, 12);
+}
+
+#define S2_PTE_AF		(1ULL << 10)
+#define S2_PTE_SH_INNER		(3ULL << 8)
+#define S2_PTE_S2AP_RW		(3ULL << 6)
+#define S2_PTE_ATTR_NORMAL_WB	(0xfULL << 2)
+#define S2_PTE_TYPE_TABLE	(1ULL << 1)
+#define S2_PTE_TYPE_PAGE	(1ULL << 1)
+#define S2_PTE_VALID		1ULL
+
+/* No block mappings for now. */
+static void create_one_s2_mapping(struct s2_mmu *mmu, u64 ipa, u64 pa,
+				  struct page_pool *pp)
+{
+	int levels = stage2_levels(mmu->page_size_shift, mmu->ipa_bits);
+	u64 index, pte, pte_new, table_attr, page_attr;
+	gpa_t pte_addr, pt_base = mmu->pgd;
+
+	table_attr = S2_PTE_TYPE_TABLE | S2_PTE_VALID;
+	page_attr = S2_PTE_AF | S2_PTE_SH_INNER | S2_PTE_S2AP_RW |
+		    S2_PTE_ATTR_NORMAL_WB | S2_PTE_TYPE_PAGE | S2_PTE_VALID;
+
+	for (int level = 4 - levels; level <= 3; level++) {
+		index = get_index(mmu, ipa, level);
+		pte_addr = pt_base + index * 8;
+		pte = *((u64 *)pte_gpa_to_gva(pte_addr));
+
+		if (level == 3) {
+			/* Last level, install leaf entry. */
+			pte_new = pa & ~GENMASK_ULL(mmu->page_size_shift - 1, 0);
+			pte_new |= page_attr;
+			*((u64 *)pte_gpa_to_gva(pte_addr)) = pte_new;
+		} else if (!(pte & S2_PTE_VALID)) {
+			/* Empty next level table, allocate and install. */
+			pte_new = alloc_page(pp);
+			pte_new |= table_attr;
+			*((u64 *)pte_gpa_to_gva(pte_addr)) = pte_new;
+			pt_base = pte_to_pt_base(pte_new);
+		} else {
+			/* Next level table found, descend into it. */
+			pt_base = pte_to_pt_base(pte);
+		}
+	}
+}
+
+void create_s2_mapping(struct s2_mmu *mmu, u64 ipa, u64 pa, size_t size,
+		       struct page_pool *pp)
+{
+	u64 ipa_end;
+	u64 mask = pp->page_size - 1;
+
+	ipa_end = (ipa + size + mask) & ~mask;
+	ipa &= ~mask;
+	pa &= ~mask;
+
+	while (ipa < ipa_end) {
+		create_one_s2_mapping(mmu, ipa, pa, pp);
+		pa += pp->page_size;
+		ipa += pp->page_size;
+	}
+	dsb(ishst);
+}
+
+void init_s2_mmu(struct s2_mmu *mmu, unsigned int vmid, gpa_t pgd,
+		 size_t page_size, u64 ipa_bits)
+{
+	u64 ps, tg0, sl0_base, mmfr0 = read_sysreg(id_aa64mmfr0_el1);
+	int levels;
+
+	mmu->vmid = vmid;
+	mmu->pgd = pgd;
+	mmu->ipa_bits = ipa_bits;
+	mmu->vtcr = 0;
+
+	switch (page_size) {
+	case SZ_4K:
+		tg0 = VTCR_EL2_TG0_4K;
+		mmu->page_size_shift = 12;
+		sl0_base = 2;
+		break;
+	case SZ_16K:
+		tg0 = VTCR_EL2_TG0_16K;
+		mmu->page_size_shift = 14;
+		sl0_base = 3;
+		break;
+	case SZ_64K:
+	default:
+		tg0 = VTCR_EL2_TG0_64K;
+		mmu->page_size_shift = 16;
+		sl0_base = 3;
+		break;
+	}
+
+	levels = stage2_levels(mmu->page_size_shift, mmu->ipa_bits);
+	mmu->vtcr |= FIELD_PREP(VTCR_EL2_SL0, (sl0_base - (4 - levels)));
+
+	ps = SYS_FIELD_GET(ID_AA64MMFR0_EL1, PARANGE, mmfr0);
+	/* cap ps to 48-bit */
+	ps = ps > 0b0101 ? 0b0101 : ps;
+	mmu->vtcr |= VTCR_EL2_RES1 | SYS_FIELD_PREP(VTCR_EL2, PS, ps)           |
+				    SYS_FIELD_PREP(VTCR_EL2, TG0, tg0)         |
+				    SYS_FIELD_PREP_ENUM(VTCR_EL2, SH0, INNER)  |
+				    SYS_FIELD_PREP_ENUM(VTCR_EL2, ORGN0, WBWA) |
+				    SYS_FIELD_PREP_ENUM(VTCR_EL2, IRGN0, WBWA);
+
+	mmu->vtcr |= FIELD_PREP(VTCR_EL2_T0SZ, 64 - ipa_bits);
+}
+
 void __sysreg_save_el1_state(struct cpu_context *ctxt)
 {
 	ctxt->sys_regs[SP_EL1] = read_sysreg(sp_el1);
-- 
2.43.0


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

* [PATCH v3 9/9] KVM: arm64: selftests: shadow_stage2: Turn on stage-2 translation for the nested guest
  2026-05-16 18:29 [PATCH v3 0/9] KVM: arm64: selftests: Basic nested guest support Wei-Lin Chang
                   ` (7 preceding siblings ...)
  2026-05-16 18:30 ` [PATCH v3 8/9] KVM: arm64: selftests: Add infrastructure for using stage-2 in guest Wei-Lin Chang
@ 2026-05-16 18:30 ` Wei-Lin Chang
  2026-05-19 21:31 ` [PATCH v3 0/9] KVM: arm64: selftests: Basic nested guest support Oliver Upton
  9 siblings, 0 replies; 11+ messages in thread
From: Wei-Lin Chang @ 2026-05-16 18:30 UTC (permalink / raw)
  To: linux-kernel, kvm, linux-kselftest, linux-arm-kernel, kvmarm
  Cc: Paolo Bonzini, Shuah Khan, Marc Zyngier, Oliver Upton, Joey Gouly,
	Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon,
	Itaru Kitayama, Wei-Lin Chang

Utilize the stage-2 library functions to initialize a s2_mmu, build a
stage-2 page table, and turn on stage-2 translation for the nested
guest. This better tests out the shadow page table code in KVM.

Signed-off-by: Wei-Lin Chang <weilin.chang@arm.com>
---
 .../selftests/kvm/arm64/shadow_stage2.c       | 23 ++++++++++++++++---
 1 file changed, 20 insertions(+), 3 deletions(-)

diff --git a/tools/testing/selftests/kvm/arm64/shadow_stage2.c b/tools/testing/selftests/kvm/arm64/shadow_stage2.c
index 2b274b810dcf..5bce55abdea7 100644
--- a/tools/testing/selftests/kvm/arm64/shadow_stage2.c
+++ b/tools/testing/selftests/kvm/arm64/shadow_stage2.c
@@ -51,9 +51,11 @@ static void l2_guest_code(void)
 static void guest_code(void)
 {
 	struct vcpu vcpu;
+	struct s2_mmu mmu;
 	struct hyp_data hyp_data;
 	int ret, i = 0;
-	gpa_t l2_pc, l2_stack_top;
+	gpa_t l2_pc, l2_stack_start, l2_stack_top, s2_pgd;
+	gpa_t do_hvc_gpa;
 	struct page_pool pp;
 	u64 mmfr0 = read_sysreg(id_aa64mmfr0_el1);
 
@@ -68,11 +70,20 @@ static void guest_code(void)
 	if (!has_tgran_2(mmfr0, pp.page_size))
 		GUEST_SYNC1(TGRAN2NOSUP);
 
-	l2_stack_top = alloc_page(&pp) + pp.page_size;
+	l2_stack_start = alloc_page(&pp);
+	l2_stack_top = l2_stack_start + pp.page_size;
 	l2_pc = ucall_translate_to_gpa(l2_guest_code);
+	do_hvc_gpa = ucall_translate_to_gpa(do_hvc);
+
+	s2_pgd = alloc_page(&pp);
 
 	init_vcpu(&vcpu, l2_pc, l2_stack_top);
-	prepare_hyp_no_s2();
+	init_s2_mmu(&mmu, 0, s2_pgd, pp.page_size, 40);
+	create_s2_mapping(&mmu, l2_pc, l2_pc, pp.page_size * 2, &pp);
+	create_s2_mapping(&mmu, do_hvc_gpa, do_hvc_gpa, pp.page_size, &pp);
+	create_s2_mapping(&mmu, l2_stack_start, l2_stack_start, pp.page_size, &pp);
+
+	prepare_hyp(&mmu);
 
 	while (true) {
 		GUEST_PRINTF("L2 enter\n");
@@ -113,6 +124,12 @@ int main(void)
 	vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS,
 				    L2_PAGE_POOL_ADDR, L2_PAGE_POOL_MEMSLOT,
 				    L2_PAGE_POOL_NPAGES, 0);
+	/*
+	 * This idmap allows L1 to traverse and build its guest stage-2, where
+	 * it must do a PA to VA conversion in order to descend to the next
+	 * level.
+	 */
+	virt_map(vm, L2_PAGE_POOL_ADDR, L2_PAGE_POOL_ADDR, L2_PAGE_POOL_NPAGES);
 
 	while (true) {
 		vcpu_run(vcpu);
-- 
2.43.0


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

* Re: [PATCH v3 0/9] KVM: arm64: selftests: Basic nested guest support
  2026-05-16 18:29 [PATCH v3 0/9] KVM: arm64: selftests: Basic nested guest support Wei-Lin Chang
                   ` (8 preceding siblings ...)
  2026-05-16 18:30 ` [PATCH v3 9/9] KVM: arm64: selftests: shadow_stage2: Turn on stage-2 translation for the nested guest Wei-Lin Chang
@ 2026-05-19 21:31 ` Oliver Upton
  9 siblings, 0 replies; 11+ messages in thread
From: Oliver Upton @ 2026-05-19 21:31 UTC (permalink / raw)
  To: Wei-Lin Chang
  Cc: linux-kernel, kvm, linux-kselftest, linux-arm-kernel, kvmarm,
	Paolo Bonzini, Shuah Khan, Marc Zyngier, Joey Gouly,
	Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon,
	Itaru Kitayama

Hi Wei-Lin,

Thank you very much for the series.

I haven't had time to read through much of this yet, but I noticed
Itaru's comment about leaving stage-1 translation disabled for the L2.

Since selftests expects things like atomics to work we will definitely
need the stage-1 MMU to be enabled w/ Normal mappings (this was broken
in the past [*]). I wonder if we can (ab)use the pre-existing stage-1
tables that the selftests library creates on behalf of the L1. After all,
L1 and L2 are both effectively in the same virtual address space.

[*] https://lore.kernel.org/kvmarm/20250405001042.1470552-1-rananta@google.com/

Thanks,
Oliver

On Sat, May 16, 2026 at 07:29:54PM +0100, Wei-Lin Chang wrote:
> Hi,
> 
> This is v3 of adding basic support for running nested guests (L2) in
> kselftest. This time a framework for enabling stage-2 in the guest is
> added, including the s2_mmu struct, s2 translation control setup, and
> a stage-2 page table generator. Similar to L2 vCPU management, all
> stage-2 work is done in the guest instead of userspace.
> 
> An additional shadow_stage2 test is added which leverages the framework
> to run L2 with stage-2 enabled.
> 
> * Changes from v2 [1]:
> 
>   - Update vm_paddr_t to gpa_t, vm_vaddr_t to gva_t.
> 
>   - Added framework for enabling stage-2 in the guest.
> 
>   - Added shadow_stage2 test.
> 
> Thanks!
> 
> [1]: https://lore.kernel.org/kvmarm/20260412142216.3806482-1-weilin.chang@arm.com/
> 
> Wei-Lin Chang (9):
>   KVM: arm64: selftests: Add GPR save/restore functions for NV
>   KVM: arm64: selftests: Add helpers for guest hypervisors
>   KVM: arm64: selftests: Add hello_nested basic NV selftest
>   KVM: arm64: selftests: Enhance hello_nested test
>   KVM: arm64: selftests: Add shadow_stage2 test
>   KVM: arm64: selftests: shadow_stage2: Allocate L2 stack from dedicated
>     pool
>   KVM: arm64: selftests: shadow_stage2: Check supported stage-2 granule
>     size
>   KVM: arm64: selftests: Add infrastructure for using stage-2 in guest
>   KVM: arm64: selftests: shadow_stage2: Turn on stage-2 translation for
>     the nested guest
> 
>  tools/testing/selftests/kvm/Makefile.kvm      |   5 +
>  .../selftests/kvm/arm64/hello_nested.c        | 134 +++++++++
>  .../selftests/kvm/arm64/shadow_stage2.c       | 165 +++++++++++
>  .../selftests/kvm/include/arm64/nested.h      |  85 ++++++
>  tools/testing/selftests/kvm/lib/arm64/entry.S | 137 +++++++++
>  .../selftests/kvm/lib/arm64/hyp-entry.S       |  77 +++++
>  .../testing/selftests/kvm/lib/arm64/nested.c  | 264 ++++++++++++++++++
>  7 files changed, 867 insertions(+)
>  create mode 100644 tools/testing/selftests/kvm/arm64/hello_nested.c
>  create mode 100644 tools/testing/selftests/kvm/arm64/shadow_stage2.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] 11+ messages in thread

end of thread, other threads:[~2026-05-19 21:31 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-16 18:29 [PATCH v3 0/9] KVM: arm64: selftests: Basic nested guest support Wei-Lin Chang
2026-05-16 18:29 ` [PATCH v3 1/9] KVM: arm64: selftests: Add GPR save/restore functions for NV Wei-Lin Chang
2026-05-16 18:29 ` [PATCH v3 2/9] KVM: arm64: selftests: Add helpers for guest hypervisors Wei-Lin Chang
2026-05-16 18:29 ` [PATCH v3 3/9] KVM: arm64: selftests: Add hello_nested basic NV selftest Wei-Lin Chang
2026-05-16 18:29 ` [PATCH v3 4/9] KVM: arm64: selftests: Enhance hello_nested test Wei-Lin Chang
2026-05-16 18:29 ` [PATCH v3 5/9] KVM: arm64: selftests: Add shadow_stage2 test Wei-Lin Chang
2026-05-16 18:30 ` [PATCH v3 6/9] KVM: arm64: selftests: shadow_stage2: Allocate L2 stack from dedicated pool Wei-Lin Chang
2026-05-16 18:30 ` [PATCH v3 7/9] KVM: arm64: selftests: shadow_stage2: Check supported stage-2 granule size Wei-Lin Chang
2026-05-16 18:30 ` [PATCH v3 8/9] KVM: arm64: selftests: Add infrastructure for using stage-2 in guest Wei-Lin Chang
2026-05-16 18:30 ` [PATCH v3 9/9] KVM: arm64: selftests: shadow_stage2: Turn on stage-2 translation for the nested guest Wei-Lin Chang
2026-05-19 21:31 ` [PATCH v3 0/9] KVM: arm64: selftests: Basic nested guest support Oliver Upton

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