* [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; 16+ 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] 16+ 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; 16+ 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] 16+ 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; 16+ 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] 16+ 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; 16+ 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] 16+ 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-22 2:02 ` Itaru Kitayama
2026-05-28 4:59 ` [PATCH] KVM: selftest: arm64: Run shadow_stage2 varying guest modes Itaru Kitayama
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, 2 replies; 16+ 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] 16+ messages in thread* Re: [PATCH v3 5/9] KVM: arm64: selftests: Add shadow_stage2 test
2026-05-16 18:29 ` [PATCH v3 5/9] KVM: arm64: selftests: Add shadow_stage2 test Wei-Lin Chang
@ 2026-05-22 2:02 ` Itaru Kitayama
2026-05-28 4:59 ` [PATCH] KVM: selftest: arm64: Run shadow_stage2 varying guest modes Itaru Kitayama
1 sibling, 0 replies; 16+ messages in thread
From: Itaru Kitayama @ 2026-05-22 2:02 UTC (permalink / raw)
To: Wei-Lin Chang
Cc: linux-kernel, kvm, linux-kselftest, linux-arm-kernel, kvmarm,
Paolo Bonzini, Shuah Khan, Marc Zyngier, Oliver Upton, Joey Gouly,
Suzuki K Poulose, Zenghui Yu, Catalin Marinas, Will Deacon
On Sat, May 16, 2026 at 07:29:59PM +0100, Wei-Lin Chang wrote:
> 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);
This assumes the default P40V48 4KB guest creation, so perhaps you'd
want to change this to __vm_create(VM_SHAPE(), 1, 0) so the test runs too on other
16KB and 64KB kernels? I've been testing it with your stage 2 unmapping
optimization series together with this v3 KVM selftest series.
Thanks,
Itaru.
> +
> + 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 [flat|nested] 16+ messages in thread* [PATCH] KVM: selftest: arm64: Run shadow_stage2 varying guest modes
2026-05-16 18:29 ` [PATCH v3 5/9] KVM: arm64: selftests: Add shadow_stage2 test Wei-Lin Chang
2026-05-22 2:02 ` Itaru Kitayama
@ 2026-05-28 4:59 ` Itaru Kitayama
2026-05-28 16:02 ` Wei-Lin Chang
1 sibling, 1 reply; 16+ messages in thread
From: Itaru Kitayama @ 2026-05-28 4:59 UTC (permalink / raw)
To: weilin.chang
Cc: catalin.marinas, itaru.kitayama, joey.gouly, kvm, kvmarm,
linux-arm-kernel, linux-kernel, linux-kselftest, maz, oupton,
pbonzini, shuah, suzuki.poulose, will, yuzenghui
Refactor main() to use for_each_guest_mode() helper to create
a single VCPU VM with a guest mode ID.
Signed-off-by: Itaru Kitayama <itaru.kitayama@fujitsu•com>
---
tools/testing/selftests/kvm/arm64/shadow_stage2.c | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/tools/testing/selftests/kvm/arm64/shadow_stage2.c b/tools/testing/selftests/kvm/arm64/shadow_stage2.c
index 5bce55abdea7..05acca22eafe 100644
--- a/tools/testing/selftests/kvm/arm64/shadow_stage2.c
+++ b/tools/testing/selftests/kvm/arm64/shadow_stage2.c
@@ -105,7 +105,7 @@ static void guest_code(void)
GUEST_DONE();
}
-int main(void)
+int run_test(enum vm_guest_mode mode, void *unused)
{
struct kvm_vcpu_init init;
struct kvm_vcpu *vcpu;
@@ -114,7 +114,8 @@ int main(void)
gpa_t gpa;
TEST_REQUIRE(kvm_check_cap(KVM_CAP_ARM_EL2));
- vm = vm_create(1);
+ vm = __vm_create(VM_SHAPE(mode), 1, 0);
+
kvm_get_default_vcpu_target(vm, &init);
init.features[0] |= BIT(KVM_ARM_VCPU_HAS_EL2);
@@ -163,3 +164,7 @@ int main(void)
kvm_vm_free(vm);
return 0;
}
+
+int main(void) {
+ for_each_guest_mode(run_test, NULL);
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* Re: [PATCH] KVM: selftest: arm64: Run shadow_stage2 varying guest modes
2026-05-28 4:59 ` [PATCH] KVM: selftest: arm64: Run shadow_stage2 varying guest modes Itaru Kitayama
@ 2026-05-28 16:02 ` Wei-Lin Chang
2026-05-28 22:14 ` Itaru Kitayama
0 siblings, 1 reply; 16+ messages in thread
From: Wei-Lin Chang @ 2026-05-28 16:02 UTC (permalink / raw)
To: Itaru Kitayama
Cc: catalin.marinas, joey.gouly, kvm, kvmarm, linux-arm-kernel,
linux-kernel, linux-kselftest, maz, oupton, pbonzini, shuah,
suzuki.poulose, will, yuzenghui
Hi Itaru,
On Thu, May 28, 2026 at 01:59:30PM +0900, Itaru Kitayama wrote:
> Refactor main() to use for_each_guest_mode() helper to create
> a single VCPU VM with a guest mode ID.
>
> Signed-off-by: Itaru Kitayama <itaru.kitayama@fujitsu•com>
> ---
> tools/testing/selftests/kvm/arm64/shadow_stage2.c | 9 +++++++--
> 1 file changed, 7 insertions(+), 2 deletions(-)
>
> diff --git a/tools/testing/selftests/kvm/arm64/shadow_stage2.c b/tools/testing/selftests/kvm/arm64/shadow_stage2.c
> index 5bce55abdea7..05acca22eafe 100644
> --- a/tools/testing/selftests/kvm/arm64/shadow_stage2.c
> +++ b/tools/testing/selftests/kvm/arm64/shadow_stage2.c
> @@ -105,7 +105,7 @@ static void guest_code(void)
> GUEST_DONE();
> }
>
> -int main(void)
> +int run_test(enum vm_guest_mode mode, void *unused)
> {
> struct kvm_vcpu_init init;
> struct kvm_vcpu *vcpu;
> @@ -114,7 +114,8 @@ int main(void)
> gpa_t gpa;
>
> TEST_REQUIRE(kvm_check_cap(KVM_CAP_ARM_EL2));
> - vm = vm_create(1);
> + vm = __vm_create(VM_SHAPE(mode), 1, 0);
> +
>
> kvm_get_default_vcpu_target(vm, &init);
> init.features[0] |= BIT(KVM_ARM_VCPU_HAS_EL2);
> @@ -163,3 +164,7 @@ int main(void)
> kvm_vm_free(vm);
> return 0;
> }
> +
> +int main(void) {
> + for_each_guest_mode(run_test, NULL);
> +}
> --
> 2.43.0
>
Thanks! I think this is valuable.
I can add your patch into the next version if you don't mind?
Thanks,
Wei-Lin Chang
^ permalink raw reply [flat|nested] 16+ messages in thread* Re: [PATCH] KVM: selftest: arm64: Run shadow_stage2 varying guest modes
2026-05-28 16:02 ` Wei-Lin Chang
@ 2026-05-28 22:14 ` Itaru Kitayama
0 siblings, 0 replies; 16+ messages in thread
From: Itaru Kitayama @ 2026-05-28 22:14 UTC (permalink / raw)
To: Wei-Lin Chang
Cc: catalin.marinas, joey.gouly, kvm, kvmarm, linux-arm-kernel,
linux-kernel, linux-kselftest, maz, oupton, pbonzini, shuah,
suzuki.poulose, will, yuzenghui
On Thu, May 28, 2026 at 05:02:30PM +0100, Wei-Lin Chang wrote:
> Hi Itaru,
>
> On Thu, May 28, 2026 at 01:59:30PM +0900, Itaru Kitayama wrote:
> > Refactor main() to use for_each_guest_mode() helper to create
> > a single VCPU VM with a guest mode ID.
> >
> > Signed-off-by: Itaru Kitayama <itaru.kitayama@fujitsu•com>
> > ---
> > tools/testing/selftests/kvm/arm64/shadow_stage2.c | 9 +++++++--
> > 1 file changed, 7 insertions(+), 2 deletions(-)
> >
> > diff --git a/tools/testing/selftests/kvm/arm64/shadow_stage2.c b/tools/testing/selftests/kvm/arm64/shadow_stage2.c
> > index 5bce55abdea7..05acca22eafe 100644
> > --- a/tools/testing/selftests/kvm/arm64/shadow_stage2.c
> > +++ b/tools/testing/selftests/kvm/arm64/shadow_stage2.c
> > @@ -105,7 +105,7 @@ static void guest_code(void)
> > GUEST_DONE();
> > }
> >
> > -int main(void)
> > +int run_test(enum vm_guest_mode mode, void *unused)
> > {
> > struct kvm_vcpu_init init;
> > struct kvm_vcpu *vcpu;
> > @@ -114,7 +114,8 @@ int main(void)
> > gpa_t gpa;
> >
> > TEST_REQUIRE(kvm_check_cap(KVM_CAP_ARM_EL2));
> > - vm = vm_create(1);
> > + vm = __vm_create(VM_SHAPE(mode), 1, 0);
> > +
> >
> > kvm_get_default_vcpu_target(vm, &init);
> > init.features[0] |= BIT(KVM_ARM_VCPU_HAS_EL2);
> > @@ -163,3 +164,7 @@ int main(void)
> > kvm_vm_free(vm);
> > return 0;
> > }
> > +
> > +int main(void) {
> > + for_each_guest_mode(run_test, NULL);
> > +}
> > --
> > 2.43.0
> >
>
> Thanks! I think this is valuable.
> I can add your patch into the next version if you don't mind?
No, I would not, for the record your test rans with all the available guest
modes without an issue (ie ended DONE!) on QEMU in tcg mode.
Thanks,
Itaru.
>
> Thanks,
> Wei-Lin Chang
^ permalink raw reply [flat|nested] 16+ 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; 16+ 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] 16+ 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; 16+ 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] 16+ 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; 16+ 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] 16+ 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; 16+ 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] 16+ 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
2026-05-29 13:38 ` Wei-Lin Chang
9 siblings, 1 reply; 16+ 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] 16+ messages in thread* Re: [PATCH v3 0/9] KVM: arm64: selftests: Basic nested guest support
2026-05-19 21:31 ` [PATCH v3 0/9] KVM: arm64: selftests: Basic nested guest support Oliver Upton
@ 2026-05-29 13:38 ` Wei-Lin Chang
0 siblings, 0 replies; 16+ messages in thread
From: Wei-Lin Chang @ 2026-05-29 13:38 UTC (permalink / raw)
To: Oliver Upton
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 Oliver,
On Tue, May 19, 2026 at 02:31:09PM -0700, Oliver Upton wrote:
> 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 for the pointer, I see, only inner/outer shareable, write-back
normal memory guarantees atomics to work. Interestingly, treating the
atomic instruction as a NOP is one of the permitted outcomes if other
memory attributes are used...
However, the L2s in this series aren't using atomic instructions, and
right now for the current organization of this series, the L1 guest
hypervisor infrastructure is in lib/ for future NV selftests to share L2
creation code, but L2 code are entirely per selftest. This means other
selftest L2s can reuse the L1 stage-1 tables themselves if the implicit
memory attributes that comes from MMU being off is not desired.
Sorry if that sounded defensive, I am not opposed to always reusing the
L1 stage-1 tables in L2, just wanted to make sure the point gets across.
Always reusing L1 stage-1 tables in L2 allows L2 access to lib/ (which
should be helpful for testing recursive NV), and L1-L2 code sharing
intra-selftest. OTOH things are a bit simpler with MMU off in this case
with testing the shadow stage2.
What do you think?
Thanks,
Wei-Lin Chang
>
> 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] 16+ messages in thread