MIPS: math-emu: Implement the FCCR, FEXR and FENR registers

Implement the FCCR, FEXR and FENR "shadow" FPU registers for the
architecture levels that include them, for the CFC1 and CTC1
instructions in the full emulation mode.

For completeness add macros for the CP1 UFR and UNFR registers too, no
actual implementation though.

Signed-off-by: Maciej W. Rozycki <macro@linux-mips.org>
Cc: linux-mips@linux-mips.org
Patchwork: https://patchwork.linux-mips.org/patch/9708/
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
diff --git a/arch/mips/math-emu/cp1emu.c b/arch/mips/math-emu/cp1emu.c
index 7aa42b2..8034ee4 100644
--- a/arch/mips/math-emu/cp1emu.c
+++ b/arch/mips/math-emu/cp1emu.c
@@ -64,11 +64,14 @@
 /* Control registers */
 
 #define FPCREG_RID	0	/* $0  = revision id */
+#define FPCREG_FCCR	25	/* $25 = fccr */
+#define FPCREG_FEXR	26	/* $26 = fexr */
+#define FPCREG_FENR	28	/* $28 = fenr */
 #define FPCREG_CSR	31	/* $31 = csr */
 
 /* convert condition code register number to csr bit */
 const unsigned int fpucondbit[8] = {
-	FPU_CSR_COND0,
+	FPU_CSR_COND,
 	FPU_CSR_COND1,
 	FPU_CSR_COND2,
 	FPU_CSR_COND3,
@@ -846,17 +849,53 @@
 static inline void cop1_cfc(struct pt_regs *xcp, struct mips_fpu_struct *ctx,
 			    mips_instruction ir)
 {
-	u32 value;
+	u32 fcr31 = ctx->fcr31;
+	u32 value = 0;
 
-	if (MIPSInst_RD(ir) == FPCREG_CSR) {
-		value = ctx->fcr31;
+	switch (MIPSInst_RD(ir)) {
+	case FPCREG_CSR:
+		value = fcr31;
 		pr_debug("%p gpr[%d]<-csr=%08x\n",
-			 (void *)xcp->cp0_epc,
-			 MIPSInst_RT(ir), value);
-	} else if (MIPSInst_RD(ir) == FPCREG_RID)
+			 (void *)xcp->cp0_epc, MIPSInst_RT(ir), value);
+		break;
+
+	case FPCREG_FENR:
+		if (!cpu_has_mips_r)
+			break;
+		value = (fcr31 >> (FPU_CSR_FS_S - MIPS_FENR_FS_S)) &
+			MIPS_FENR_FS;
+		value |= fcr31 & (FPU_CSR_ALL_E | FPU_CSR_RM);
+		pr_debug("%p gpr[%d]<-enr=%08x\n",
+			 (void *)xcp->cp0_epc, MIPSInst_RT(ir), value);
+		break;
+
+	case FPCREG_FEXR:
+		if (!cpu_has_mips_r)
+			break;
+		value = fcr31 & (FPU_CSR_ALL_X | FPU_CSR_ALL_S);
+		pr_debug("%p gpr[%d]<-exr=%08x\n",
+			 (void *)xcp->cp0_epc, MIPSInst_RT(ir), value);
+		break;
+
+	case FPCREG_FCCR:
+		if (!cpu_has_mips_r)
+			break;
+		value = (fcr31 >> (FPU_CSR_COND_S - MIPS_FCCR_COND0_S)) &
+			MIPS_FCCR_COND0;
+		value |= (fcr31 >> (FPU_CSR_COND1_S - MIPS_FCCR_COND1_S)) &
+			 (MIPS_FCCR_CONDX & ~MIPS_FCCR_COND0);
+		pr_debug("%p gpr[%d]<-ccr=%08x\n",
+			 (void *)xcp->cp0_epc, MIPSInst_RT(ir), value);
+		break;
+
+	case FPCREG_RID:
 		value = current_cpu_data.fpu_id;
-	else
-		value = 0;
+		break;
+
+	default:
+		break;
+	}
+
 	if (MIPSInst_RT(ir))
 		xcp->regs[MIPSInst_RT(ir)] = value;
 }
@@ -867,6 +906,7 @@
 static inline void cop1_ctc(struct pt_regs *xcp, struct mips_fpu_struct *ctx,
 			    mips_instruction ir)
 {
+	u32 fcr31 = ctx->fcr31;
 	u32 value;
 
 	if (MIPSInst_RT(ir) == 0)
@@ -874,16 +914,52 @@
 	else
 		value = xcp->regs[MIPSInst_RT(ir)];
 
-	/* we only have one writable control reg
-	 */
-	if (MIPSInst_RD(ir) == FPCREG_CSR) {
+	switch (MIPSInst_RD(ir)) {
+	case FPCREG_CSR:
 		pr_debug("%p gpr[%d]->csr=%08x\n",
-			 (void *)xcp->cp0_epc,
-			 MIPSInst_RT(ir), value);
+			 (void *)xcp->cp0_epc, MIPSInst_RT(ir), value);
 
 		/* Don't write reserved bits.  */
-		ctx->fcr31 = value & ~FPU_CSR_RSVD;
+		fcr31 = value & ~FPU_CSR_RSVD;
+		break;
+
+	case FPCREG_FENR:
+		if (!cpu_has_mips_r)
+			break;
+		pr_debug("%p gpr[%d]->enr=%08x\n",
+			 (void *)xcp->cp0_epc, MIPSInst_RT(ir), value);
+		fcr31 &= ~(FPU_CSR_FS | FPU_CSR_ALL_E | FPU_CSR_RM);
+		fcr31 |= (value << (FPU_CSR_FS_S - MIPS_FENR_FS_S)) &
+			 FPU_CSR_FS;
+		fcr31 |= value & (FPU_CSR_ALL_E | FPU_CSR_RM);
+		break;
+
+	case FPCREG_FEXR:
+		if (!cpu_has_mips_r)
+			break;
+		pr_debug("%p gpr[%d]->exr=%08x\n",
+			 (void *)xcp->cp0_epc, MIPSInst_RT(ir), value);
+		fcr31 &= ~(FPU_CSR_ALL_X | FPU_CSR_ALL_S);
+		fcr31 |= value & (FPU_CSR_ALL_X | FPU_CSR_ALL_S);
+		break;
+
+	case FPCREG_FCCR:
+		if (!cpu_has_mips_r)
+			break;
+		pr_debug("%p gpr[%d]->ccr=%08x\n",
+			 (void *)xcp->cp0_epc, MIPSInst_RT(ir), value);
+		fcr31 &= ~(FPU_CSR_CONDX | FPU_CSR_COND);
+		fcr31 |= (value << (FPU_CSR_COND_S - MIPS_FCCR_COND0_S)) &
+			 FPU_CSR_COND;
+		fcr31 |= (value << (FPU_CSR_COND1_S - MIPS_FCCR_COND1_S)) &
+			 FPU_CSR_CONDX;
+		break;
+
+	default:
+		break;
 	}
+
+	ctx->fcr31 = fcr31;
 }
 
 /*