Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

MIPS: KVM: Emulate FPU bits in COP0 interface

Emulate FPU related parts of COP0 interface so that the guest will be
able to enable/disable the following once the FPU capability has been
wired up:
- The FPU (Status.CU1)
- 64-bit FP register mode (Status.FR)
- Hybrid FP register mode (Config5.FRE)

Changing Status.CU1 has no immediate effect if the FPU state isn't live,
as the FPU state is restored lazily on first use. After that, changes
take place immediately in the host Status.CU1, so that the guest can
start getting coprocessor unusable exceptions right away for guest FPU
operations if it is disabled. The FPU state is saved lazily too, as the
FPU may get re-enabled in the near future anyway.

Any change to Status.FR causes the FPU state to be discarded and FPU
disabled, as the register state is architecturally UNPREDICTABLE after
such a change. This should also ensure that the FPU state is fully
initialised (with stale state, but that's fine) when it is next used in
the new FP mode.

Any change to the Config5.FRE bit is immediately updated in the host
state so that the guest can get the relevant exceptions right away for
single-precision FPU operations.

Signed-off-by: James Hogan <james.hogan@imgtec.com>
Cc: Paolo Bonzini <pbonzini@redhat.com>
Cc: Paul Burton <paul.burton@imgtec.com>
Cc: Ralf Baechle <ralf@linux-mips.org>
Cc: Gleb Natapov <gleb@kernel.org>
Cc: linux-mips@linux-mips.org
Cc: kvm@vger.kernel.org

+100 -11
+100 -11
arch/mips/kvm/emulate.c
··· 893 893 */ 894 894 unsigned int kvm_mips_config1_wrmask(struct kvm_vcpu *vcpu) 895 895 { 896 - /* Read-only */ 897 - return 0; 896 + unsigned int mask = 0; 897 + 898 + /* Permit FPU to be present if FPU is supported */ 899 + if (kvm_mips_guest_can_have_fpu(&vcpu->arch)) 900 + mask |= MIPS_CONF1_FP; 901 + 902 + return mask; 898 903 } 899 904 900 905 /** ··· 937 932 */ 938 933 unsigned int kvm_mips_config5_wrmask(struct kvm_vcpu *vcpu) 939 934 { 940 - /* Read-only */ 941 - return 0; 935 + unsigned int mask = 0; 936 + 937 + /* 938 + * Permit guest FPU mode changes if FPU is enabled and the relevant 939 + * feature exists according to FIR register. 940 + */ 941 + if (kvm_mips_guest_has_fpu(&vcpu->arch)) { 942 + if (cpu_has_fre) 943 + mask |= MIPS_CONF5_FRE; 944 + /* We don't support UFR or UFE */ 945 + } 946 + 947 + return mask; 942 948 } 943 949 944 950 enum emulation_result kvm_mips_emulate_CP0(uint32_t inst, uint32_t *opc, ··· 1089 1073 kvm_mips_write_compare(vcpu, 1090 1074 vcpu->arch.gprs[rt]); 1091 1075 } else if ((rd == MIPS_CP0_STATUS) && (sel == 0)) { 1092 - kvm_write_c0_guest_status(cop0, 1093 - vcpu->arch.gprs[rt]); 1076 + unsigned int old_val, val, change; 1077 + 1078 + old_val = kvm_read_c0_guest_status(cop0); 1079 + val = vcpu->arch.gprs[rt]; 1080 + change = val ^ old_val; 1081 + 1082 + /* Make sure that the NMI bit is never set */ 1083 + val &= ~ST0_NMI; 1084 + 1094 1085 /* 1095 - * Make sure that CU1 and NMI bits are 1096 - * never set 1086 + * Don't allow CU1 or FR to be set unless FPU 1087 + * capability enabled and exists in guest 1088 + * configuration. 1097 1089 */ 1098 - kvm_clear_c0_guest_status(cop0, 1099 - (ST0_CU1 | ST0_NMI)); 1090 + if (!kvm_mips_guest_has_fpu(&vcpu->arch)) 1091 + val &= ~(ST0_CU1 | ST0_FR); 1092 + 1093 + /* 1094 + * Also don't allow FR to be set if host doesn't 1095 + * support it. 1096 + */ 1097 + if (!(current_cpu_data.fpu_id & MIPS_FPIR_F64)) 1098 + val &= ~ST0_FR; 1099 + 1100 + 1101 + /* Handle changes in FPU mode */ 1102 + preempt_disable(); 1103 + 1104 + /* 1105 + * FPU and Vector register state is made 1106 + * UNPREDICTABLE by a change of FR, so don't 1107 + * even bother saving it. 1108 + */ 1109 + if (change & ST0_FR) 1110 + kvm_drop_fpu(vcpu); 1111 + 1112 + /* 1113 + * Propagate CU1 (FPU enable) changes 1114 + * immediately if the FPU context is already 1115 + * loaded. When disabling we leave the context 1116 + * loaded so it can be quickly enabled again in 1117 + * the near future. 1118 + */ 1119 + if (change & ST0_CU1 && 1120 + vcpu->arch.fpu_inuse & KVM_MIPS_FPU_FPU) 1121 + change_c0_status(ST0_CU1, val); 1122 + 1123 + preempt_enable(); 1124 + 1125 + kvm_write_c0_guest_status(cop0, val); 1100 1126 1101 1127 #ifdef CONFIG_KVM_MIPS_DYN_TRANS 1102 - kvm_mips_trans_mtc0(inst, opc, vcpu); 1128 + /* 1129 + * If FPU present, we need CU1/FR bits to take 1130 + * effect fairly soon. 1131 + */ 1132 + if (!kvm_mips_guest_has_fpu(&vcpu->arch)) 1133 + kvm_mips_trans_mtc0(inst, opc, vcpu); 1103 1134 #endif 1135 + } else if ((rd == MIPS_CP0_CONFIG) && (sel == 5)) { 1136 + unsigned int old_val, val, change, wrmask; 1137 + 1138 + old_val = kvm_read_c0_guest_config5(cop0); 1139 + val = vcpu->arch.gprs[rt]; 1140 + 1141 + /* Only a few bits are writable in Config5 */ 1142 + wrmask = kvm_mips_config5_wrmask(vcpu); 1143 + change = (val ^ old_val) & wrmask; 1144 + val = old_val ^ change; 1145 + 1146 + 1147 + /* Handle changes in FPU modes */ 1148 + preempt_disable(); 1149 + 1150 + /* 1151 + * Propagate FRE changes immediately if the FPU 1152 + * context is already loaded. 1153 + */ 1154 + if (change & MIPS_CONF5_FRE && 1155 + vcpu->arch.fpu_inuse & KVM_MIPS_FPU_FPU) 1156 + change_c0_config5(MIPS_CONF5_FRE, val); 1157 + 1158 + preempt_enable(); 1159 + 1160 + kvm_write_c0_guest_config5(cop0, val); 1104 1161 } else if ((rd == MIPS_CP0_CAUSE) && (sel == 0)) { 1105 1162 uint32_t old_cause, new_cause; 1106 1163