]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
KVM: arm64: nv: Handle CNTHCTL_EL2 specially
authorMarc Zyngier <maz@kernel.org>
Wed, 23 Oct 2024 14:53:14 +0000 (15:53 +0100)
committerOliver Upton <oliver.upton@linux.dev>
Thu, 31 Oct 2024 02:42:29 +0000 (02:42 +0000)
Accessing CNTHCTL_EL2 is fraught with danger if running with
HCR_EL2.E2H=1: half of the bits are held in CNTKCTL_EL1, and
thus can be changed behind our back, while the rest lives
in the CNTHCTL_EL2 shadow copy that is memory-based.

Yes, this is a lot of fun!

Make sure that we merge the two on read access, while we can
write to CNTKCTL_EL1 in a more straightforward manner.

Signed-off-by: Marc Zyngier <maz@kernel.org>
Link: https://lore.kernel.org/r/20241023145345.1613824-7-maz@kernel.org
Signed-off-by: Oliver Upton <oliver.upton@linux.dev>
arch/arm64/kvm/sys_regs.c
include/kvm/arm_arch_timer.h

index ae0ecd8ae6713f7ae97e444e0ef7be0508ffe443..9fb2847f5abcaff57e1b206a178a5efb02d5293b 100644 (file)
@@ -157,6 +157,21 @@ u64 vcpu_read_sys_reg(const struct kvm_vcpu *vcpu, int reg)
                if (!is_hyp_ctxt(vcpu))
                        goto memory_read;
 
+               /*
+                * CNTHCTL_EL2 requires some special treatment to
+                * account for the bits that can be set via CNTKCTL_EL1.
+                */
+               switch (reg) {
+               case CNTHCTL_EL2:
+                       if (vcpu_el2_e2h_is_set(vcpu)) {
+                               val = read_sysreg_el1(SYS_CNTKCTL);
+                               val &= CNTKCTL_VALID_BITS;
+                               val |= __vcpu_sys_reg(vcpu, reg) & ~CNTKCTL_VALID_BITS;
+                               return val;
+                       }
+                       break;
+               }
+
                /*
                 * If this register does not have an EL1 counterpart,
                 * then read the stored EL2 version.
@@ -207,6 +222,19 @@ void vcpu_write_sys_reg(struct kvm_vcpu *vcpu, u64 val, int reg)
                 */
                __vcpu_sys_reg(vcpu, reg) = val;
 
+               switch (reg) {
+               case CNTHCTL_EL2:
+                       /*
+                        * If E2H=0, CNHTCTL_EL2 is a pure shadow register.
+                        * Otherwise, some of the bits are backed by
+                        * CNTKCTL_EL1, while the rest is kept in memory.
+                        * Yes, this is fun stuff.
+                        */
+                       if (vcpu_el2_e2h_is_set(vcpu))
+                               write_sysreg_el1(val, SYS_CNTKCTL);
+                       return;
+               }
+
                /* No EL1 counterpart? We're done here.? */
                if (reg == el1r)
                        return;
index c819c5d16613bcc59e3b8d3f9457e4a941d4fff4..fd650a8789b919729b2dda9e964940b7ca4fe3b2 100644 (file)
@@ -147,6 +147,9 @@ u64 timer_get_cval(struct arch_timer_context *ctxt);
 void kvm_timer_cpu_up(void);
 void kvm_timer_cpu_down(void);
 
+/* CNTKCTL_EL1 valid bits as of DDI0487J.a */
+#define CNTKCTL_VALID_BITS     (BIT(17) | GENMASK_ULL(9, 0))
+
 static inline bool has_cntpoff(void)
 {
        return (has_vhe() && cpus_have_final_cap(ARM64_HAS_ECV_CNTPOFF));