Thread (2 messages) 2 messages, 2 authors, 1d ago

[PATCH v2] KVM: arm64: selftests: Test VNCR translation invalidation

flat view
WARM1d

From: Itaru Kitayama <hidden>
Date: 2026-10-22 08:11:31
Also in: kvm, kvmarm, linux-kselftest, lkml
Subsystem: kernel selftest framework, kernel virtual machine (kvm), kernel virtual machine for arm64 (kvm/arm64), the rest · Maintainers: Shuah Khan, Shuah Khan, Paolo Bonzini, Marc Zyngier, Oliver Upton, Linus Torvalds

Add a standalone KVM selftest that checks VNCR accesses after L1
updates its stage-1 mapping and issues a TLBI.

Keep VNCR_EL2 unchanged while remapping its backing page A to B then
again to A. At each step, L2 reads and writes TPIDR_EL2, and L1
verifies that the accesses reach the selected page while leaving the
other page unchanged.

The test runs with L1-managed stage-2 translation disabled.

Signed-off-by: Itaru Kitayama <redacted>
---
Changes in v2:
- Fix inverted page-frame and page-offset masks when mapping
  the page-table descriptor, as reported by sashiko-bot
- Link to v1: https://patch.msgid.link/20261022-b4-vncr-mapping-test-v1-1-e9ad54d506be@fujitsu.com (local)
---
 tools/testing/selftests/kvm/Makefile.kvm           |   1 +
 .../selftests/kvm/arm64/nv_vncr_tlbi_test.c        | 303 +++++++++++++++++++++
 2 files changed, 304 insertions(+)
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index f43549a56f0b..9f0af6ef54ba 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -181,6 +181,7 @@ TEST_GEN_PROGS_arm64 += arm64/host_sve
 TEST_GEN_PROGS_arm64 += arm64/hypercalls
 TEST_GEN_PROGS_arm64 += arm64/external_aborts
 TEST_GEN_PROGS_arm64 += arm64/mmio_sign_ext
+TEST_GEN_PROGS_arm64 += arm64/nv_vncr_tlbi_test
 TEST_GEN_PROGS_arm64 += arm64/page_fault_test
 TEST_GEN_PROGS_arm64 += arm64/psci_test
 TEST_GEN_PROGS_arm64 += arm64/sea_to_user
diff --git a/tools/testing/selftests/kvm/arm64/nv_vncr_tlbi_test.c b/tools/testing/selftests/kvm/arm64/nv_vncr_tlbi_test.c
new file mode 100644
index 000000000000..283e03bb007b
--- /dev/null
+++ b/tools/testing/selftests/kvm/arm64/nv_vncr_tlbi_test.c
@@ -0,0 +1,303 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Check VNCR translation invalidation through observable memory effects.
+ *
+ * L1 keeps VNCR_EL2 unchanged while remapping its virtual address:
+ *
+ *     VNCR VA -> page A -> page B -> page A
+ *
+ * L2 reads and writes TPIDR_EL2 with HCR_EL2.{NV2,NV} == {1,1}.
+ * These accesses use the TPIDR_EL2 slot at VNCR offset 0x090.
+ *
+ * After each L2 HVC, L1 checks:
+ *   - the value returned by the redirected register read;
+ *   - the value written to the selected backing page;
+ *   - that the other backing page remains unchanged.
+ *
+ * No L3 and no L1-managed stage-2 translation are needed.
+ */
+
+#include "kvm_util.h"
+#include "processor.h"
+#include "test_util.h"
+#include "ucall_common.h"
+
+#define VNCR_TPIDR_OFFSET	0x090
+
+#define SEED_A			0x1111222233334444ULL
+#define SEED_B			0x5555666677778888ULL
+#define WRITE_A			0xaaaabbbbccccddddULL
+#define WRITE_B			0x123456789abcdef0ULL
+#define WRITE_A_AGAIN		0xfedcba9876543210ULL
+
+#define L1_HCR			(HCR_EL2_RW | HCR_EL2_E2H | \
+				 HCR_EL2_NV | HCR_EL2_NV2)
+#define L2_PSTATE		(PSR_MODE_EL1h | PSR_D_BIT | PSR_A_BIT | \
+				 PSR_I_BIT | PSR_F_BIT)
+
+static struct test_config {
+	u64 vncr_va;
+	u64 *vncr_ptep;
+	u64 pte_a;
+	u64 pte_b;
+	u64 *slot_a;
+	u64 *slot_b;
+	u64 l2_sp;
+} cfg;
+
+static unsigned int round_nr;
+
+extern void l2_probe(void);
+extern char l2_after_hvc[];
+
+/*
+ * x1: value to write.
+ * x0: value read, observed by L1's HVC handler.
+ *
+ * No stack accesses or C calls in L2.
+ * Use the SMCCC convention of a zero HVC immediate.
+ */
+asm(
+".pushsection .text,\"ax\"\n"
+".balign 4\n"
+".global l2_probe\n"
+".type l2_probe, %function\n"
+"l2_probe:\n"
+"	mrs	x0, tpidr_el2\n"
+"	msr	tpidr_el2, x1\n"
+"	dsb	ish\n"
+"	hvc	#0\n"
+".global l2_after_hvc\n"
+"l2_after_hvc:\n"
+"	brk	#0\n"
+".size l2_probe, . - l2_probe\n"
+".popsection\n"
+);
+
+static void remap_vncr(u64 new_pte)
+{
+	u64 hcr = read_sysreg(hcr_el2);
+	u64 operand = cfg.vncr_va >> 12;
+
+	/*
+	 * Use the VHE host translation regime for the EL1 TLBI
+	 * instruction: HCR_EL2.{E2H,TGE} == {1,1}.
+	 */
+	write_sysreg(hcr | HCR_EL2_TGE, hcr_el2);
+	isb();
+
+	/* Break before make. VNCR_EL2 itself is never rewritten. */
+	WRITE_ONCE(*cfg.vncr_ptep, 0);
+	dsb(ishst);
+
+	asm volatile("tlbi vaae1is, %0" : : "r" (operand) : "memory");
+	dsb(ish);
+	isb();
+
+	WRITE_ONCE(*cfg.vncr_ptep, new_pte);
+	dsb(ishst);
+	isb();
+
+	/* Permit return to L2 at EL1. */
+	write_sysreg(hcr, hcr_el2);
+	isb();
+}
+
+static void handle_l2_hvc(struct ex_regs *regs)
+{
+	u64 esr = read_sysreg(esr_el1);
+
+	GUEST_ASSERT_EQ(ESR_ELx_EC(esr), ESR_ELx_EC_HVC64);
+	GUEST_ASSERT_EQ(esr & 0xffff, 0x0);
+	GUEST_ASSERT_EQ(regs->pc, (u64)l2_after_hvc);
+	GUEST_ASSERT_EQ(read_sysreg_s(SYS_VNCR_EL2), cfg.vncr_va);
+
+	dsb(ish);
+
+	switch (round_nr) {
+	case 0:
+		/* Warmed translation: VNCR VA -> A. */
+		GUEST_ASSERT_EQ(regs->regs[0], SEED_A);
+		GUEST_ASSERT_EQ(READ_ONCE(*cfg.slot_a), WRITE_A);
+		GUEST_ASSERT_EQ(READ_ONCE(*cfg.slot_b), SEED_B);
+
+		remap_vncr(cfg.pte_b);
+		regs->regs[1] = WRITE_B;
+		break;
+
+	case 1:
+		/* Following TLBI, reads and writes must now use B. */
+		GUEST_ASSERT_EQ(regs->regs[0], SEED_B);
+		GUEST_ASSERT_EQ(READ_ONCE(*cfg.slot_b), WRITE_B);
+		GUEST_ASSERT_EQ(READ_ONCE(*cfg.slot_a), WRITE_A);
+
+		remap_vncr(cfg.pte_a);
+		regs->regs[1] = WRITE_A_AGAIN;
+		break;
+
+	case 2:
+		/* Check invalidation again, with the mapping reversed. */
+		GUEST_ASSERT_EQ(regs->regs[0], WRITE_A);
+		GUEST_ASSERT_EQ(READ_ONCE(*cfg.slot_a), WRITE_A_AGAIN);
+		GUEST_ASSERT_EQ(READ_ONCE(*cfg.slot_b), WRITE_B);
+
+		GUEST_DONE();
+	default:
+		GUEST_FAIL("Unexpected round %u", round_nr);
+		return;
+	}
+
+	round_nr++;
+	regs->pc = (u64)l2_probe;
+	regs->pstate = L2_PSTATE;
+}
+
+static void guest_code(void)
+{
+	GUEST_ASSERT_EQ(get_current_el(), 2);
+	GUEST_ASSERT(read_sysreg(hcr_el2) & HCR_EL2_E2H);
+
+	/*
+	 * With E2H=1, these EL1 accessors read L1's own configuration.
+	 * EL12 accessors configure the EL1 context used to execute L2.
+	 */
+	write_sysreg_s(read_sysreg(tcr_el1), SYS_TCR_EL12);
+	write_sysreg_s(read_sysreg(ttbr0_el1), SYS_TTBR0_EL12);
+	write_sysreg_s(read_sysreg(mair_el1), SYS_MAIR_EL12);
+	write_sysreg_s(read_sysreg(cpacr_el1), SYS_CPACR_EL12);
+	write_sysreg_s(read_sysreg(vbar_el1), SYS_VBAR_EL12);
+	write_sysreg_s(read_sysreg(sctlr_el1), SYS_SCTLR_EL12);
+	write_sysreg(cfg.l2_sp, sp_el1);
+	isb();
+
+	/*
+	 * These aliases remain mapped throughout the test. They allow
+	 * inspection of both backing pages independently of VNCR VA.
+	 */
+	WRITE_ONCE(*cfg.slot_a, SEED_A);
+	WRITE_ONCE(*cfg.slot_b, SEED_B);
+	dsb(ish);
+
+	/* Program the VNCR address exactly once. */
+	write_sysreg_s(cfg.vncr_va, SYS_VNCR_EL2);
+
+	/* VM=0, TGE=0; enable nested register redirection for L2. */
+	write_sysreg(L1_HCR, hcr_el2);
+	isb();
+
+	GUEST_ASSERT_EQ(read_sysreg(hcr_el2) &
+			(HCR_EL2_NV | HCR_EL2_NV2),
+			HCR_EL2_NV | HCR_EL2_NV2);
+	GUEST_ASSERT(!(read_sysreg(hcr_el2) & HCR_EL2_VM));
+
+	write_sysreg((u64)l2_probe, elr_el2);
+	write_sysreg(L2_PSTATE, spsr_el2);
+	isb();
+
+	asm volatile(
+		"mov x1, %0\n"
+		"eret\n"
+		:
+		: "r" (WRITE_A)
+		: "x1", "memory");
+
+	__builtin_unreachable();
+}
+
+/* Add a guest virtual alias for an existing guest physical page. */
+static u64 map_alias(struct kvm_vm *vm, u64 gpa)
+{
+	u64 va = vm_unused_gva_gap(vm, vm->page_size,
+				  KVM_UTIL_MIN_VADDR);
+
+	virt_map(vm, va, gpa, 1);
+	return va;
+}
+
+static void prepare_memory(struct kvm_vm *vm)
+{
+	u64 a = vm_alloc_page(vm);
+	u64 b = vm_alloc_page(vm);
+	u64 page_offset_mask = (u64)vm->page_size - 1;
+	u64 stack = vm_alloc_pages(vm, DEFAULT_STACK_PGS);
+	u64 ptep_gpa, ptep_alias;
+	u64 *ptep;
+
+	cfg.slot_a = (u64 *)(a + VNCR_TPIDR_OFFSET);
+	cfg.slot_b = (u64 *)(b + VNCR_TPIDR_OFFSET);
+	cfg.l2_sp = stack + DEFAULT_STACK_PGS * vm->page_size;
+
+	/* Initially, the separate VNCR virtual address maps page A. */
+	cfg.vncr_va = map_alias(vm, addr_gva2gpa(vm, a));
+
+	ptep = virt_get_pte_hva(vm, cfg.vncr_va);
+	cfg.pte_a = *ptep;
+	cfg.pte_b = *virt_get_pte_hva(vm, b);
+
+	TEST_ASSERT(cfg.pte_a != cfg.pte_b,
+		    "Backing pages must have different descriptors");
+	TEST_ASSERT((cfg.pte_a & (PTE_VALID | PTE_TYPE_PAGE)) ==
+		    (PTE_VALID | PTE_TYPE_PAGE),
+		    "VNCR mapping must use a leaf page descriptor");
+
+	/*
+	 * Map the page containing the descriptor into L1, so L1 can
+	 * perform its own page-table update and architectural TLBI.
+	 */
+	ptep_gpa = addr_hva2gpa(vm, ptep);
+	ptep_alias = map_alias(vm, ptep_gpa & ~page_offset_mask);
+	cfg.vncr_ptep = (u64 *)(ptep_alias + (ptep_gpa & page_offset_mask));
+
+	sync_global_to_guest(vm, cfg);
+}
+
+int main(void)
+{
+	struct kvm_vm *vm;
+	struct kvm_vcpu *vcpu;
+	struct ucall uc;
+	u64 mmfr4, cmd;
+
+	TEST_REQUIRE(test_supports_el2());
+
+	vm = __vm_create_with_one_vcpu(&vcpu, 32, guest_code);
+	TEST_REQUIRE(vcpu_has_el2(vcpu));
+
+	/*
+	 * Target KVM's exposed NV2-only-or-later nested capability.
+	 * Merely having a virtual EL2 is insufficient for this test.
+	 */
+	mmfr4 = vcpu_get_reg(vcpu,
+			    KVM_ARM64_SYS_REG(SYS_ID_AA64MMFR4_EL1));
+	TEST_REQUIRE(FIELD_GET(ID_AA64MMFR4_EL1_NV_frac, mmfr4) >=
+		     ID_AA64MMFR4_EL1_NV_frac_NV2_ONLY);
+
+	vm_init_descriptor_tables(vm);
+	vcpu_init_descriptor_tables(vcpu);
+	vm_install_sync_handler(vm, VECTOR_SYNC_LOWER_64,
+				ESR_ELx_EC_HVC64, handle_l2_hvc);
+
+	prepare_memory(vm);
+
+	for (;;) {
+		vcpu_run(vcpu);
+		cmd = get_ucall(vcpu, &uc);
+
+		switch (cmd) {
+		case UCALL_PRINTF:
+			REPORT_GUEST_PRINTF(uc);
+			break;
+		case UCALL_ABORT:
+			REPORT_GUEST_ASSERT(uc);
+			break;
+		case UCALL_DONE:
+			pr_info("PASS: VNCR accesses followed A -> B -> A mappings; "
+		"inactive backing slots remained unchanged\n");
+			kvm_vm_free(vm);
+			return 0;
+		default:
+			TEST_FAIL("Unexpected ucall %lu, KVM exit reason %u",
+				  cmd, vcpu->run->exit_reason);
+		}
+	}
+}
---
base-commit: c03c1a14507dfaf8b856c2608f9ac216021fc222
change-id: 20261022-b4-vncr-mapping-test-3a18cb49a2e2

Best regards,
--  
Itaru Kitayama [off-list ref]

Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help