/*
* Copyright (c) 1995 Gordon W. Ross
* some portion Copyright (c) 1995 Ken Nakata
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
* 4. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Gordon Ross
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* mc68881 emulator
* XXX - Just a start at it for now...
*/
/* always set this (to avoid a warning) */
insn.is_pc = frame->f_pc;
insn.is_nextpc = 0;
if (frame->f_format == 4) {
/*
* A format 4 is generated by the 68{EC,LC}040. The PC is
* already set to the instruction following the faulting
* instruction. We need to calculate that, anyway. The
* fslw is the PC of the faulted instruction, which is what
* we expect to be in f_pc.
*
* XXX - This is a hack; it assumes we at least know the
* sizes of all instructions we run across.
* XXX TODO: This may not be true, so we might want to save
* the PC in order to restore it later.
*/
#if 0
insn.is_nextpc = frame->f_pc;
#endif
insn.is_pc = frame->f_fmt4.f_fslw;
frame->f_pc = insn.is_pc;
}
if ((sval & 0xf000) != 0xf000) {
DPRINTF(("%s: not coproc. insn.: opcode=0x%x\n",
__func__, sval));
fpe_abort(frame, ksi, SIGILL, ILL_ILLOPC);
}
if ((sval & 0x0E00) != 0x0200) {
DPRINTF(("%s: bad coproc. id: opcode=0x%x\n", __func__, sval));
fpe_abort(frame, ksi, SIGILL, ILL_ILLOPC);
}
insn.is_opcode = sval;
optype = (sval & 0x01C0);
if (ufetch_short((void *)(insn.is_pc + 2), &sval)) {
DPRINTF(("%s: fault reading word1\n", __func__));
fpe_abort(frame, ksi, SIGSEGV, SEGV_ACCERR);
}
insn.is_word1 = sval;
/* all FPU instructions are at least 4-byte long */
insn.is_advance = 4;
DUMP_INSN(&insn);
/*
* Which family (or type) of opcode is it?
* Tests ordered by likelihood (hopefully).
* Certainly, type 0 is the most common.
*/
if (optype == 0x0000) {
/* type=0: generic */
if ((sval & 0x8000)) {
if ((sval & 0x4000)) {
DPRINTF(("%s: fmovm FPr\n", __func__));
sig = fpu_emul_fmovm(&fe, &insn);
} else {
DPRINTF(("%s: fmovm FPcr\n", __func__));
sig = fpu_emul_fmovmcr(&fe, &insn);
}
} else {
if ((sval & 0xe000) == 0x6000) {
/* fstore = fmove FPn,mem */
DPRINTF(("%s: fmove to mem\n", __func__));
sig = fpu_emul_fstore(&fe, &insn);
} else if ((sval & 0xfc00) == 0x5c00) {
/* fmovecr */
DPRINTF(("%s: fmovecr\n", __func__));
sig = fpu_emul_fmovecr(&fe, &insn);
} else if ((sval & 0xa07f) == 0x26) {
/* fscale */
DPRINTF(("%s: fscale\n", __func__));
sig = fpu_emul_fscale(&fe, &insn);
} else {
DPRINTF(("%s: other type0\n", __func__));
/* all other type0 insns are arithmetic */
sig = fpu_emul_arith(&fe, &insn);
}
if (sig == 0) {
DPRINTF(("%s: type 0 returned 0\n", __func__));
sig = fpu_upd_excp(&fe);
}
}
} else if (optype == 0x0080 || optype == 0x00C0) {
/* type=2 or 3: fbcc, short or long disp. */
DPRINTF(("%s: fbcc %s\n", __func__,
(optype & 0x40) ? "long" : "short"));
sig = fpu_emul_brcc(&fe, &insn);
} else if (optype == 0x0040) {
/* type=1: fdbcc, fscc, ftrapcc */
DPRINTF(("%s: type1\n", __func__));
sig = fpu_emul_type1(&fe, &insn);
/* real FTRAPcc raises T_TRAPVINST if the condition is met. */
if (sig == SIGFPE) {
ksi->ksi_trap = T_TRAPVINST;
}
} else {
/* type=4: fsave (privileged) */
/* type=5: frestore (privileged) */
/* type=6: reserved */
/* type=7: reserved */
DPRINTF(("%s: bad opcode type: opcode=0x%x\n", __func__,
insn.is_opcode));
sig = SIGILL;
}
DUMP_INSN(&insn);
/*
* XXX it is not clear to me, if we should progress the PC always,
* for SIGFPE || 0, or only for 0; however, without SIGFPE, we
* don't pass the signalling regression tests. -is
*/
if ((sig == 0) || (sig == SIGFPE))
frame->f_pc += insn.is_advance;
#if defined(DDB) && defined(DEBUG_FPE)
else {
printf("%s: sig=%d, opcode=%x, word1=%x\n", __func__,
sig, insn.is_opcode, insn.is_word1);
kdb_trap(-1, (db_regs_t *)&frame);
}
#endif
#if 0 /* XXX something is wrong */
if (frame->f_format == 4) {
/* XXX Restore PC -- 68{EC,LC}040 only */
if (insn.is_nextpc)
frame->f_pc = insn.is_nextpc;
}
#endif
DPRINTF(("%s: result is a ", __func__));
if (fp->fp_sign) {
DPRINTF(("negative "));
fpsr |= FPSR_NEG;
} else {
DPRINTF(("positive "));
}
switch (fp->fp_class) {
case FPC_SNAN:
DPRINTF(("signaling NAN\n"));
fpsr |= (FPSR_NAN | FPSR_SNAN);
break;
case FPC_QNAN:
DPRINTF(("quiet NAN\n"));
fpsr |= FPSR_NAN;
break;
case FPC_ZERO:
DPRINTF(("Zero\n"));
fpsr |= FPSR_ZERO;
break;
case FPC_INF:
DPRINTF(("Inf\n"));
fpsr |= FPSR_INF;
break;
default:
DPRINTF(("Number\n"));
/* anything else is treated as if it is a number */
break;
}
fe->fe_fpsr = fe->fe_fpframe->fpf_fpsr = fpsr;
DPRINTF(("%s: new fpsr=%08x\n", __func__, fe->fe_fpframe->fpf_fpsr));
return fpsr;
}
static int
fpu_emul_fmovmcr(struct fpemu *fe, struct instruction *insn)
{
struct frame *frame = fe->fe_frame;
struct fpframe *fpf = fe->fe_fpframe;
int sig;
int reglist;
int regcount;
int fpu_to_mem;
int modreg;
uint32_t tmp[3];
/* move to/from control registers */
reglist = (insn->is_word1 & 0x1c00) >> 10;
/* Bit 13 selects direction (FPU to/from Mem) */
fpu_to_mem = insn->is_word1 & 0x2000;
/* Check an illegal mod/reg. */
modreg = insn->is_opcode & 077;
if (fpu_to_mem) {
/* PCrel, #imm are illegal. */
if (modreg >= 072) {
return SIGILL;
}
} else {
/* All mod/reg can be specified. */
if (modreg >= 075) {
return SIGILL;
}
}
/*
* If reglist is 0b000, treat it as FPIAR. This is not specification
* but the behavior described in the 6888x user's manual.
*/
if (reglist == 0)
reglist = 1;
/*
* For data register, only single register can be transferred.
* For addr register, only FPIAR can be transferred.
*/
if ((insn->is_ea.ea_flags & EA_DIRECT)) {
if (insn->is_ea.ea_regnum < 8) {
if (regcount != 1) {
return SIGILL;
}
} else {
if (reglist != 1) {
return SIGILL;
}
}
}
if (fpu_to_mem) {
uint32_t *s = &tmp[0];
if ((reglist & 4)) {
*s++ = fpf->fpf_fpcr;
}
if ((reglist & 2)) {
*s++ = fpf->fpf_fpsr;
}
if ((reglist & 1)) {
*s++ = fpf->fpf_fpiar;
}
/*
* type 0: fmovem
* Separated out of fpu_emul_type0 for efficiency.
* In this function, we know:
* (opcode & 0x01C0) == 0
* (word1 & 0x8000) == 0x8000
*
* No conversion or rounding is done by this instruction,
* and the FPSR is not affected.
*/
static int
fpu_emul_fmovm(struct fpemu *fe, struct instruction *insn)
{
struct frame *frame = fe->fe_frame;
struct fpframe *fpf = fe->fe_fpframe;
int word1, sig;
int reglist, regmask, regnum;
int modreg;
int fpu_to_mem, order;
/* int w1_post_incr; */
int *fpregs;
insn->is_datasize = 12;
word1 = insn->is_word1;
/* Bit 13 selects direction (FPU to/from Mem) */
fpu_to_mem = word1 & 0x2000;
/* take care of special cases */
if (x->fp_class < 0 || y->fp_class < 0) {
/* if either of two is a SNAN, result is SNAN */
x->fp_class =
(y->fp_class < x->fp_class) ? y->fp_class : x->fp_class;
} else if (x->fp_class == FPC_INF) {
if (y->fp_class == FPC_INF) {
/* both infinities */
if (x->fp_sign == y->fp_sign) {
/* return a signed zero */
x->fp_class = FPC_ZERO;
} else {
/* return a faked number w/x's sign */
x->fp_class = FPC_NUM;
x->fp_exp = 16383;
x->fp_mant[0] = FP_1;
}
} else {
/* y is a number */
/* return a forged number w/x's sign */
x->fp_class = FPC_NUM;
x->fp_exp = 16383;
x->fp_mant[0] = FP_1;
}
} else if (y->fp_class == FPC_INF) {
/* x is a Num but y is an Inf */
/* return a forged number w/y's sign inverted */
x->fp_class = FPC_NUM;
x->fp_sign = !y->fp_sign;
x->fp_exp = 16383;
x->fp_mant[0] = FP_1;
} else {
/*
* x and y are both numbers or zeros,
* or pair of a number and a zero
*/
y->fp_sign = !y->fp_sign;
x = fpu_add(fe); /* (x - y) */
/*
* FCMP does not set Inf bit in CC, so return a forged number
* (value doesn't matter) if Inf is the result of fsub.
*/
if (x->fp_class == FPC_INF) {
x->fp_class = FPC_NUM;
x->fp_exp = 16383;
x->fp_mant[0] = FP_1;
}
}
return x;
}
/*
* arithmetic operations
*/
static int
fpu_emul_arith(struct fpemu *fe, struct instruction *insn)
{
struct frame *frame = fe->fe_frame;
uint32_t *fpregs = &(fe->fe_fpframe->fpf_regs[0]);
struct fpn *res;
int word1, sig = 0;
int regnum, format;
int modreg;
int discard_result = 0;
uint32_t buf[3];
#ifdef DEBUG_FPE
int flags;
char regname;
#endif
fpu_load_ea(frame, insn, &insn->is_ea, (char*)buf);
if (format == FTYPE_WRD) {
/* sign-extend */
buf[0] &= 0xffff;
if (buf[0] & 0x8000)
buf[0] |= 0xffff0000;
format = FTYPE_LNG;
} else if (format == FTYPE_BYT) {
/* sign-extend */
buf[0] &= 0xff;
if (buf[0] & 0x80)
buf[0] |= 0xffffff00;
format = FTYPE_LNG;
}
DPRINTF(("%s: src = %08x %08x %08x, siz = %d\n", __func__,
buf[0], buf[1], buf[2], insn->is_datasize));
fpu_explode(fe, &fe->fe_f2, format, buf);
}
DUMP_INSN(insn);
/*
* An arithmetic instruction emulate function has a prototype of
* struct fpn *fpu_op(struct fpemu *);
*
* 1) If the instruction is monadic, then fpu_op() must use
* fe->fe_f2 as its operand, and return a pointer to the
* result.
*
* 2) If the instruction is diadic, then fpu_op() must use
* fe->fe_f1 and fe->fe_f2 as its two operands, and return a
* pointer to the result.
*
*/
res = NULL;
switch (word1 & 0x7f) {
case 0x00: /* fmove */
res = &fe->fe_f2;
break;
case 0x01: /* fint */
res = fpu_int(fe);
break;
case 0x02: /* fsinh */
res = fpu_sinh(fe);
break;
case 0x03: /* fintrz */
res = fpu_intrz(fe);
break;
case 0x04: /* fsqrt */
res = fpu_sqrt(fe);
break;
case 0x06: /* flognp1 */
res = fpu_lognp1(fe);
break;
case 0x08: /* fetoxm1 */
res = fpu_etoxm1(fe);
break;
case 0x09: /* ftanh */
res = fpu_tanh(fe);
break;
case 0x0A: /* fatan */
res = fpu_atan(fe);
break;
case 0x0C: /* fasin */
res = fpu_asin(fe);
break;
case 0x0D: /* fatanh */
res = fpu_atanh(fe);
break;
case 0x0E: /* fsin */
res = fpu_sin(fe);
break;
case 0x0F: /* ftan */
res = fpu_tan(fe);
break;
case 0x10: /* fetox */
res = fpu_etox(fe);
break;
case 0x11: /* ftwotox */
res = fpu_twotox(fe);
break;
case 0x12: /* ftentox */
res = fpu_tentox(fe);
break;
case 0x14: /* flogn */
res = fpu_logn(fe);
break;
case 0x15: /* flog10 */
res = fpu_log10(fe);
break;
case 0x16: /* flog2 */
res = fpu_log2(fe);
break;
case 0x18: /* fabs */
fe->fe_f2.fp_sign = 0;
res = &fe->fe_f2;
break;
case 0x19: /* fcosh */
res = fpu_cosh(fe);
break;
case 0x1A: /* fneg */
fe->fe_f2.fp_sign = !fe->fe_f2.fp_sign;
res = &fe->fe_f2;
break;
case 0x1C: /* facos */
res = fpu_acos(fe);
break;
case 0x1D: /* fcos */
res = fpu_cos(fe);
break;
case 0x1E: /* fgetexp */
res = fpu_getexp(fe);
break;
case 0x1F: /* fgetman */
res = fpu_getman(fe);
break;
case 0x20: /* fdiv */
case 0x24: /* fsgldiv: cheating - better than nothing */
res = fpu_div(fe);
break;
case 0x21: /* fmod */
res = fpu_mod(fe);
break;
case 0x28: /* fsub */
fe->fe_f2.fp_sign = !fe->fe_f2.fp_sign; /* f2 = -f2 */
/* FALLTHROUGH */
case 0x22: /* fadd */
res = fpu_add(fe);
break;
case 0x23: /* fmul */
case 0x27: /* fsglmul: cheating - better than nothing */
res = fpu_mul(fe);
break;
case 0x25: /* frem */
res = fpu_rem(fe);
break;
case 0x26:
/* fscale is handled by a separate function */
break;
case 0x30:
case 0x31:
case 0x32:
case 0x33:
case 0x34:
case 0x35:
case 0x36:
case 0x37: /* fsincos */
res = fpu_sincos(fe, word1 & 7);
break;
case 0x38: /* fcmp */
res = fpu_cmp(fe);
discard_result = 1;
break;
case 0x3A: /* ftst */
res = &fe->fe_f2;
discard_result = 1;
break;
default: /* possibly 040/060 instructions */
DPRINTF(("%s: bad opcode=0x%x, word1=0x%x\n", __func__,
insn->is_opcode, insn->is_word1));
sig = SIGILL;
}
/* for sanity */
if (res == NULL)
sig = SIGILL;
if (sig == 0) {
if (!discard_result)
fpu_implode(fe, res, FTYPE_EXT, &fpregs[regnum * 3]);
/* update fpsr according to the result of operation */
fpu_upd_fpsr(fe, res);
#ifdef DEBUG_FPE
if (!discard_result) {
printf("%s: %08x,%08x,%08x stored in FP%d\n", __func__,
fpregs[regnum * 3],
fpregs[regnum * 3 + 1],
fpregs[regnum * 3 + 2],
regnum);
} else {
static const char *class_name[] =
{ "SNAN", "QNAN", "ZERO", "NUM", "INF" };
printf("%s: result(%s,%c,%d,%08x,%08x,%08x) "
"discarded\n", __func__,
class_name[res->fp_class + 2],
res->fp_sign ? '-' : '+', res->fp_exp,
res->fp_mant[0], res->fp_mant[1],
res->fp_mant[2]);
}
#endif
} else {
DPRINTF(("%s: received signal %d\n", __func__, sig));
}
/*
* test condition code according to the predicate in the opcode.
* returns -1 when the predicate evaluates to true, 0 when false.
* signal numbers are returned when an error is detected.
*/
static int
test_cc(struct fpemu *fe, int pred)
{
int result, sig_bsun;
int fpsr;
/*
* condition real 68882
* mnemonic in manual condition
* -------- ---------- ----------
* 0000 F 0 <- = ~NAN & 0 & ~Z | 0
* 0001 EQ Z <- = ~NAN & 0 | Z | 0
* 0010 OGT ~(NAN|Z|N) <- = ~NAN & ~N & ~Z | 0
* 0011 OGE Z|~(NAN|N) <- = ~NAN & ~N | Z | 0
* 0100 OLT N&~(NAN|Z) <- = ~NAN & N & ~Z | 0
* 0101 OLE Z|(N&~NAN) <- = ~NAN & N | Z | 0
* 0110 OGL ~(NAN|Z) <- = ~NAN & 1 & ~Z | 0
* 0111 OR ~NAN Z|~NAN = ~NAN & 1 | Z | 0
*
* 1000 UN NAN <- = 1 & 0 & ~Z | NAN
* 1001 UEQ NAN|Z <- = 1 & 0 | Z | NAN
* 1010 UGT NAN|~(N|Z) <- = 1 & ~N & ~Z | NAN
* 1011 UGE NAN|(Z|~N) <- = 1 & ~N | Z | NAN
* 1100 ULT NAN|(N&~Z) <- = 1 & N & ~Z | NAN
* 1101 ULE NAN|(Z|N) <- = 1 & N | Z | NAN
* 1110 NE ~Z NAN|(~Z) = 1 & 1 & ~Z | NAN
* 1111 T 1 <- = 1 & 1 | Z | NAN
*/
if ((pred & 0x08) == 0) {
result = ((fpsr & FPSR_NAN) == 0);
} else {
result = 1;
}
switch (pred & 0x06) {
case 0x00: /* AND 0 */
result &= 0;
break;
case 0x02: /* AND ~N */
result &= ((fpsr & FPSR_NEG) == 0);
break;
case 0x04: /* AND N */
result &= ((fpsr & FPSR_NEG) != 0);
break;
case 0x06: /* AND 1 */
result &= 1;
break;
}
if ((pred & 0x01) == 0) {
result &= ((fpsr & FPSR_ZERO) == 0);
} else {
result |= ((fpsr & FPSR_ZERO) != 0);
}
if ((pred & 0x08) != 0) {
result |= ((fpsr & FPSR_NAN) != 0);
}
DPRINTF(("=> %s (%d)\n", result ? "true" : "false", result));
/* if it's an IEEE unaware test and NAN is set, BSUN is set */
if (sig_bsun && (fpsr & FPSR_NAN)) {
fpsr |= FPSR_BSUN;
}
/* if BSUN is set, IOP is set too */
if ((fpsr & FPSR_BSUN)) {
fpsr |= FPSR_AIOP;
}
/* put fpsr back */
fe->fe_fpframe->fpf_fpsr = fe->fe_fpsr = fpsr;
return -result;
}
/*
* type 1: fdbcc, fscc, ftrapcc
* In this function, we know:
* (opcode & 0x01C0) == 0x0040
* return SIGILL for an illegal instruction.
* return SIGFPE if FTRAPcc's condition is met.
*/
static int
fpu_emul_type1(struct fpemu *fe, struct instruction *insn)
{
struct frame *frame = fe->fe_frame;
int advance, sig, branch, displ;
unsigned short sval;
case 070: /* ftrapcc or fscc */
advance = 4;
if ((insn->is_opcode & 07) >= 2) {
switch (insn->is_opcode & 07) {
case 3: /* long opr */
advance += 2;
case 2: /* word opr */
advance += 2;
case 4: /* no opr */
break;
default:
return SIGILL;
break;
}
insn->is_advance = advance;
if (branch) {
/* trap */
sig = SIGFPE;
}
break;
}
/* FALLTHROUGH */
default: /* fscc */
insn->is_datasize = 1; /* always byte */
sig = fpu_decode_ea(frame, insn, &insn->is_ea, insn->is_opcode);
if (sig) {
break;
}
/* set result */
sig = fpu_store_ea(frame, insn, &insn->is_ea, (char *)&branch);
break;
}
return sig;
}
/*
* Type 2 or 3: fbcc (also fnop)
* In this function, we know:
* (opcode & 0x0180) == 0x0080
*/
static int
fpu_emul_brcc(struct fpemu *fe, struct instruction *insn)
{
int displ, word2;
int sig;
unsigned short sval;
/*
* Get branch displacement.
*/
displ = insn->is_word1;
if (insn->is_opcode & 0x40) {
if (ufetch_short((void *)(insn->is_pc + insn->is_advance),
&sval)) {
DPRINTF(("%s: fault reading word2\n", __func__));
return SIGSEGV;
}
word2 = sval;
displ <<= 16;
displ |= word2;
insn->is_advance += 2;
} else {
/* displacement is word sized */
if (displ & 0x8000)
displ |= 0xFFFF0000;
}
/* XXX: If CC, insn->is_pc += displ */
sig = test_cc(fe, insn->is_opcode);
fe->fe_fpframe->fpf_fpsr = fe->fe_fpsr;
if (fe->fe_fpsr & fe->fe_fpcr & FPSR_EXCP) {
return SIGFPE; /* caught an exception */
}
if (sig == -1) {
/*
* branch does take place; 2 is the offset to the 1st disp word
*/
insn->is_advance = displ + 2;
#if 0 /* XXX */
insn->is_nextpc = insn->is_pc + insn->is_advance;
#endif
} else if (sig)
return SIGILL; /* got a signal */
DPRINTF(("%s: %s insn @ %x (%x+%x) (disp=%x)\n", __func__,
(sig == -1) ? "BRANCH to" : "NEXT",
insn->is_pc + insn->is_advance, insn->is_pc, insn->is_advance,
displ));
return 0;
}