// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2005-2018 Andes Technology Corporation #include #include #include #include #include #define DPFROMREG(dp, x) (dp = (void *)((unsigned long *)fpu_reg + 2*x)) #ifdef __NDS32_EL__ #define SPFROMREG(sp, x)\ ((sp) = (void *)((unsigned long *)fpu_reg + (x^1))) #else #define SPFROMREG(sp, x) ((sp) = (void *)((unsigned long *)fpu_reg + x)) #endif #define DEF3OP(name, p, f1, f2) \ void fpemu_##name##p(void *ft, void *fa, void *fb) \ { \ f1(fa, fa, fb); \ f2(ft, ft, fa); \ } #define DEF3OPNEG(name, p, f1, f2, f3) \ void fpemu_##name##p(void *ft, void *fa, void *fb) \ { \ f1(fa, fa, fb); \ f2(ft, ft, fa); \ f3(ft, ft); \ } DEF3OP(fmadd, s, fmuls, fadds); DEF3OP(fmsub, s, fmuls, fsubs); DEF3OP(fmadd, d, fmuld, faddd); DEF3OP(fmsub, d, fmuld, fsubd); DEF3OPNEG(fnmadd, s, fmuls, fadds, fnegs); DEF3OPNEG(fnmsub, s, fmuls, fsubs, fnegs); DEF3OPNEG(fnmadd, d, fmuld, faddd, fnegd); DEF3OPNEG(fnmsub, d, fmuld, fsubd, fnegd); static const unsigned char cmptab[8] = { SF_CEQ, SF_CEQ, SF_CLT, SF_CLT, SF_CLT | SF_CEQ, SF_CLT | SF_CEQ, SF_CUN, SF_CUN }; enum ARGTYPE { S1S = 1, S2S, S1D, CS, D1D, D2D, D1S, CD }; union func_t { void (*t)(void *ft, void *fa, void *fb); void (*b)(void *ft, void *fa); }; /* * Emulate a single FPU arithmetic instruction. */ static int fpu_emu(struct fpu_struct *fpu_reg, unsigned long insn) { int rfmt; /* resulting format */ union func_t func; int ftype = 0; switch (rfmt = NDS32Insn_OPCODE_COP0(insn)) { case fs1_op:{ switch (NDS32Insn_OPCODE_BIT69(insn)) { case fadds_op: func.t = fadds; ftype = S2S; break; case fsubs_op: func.t = fsubs; ftype = S2S; break; case fmadds_op: func.t = fpemu_fmadds; ftype = S2S; break; case fmsubs_op: func.t = fpemu_fmsubs; ftype = S2S; break; case fnmadds_op: func.t = fpemu_fnmadds; ftype = S2S; break; case fnmsubs_op: func.t = fpemu_fnmsubs; ftype = S2S; break; case fmuls_op: func.t = fmuls; ftype = S2S; break; case fdivs_op: func.t = fdivs; ftype = S2S; break; case fs1_f2op_op: switch (NDS32Insn_OPCODE_BIT1014(insn)) { case fs2d_op: func.b = fs2d; ftype = S1D; break; case fsqrts_op: func.b = fsqrts; ftype = S1S; break; default: return SIGILL; } break; default: return SIGILL; } break; } case fs2_op: switch (NDS32Insn_OPCODE_BIT69(insn)) { case fcmpeqs_op: case fcmpeqs_e_op: case fcmplts_op: case fcmplts_e_op: case fcmples_op: case fcmples_e_op: case fcmpuns_op: case fcmpuns_e_op: ftype = CS; break; default: return SIGILL; } break; case fd1_op:{ switch (NDS32Insn_OPCODE_BIT69(insn)) { case faddd_op: func.t = faddd; ftype = D2D; break; case fsubd_op: func.t = fsubd; ftype = D2D; break; case fmaddd_op: func.t = fpemu_fmaddd; ftype = D2D; break; case fmsubd_op: func.t = fpemu_fmsubd; ftype = D2D; break; case fnmaddd_op: func.t = fpemu_fnmaddd; ftype = D2D; break; case fnmsubd_op: func.t = fpemu_fnmsubd; ftype = D2D; break; case fmuld_op: func.t = fmuld; ftype = D2D; break; case fdivd_op: func.t = fdivd; ftype = D2D; break; case fd1_f2op_op: switch (NDS32Insn_OPCODE_BIT1014(insn)) { case fd2s_op: func.b = fd2s; ftype = D1S; break; case fsqrtd_op: func.b = fsqrtd; ftype = D1D; break; default: return SIGILL; } break; default: return SIGILL; } break; } case fd2_op: switch (NDS32Insn_OPCODE_BIT69(insn)) { case fcmpeqd_op: case fcmpeqd_e_op: case fcmpltd_op: case fcmpltd_e_op: case fcmpled_op: case fcmpled_e_op: case fcmpund_op: case fcmpund_e_op: ftype = CD; break; default: return SIGILL; } break; default: return SIGILL; } switch (ftype) { case S1S:{ void *ft, *fa; SPFROMREG(ft, NDS32Insn_OPCODE_Rt(insn)); SPFROMREG(fa, NDS32Insn_OPCODE_Ra(insn)); func.b(ft, fa); break; } case S2S:{ void *ft, *fa, *fb; SPFROMREG(ft, NDS32Insn_OPCODE_Rt(insn)); SPFROMREG(fa, NDS32Insn_OPCODE_Ra(insn)); SPFROMREG(fb, NDS32Insn_OPCODE_Rb(insn)); func.t(ft, fa, fb); break; } case S1D:{ void *ft, *fa; DPFROMREG(ft, NDS32Insn_OPCODE_Rt(insn)); SPFROMREG(fa, NDS32Insn_OPCODE_Ra(insn)); func.b(ft, fa); break; } case CS:{ unsigned int cmpop = NDS32Insn_OPCODE_BIT69(insn); void *ft, *fa, *fb; SPFROMREG(ft, NDS32Insn_OPCODE_Rt(insn)); SPFROMREG(fa, NDS32Insn_OPCODE_Ra(insn)); SPFROMREG(fb, NDS32Insn_OPCODE_Rb(insn)); if (cmpop < 0x8) { cmpop = cmptab[cmpop]; fcmps(ft, fa, fb, cmpop); } else return SIGILL; break; } case D1D:{ void *ft, *fa; DPFROMREG(ft, NDS32Insn_OPCODE_Rt(insn)); DPFROMREG(fa, NDS32Insn_OPCODE_Ra(insn)); func.b(ft, fa); break; } case D2D:{ void *ft, *fa, *fb; DPFROMREG(ft, NDS32Insn_OPCODE_Rt(insn)); DPFROMREG(fa, NDS32Insn_OPCODE_Ra(insn)); DPFROMREG(fb, NDS32Insn_OPCODE_Rb(insn)); func.t(ft, fa, fb); break; } case D1S:{ void *ft, *fa; SPFROMREG(ft, NDS32Insn_OPCODE_Rt(insn)); DPFROMREG(fa, NDS32Insn_OPCODE_Ra(insn)); func.b(ft, fa); break; } case CD:{ unsigned int cmpop = NDS32Insn_OPCODE_BIT69(insn); void *ft, *fa, *fb; SPFROMREG(ft, NDS32Insn_OPCODE_Rt(insn)); DPFROMREG(fa, NDS32Insn_OPCODE_Ra(insn)); DPFROMREG(fb, NDS32Insn_OPCODE_Rb(insn)); if (cmpop < 0x8) { cmpop = cmptab[cmpop]; fcmpd(ft, fa, fb, cmpop); } else return SIGILL; break; } default: return SIGILL; } /* * If an exception is required, generate a tidy SIGFPE exception. */ #if IS_ENABLED(CONFIG_SUPPORT_DENORMAL_ARITHMETIC) if (((fpu_reg->fpcsr << 5) & fpu_reg->fpcsr & FPCSR_mskALLE_NO_UDFE) || ((fpu_reg->fpcsr & FPCSR_mskUDF) && (fpu_reg->UDF_trap))) #else if ((fpu_reg->fpcsr << 5) & fpu_reg->fpcsr & FPCSR_mskALLE) #endif return SIGFPE; return 0; } int do_fpuemu(struct pt_regs *regs, struct fpu_struct *fpu) { unsigned long insn = 0, addr = regs->ipc; unsigned long emulpc, contpc; unsigned char *pc = (void *)&insn; char c; int i = 0, ret; for (i = 0; i < 4; i++) { if (__get_user(c, (unsigned char *)addr++)) return SIGBUS; *pc++ = c; } insn = be32_to_cpu(insn); emulpc = regs->ipc; contpc = regs->ipc + 4; if (NDS32Insn_OPCODE(insn) != cop0_op) return SIGILL; switch (NDS32Insn_OPCODE_COP0(insn)) { case fs1_op: case fs2_op: case fd1_op: case fd2_op: { /* a real fpu computation instruction */ ret = fpu_emu(fpu, insn); if (!ret) regs->ipc = contpc; } break; default: return SIGILL; } return ret; }