* [PATCH v1 1/4] KVM: arm64: Don't WARN on an unsupported TLBI OS from vEL1
2026-09-25 9:06 [PATCH v1 0/4] KVM: arm64: Fix HCR_EL2 for non-protected VMs in pKVM Fuad Tabba
@ 2026-09-25 9:06 ` Fuad Tabba
2026-09-25 9:06 ` [PATCH v1 2/4] KVM: arm64: Clear HCR_EL2.RW for 32-bit non-protected vCPUs Fuad Tabba
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Fuad Tabba @ 2026-09-25 9:06 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel
Cc: Joey Gouly, Suzuki K Poulose, Zenghui Yu, Steffen Eiden,
Catalin Marinas, Will Deacon, Mark Rutland, Quentin Perret,
Vincent Donnefort, Fuad Tabba, linux-kernel
A VMM can trigger the WARN_ON(!vcpu_is_el2()) in handle_tlbi_el1() on a
CPU with FEAT_EVT2 and FEAT_TLBIOS but no FEAT_FGT, by setting
ID_AA64ISAR0_EL1.TLB to NI and having the guest execute TLBI VMALLE1OS.
Without FGT, HCR_EL2.TTLBOS traps the instruction. KVM records no FGT
trap information on such a host, so triage_sysreg_trap() doesn't UNDEF
it. The instruction reaches handle_tlbi_el1(), which warns before
checking whether the guest supports it.
Check support before the warning.
Fixes: 0cb8aae226768 ("KVM: arm64: nv: Add handling of outer-shareable TLBI operations")
Cc: stable@vger.kernel.org
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
arch/arm64/kvm/sys_regs.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index 44aae52c473d7..0ce29ce678b08 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -4257,6 +4257,10 @@ static bool handle_tlbi_el1(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
{
u32 sys_encoding = sys_insn(p->Op0, p->Op1, p->CRn, p->CRm, p->Op2);
+ /* Without FGT, HCR_EL2.TTLBOS also traps a hidden TLBI OS from vEL1 */
+ if (!kvm_supported_tlbi_s1e1_op(vcpu, sys_encoding))
+ return undef_access(vcpu, p, r);
+
/*
* If we're here, this is because we've trapped on a EL1 TLBI
* instruction that affects the EL1 translation regime while
@@ -4279,9 +4283,6 @@ static bool handle_tlbi_el1(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
WARN_ON(!vcpu_is_el2(vcpu));
- if (!kvm_supported_tlbi_s1e1_op(vcpu, sys_encoding))
- return undef_access(vcpu, p, r);
-
if (vcpu_el2_e2h_is_set(vcpu) && vcpu_el2_tge_is_set(vcpu)) {
kvm_handle_s1e2_tlbi(vcpu, sys_encoding, p->regval);
return true;
--
2.39.5
^ permalink raw reply [flat|nested] 5+ messages in thread* [PATCH v1 2/4] KVM: arm64: Clear HCR_EL2.RW for 32-bit non-protected vCPUs
2026-09-25 9:06 [PATCH v1 0/4] KVM: arm64: Fix HCR_EL2 for non-protected VMs in pKVM Fuad Tabba
2026-09-25 9:06 ` [PATCH v1 1/4] KVM: arm64: Don't WARN on an unsupported TLBI OS from vEL1 Fuad Tabba
@ 2026-09-25 9:06 ` Fuad Tabba
2026-09-25 9:06 ` [PATCH v1 3/4] KVM: arm64: Use the host's HCR_EL2 for non-protected VMs in pKVM Fuad Tabba
2026-09-25 9:06 ` [PATCH v1 4/4] KVM: arm64: selftests: Check a feature hidden in an ID register is UNDEF Fuad Tabba
3 siblings, 0 replies; 5+ messages in thread
From: Fuad Tabba @ 2026-09-25 9:06 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel
Cc: Joey Gouly, Suzuki K Poulose, Zenghui Yu, Steffen Eiden,
Catalin Marinas, Will Deacon, Mark Rutland, Quentin Perret,
Vincent Donnefort, Fuad Tabba, linux-kernel
In pKVM, KVM_RUN on a vCPU created with KVM_ARM_VCPU_EL1_32BIT fails
with KVM_EXIT_FAIL_ENTRY. pkvm_vcpu_reset_hcr(), pKVM's EL2 counterpart
of vcpu_set_hcr(), never clears HCR_EL2.RW, so the first ERET into the
vCPU is an illegal exception return. Protected VMs are AArch64-only, so
only non-protected VMs are affected.
Clear RW for 32-bit vCPUs. The vCPU's features come from the host, and
on a CPU without AArch32 EL1 a cleared RW would make EL2 switch
registers that are UNDEFINED there. Clear it only when the system has
AArch32 EL1, the same check system_supported_vcpu_features() makes on
the host.
Fixes: b56680de9c648 ("KVM: arm64: Initialize trap register values in hyp in pKVM")
Cc: stable@vger.kernel.org
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
arch/arm64/kvm/hyp/nvhe/pkvm.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/arch/arm64/kvm/hyp/nvhe/pkvm.c b/arch/arm64/kvm/hyp/nvhe/pkvm.c
index 459bd9eb7e4bc..affc9595fda20 100644
--- a/arch/arm64/kvm/hyp/nvhe/pkvm.c
+++ b/arch/arm64/kvm/hyp/nvhe/pkvm.c
@@ -54,6 +54,14 @@ static void pkvm_vcpu_reset_hcr(struct kvm_vcpu *vcpu)
else
vcpu->arch.hcr_el2 |= HCR_TID2;
+ /*
+ * At EL2, vcpu_el1_is_32bit() reads HCR_EL2.RW, and EL2 switches the
+ * *32_EL2 registers when it returns true; they're UNDEFINED without AArch32 EL1.
+ */
+ if (vcpu_has_feature(vcpu, KVM_ARM_VCPU_EL1_32BIT) &&
+ cpus_have_final_cap(ARM64_HAS_32BIT_EL1))
+ vcpu->arch.hcr_el2 &= ~HCR_RW;
+
if (vcpu_has_ptrauth(vcpu))
vcpu->arch.hcr_el2 |= (HCR_API | HCR_APK);
--
2.39.5
^ permalink raw reply [flat|nested] 5+ messages in thread* [PATCH v1 3/4] KVM: arm64: Use the host's HCR_EL2 for non-protected VMs in pKVM
2026-09-25 9:06 [PATCH v1 0/4] KVM: arm64: Fix HCR_EL2 for non-protected VMs in pKVM Fuad Tabba
2026-09-25 9:06 ` [PATCH v1 1/4] KVM: arm64: Don't WARN on an unsupported TLBI OS from vEL1 Fuad Tabba
2026-09-25 9:06 ` [PATCH v1 2/4] KVM: arm64: Clear HCR_EL2.RW for 32-bit non-protected vCPUs Fuad Tabba
@ 2026-09-25 9:06 ` Fuad Tabba
2026-09-25 9:06 ` [PATCH v1 4/4] KVM: arm64: selftests: Check a feature hidden in an ID register is UNDEF Fuad Tabba
3 siblings, 0 replies; 5+ messages in thread
From: Fuad Tabba @ 2026-09-25 9:06 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel
Cc: Joey Gouly, Suzuki K Poulose, Zenghui Yu, Steffen Eiden,
Catalin Marinas, Will Deacon, Mark Rutland, Quentin Perret,
Vincent Donnefort, Fuad Tabba, linux-kernel
In pKVM, a non-protected VM gets only TWI, TWE and VSE from the HCR_EL2
the host computes for it. EL2 sets the rest in pkvm_vcpu_reset_hcr(),
which covers only part of vcpu_set_hcr(). On a CPU with MTE the VM can
then read GMID_EL1, on one without FGT it can execute a TLBI OS its ID
registers hide, and it never gets the host's TVM, VI or VF.
Use the host's HCR_EL2 on every entry instead, except for the bits EL2
owns. The other bits only control what the VM's own execution traps on
and which virtual exceptions are pending for it. The host computes them
from the vCPU's features, ID registers and flags, which EL2 already
takes from the host for a non-protected VM, as it takes MDCR_EL2,
HCRX_EL2 and the fine-grained traps.
ATA, an owned bit, stays clear, as pKVM doesn't support MTE for any
guest. TID2 and TID4 move to pvm_init_traps_hcr(), since EL2 now sets
them only for a protected VM. A protected VM's HCR_EL2 is unchanged.
Fixes: b56680de9c648 ("KVM: arm64: Initialize trap register values in hyp in pKVM")
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
arch/arm64/include/asm/kvm_arm.h | 1 +
arch/arm64/kvm/hyp/include/nvhe/pkvm.h | 13 +++++++++++++
arch/arm64/kvm/hyp/nvhe/hyp-main.c | 7 ++++---
arch/arm64/kvm/hyp/nvhe/pkvm.c | 18 ++++++++----------
4 files changed, 26 insertions(+), 13 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_arm.h b/arch/arm64/include/asm/kvm_arm.h
index 4bfbd827c5aa7..8d187650e463e 100644
--- a/arch/arm64/include/asm/kvm_arm.h
+++ b/arch/arm64/include/asm/kvm_arm.h
@@ -30,6 +30,7 @@
#define HCR_AMVOFFEN __HCR(AMVOFFEN)
#define HCR_TICAB __HCR(TICAB)
#define HCR_TID4 __HCR(TID4)
+#define HCR_GPF __HCR(GPF)
#define HCR_FIEN __HCR(FIEN)
#define HCR_FWB __HCR(FWB)
#define HCR_NV2 __HCR(NV2)
diff --git a/arch/arm64/kvm/hyp/include/nvhe/pkvm.h b/arch/arm64/kvm/hyp/include/nvhe/pkvm.h
index c904647d2f760..75b1122db4c91 100644
--- a/arch/arm64/kvm/hyp/include/nvhe/pkvm.h
+++ b/arch/arm64/kvm/hyp/include/nvhe/pkvm.h
@@ -12,6 +12,19 @@
#include <nvhe/gfp.h>
#include <nvhe/spinlock.h>
+/*
+ * HCR_EL2 bits EL2 owns for a non-protected VM, whatever the host sets: those
+ * that restrict the guest, configure EL2 or what it switches (E2H, RW), or
+ * enable state EL2 doesn't switch or support. RES0 is included, so a bit comes
+ * from the host only once arch/arm64/tools/sysreg describes it.
+ */
+#define PKVM_HCR_EL2_OWNED ((HCR_GUEST_FLAGS & ~(HCR_TWI | HCR_TWE)) | HCR_BSU | \
+ HCR_E2H | HCR_TGE | HCR_TEA | HCR_GPF | HCR_TERR | \
+ HCR_FWB | HCR_DC | HCR_ID | HCR_CD | HCR_NV | \
+ HCR_NV1 | HCR_NV2 | HCR_API | HCR_APK | HCR_ATA | \
+ HCR_DCT | HCR_FIEN | HCR_AMVOFFEN | HCR_ENSCXT | \
+ HCR_EL2_RES0)
+
/*
* Holds the relevant data for maintaining the vcpu state completely at hyp.
*/
diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
index 9a3b92e626adb..dec99d5bbee78 100644
--- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
+++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
@@ -216,6 +216,7 @@ static void sync_debug_state(struct pkvm_hyp_vcpu *hyp_vcpu)
static void flush_hyp_vcpu(struct pkvm_hyp_vcpu *hyp_vcpu)
{
struct kvm_vcpu *host_vcpu = hyp_vcpu->host_vcpu;
+ u64 host_hcr_mask = HCR_TWI | HCR_TWE | HCR_VSE;
fpsimd_sve_flush();
flush_debug_state(hyp_vcpu);
@@ -228,6 +229,7 @@ static void flush_hyp_vcpu(struct pkvm_hyp_vcpu *hyp_vcpu)
if (!pkvm_hyp_vcpu_is_protected(hyp_vcpu)) {
if (vcpu_get_flag(host_vcpu, PKVM_HOST_STATE_DIRTY))
flush_hyp_vcpu_state(hyp_vcpu);
+ host_hcr_mask = ~PKVM_HCR_EL2_OWNED;
} else {
hyp_vcpu->vcpu.arch.ctxt = host_vcpu->arch.ctxt;
}
@@ -241,9 +243,8 @@ static void flush_hyp_vcpu(struct pkvm_hyp_vcpu *hyp_vcpu)
* trap-control bit, so it must flow to the hyp vCPU alongside TWI/TWE
* for the vSError to be delivered. sync_hyp_vcpu() reflects it back.
*/
- hyp_vcpu->vcpu.arch.hcr_el2 &= ~(HCR_TWI | HCR_TWE | HCR_VSE);
- hyp_vcpu->vcpu.arch.hcr_el2 |= READ_ONCE(host_vcpu->arch.hcr_el2) &
- (HCR_TWI | HCR_TWE | HCR_VSE);
+ hyp_vcpu->vcpu.arch.hcr_el2 &= ~host_hcr_mask;
+ hyp_vcpu->vcpu.arch.hcr_el2 |= READ_ONCE(host_vcpu->arch.hcr_el2) & host_hcr_mask;
hyp_vcpu->vcpu.arch.iflags = host_vcpu->arch.iflags;
diff --git a/arch/arm64/kvm/hyp/nvhe/pkvm.c b/arch/arm64/kvm/hyp/nvhe/pkvm.c
index affc9595fda20..2cf704036871b 100644
--- a/arch/arm64/kvm/hyp/nvhe/pkvm.c
+++ b/arch/arm64/kvm/hyp/nvhe/pkvm.c
@@ -30,6 +30,7 @@ unsigned int kvm_host_sve_max_vl;
*/
static DEFINE_PER_CPU(struct pkvm_hyp_vcpu *, loaded_hyp_vcpu);
+/* A non-protected VM keeps only the PKVM_HCR_EL2_OWNED bits of the HCR_EL2 set here. */
static void pkvm_vcpu_reset_hcr(struct kvm_vcpu *vcpu)
{
vcpu->arch.hcr_el2 = HCR_GUEST_FLAGS;
@@ -47,13 +48,6 @@ static void pkvm_vcpu_reset_hcr(struct kvm_vcpu *vcpu)
if (cpus_have_final_cap(ARM64_HAS_STAGE2_FWB))
vcpu->arch.hcr_el2 |= HCR_FWB;
- if (cpus_have_final_cap(ARM64_HAS_EVT) &&
- !cpus_have_final_cap(ARM64_MISMATCHED_CACHE_TYPE) &&
- kvm_read_vm_id_reg(vcpu->kvm, SYS_CTR_EL0) == read_cpuid(CTR_EL0))
- vcpu->arch.hcr_el2 |= HCR_TID4;
- else
- vcpu->arch.hcr_el2 |= HCR_TID2;
-
/*
* At EL2, vcpu_el1_is_32bit() reads HCR_EL2.RW, and EL2 switches the
* *32_EL2 registers when it returns true; they're UNDEFINED without AArch32 EL1.
@@ -64,9 +58,6 @@ static void pkvm_vcpu_reset_hcr(struct kvm_vcpu *vcpu)
if (vcpu_has_ptrauth(vcpu))
vcpu->arch.hcr_el2 |= (HCR_API | HCR_APK);
-
- if (kvm_has_mte(vcpu->kvm))
- vcpu->arch.hcr_el2 |= HCR_ATA;
}
static void pvm_init_traps_hcr(struct kvm_vcpu *vcpu)
@@ -84,6 +75,13 @@ static void pvm_init_traps_hcr(struct kvm_vcpu *vcpu)
*/
val |= HCR_TACR | HCR_TIDCP | HCR_TID3 | HCR_TID1;
+ if (cpus_have_final_cap(ARM64_HAS_EVT) &&
+ !cpus_have_final_cap(ARM64_MISMATCHED_CACHE_TYPE) &&
+ kvm_read_vm_id_reg(kvm, SYS_CTR_EL0) == read_cpuid(CTR_EL0))
+ val |= HCR_TID4;
+ else
+ val |= HCR_TID2;
+
if (!kvm_has_feat(kvm, ID_AA64PFR0_EL1, RAS, IMP)) {
val |= HCR_TERR | HCR_TEA;
val &= ~(HCR_FIEN);
--
2.39.5
^ permalink raw reply [flat|nested] 5+ messages in thread* [PATCH v1 4/4] KVM: arm64: selftests: Check a feature hidden in an ID register is UNDEF
2026-09-25 9:06 [PATCH v1 0/4] KVM: arm64: Fix HCR_EL2 for non-protected VMs in pKVM Fuad Tabba
` (2 preceding siblings ...)
2026-09-25 9:06 ` [PATCH v1 3/4] KVM: arm64: Use the host's HCR_EL2 for non-protected VMs in pKVM Fuad Tabba
@ 2026-09-25 9:06 ` Fuad Tabba
3 siblings, 0 replies; 5+ messages in thread
From: Fuad Tabba @ 2026-09-25 9:06 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel
Cc: Joey Gouly, Suzuki K Poulose, Zenghui Yu, Steffen Eiden,
Catalin Marinas, Will Deacon, Mark Rutland, Quentin Perret,
Vincent Donnefort, Fuad Tabba, linux-kernel
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. In pKVM, a hidden TLBI OS executed on a CPU without FGT until
"KVM: arm64: Use the host's HCR_EL2 for non-protected VMs in pKVM".
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);
+ }
+ }
+}
+
+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();
+}
--
2.39.5
^ permalink raw reply [flat|nested] 5+ messages in thread