KVM: arm64: Prevent guests from enabling HA/HD on Ampere1
authorOliver Upton <oliver.upton@linux.dev>
Fri, 9 Jun 2023 22:01:04 +0000 (22:01 +0000)
committerOliver Upton <oliver.upton@linux.dev>
Fri, 16 Jun 2023 00:31:44 +0000 (00:31 +0000)
An erratum in the HAFDBS implementation in AmpereOne was addressed by
clearing the feature in the ID register, with the expectation that
software would not attempt to use the corresponding controls in TCR_EL1.
The architecture, on the other hand, takes a much more pedantic stance
on the subject, requiring the TCR bits behave as RES0.

Take an extremely conservative stance on the issue and leverage the
precise write trap afforded by FGT. Handle guest writes by clearing HA
and HD before writing the intended value to the EL1 register alias.

Link: https://lore.kernel.org/r/20230609220104.1836988-4-oliver.upton@linux.dev
Signed-off-by: Oliver Upton <oliver.upton@linux.dev>
arch/arm64/kvm/hyp/include/hyp/switch.h

index 901e47a..04cc34d 100644 (file)
@@ -75,6 +75,9 @@ static inline bool __hfgxtr_traps_required(void)
        if (cpus_have_final_cap(ARM64_SME))
                return true;
 
+       if (cpus_have_final_cap(ARM64_WORKAROUND_AMPERE_AC03_CPU_38))
+               return true;
+
        return false;
 }
 
@@ -89,6 +92,12 @@ static inline void __activate_traps_hfgxtr(void)
                w_clr |= tmp;
        }
 
+       /*
+        * Trap guest writes to TCR_EL1 to prevent it from enabling HA or HD.
+        */
+       if (cpus_have_final_cap(ARM64_WORKAROUND_AMPERE_AC03_CPU_38))
+               w_set |= HFGxTR_EL2_TCR_EL1_MASK;
+
        sysreg_clear_set_s(SYS_HFGRTR_EL2, r_clr, r_set);
        sysreg_clear_set_s(SYS_HFGWTR_EL2, w_clr, w_set);
 }
@@ -104,6 +113,9 @@ static inline void __deactivate_traps_hfgxtr(void)
                w_set |= tmp;
        }
 
+       if (cpus_have_final_cap(ARM64_WORKAROUND_AMPERE_AC03_CPU_38))
+               w_clr |= HFGxTR_EL2_TCR_EL1_MASK;
+
        sysreg_clear_set_s(SYS_HFGRTR_EL2, r_clr, r_set);
        sysreg_clear_set_s(SYS_HFGWTR_EL2, w_clr, w_set);
 }
@@ -408,12 +420,39 @@ static bool kvm_hyp_handle_cntpct(struct kvm_vcpu *vcpu)
        return true;
 }
 
+static bool handle_ampere1_tcr(struct kvm_vcpu *vcpu)
+{
+       u32 sysreg = esr_sys64_to_sysreg(kvm_vcpu_get_esr(vcpu));
+       int rt = kvm_vcpu_sys_get_rt(vcpu);
+       u64 val = vcpu_get_reg(vcpu, rt);
+
+       if (sysreg != SYS_TCR_EL1)
+               return false;
+
+       /*
+        * Affected parts do not advertise support for hardware Access Flag /
+        * Dirty state management in ID_AA64MMFR1_EL1.HAFDBS, but the underlying
+        * control bits are still functional. The architecture requires these be
+        * RES0 on systems that do not implement FEAT_HAFDBS.
+        *
+        * Uphold the requirements of the architecture by masking guest writes
+        * to TCR_EL1.{HA,HD} here.
+        */
+       val &= ~(TCR_HD | TCR_HA);
+       write_sysreg_el1(val, SYS_TCR);
+       return true;
+}
+
 static bool kvm_hyp_handle_sysreg(struct kvm_vcpu *vcpu, u64 *exit_code)
 {
        if (cpus_have_final_cap(ARM64_WORKAROUND_CAVIUM_TX2_219_TVM) &&
            handle_tx2_tvm(vcpu))
                return true;
 
+       if (cpus_have_final_cap(ARM64_WORKAROUND_AMPERE_AC03_CPU_38) &&
+           handle_ampere1_tcr(vcpu))
+               return true;
+
        if (static_branch_unlikely(&vgic_v3_cpuif_trap) &&
            __vgic_v3_perform_cpuif_access(vcpu) == 1)
                return true;