From: Zenghui Yu <zenghui.yu@linux.dev>
To: Oliver Upton <oupton@kernel.org>
Cc: kvmarm@lists.linux.dev, Marc Zyngier <maz@kernel.org>,
Joey Gouly <joey.gouly@arm.com>,
Suzuki K Poulose <suzuki.poulose@arm.com>,
Zenghui Yu <yuzenghui@huawei.com>
Subject: Re: [PATCH v3 15/15] KVM: arm64: selftests: Add test for AT emulation
Date: Sat, 28 Feb 2026 17:43:59 +0800 [thread overview]
Message-ID: <b951dcfb-0ad1-4d7b-b6ce-d54b272dd9be@linux.dev> (raw)
In-Reply-To: <20251124190158.177318-16-oupton@kernel.org>
Hi Oliver,
On 11/25/25 3:01 AM, Oliver Upton wrote:
> Add a basic test for AT emulation in the EL2&0 and EL1&0 translation
> regimes.
>
> Signed-off-by: Oliver Upton <oupton@kernel.org>
> ---
> tools/testing/selftests/kvm/Makefile.kvm | 1 +
> tools/testing/selftests/kvm/arm64/at.c | 166 ++++++++++++++++++
> .../testing/selftests/kvm/include/kvm_util.h | 1 +
> tools/testing/selftests/kvm/lib/kvm_util.c | 10 ++
> 4 files changed, 178 insertions(+)
> create mode 100644 tools/testing/selftests/kvm/arm64/at.c
>
> diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
> index 148d427ff24b..81b3aa54678a 100644
> --- a/tools/testing/selftests/kvm/Makefile.kvm
> +++ b/tools/testing/selftests/kvm/Makefile.kvm
> @@ -156,6 +156,7 @@ TEST_GEN_PROGS_EXTENDED_x86 += x86/nx_huge_pages_test
> TEST_GEN_PROGS_arm64 = $(TEST_GEN_PROGS_COMMON)
> TEST_GEN_PROGS_arm64 += arm64/aarch32_id_regs
> 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/host_sve
> diff --git a/tools/testing/selftests/kvm/arm64/at.c b/tools/testing/selftests/kvm/arm64/at.c
> new file mode 100644
> index 000000000000..acecb6ab5071
> --- /dev/null
> +++ b/tools/testing/selftests/kvm/arm64/at.c
> @@ -0,0 +1,166 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * at - Test for KVM's AT emulation in the EL2&0 and EL1&0 translation regimes.
> + */
> +#include "kvm_util.h"
> +#include "processor.h"
> +#include "test_util.h"
> +#include "ucall.h"
> +
> +#include <asm/sysreg.h>
> +
> +#define TEST_ADDR 0x80000000
> +
> +enum {
> + CLEAR_ACCESS_FLAG,
> + TEST_ACCESS_FLAG,
> +};
> +
> +static u64 *ptep_hva;
> +
> +#define copy_el2_to_el1(reg) \
> + write_sysreg_s(read_sysreg_s(SYS_##reg##_EL1), SYS_##reg##_EL12)
> +
> +/* Yes, this is an ugly hack */
> +#define __at(op, addr) write_sysreg_s(addr, op)
> +
> +#define test_at_insn(op, expect_fault) \
> +do { \
> + u64 par, fsc; \
> + bool fault; \
> + \
> + GUEST_SYNC(CLEAR_ACCESS_FLAG); \
> + \
> + __at(OP_AT_##op, TEST_ADDR); \
> + isb(); \
> + par = read_sysreg(par_el1); \
> + \
> + fault = par & SYS_PAR_EL1_F; \
> + fsc = FIELD_GET(SYS_PAR_EL1_FST, par); \
> + \
> + __GUEST_ASSERT((expect_fault) == fault, \
> + "AT "#op": %sexpected fault (par: %lx)1", \
> + (expect_fault) ? "" : "un", par); \
> + if ((expect_fault)) { \
> + __GUEST_ASSERT(fsc == ESR_ELx_FSC_ACCESS_L(3), \
> + "AT "#op": expected access flag fault (par: %lx)", \
> + par); \
> + } else { \
> + GUEST_ASSERT_EQ(FIELD_GET(SYS_PAR_EL1_ATTR, par), MAIR_ATTR_NORMAL); \
> + GUEST_ASSERT_EQ(FIELD_GET(SYS_PAR_EL1_SH, par), PTE_SHARED >> 8); \
> + GUEST_ASSERT_EQ(par & SYS_PAR_EL1_PA, TEST_ADDR); \
> + GUEST_SYNC(TEST_ACCESS_FLAG); \
> + } \
> +} while (0)
> +
> +static void test_at(bool expect_fault)
> +{
> + test_at_insn(S1E2R, expect_fault);
> + test_at_insn(S1E2W, expect_fault);
> +
> + /* Reuse the stage-1 MMU context from EL2 at EL1 */
> + copy_el2_to_el1(SCTLR);
> + copy_el2_to_el1(MAIR);
> + copy_el2_to_el1(TCR);
> + copy_el2_to_el1(TTBR0);
> + copy_el2_to_el1(TTBR1);
> +
> + /* Disable stage-2 translation and enter a non-host context */
> + write_sysreg(0, vtcr_el2);
> + write_sysreg(0, vttbr_el2);
> + sysreg_clear_set(hcr_el2, HCR_EL2_TGE | HCR_EL2_VM, 0);
> + isb();
> +
> + test_at_insn(S1E1R, expect_fault);
> + test_at_insn(S1E1W, expect_fault);
> +}
> +
> +static void guest_code(void)
> +{
> + sysreg_clear_set(tcr_el1, TCR_HA, 0);
> + isb();
> +
> + test_at(true);
> +
> + if (!SYS_FIELD_GET(ID_AA64MMFR1_EL1, HAFDBS, read_sysreg(id_aa64mmfr1_el1)))
> + GUEST_DONE();
> +
> + /*
> + * KVM's software PTW makes the implementation choice that the AT
> + * instruction sets the access flag.
> + */
> + sysreg_clear_set(tcr_el1, 0, TCR_HA);
> + isb();
> + test_at(false);
> +
> + GUEST_DONE();
> +}
> +
> +static void handle_sync(struct kvm_vcpu *vcpu, struct ucall *uc)
> +{
> + switch (uc->args[1]) {
> + case CLEAR_ACCESS_FLAG:
> + /*
> + * Delete + reinstall the memslot to invalidate stage-2
> + * mappings of the stage-1 page tables, forcing KVM to
> + * use the 'slow' AT emulation path.
Once the guest continues executing instructions, we would get a stage 2
translation fault (on a S1 PTW) immediately and re-build the stage 2
mappings for the S1 page tables. Depends on how much we have re-built
for S1 page tables, the "forcing KVM to use the 'slow' AT emulation
path" may not always be true.
I tested it in a QEMU guest (with "-machine virt,virtualization=on \
-cpu max -accel tcg", so it's essentially a NV2 capable HW!) and it
failed at the first test_at_insn(S1E2R, expect_fault):
[root@localhost arm64]# getconf PAGESIZE
65536
[root@localhost arm64]# ./at
Random seed: 0x6b8b4567
__vm_create: mode='PA-bits:40, VA-bits:48, 4K pages' type='0', pages='672'
==== Test Assertion Failure ====
arm64/at.c:58: (expect_fault) == fault
pid=824 tid=824 errno=4 - Interrupted system call
1 0x0000000000402d8f: run_test at at.c:137
2 0x00000000004020e3: main at at.c:162
3 0x0000ffffb07daf3b: ?? ??:0
4 0x0000ffffb07db007: ?? ??:0
5 0x000000000040222f: _start at ??:?
AT S1E2R: expected fault (par: ff00000080000b80)1
because in my test, the S1 page tables used to translate TEST_ADDR are
located at
level 0: 0x180000
level 1: 0x181000
level 2: 0x187000
level 3: 0x188000
All of them had already been re-mapped in stage 2 [*] (in the same 64KB
physical page) before KVM started to emulate AT S1E2R. The AT_S1E1R
(fast path) will successfully get the translation result and it's IMP
DEF that whether an access flag fault will be triggered (QEMU chooses to
not fault). Whatever, [*] is out of expect and result in the failure.
I have no idea that if we can actually force the slow emulation path.
Thanks,
Zenghui
> + *
> + * This and clearing the access flag from host userspace
> + * ensures that the access flag cannot be set speculatively
> + * and is reliably cleared at the time of the AT instruction.
> + */
> + clear_bit(__ffs(PTE_AF), ptep_hva);
> + vm_mem_region_reload(vcpu->vm, vcpu->vm->memslots[MEM_REGION_PT]);
> + break;
> + case TEST_ACCESS_FLAG:
> + TEST_ASSERT(test_bit(__ffs(PTE_AF), ptep_hva),
> + "Expected access flag to be set (desc: %lu)", *ptep_hva);
> + break;
> + default:
> + TEST_FAIL("Unexpected SYNC arg: %lu", uc->args[1]);
> + }
> +}
> +
> +static void run_test(struct kvm_vcpu *vcpu)
> +{
> + struct ucall uc;
> +
> + while (true) {
> + vcpu_run(vcpu);
> + switch (get_ucall(vcpu, &uc)) {
> + case UCALL_DONE:
> + return;
> + case UCALL_SYNC:
> + handle_sync(vcpu, &uc);
> + continue;
> + case UCALL_ABORT:
> + REPORT_GUEST_ASSERT(uc);
> + return;
> + default:
> + TEST_FAIL("Unexpeced ucall: %lu", uc.cmd);
> + }
> + }
> +}
> +
> +int main(void)
> +{
> + struct kvm_vcpu_init init;
> + struct kvm_vcpu *vcpu;
> + struct kvm_vm *vm;
> +
> + 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);
> +
> + virt_map(vm, TEST_ADDR, TEST_ADDR, 1);
> + ptep_hva = virt_get_pte_hva_at_level(vm, TEST_ADDR, 3);
> + run_test(vcpu);
> +
> + kvm_vm_free(vm);
> + return 0;
> +}
> diff --git a/tools/testing/selftests/kvm/include/kvm_util.h b/tools/testing/selftests/kvm/include/kvm_util.h
> index d3f3e455c031..41467dad9178 100644
> --- a/tools/testing/selftests/kvm/include/kvm_util.h
> +++ b/tools/testing/selftests/kvm/include/kvm_util.h
> @@ -715,6 +715,7 @@ static inline bool vm_arch_has_protected_memory(struct kvm_vm *vm)
> #endif
>
> void vm_mem_region_set_flags(struct kvm_vm *vm, uint32_t slot, uint32_t flags);
> +void vm_mem_region_reload(struct kvm_vm *vm, uint32_t slot);
> void vm_mem_region_move(struct kvm_vm *vm, uint32_t slot, uint64_t new_gpa);
> void vm_mem_region_delete(struct kvm_vm *vm, uint32_t slot);
> struct kvm_vcpu *__vm_vcpu_add(struct kvm_vm *vm, uint32_t vcpu_id);
> diff --git a/tools/testing/selftests/kvm/lib/kvm_util.c b/tools/testing/selftests/kvm/lib/kvm_util.c
> index 1a93d6361671..d6538bb17740 100644
> --- a/tools/testing/selftests/kvm/lib/kvm_util.c
> +++ b/tools/testing/selftests/kvm/lib/kvm_util.c
> @@ -1201,6 +1201,16 @@ void vm_mem_region_set_flags(struct kvm_vm *vm, uint32_t slot, uint32_t flags)
> ret, errno, slot, flags);
> }
>
> +void vm_mem_region_reload(struct kvm_vm *vm, uint32_t slot)
> +{
> + struct userspace_mem_region *region = memslot2region(vm, slot);
> + struct kvm_userspace_memory_region2 tmp = region->region;
> +
> + tmp.memory_size = 0;
> + vm_ioctl(vm, KVM_SET_USER_MEMORY_REGION2, &tmp);
> + vm_ioctl(vm, KVM_SET_USER_MEMORY_REGION2, ®ion->region);
> +}
> +
> /*
> * VM Memory Region Move
> *
next prev parent reply other threads:[~2026-02-28 9:44 UTC|newest]
Thread overview: 21+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-11-24 19:01 [PATCH v3 00/15] KVM: arm64: nv: Implement FEAT_XNX and FEAT_HAF Oliver Upton
2025-11-24 19:01 ` [PATCH v3 01/15] arm64: Detect FEAT_XNX Oliver Upton
2025-11-24 19:01 ` [PATCH v3 02/15] KVM: arm64: Add support for FEAT_XNX stage-2 permissions Oliver Upton
2025-11-24 19:01 ` [PATCH v3 03/15] KVM: arm64: nv: Forward FEAT_XNX permissions to the shadow stage-2 Oliver Upton
2025-11-24 19:01 ` [PATCH v3 04/15] KVM: arm64: Teach ptdump about FEAT_XNX permissions Oliver Upton
2025-11-25 17:39 ` Nathan Chancellor
2025-11-24 19:01 ` [PATCH v3 05/15] KVM: arm64: nv: Advertise support for FEAT_XNX Oliver Upton
2025-11-24 19:01 ` [PATCH v3 06/15] KVM: arm64: Call helper for reading descriptors directly Oliver Upton
2025-11-24 19:01 ` [PATCH v3 07/15] KVM: arm64: nv: Stop passing vCPU through void ptr in S2 PTW Oliver Upton
2025-11-24 19:01 ` [PATCH v3 08/15] KVM: arm64: Handle endianness in read helper for emulated PTW Oliver Upton
2025-11-24 19:01 ` [PATCH v3 09/15] KVM: arm64: nv: Use pgtable definitions in stage-2 walk Oliver Upton
2025-11-24 19:01 ` [PATCH v3 10/15] KVM: arm64: Add helper for swapping guest descriptor Oliver Upton
2025-11-24 19:01 ` [PATCH v3 11/15] KVM: arm64: Propagate PTW errors up to AT emulation Oliver Upton
2025-11-24 19:01 ` [PATCH v3 12/15] KVM: arm64: Implement HW access flag management in stage-1 SW PTW Oliver Upton
2025-11-24 19:01 ` [PATCH v3 13/15] KVM: arm64: nv: Implement HW access flag management in stage-2 " Oliver Upton
2025-11-24 19:01 ` [PATCH v3 14/15] KVM: arm64: nv: Expose hardware access flag management to NV guests Oliver Upton
2025-11-24 19:01 ` [PATCH v3 15/15] KVM: arm64: selftests: Add test for AT emulation Oliver Upton
2026-02-28 9:43 ` Zenghui Yu [this message]
2026-03-17 12:51 ` Zenghui Yu
2025-11-24 21:25 ` [PATCH v3 00/15] KVM: arm64: nv: Implement FEAT_XNX and FEAT_HAF Marc Zyngier
2025-11-24 22:44 ` Oliver Upton
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=b951dcfb-0ad1-4d7b-b6ce-d54b272dd9be@linux.dev \
--to=zenghui.yu@linux.dev \
--cc=joey.gouly@arm.com \
--cc=kvmarm@lists.linux.dev \
--cc=maz@kernel.org \
--cc=oupton@kernel.org \
--cc=suzuki.poulose@arm.com \
--cc=yuzenghui@huawei.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox