KVM: arm64: Fix HCR_EL2 for non-protected VMs in pKVM
Fuad Tabba
reviewercover KVM: arm64: Fix HCR_EL2 for non-protected VMs in pKVM
Hi folks,
In pKVM, EL2 sets a non-protected VM's HCR_EL2 in pkvm_vcpu_reset_hcr(), which covers only part of vcpu_set_hcr(), and takes only TWI, TWE and VSE from the host. As a result, an AArch32 VM can't run, a VM can read GMID_EL1 or execute a TLBI OS its ID registers hide, and the host's TVM, VI and VF never reach it.
Rather than add each missing bit at EL2, the third patch uses the host's HCR_EL2 on every entry, except for the bits EL2 owns. A bit added to vcpu_set_hcr() then reaches pKVM with no change at EL2, unless it opens state EL2 doesn't switch, in which case it goes in PKVM_HCR_EL2_OWNED, as ATA and DCT do.
The third patch depends on the first: once TTLBOS reaches the VM, a trapped TLBI OS from a non-nested guest hits a WARN in handle_tlbi_el1(), as it does without pKVM.
The last patch adds a selftest that checks a feature hidden in an ID register is UNDEFINED in the guest. Its TLBI OS case fails in pKVM before the third patch.
VSE still comes from the host as before. Syncing it back after delivery is a separate fix [1].
Based on Linux 7.3-rc4 (93f51579e7df2).
Cheers,
/fuad
[1] https://lore.kernel.org/all/[email protected]/
Fuad Tabba (4):
KVM: arm64: Don't WARN on an unsupported TLBI OS from vEL1
KVM: arm64: Clear HCR_EL2.RW for 32-bit non-protected vCPUs
KVM: arm64: Use the host's HCR_EL2 for non-protected VMs in pKVM
KVM: arm64: selftests: Check a feature hidden in an ID register is
UNDEF
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 | 24 ++- arch/arm64/kvm/sys_regs.c | 7 +- tools/testing/selftests/kvm/Makefile.kvm | 1 + .../selftests/kvm/arm64/hidden_features.c | 184 ++++++++++++++++++ 7 files changed, 222 insertions(+), 15 deletions(-) create mode 100644 tools/testing/selftests/kvm/arm64/hidden_features.c
base-commit: 93f51579e7df248780214094418f205253383cc5
Signature · 1 line
2.39.5
Fuad Tabba
reviewerPATCH 1/4 KVM: arm64: Don't WARN on an unsupported TLBI OS from vEL1
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.
--- arch/arm64/kvm/sys_regs.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-)
arch/arm64/kvm/sys_regs.c +4 -3
@@ -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;
Signature · 1 line
2.39.5
Re: [PATCH v1 1/4] KVM: arm64: Don't WARN on an unsupported TLBI OS from vEL1
Quoted 9 lines · Fuad Tabba
A VMM can trigger the WARN_ON(!vcpu_is_el2()) in
handle_tlbi_el1()on a CPU withFEAT_EVT2andFEAT_TLBIOSbut noFEAT_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 reacheshandle_tlbi_el1(), which warns before checking whether the guest supports it.
Quoted 5 lines
Check support before the warning.
Fixes 0cb8aae226768 ("KVM: arm64: nv: Add handling of outer-shareable TLBI operations")Signed-off-by Fuad Tabba <[email protected]>
Looks correct to me:
Quoted 32 lines
--- arch/arm64/kvm/sys_regs.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-)arch/arm64/kvm/sys_regs.c +4
-3@@ -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;Signature · 1 line
2.39.5
Fuad Tabba
reviewerPATCH 2/4 KVM: arm64: Clear HCR_EL2.RW for 32-bit non-protected vCPUs
In reply to Fuad TabbaIn 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.
--- arch/arm64/kvm/hyp/nvhe/pkvm.c | 8 ++++++++ 1 file changed, 8 insertions(+)
arch/arm64/kvm/hyp/nvhe/pkvm.c +8 -0
@@ -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 );
Signature · 1 line
2.39.5
Marc Zyngier
maintainerRe: [PATCH v1 2/4] KVM: arm64: Clear HCR_EL2.RW for 32-bit non-protected vCPUs
Quoted 7 lines · Fuad Tabba
In pKVM,
KVM_RUNon a vCPU created withKVM_ARM_VCPU_EL1_32BITfails withKVM_EXIT_FAIL_ENTRY.pkvm_vcpu_reset_hcr(), pKVM's EL2 counterpart ofvcpu_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.
Quoted 5 lines
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.
Apologies from repainting the proverbial bike shed, but I find the way the above is written very hard to understand, because you are describing minute aspects of the code without giving the big picture upfront.
I'd rather see something like:
"pKVM maintains its own copy of a per-vcpu HCR_EL2. On initialisation of the hypervisor's private vcpu structure, HCR_EL2.RW is set to 1 unconditionally. However, nothing forbids userspace to create a non-protected, AArch32 guest. Since HCR_EL2.RW==1, entering the guest fails with an IL exception.
Make sure HCR_EL2.RW is cleared when the vcpu is AArch32 at EL1, and that the HW actually supports this."
which describes the problem without the reader having to dig into code. It makes matching the description and the code very easy. At least for me, YMMV...
Quoted 13 lines
Fixes b56680de9c648 ("KVM: arm64: Initialize trap register values in hyp in pKVM")Signed-off-by Fuad Tabba <[email protected]>--- arch/arm64/kvm/hyp/nvhe/pkvm.c | 8 ++++++++ 1 file changed, 8 insertions(+)arch/arm64/kvm/hyp/nvhe/pkvm.c +0
-0
arch/arm64/kvm/hyp/nvhe/pkvm.c static void pkvm_vcpu_reset_hcr(struct kvm_vcpu *vcpu) · line 60
Quoted 7 lines
arch/arm64/kvm/hyp/nvhe/pkvm.c +4
-0@@ -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. +*/
That's another example: yes, this is all true. But how relevant is it? Are you clearing RW just for the sake of vcpu_el1_is_32bit() to work and prevent an UNDEF? No. It is so that 32bit guests do work. The above really is only paraphrasing the ARM ARM.
What you don't describe is that if ARM64_HAS_32BIT_EL1 is not implemented, you let the vcpu walk to the cliff with RW==1, and rely on the CPU generating an IL exception. For me, this is the important piece of information that needs to be captured.
arch/arm64/kvm/hyp/nvhe/pkvm.c static void pkvm_vcpu_reset_hcr(struct kvm_vcpu *vcpu) · line 67
Quoted 7 lines
arch/arm64/kvm/hyp/nvhe/pkvm.c +4
-0+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 );
Other than that, the patch looks great! :)
Thanks,
M.
Signature · 1 line
Jazz isn't dead. It just smells funny.
Fuad Tabba
reviewerRe: [PATCH v1 2/4] KVM: arm64: Clear HCR_EL2.RW for 32-bit non-protected vCPUs
Hi Marc,
On Fri, 25 Sep 2026 16:01:35 +0100, Marc Zyngier <[email protected]> wrote:
[...]
Quoted 4 lines
Apologies from repainting the proverbial bike shed, but I find the way the above is written very hard to understand, because you are describing minute aspects of the code without giving the big picture upfront.
Writing commit messages and comments is hard! :) I'm working on it...
Quoted 9 lines
I'd rather see something like:
"pKVM maintains its own copy of a per-vcpu
HCR_EL2. On initialisation of the hypervisor's private vcpu structure, HCR_EL2.RW is set to 1 unconditionally. However, nothing forbids userspace to create a non-protected, AArch32 guest. Since HCR_EL2.RW==1, entering the guest fails with an IL exception.
Quoted 6 lines
Make sure HCR_EL2.RW is cleared when the vcpu is AArch32 at EL1, and that the HW actually supports this."
which describes the problem without the reader having to dig into code. It makes matching the description and the code very easy. At least for me, YMMV...
Much clearer, I'll use that for v2, and give the other messages in the series the same treatment.
[...]
Quoted 9 lines
That's another example: yes, this is all true. But how relevant is it? Are you clearing RW just for the sake of
vcpu_el1_is_32bit()to work and prevent an UNDEF? No. It is so that 32bit guests do work. The above really is only paraphrasing the ARM ARM.What you don't describe is that if
ARM64_HAS_32BIT_EL1is not implemented, you let the vcpu walk to the cliff with RW==1, and rely on the CPU generating an IL exception. For me, this is the important piece of information that needs to be captured.
The comment should say what happens without AArch32 EL1: RW stays set and the entry fails with the IL exception, rather than EL2 switching registers that are UNDEFINED there. I'll rewrite it for v2.
[...]
Quoted 1 line
Other than that, the patch looks great! :)
Sashiko didn't complain... :D
Cheers,
/fuad
Fuad Tabba
reviewerPATCH 3/4 KVM: arm64: Use the host's HCR_EL2 for non-protected VMs in pKVM
In reply to Fuad TabbaIn 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.
--- 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(-)
arch/arm64/include/asm/kvm_arm.h +1 -0
@@ -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)
arch/arm64/kvm/hyp/include/nvhe/pkvm.h +13 -0
@@ -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. */
arch/arm64/kvm/hyp/nvhe/hyp-main.c +4 -3
@@ -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;
arch/arm64/kvm/hyp/nvhe/pkvm.c +8 -10
@@ -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 );
Signature · 1 line
2.39.5
Marc Zyngier
maintainerRe: [PATCH v1 3/4] KVM: arm64: Use the host's HCR_EL2 for non-protected VMs in pKVM
Quoted 14 lines · Fuad Tabba
In pKVM, a non-protected VM gets only TWI, TWE and VSE from the
HCR_EL2the host computes for it. EL2 sets the rest inpkvm_vcpu_reset_hcr(), which covers only part ofvcpu_set_hcr(). On a CPU with MTE the VM can then readGMID_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_EL2on 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 takesMDCR_EL2,HCRX_EL2and the fine-grained traps.
Quoted 3 lines
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'sHCR_EL2is unchanged.
But the host does set these bits for non-protected VMs. What is going to honor these traps?
No mention of why you are adding HCR_EL2_GPF here?
Quoted 33 lines
Fixes b56680de9c648 ("KVM: arm64: Initialize trap register values in hyp in pKVM")Signed-off-by Fuad Tabba <[email protected]>--- 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(-)arch/arm64/include/asm/kvm_arm.h +1
-0@@ -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)arch/arm64/kvm/hyp/include/nvhe/pkvm.h +3
-0@@ -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
arch/arm64/kvm/hyp/include/nvhe/pkvm.h line 27
Quoted 10 lines
arch/arm64/kvm/hyp/include/nvhe/pkvm.h +10
-0+* 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) +
The name of the macro doesn't indicate that this only applies to protected VM.
Also, please don't add new uses of the compat HCR macros. I really want to remove them (probably post -rc1).
Quoted 16 lines
arch/arm64/kvm/hyp/include/nvhe/pkvm.h +0
-0/* * Holds the relevant data for maintaining the vcpu state completely at hyp. */ arch/arm64/kvm/hyp/nvhe/hyp-main.c +1
-0@@ -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);
arch/arm64/kvm/hyp/nvhe/hyp-main.c static void flush_hyp_vcpu(struct pkvm_hyp_vcpu *hyp_vcpu) · line 232
Quoted 4 lines
arch/arm64/kvm/hyp/nvhe/hyp-main.c +1
-0@@ -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 ;
This feels fragile. You start with a restrictive set (TWI, TWE, VSE), and then drop it all. At this point, I have no idea what you are letting in.
It would be better to express things in a consistent way:
- either the bits that are controlled by the host for either protected and non-protected guests,
- or the bits that are controlled by the hypervisor for either cases.
Here, you're mixing both, and that's confusing.
Thanks,
M.
Signature · 1 line
Jazz isn't dead. It just smells funny.
Fuad Tabba
reviewerRe: [PATCH v1 3/4] KVM: arm64: Use the host's HCR_EL2 for non-protected VMs in pKVM
Hi Marc,
On Sun, 27 Sep 2026 10:15:07 +0100, Marc Zyngier <[email protected]> wrote:
[...]
Quoted 6 lines
Quoted 3 lines
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'sHCR_EL2is unchanged.But the host does set these bits for non-protected VMs. What is going to honor these traps?
The host, as it does today: the traps of a non-protected VM that the hypervisor doesn't handle itself are forwarded to the host. What changes is where the bits come from: for a non-protected VM they come from the host's HCR_EL2 with the rest, and the hypervisor only sets them itself for a protected VM. I'll rewrite the message to make that clear.
Quoted 2 lines
No mention of why you are adding
HCR_EL2_GPFhere?
It was only there to name the bit in the mask below. It's gone in v2.
[...]
Quoted 11 lines
arch/arm64/kvm/hyp/include/nvhe/pkvm.h line 27
Quoted 7 lines
arch/arm64/kvm/hyp/include/nvhe/pkvm.h +7
-0+#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) +The name of the macro doesn't indicate that this only applies to protected VM.
Quoted 2 lines
Also, please don't add new uses of the compat HCR macros. I really want to remove them (probably post -rc1).
Will do: the masks in v2 are named per VM type and use the HCR_EL2_* names.
[...]
Quoted 4 lines
This feels fragile. You start with a restrictive set (TWI, TWE, VSE), and then drop it all. At this point, I have no idea what you are letting in.
Quoted 8 lines
It would be better to express things in a consistent way:
- either the bits that are controlled by the host for either protected and non-protected guests,
- or the bits that are controlled by the hypervisor for either cases.
Here, you're mixing both, and that's confusing.
Ack. I'll go with the bits the host controls, one set per VM type:
TWI, TWE and VSE for a protected VM, and those plus the per-VM traps and virtual interrupts for a non-protected one. The flush then applies the same mask logic to both.
Cheers,
/fuad
Fuad Tabba
reviewerPATCH 4/4 KVM: arm64: selftests: Check a feature hidden in an ID register is UNDEF
In reply to Fuad TabbaUserspace 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.
--- 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
tools/testing/selftests/kvm/Makefile.kvm +1 -0
@@ -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
tools/testing/selftests/kvm/arm64/hidden_features.c new file +184 -0
184 lines not on this page · raw message
Signature · 1 line
2.39.5