From: Venkata Rao Kakani <venkata.kakani@oss.qualcomm.com>
To: Fuad Tabba <fuad.tabba@linux.dev>, Marc Zyngier <maz@kernel.org>,
Oliver Upton <oupton@kernel.org>,
kvmarm@lists.linux.dev, linux-arm-kernel@lists.infradead.org
Cc: Joey Gouly <joey.gouly@arm.com>,
Suzuki K Poulose <suzuki.poulose@arm.com>,
Zenghui Yu <yuzenghui@huawei.com>,
Steffen Eiden <seiden@linux.ibm.com>,
Catalin Marinas <catalin.marinas@arm.com>,
Will Deacon <will@kernel.org>,
Mark Rutland <mark.rutland@arm.com>,
Quentin Perret <qperret@google.com>,
Vincent Donnefort <vdonnefort@google.com>,
Wei-Lin Chang <weilin.chang@arm.com>,
Fuad Tabba <tabba@google.com>,
linux-kernel@vger.kernel.org
Subject: Re: [PATCH v2 4/4] KVM: arm64: selftests: Check a feature hidden in an ID register is UNDEF
Date: Mon, 28 Sep 2026 14:50:11 +0530 [thread overview]
Message-ID: <4095827e-dbbb-4ab9-8610-1bc317aa096f@oss.qualcomm.com> (raw)
In-Reply-To: <20260928064643.3265087-5-fuad.tabba@linux.dev>
On 28-09-2026 12:16 pm, Fuad Tabba wrote:
> Userspace can hide a feature from a guest by clearing its field in a
> writable ID register, and KVM then makes the feature's instructions
> UNDEFINED in the guest by trapping or disabling them. No selftest checks
> that.
>
> Add a test that runs the instruction of each of TLBI OS, MOPS, TCR2_EL1
> and FPMR once with its field as advertised and once with it cleared, and
> expects an UNDEF only when cleared. A feature the vCPU doesn't advertise
> is skipped, as is hidden TLBI OS on a CPU with neither FGT nor
> FEAT_EVT2, where KVM can't trap it.
>
> Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
> ---
> tools/testing/selftests/kvm/Makefile.kvm | 1 +
> .../selftests/kvm/arm64/hidden_features.c | 184 ++++++++++++++++++
> 2 files changed, 185 insertions(+)
> create mode 100644 tools/testing/selftests/kvm/arm64/hidden_features.c
>
> diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
> index 96bab7002d39e..864fdca7f362e 100644
> --- a/tools/testing/selftests/kvm/Makefile.kvm
> +++ b/tools/testing/selftests/kvm/Makefile.kvm
> @@ -194,6 +194,7 @@ TEST_GEN_PROGS_arm64 += arm64/vgic_v5
> TEST_GEN_PROGS_arm64 += arm64/vpmu_counter_access
> TEST_GEN_PROGS_arm64 += arm64/no-vgic
> TEST_GEN_PROGS_arm64 += arm64/idreg-idst
> +TEST_GEN_PROGS_arm64 += arm64/hidden_features
> TEST_GEN_PROGS_arm64 += arm64/kvm-uuid
> TEST_GEN_PROGS_arm64 += access_tracking_perf_test
> TEST_GEN_PROGS_arm64 += arch_timer
> diff --git a/tools/testing/selftests/kvm/arm64/hidden_features.c b/tools/testing/selftests/kvm/arm64/hidden_features.c
> new file mode 100644
> index 0000000000000..194d746e7605e
> --- /dev/null
> +++ b/tools/testing/selftests/kvm/arm64/hidden_features.c
> @@ -0,0 +1,184 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * hidden_features - Check that a feature's instruction runs in the guest when
> + * its ID register field is advertised, and is UNDEFINED when userspace clears
> + * the field.
> + *
> + * Copyright (c) 2026 Google LLC
> + * Author: Fuad Tabba <fuad.tabba@linux.dev>
> + */
> +#include "kvm_util.h"
> +#include "processor.h"
> +#include "test_util.h"
> +
> +static volatile bool undef;
> +
> +static void guest_tlbi_os(void)
> +{
> + /* tlbi vmalle1os */
> + asm volatile("sys #0, c8, c1, #0\n\tdsb ish\n\tisb" ::: "memory");
> +}
> +
> +static void guest_mops(void)
> +{
> + register u64 *d asm("x0");
> + register u64 n asm("x1");
> + register u64 s asm("x2");
> + u64 buf[8];
> +
> + d = buf;
> + n = sizeof(buf);
> + s = 0;
> + /* setp [x0]!, x1!, x2; setm; sete */
> + asm volatile(".inst 0x19c20420\n\t.inst 0x19c24420\n\t.inst 0x19c28420"
> + : "+r"(d), "+r"(n) : "r"(s) : "cc", "memory");
> +}
> +
> +static void guest_tcr2(void)
> +{
> + read_sysreg_s(SYS_TCR2_EL1);
> +}
> +
> +static void guest_fpmr(void)
> +{
> + read_sysreg_s(SYS_FPMR);
> +}
> +
> +struct feature {
> + const char *name;
> + u64 id_reg;
> + u64 mask;
> + u8 shift;
> + u64 min;
> + void (*insn)(void);
> + bool (*trappable)(struct kvm_vcpu *vcpu);
> +};
> +
> +/* Without FGT, KVM traps a hidden TLBI OS only through HCR_EL2.TTLBOS (FEAT_EVT2). */
> +static bool tlbi_os_trappable(struct kvm_vcpu *vcpu)
> +{
> + u64 mmfr0 = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64MMFR0_EL1));
> + u64 mmfr2 = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64MMFR2_EL1));
> +
> + return SYS_FIELD_GET(ID_AA64MMFR0_EL1, FGT, mmfr0) >= ID_AA64MMFR0_EL1_FGT_IMP ||
> + SYS_FIELD_GET(ID_AA64MMFR2_EL1, EVT, mmfr2) >= ID_AA64MMFR2_EL1_EVT_TTLBxS;
> +}
> +
> +#define FEATURE(n, reg, field, min_val, fn, trap) \
> +{ \
> + .name = n, \
> + .id_reg = SYS_##reg, \
> + .mask = reg##_##field##_MASK, \
> + .shift = reg##_##field##_SHIFT, \
> + .min = reg##_##field##_##min_val, \
> + .insn = fn, \
> + .trappable = trap, \
> +}
> +
> +static const struct feature features[] = {
> + FEATURE("TLBI OS", ID_AA64ISAR0_EL1, TLB, OS, guest_tlbi_os, tlbi_os_trappable),
> + FEATURE("MOPS", ID_AA64ISAR2_EL1, MOPS, IMP, guest_mops, NULL),
> + FEATURE("TCR2_EL1", ID_AA64MMFR3_EL1, TCRX, IMP, guest_tcr2, NULL),
> + FEATURE("FPMR", ID_AA64PFR2_EL1, FPMR, IMP, guest_fpmr, NULL),
> +};
> +
> +static void guest_code(const struct feature *feat)
> +{
> + undef = false;
> + feat->insn();
> + GUEST_SYNC(undef);
> + GUEST_DONE();
> +}
> +
> +static void guest_undef_handler(struct ex_regs *regs)
> +{
> + undef = true;
> + regs->pc += 4;
> +}
> +
> +static bool run(const struct feature *feat, bool hide)
> +{
> + struct kvm_vcpu *vcpu;
> + struct kvm_vm *vm;
> + struct ucall uc;
> + bool got = false;
> + u64 val;
> +
> + vm = vm_create_with_one_vcpu(&vcpu, (void *)guest_code);
> + vm_init_descriptor_tables(vm);
> + vcpu_init_descriptor_tables(vcpu);
> + vm_install_sync_handler(vm, VECTOR_SYNC_CURRENT, ESR_ELx_EC_UNKNOWN, guest_undef_handler);
> + vcpu_args_set(vcpu, 1, feat);
> +
> + if (hide) {
> + val = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(feat->id_reg));
> + vcpu_set_reg(vcpu, KVM_ARM64_SYS_REG(feat->id_reg), val & ~feat->mask);
> + }
> +
> + for (;;) {
> + vcpu_run(vcpu);
> + switch (get_ucall(vcpu, &uc)) {
> + case UCALL_SYNC:
> + got = uc.args[1];
> + break;
> + case UCALL_ABORT:
> + REPORT_GUEST_ASSERT(uc);
> + break;
> + case UCALL_DONE:
> + kvm_vm_free(vm);
> + return got;
> + default:
> + TEST_FAIL("Unknown ucall %lu", uc.cmd);
> + }
> + }
Return missing?
> +}
> +
> +static void probe_feature(const struct feature *feat, bool *present, bool *trappable)
> +{
> + struct kvm_vcpu *vcpu;
> + struct kvm_vm *vm;
> + u64 val;
> +
> + vm = vm_create_with_one_vcpu(&vcpu, NULL);
> + val = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(feat->id_reg));
> + *present = ((val & feat->mask) >> feat->shift) >= feat->min;
> + *trappable = !feat->trappable || feat->trappable(vcpu);
> + kvm_vm_free(vm);
> +}
> +
> +int main(void)
> +{
> + const struct feature *feat;
> + bool present, trappable;
> + int i;
> +
> + test_disable_default_vgic();
> +
> + ksft_print_header();
> + ksft_set_plan(ARRAY_SIZE(features) * 2);
> +
> + for (i = 0; i < ARRAY_SIZE(features); i++) {
> + feat = &features[i];
> +
> + probe_feature(feat, &present, &trappable);
> + if (!present) {
> + ksft_test_result_skip("%s advertised, not supported\n", feat->name);
> + ksft_test_result_skip("%s hidden, not supported\n", feat->name);
> + continue;
> + }
> +
> + if (run(feat, false))
> + ksft_test_result_fail("%s advertised, UNDEF\n", feat->name);
> + else
> + ksft_test_result_pass("%s advertised\n", feat->name);
> +
> + if (!trappable)
> + ksft_test_result_skip("%s hidden, not trappable\n", feat->name);
> + else if (run(feat, true))
> + ksft_test_result_pass("%s hidden\n", feat->name);
> + else
> + ksft_test_result_fail("%s hidden, no UNDEF\n", feat->name);
> + }
> +
> + ksft_finished();
> +}
next prev parent reply other threads:[~2026-09-28 9:20 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-28 6:46 [PATCH v2 0/4] KVM: arm64: Fix HCR_EL2 for non-protected VMs in pKVM Fuad Tabba
2026-09-28 6:46 ` [PATCH v2 1/4] KVM: arm64: Don't WARN on an unsupported TLBI OS from vEL1 Fuad Tabba
2026-09-28 17:05 ` Oliver Upton
2026-09-28 19:17 ` Fuad Tabba
2026-09-28 6:46 ` [PATCH v2 2/4] KVM: arm64: Clear HCR_EL2.RW for 32-bit non-protected vCPUs Fuad Tabba
2026-09-28 6:46 ` [PATCH v2 3/4] KVM: arm64: Use the host's HCR_EL2 for non-protected VMs in pKVM Fuad Tabba
2026-09-28 6:46 ` [PATCH v2 4/4] KVM: arm64: selftests: Check a feature hidden in an ID register is UNDEF Fuad Tabba
2026-09-28 9:20 ` Venkata Rao Kakani [this message]
2026-09-28 9:28 ` Fuad Tabba
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=4095827e-dbbb-4ab9-8610-1bc317aa096f@oss.qualcomm.com \
--to=venkata.kakani@oss.qualcomm.com \
--cc=catalin.marinas@arm.com \
--cc=fuad.tabba@linux.dev \
--cc=joey.gouly@arm.com \
--cc=kvmarm@lists.linux.dev \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=mark.rutland@arm.com \
--cc=maz@kernel.org \
--cc=oupton@kernel.org \
--cc=qperret@google.com \
--cc=seiden@linux.ibm.com \
--cc=suzuki.poulose@arm.com \
--cc=tabba@google.com \
--cc=vdonnefort@google.com \
--cc=weilin.chang@arm.com \
--cc=will@kernel.org \
--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
all inboxes | Powered by JetHome®