mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
406 lines
7.6 KiB
406 lines
7.6 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
// Copyright (C) 2005-2018 Andes Technology Corporation |
|
|
|
#include <asm/bitfield.h> |
|
#include <asm/uaccess.h> |
|
#include <asm/sfp-machine.h> |
|
#include <asm/fpuemu.h> |
|
#include <asm/nds32_fpu_inst.h> |
|
|
|
#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 fs2si_op: |
|
func.b = fs2si; |
|
ftype = S1S; |
|
break; |
|
case fs2si_z_op: |
|
func.b = fs2si_z; |
|
ftype = S1S; |
|
break; |
|
case fs2ui_op: |
|
func.b = fs2ui; |
|
ftype = S1S; |
|
break; |
|
case fs2ui_z_op: |
|
func.b = fs2ui_z; |
|
ftype = S1S; |
|
break; |
|
case fsi2s_op: |
|
func.b = fsi2s; |
|
ftype = S1S; |
|
break; |
|
case fui2s_op: |
|
func.b = fui2s; |
|
ftype = S1S; |
|
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 fd2si_op: |
|
func.b = fd2si; |
|
ftype = D1S; |
|
break; |
|
case fd2si_z_op: |
|
func.b = fd2si_z; |
|
ftype = D1S; |
|
break; |
|
case fd2ui_op: |
|
func.b = fd2ui; |
|
ftype = D1S; |
|
break; |
|
case fd2ui_z_op: |
|
func.b = fd2ui_z; |
|
ftype = D1S; |
|
break; |
|
case fsi2d_op: |
|
func.b = fsi2d; |
|
ftype = D1S; |
|
break; |
|
case fui2d_op: |
|
func.b = fui2d; |
|
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_UDF_IEXE) |
|
|| ((fpu_reg->fpcsr << 5) & (fpu_reg->UDF_IEX_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; |
|
}
|
|
|