]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
KVM: arm64: nv: Don't adjust PSTATE.M when L2 is nesting
authorMarc Zyngier <maz@kernel.org>
Wed, 14 May 2025 10:34:49 +0000 (11:34 +0100)
committerMarc Zyngier <maz@kernel.org>
Mon, 19 May 2025 06:59:46 +0000 (07:59 +0100)
We currently check for HCR_EL2.NV being set to decide whether we
need to repaint PSTATE.M to say EL2 instead of EL1 on exit.

However, this isn't correct when L2 is itself a hypervisor, and
that L1 as set its own HCR_EL2.NV. That's because we "flatten"
the state and inherit parts of the guest's own setup. In that case,
we shouldn't adjust PSTATE.M, as this is really EL1 for both us
and the guest.

Instead of trying to try and work out how we ended-up with HCR_EL2.NV
being set by introspecting both the host and guest states, use
a per-CPU flag to remember the context (HYP or not), and use that
information to decide whether PSTATE needs tweaking.

Reviewed-by: Oliver Upton <oliver.upton@linux.dev>
Link: https://lore.kernel.org/r/20250514103501.2225951-7-maz@kernel.org
Signed-off-by: Marc Zyngier <maz@kernel.org>
arch/arm64/include/asm/kvm_host.h
arch/arm64/kvm/hyp/vhe/switch.c

index e98cfe7855a62427c9445e45f951955e42ee230c..12adab97e7f257008b3c3bf2ffaa915f0436806e 100644 (file)
@@ -654,6 +654,7 @@ struct kvm_host_data {
 #define KVM_HOST_DATA_FLAG_HAS_TRBE                    1
 #define KVM_HOST_DATA_FLAG_TRBE_ENABLED                        4
 #define KVM_HOST_DATA_FLAG_EL1_TRACING_CONFIGURED      5
+#define KVM_HOST_DATA_FLAG_VCPU_IN_HYP_CONTEXT         6
        unsigned long flags;
 
        struct kvm_cpu_context host_ctxt;
index 731a0378ed1328c6929c9b82e5d7fbc6e4b2b0d4..220dee8a45e0d6ac3951dd85eb1dc3acb01b34f8 100644 (file)
@@ -53,13 +53,23 @@ static u64 __compute_hcr(struct kvm_vcpu *vcpu)
        if (!vcpu_has_nv(vcpu))
                return hcr;
 
+       /*
+        * We rely on the invariant that a vcpu entered from HYP
+        * context must also exit in the same context, as only an ERET
+        * instruction can kick us out of it, and we obviously trap
+        * that sucker. PSTATE.M will get fixed-up on exit.
+        */
        if (is_hyp_ctxt(vcpu)) {
+               host_data_set_flag(VCPU_IN_HYP_CONTEXT);
+
                hcr |= HCR_NV | HCR_NV2 | HCR_AT | HCR_TTLB;
 
                if (!vcpu_el2_e2h_is_set(vcpu))
                        hcr |= HCR_NV1;
 
                write_sysreg_s(vcpu->arch.ctxt.vncr_array, SYS_VNCR_EL2);
+       } else {
+               host_data_clear_flag(VCPU_IN_HYP_CONTEXT);
        }
 
        return hcr | (__vcpu_sys_reg(vcpu, HCR_EL2) & ~NV_HCR_GUEST_EXCLUDE);
@@ -568,9 +578,12 @@ static inline bool fixup_guest_exit(struct kvm_vcpu *vcpu, u64 *exit_code)
 
        /*
         * If we were in HYP context on entry, adjust the PSTATE view
-        * so that the usual helpers work correctly.
+        * so that the usual helpers work correctly. This enforces our
+        * invariant that the guest's HYP context status is preserved
+        * across a run.
         */
-       if (vcpu_has_nv(vcpu) && (read_sysreg(hcr_el2) & HCR_NV)) {
+       if (vcpu_has_nv(vcpu) &&
+           unlikely(host_data_test_flag(VCPU_IN_HYP_CONTEXT))) {
                u64 mode = *vcpu_cpsr(vcpu) & (PSR_MODE_MASK | PSR_MODE32_BIT);
 
                switch (mode) {
@@ -586,6 +599,10 @@ static inline bool fixup_guest_exit(struct kvm_vcpu *vcpu, u64 *exit_code)
                *vcpu_cpsr(vcpu) |= mode;
        }
 
+       /* Apply extreme paranoia! */
+       BUG_ON(vcpu_has_nv(vcpu) &&
+              !!host_data_test_flag(VCPU_IN_HYP_CONTEXT) != is_hyp_ctxt(vcpu));
+
        return __fixup_guest_exit(vcpu, exit_code, hyp_exit_handlers);
 }