|
@@ -893,8 +893,13 @@ enum emulation_result kvm_mips_emul_tlbp(struct kvm_vcpu *vcpu)
|
|
|
*/
|
|
|
unsigned int kvm_mips_config1_wrmask(struct kvm_vcpu *vcpu)
|
|
|
{
|
|
|
- /* Read-only */
|
|
|
- return 0;
|
|
|
+ unsigned int mask = 0;
|
|
|
+
|
|
|
+ /* Permit FPU to be present if FPU is supported */
|
|
|
+ if (kvm_mips_guest_can_have_fpu(&vcpu->arch))
|
|
|
+ mask |= MIPS_CONF1_FP;
|
|
|
+
|
|
|
+ return mask;
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -932,8 +937,19 @@ unsigned int kvm_mips_config4_wrmask(struct kvm_vcpu *vcpu)
|
|
|
*/
|
|
|
unsigned int kvm_mips_config5_wrmask(struct kvm_vcpu *vcpu)
|
|
|
{
|
|
|
- /* Read-only */
|
|
|
- return 0;
|
|
|
+ unsigned int mask = 0;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Permit guest FPU mode changes if FPU is enabled and the relevant
|
|
|
+ * feature exists according to FIR register.
|
|
|
+ */
|
|
|
+ if (kvm_mips_guest_has_fpu(&vcpu->arch)) {
|
|
|
+ if (cpu_has_fre)
|
|
|
+ mask |= MIPS_CONF5_FRE;
|
|
|
+ /* We don't support UFR or UFE */
|
|
|
+ }
|
|
|
+
|
|
|
+ return mask;
|
|
|
}
|
|
|
|
|
|
enum emulation_result kvm_mips_emulate_CP0(uint32_t inst, uint32_t *opc,
|
|
@@ -1073,18 +1089,91 @@ enum emulation_result kvm_mips_emulate_CP0(uint32_t inst, uint32_t *opc,
|
|
|
kvm_mips_write_compare(vcpu,
|
|
|
vcpu->arch.gprs[rt]);
|
|
|
} else if ((rd == MIPS_CP0_STATUS) && (sel == 0)) {
|
|
|
- kvm_write_c0_guest_status(cop0,
|
|
|
- vcpu->arch.gprs[rt]);
|
|
|
+ unsigned int old_val, val, change;
|
|
|
+
|
|
|
+ old_val = kvm_read_c0_guest_status(cop0);
|
|
|
+ val = vcpu->arch.gprs[rt];
|
|
|
+ change = val ^ old_val;
|
|
|
+
|
|
|
+ /* Make sure that the NMI bit is never set */
|
|
|
+ val &= ~ST0_NMI;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Don't allow CU1 or FR to be set unless FPU
|
|
|
+ * capability enabled and exists in guest
|
|
|
+ * configuration.
|
|
|
+ */
|
|
|
+ if (!kvm_mips_guest_has_fpu(&vcpu->arch))
|
|
|
+ val &= ~(ST0_CU1 | ST0_FR);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Also don't allow FR to be set if host doesn't
|
|
|
+ * support it.
|
|
|
+ */
|
|
|
+ if (!(current_cpu_data.fpu_id & MIPS_FPIR_F64))
|
|
|
+ val &= ~ST0_FR;
|
|
|
+
|
|
|
+
|
|
|
+ /* Handle changes in FPU mode */
|
|
|
+ preempt_disable();
|
|
|
+
|
|
|
/*
|
|
|
- * Make sure that CU1 and NMI bits are
|
|
|
- * never set
|
|
|
+ * FPU and Vector register state is made
|
|
|
+ * UNPREDICTABLE by a change of FR, so don't
|
|
|
+ * even bother saving it.
|
|
|
*/
|
|
|
- kvm_clear_c0_guest_status(cop0,
|
|
|
- (ST0_CU1 | ST0_NMI));
|
|
|
+ if (change & ST0_FR)
|
|
|
+ kvm_drop_fpu(vcpu);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Propagate CU1 (FPU enable) changes
|
|
|
+ * immediately if the FPU context is already
|
|
|
+ * loaded. When disabling we leave the context
|
|
|
+ * loaded so it can be quickly enabled again in
|
|
|
+ * the near future.
|
|
|
+ */
|
|
|
+ if (change & ST0_CU1 &&
|
|
|
+ vcpu->arch.fpu_inuse & KVM_MIPS_FPU_FPU)
|
|
|
+ change_c0_status(ST0_CU1, val);
|
|
|
+
|
|
|
+ preempt_enable();
|
|
|
+
|
|
|
+ kvm_write_c0_guest_status(cop0, val);
|
|
|
|
|
|
#ifdef CONFIG_KVM_MIPS_DYN_TRANS
|
|
|
- kvm_mips_trans_mtc0(inst, opc, vcpu);
|
|
|
+ /*
|
|
|
+ * If FPU present, we need CU1/FR bits to take
|
|
|
+ * effect fairly soon.
|
|
|
+ */
|
|
|
+ if (!kvm_mips_guest_has_fpu(&vcpu->arch))
|
|
|
+ kvm_mips_trans_mtc0(inst, opc, vcpu);
|
|
|
#endif
|
|
|
+ } else if ((rd == MIPS_CP0_CONFIG) && (sel == 5)) {
|
|
|
+ unsigned int old_val, val, change, wrmask;
|
|
|
+
|
|
|
+ old_val = kvm_read_c0_guest_config5(cop0);
|
|
|
+ val = vcpu->arch.gprs[rt];
|
|
|
+
|
|
|
+ /* Only a few bits are writable in Config5 */
|
|
|
+ wrmask = kvm_mips_config5_wrmask(vcpu);
|
|
|
+ change = (val ^ old_val) & wrmask;
|
|
|
+ val = old_val ^ change;
|
|
|
+
|
|
|
+
|
|
|
+ /* Handle changes in FPU modes */
|
|
|
+ preempt_disable();
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Propagate FRE changes immediately if the FPU
|
|
|
+ * context is already loaded.
|
|
|
+ */
|
|
|
+ if (change & MIPS_CONF5_FRE &&
|
|
|
+ vcpu->arch.fpu_inuse & KVM_MIPS_FPU_FPU)
|
|
|
+ change_c0_config5(MIPS_CONF5_FRE, val);
|
|
|
+
|
|
|
+ preempt_enable();
|
|
|
+
|
|
|
+ kvm_write_c0_guest_config5(cop0, val);
|
|
|
} else if ((rd == MIPS_CP0_CAUSE) && (sel == 0)) {
|
|
|
uint32_t old_cause, new_cause;
|
|
|
|