/*
* this doesn't attempt to implement MIPS floating-point properties
* that aren't visible in the Inferno environment.
* all arithmetic is done in double precision.
* the FP trap status isn't updated.
*
* we emulate the original MIPS FP register model: 32-bits each,
* F(2n) and F(2n+1) are a double, with lower-order word first;
* note that this is little-endian order, unlike the rest of the
* machine, so double-word operations will need to swap the words
* when transferring between FP registers and memory.
*
* on some machines, we can convert to an FP internal representation when
* moving to FPU registers and back (to integer, for example) when moving
* from them. the MIPS is different: its conversion instructions operate
* on FP registers only, and there's no way to tell if data being moved
* into an FP register is integer or FP, so it must be possible to store
* integers in FP registers without conversion. Furthermore, pairs of FP
* registers can be combined into a double. So we keep the raw bits
* around as the canonical representation and convert only to and from
* Internal FP format when we must (i.e., before calling the common fpi
* code).
*/
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "ureg.h"
#include "../port/fpi.h"
#include <tos.h>
#ifdef FPEMUDEBUG
#define DBG(bits) (fpemudebug & (bits))
#define intpr _intpr
#define internsane _internsane
#define dbgstuck _dbgstuck
#else
#define DBG(bits) (0)
#define internsane(i, ur) do { USED(ur); } while(0)
#define intpr(i, reg, fmt, ufp) do {} while(0)
#define dbgstuck(pc, ur, ufp) do {} while(0)
#endif
#define OFR(memb) (uintptr)&((Ureg*)0)->memb /* offset into Ureg of memb */
#define REG(ur, r) *acpureg(ur, r) /* cpu reg in Ureg */
#define FREG(ufp, fr) (ufp)->reg[(fr) & REGMASK] /* fp reg raw bits */
/*
* instruction decoding for COP1 instructions; integer instructions
* are laid out differently.
*/
#define OP(ul) ((ul) >> 26)
#define REGMASK MASK(5) /* mask for a register number */
#define FMT(ul) (((ul) >> 21) & REGMASK) /* data type */
#define REGT(ul) (((ul) >> 16) & REGMASK) /* source2 register */
#define REGS(ul) (((ul) >> 11) & REGMASK) /* source1 register */
#define REGD(ul) (((ul) >> 6) & REGMASK) /* destination register */
#define FUNC(ul) ((ul) & MASK(6))
enum {
Dbgbasic = 1<<0, /* base debugging: ops, except'ns */
Dbgmoves = 1<<1, /* not very exciting usually */
Dbgregs = 1<<2, /* print register contents around ops */
Dbgdelay = 1<<3, /* branch-delay-slot-related machinery */
/* fpimips status codes */
Failed = -1,
Advpc, /* advance pc normally */
Leavepc, /* don't change the pc */
Leavepcret, /* ... and return to user mode now */
Nomatch,
/* no-ops */
NOP = 0x27, /* NOR R0, R0, R0 */
MIPSNOP = 0, /* SLL R0, R0, R0 */
/* fp op-codes */
COP1 = 0x11, /* fpu op */
LWC1 = 0x31, /* load float/long */
LDC1 = 0x35, /* load double/vlong */
SWC1 = 0x39, /* store float/long */
SDC1 = 0x3d, /* store double/vlong */
N = 1<<31, /* condition codes */
Z = 1<<30,
C = 1<<29,
V = 1<<28,
/* data types (format field values) */
MFC1 = 0, /* and func == 0 ... */
DMFC1, /* vlong move */
CFC1, /* ctl word move */
MTC1 = 4,
DMTC1,
CTC1, /* ... end `and func == 0' */
BRANCH = 8,
Ffloat = 16,
Fdouble,
Flong = 20,
Fvlong,
/* fp control registers */
Fpimp = 0,
Fpcsr = 31,
};
typedef struct FP1 FP1;
typedef struct FP2 FP2;
typedef struct FPcvt FPcvt;
typedef struct Instr Instr;
struct Instr { /* a COP1 instruction, broken out and registers converted */
int iw; /* whole word */
uintptr pc;
int o; /* opcode or cop1 func code */
int fmt; /* operand format */
int rm; /* first operand register */
int rn; /* second operand register */
int rd; /* destination register */
Internal *fm; /* converted from FREG(ufp, rm) */
Internal *fn;
char *dfmt;
FPsave *ufp; /* fp state, including fp registers */
Ureg *ur; /* user registers */
};
struct FP2 {
char* name;
void (*f)(Internal*, Internal*, Internal*);
};
struct FP1 {
char* name;
void (*f)(Internal*, Internal*);
};
struct FPcvt {
char* name;
void (*f)(int, int, int, Ureg *, FPsave *);
};
static int roff[32] = {
0, OFR(r1), OFR(r2), OFR(r3),
OFR(r4), OFR(r5), OFR(r6), OFR(r7),
OFR(r8), OFR(r9), OFR(r10), OFR(r11),
OFR(r12), OFR(r13), OFR(r14), OFR(r15),
OFR(r16), OFR(r17), OFR(r18), OFR(r19),
OFR(r20), OFR(r21), OFR(r22), OFR(r23),
OFR(r24), OFR(r25), OFR(r26), OFR(r27),
OFR(r28), OFR(sp), OFR(r30), OFR(r31),
};
/*
* plan 9 assumes F24 initialized to 0.0, F26 to 0.5, F28 to 1.0, F30 to 2.0.
*/
enum {
FZERO = 24,
FHALF = 26,
};
static Internal fpconst[Nfpregs] = { /* indexed by register no. */
/* s, e, l, h */
[FZERO] {0, 0x1, 0x00000000, 0x00000000}, /* 0.0 */
[FHALF] {0, 0x3FE, 0x00000000, 0x08000000}, /* 0.5 */
[28] {0, 0x3FF, 0x00000000, 0x08000000}, /* 1.0 */
[30] {0, 0x400, 0x00000000, 0x08000000}, /* 2.0 */
};
static char *fmtnames[] = {
[MFC1] "MF",
[DMFC1] "DMF",
[CFC1] "CF",
[MTC1] "MT",
[DMTC1] "DMT",
[CTC1] "CT",
[BRANCH]"BR",
[Ffloat]"F",
[Fdouble]"D",
[Flong] "W",
[Fvlong]"L",
};
static char *prednames[] = {
[0] "F",
[1] "UN",
[2] "EQ",
[3] "UEQ",
[4] "OLT",
[5] "ULT",
[6] "OLE",
[7] "ULE",
[8] "SF",
[9] "NGLE",
[10] "SEQ",
[11] "NGL",
[12] "LT",
[13] "NGE",
[14] "LE",
[15] "NGT",
};
int fpemudebug = 0; /* settable via /dev/archctl */
static ulong dummyr0;
static QLock watchlock; /* lock for watch-points */
ulong branch(Ureg*, ulong);
int isbranch(ulong *);
static int fpimips(ulong, ulong, Ureg *, FPsave *);
char *
fpemuprint(char *p, char *ep)
{
#ifdef FPEMUDEBUG
return seprint(p, ep, "fpemudebug %d\n", fpemudebug);
#else
USED(ep);
return p;
#endif
}
static ulong *
acpureg(Ureg *ur, int r)
{
r &= REGMASK;
if (r == 0 || roff[r] == 0) {
dummyr0 = 0;
return &dummyr0;
}
return (ulong *)((char*)ur + roff[r]);
}
ulong *
reg(Ureg *ur, int r) /* for faultmips */
{
return ®(ur, r);
}
static void
_internsane(Internal *i, Ureg *ur)
{
static char buf[ERRMAX];
USED(i);
if (!(DBG(Dbgbasic)))
return;
if ((unsigned)i->s > 1) {
snprint(buf, sizeof buf,
"fpuemu: bogus Internal sign at pc=%#p", ur->pc);
error(buf);
}
if ((unsigned)i->e > DoubleExpMax) {
snprint(buf, sizeof buf,
"fpuemu: bogus Internal exponent at pc=%#p", ur->pc);
error(buf);
}
}
/*
* mips binary operations (d = n operator m)
*/
static void
fadd(Internal *m, Internal *n, Internal *d)
{
(m->s == n->s? fpiadd: fpisub)(m, n, d);
}
static void
fsub(Internal *m, Internal *n, Internal *d)
{
m->s ^= 1;
(m->s == n->s? fpiadd: fpisub)(m, n, d);
}
/*
* mips unary operations
*/
static void
frnd(Internal *m, Internal *d)
{
short e;
Internal tmp;
tmp = fpconst[FHALF];
(m->s? fsub: fadd)(&tmp, m, d);
if(IsWeird(d))
return;
fpiround(d);
e = (d->e - ExpBias) + 1;
if(e <= 0)
SetZero(d);
else if(e > FractBits){
if(e < 2*FractBits)
d->l &= ~((1<<(2*FractBits - e))-1);
}else{
d->l = 0;
if(e < FractBits)
d->h &= ~((1<<(FractBits-e))-1);
}
}
/* debugging: print internal representation of an fp reg */
static void
_intpr(Internal *i, int reg, int fmt, FPsave *ufp)
{
USED(i);
if (!(DBG(Dbgregs)))
return;
if (fmt == Fdouble && reg < 31)
iprint("\tD%02d: l %08lux h %08lux =\ts %d e %04d h %08lux l %08lux\n",
reg, FREG(ufp, reg), FREG(ufp, reg+1),
i->s, i->e, i->h, i->l);
else
iprint("\tF%02d: %08lux =\ts %d e %04d h %08lux l %08lux\n",
reg, FREG(ufp, reg),
i->s, i->e, i->h, i->l);
delay(75);
}
static void
dreg2dbl(Double *dp, int reg, FPsave *ufp)
{
reg &= ~1;
dp->l = FREG(ufp, reg);
dp->h = FREG(ufp, reg+1);
}
static void
dbl2dreg(int reg, Double *dp, FPsave *ufp)
{
reg &= ~1;
FREG(ufp, reg) = dp->l;
FREG(ufp, reg+1) = dp->h;
}
static void
vreg2dbl(Double *dp, int reg, FPsave *ufp)
{
reg &= ~1;
dp->l = FREG(ufp, reg+1);
dp->h = FREG(ufp, reg);
}
static void
dbl2vreg(int reg, Double *dp, FPsave *ufp)
{
reg &= ~1;
FREG(ufp, reg+1) = dp->l;
FREG(ufp, reg) = dp->h;
}
/* convert fmt (rm) to double (rd) */
static void
fcvtd(int fmt, int rm, int rd, Ureg *ur, FPsave *ufp)
{
Double d;
Internal intrn;
switch (fmt) {
case Ffloat:
fpis2i(&intrn, &FREG(ufp, rm));
internsane(&intrn, ur);
fpii2d(&d, &intrn);
break;
case Fdouble:
dreg2dbl(&d, rm, ufp);
break;
case Flong:
fpiw2i(&intrn, &FREG(ufp, rm));
internsane(&intrn, ur);
fpii2d(&d, &intrn);
break;
case Fvlong:
vreg2dbl(&d, rm, ufp);
fpiv2i(&intrn, &d);
internsane(&intrn, ur);
fpii2d(&d, &intrn);
break;
}
dbl2dreg(rd, &d, ufp);
if (fmt != Fdouble && DBG(Dbgregs))
intpr(&intrn, rm, Fdouble, ufp);
}
/* convert fmt (rm) to single (rd) */
static void
fcvts(int fmt, int rm, int rd, Ureg *ur, FPsave *ufp)
{
Double d;
Internal intrn;
switch (fmt) {
case Ffloat:
FREG(ufp, rd) = FREG(ufp, rm);
break;
case Fdouble:
dreg2dbl(&d, rm, ufp);
fpid2i(&intrn, &d);
break;
case Flong:
fpiw2i(&intrn, &FREG(ufp, rm));
break;
case Fvlong:
vreg2dbl(&d, rm, ufp);
fpiv2i(&intrn, &d);
break;
}
if (fmt != Ffloat) {
if(DBG(Dbgregs))
intpr(&intrn, rm, Ffloat, ufp);
internsane(&intrn, ur);
fpii2s(&FREG(ufp, rd), &intrn);
}
}
/* convert fmt (rm) to long (rd) */
static void
fcvtw(int fmt, int rm, int rd, Ureg *ur, FPsave *ufp)
{
Double d;
Internal intrn;
switch (fmt) {
case Ffloat:
fpis2i(&intrn, &FREG(ufp, rm));
break;
case Fdouble:
dreg2dbl(&d, rm, ufp);
fpid2i(&intrn, &d);
break;
case Flong:
FREG(ufp, rd) = FREG(ufp, rm);
break;
case Fvlong:
vreg2dbl(&d, rm, ufp);
fpiv2i(&intrn, &d);
break;
}
if (fmt != Flong) {
if(DBG(Dbgregs))
intpr(&intrn, rm, Flong, ufp);
internsane(&intrn, ur);
fpii2w((long *)&FREG(ufp, rd), &intrn);
}
}
/* convert fmt (rm) to vlong (rd) */
static void
fcvtv(int fmt, int rm, int rd, Ureg *ur, FPsave *ufp)
{
Double d;
Internal intrn;
switch (fmt) {
case Ffloat:
fpis2i(&intrn, &FREG(ufp, rm));
break;
case Fdouble:
dreg2dbl(&d, rm, ufp);
fpid2i(&intrn, &d);
break;
case Flong:
fpiw2i(&intrn, &FREG(ufp, rm));
break;
case Fvlong:
vreg2dbl(&d, rm, ufp);
dbl2vreg(rd, &d, ufp);
break;
}
if (fmt != Fvlong) {
if(DBG(Dbgregs))
intpr(&intrn, rm, Fvlong, ufp);
internsane(&intrn, ur);
fpii2v((vlong *)&FREG(ufp, rd), &intrn);
}
}
/*
* MIPS function codes
*/
static FP2 optab2[] = { /* Fd := Fn OP Fm (binary) */
[0] {"ADDF", fadd}, /* can ignore fmt, just use doubles */
[1] {"SUBF", fsub},
[2] {"MULF", fpimul},
[3] {"DIVF", fpidiv},
};
static FP1 optab1[32] = { /* Fd := OP Fm (unary) */
[4] {"SQTF", /*fsqt*/0},
[5] {"ABSF", /*fabsf*/0}, /* inline in unaryemu... */
[6] {"MOVF", /*fmov*/0},
[7] {"NEGF", /*fmovn*/0},
[8] {"ROUND.L", /*froundl*/0}, /* 64-bit integer results ... */
[9] {"TRUNC.L", /*ftruncl*/0},
[10] {"CEIL.L", /*fceill*/0},
[11] {"FLOOR.L", /*ffloorl*/0},
[12] {"ROUND.W", frnd}, /* 32-bit integer results ... */
[13] {"TRUNC.W", /*ftrunc*/0},
[14] {"CEIL.W", /*fceil*/0},
[15] {"FLOOR.W", /*ffloor*/0},
/* 17—19 are newish MIPS32/64 conditional moves */
/* 21, 22, 28—31 are newish reciprocal or sqrt */
};
static FPcvt optabcvt[] = { /* Fd := OP(fmt, Fm) (unary) */
[32] {"CVT.S", fcvts}, /* must honour fmt as src format */
[33] {"CVT.D", fcvtd},
[36] {"CVT.W", fcvtw},
[37] {"CVT.L", fcvtv},
};
/*
* No type conversion is implied and the type of the cpu register is
* unknown, so copy the bits into reg.
* Later instructions will have to know the correct type and use the
* right format specifier to convert to or from Internal FP.
*/
static void
fld(int d, ulong ea, int n, FPsave *ufp)
{
if(DBG(Dbgmoves))
iprint("MOV%c #%lux, F%d\n", n==8? 'D': 'F', ea, d);
if (n == 4)
memmove(&FREG(ufp, d), (void *)ea, 4);
else if (n == 8){
d &= ~1;
/* NB: we swap order of the words */
memmove(&FREG(ufp, d), (void *)(ea+4), 4);
memmove(&FREG(ufp, d+1), (void *)ea, 4);
} else
panic("fld: n (%d) not 4 nor 8", n);
}
static void
fst(ulong ea, int s, int n, FPsave *ufp)
{
if(DBG(Dbgmoves))
iprint("MOV%c F%d,#%lux\n", n==8? 'D': 'F', s, ea);
if (n == 4)
memmove((void *)ea, &FREG(ufp, s), 4);
else if (n == 8){
s &= ~1;
/* NB: we swap order of the words */
memmove((void *)(ea+4), &FREG(ufp, s), 4);
memmove((void *)ea, &FREG(ufp, s+1), 4);
} else
panic("fst: n (%d) not 4 nor 8", n);
}
void
unimp(ulong pc, ulong op, char *msg)
{
char buf[120];
snprint(buf, sizeof(buf), "sys: fp: pc=%#lux unimp fp %#.8lux: %s",
pc, op, msg);
if(DBG(Dbgbasic))
iprint("FPE: %s\n", buf);
error(buf);
/* no return */
}
static int
isfpop(ulong iw)
{
switch (OP(iw)) {
case COP1:
case LWC1:
case LDC1:
case SWC1:
case SDC1:
return 1;
default:
return 0;
}
}
static int
ldst(ulong op, Ureg *ur, FPsave *ufp)
{
int rn, rd, o, size, wr;
short off;
ulong ea;
/* we're using the COP1 macros, but the fields have diff'nt meanings */
o = OP(op);
rn = FMT(op);
off = op;
ea = REG(ur, rn) + off;
rd = REGT(op);
//iprint("fpemu: ld/st (F%d)=%#lux + %d => ea %#lux\n", rn, REG(ur, rn), off, ea);
size = 4;
if (o == LDC1 || o == SDC1)
size = 8;
wr = (o == SWC1 || o == SDC1);
validaddr(ea, size, wr);
switch (o) {
case LWC1: /* load an fp register, rd, from memory */
case LDC1: /* load an fp register pair, (rd, rd+1), from memory */
fld(rd, ea, size, ufp);
break;
case SWC1: /* store an fp register, rd, into memory */
case SDC1: /* store an fp register pair, (rd, rd+1), into memory */
fst(ea, rd, size, ufp);
break;
default:
unimp(ur->pc, op, "unknown non-COP1 load or store");
return Failed;
}
return Advpc;
}
static int
cop1mov(Instr *ip)
{
int fs, rt;
uvlong vl;
FPsave *ufp;
Ureg *ur;
fs = ip->rm; /* F(s) aka rm */
rt = ip->rn; /* R(t) aka rn */
ur = ip->ur;
ufp = ip->ufp;
//iprint("fpemu: cop1 prob ld/st (R%d)=%#lux FREG%d\n", rn, REG(ip->ur, rn), rm);
/* MIPS fp register pairs are in little-endian order: low word first */
switch (ip->fmt) {
case MTC1:
/* load an fp register, F(s), from cpu register R(t) */
fld(fs, (uintptr)®(ur, rt), 4, ufp);
return Advpc;
case DMTC1:
/*
* load an fp register pair, (F(s), F(s+1)),
* from cpu registers (rt, rt+1)
*/
iprint("fpemu: 64-bit DMTC1 may have words backward\n");
rt &= ~1;
vl = (uvlong)REG(ur, rt+1) << 32 | REG(ur, rt);
fld(fs & ~1, (uintptr)&vl, 8, ufp);
return Advpc;
case MFC1:
/* store an fp register, fs, into a cpu register rt */
fst((uintptr)®(ur, rt), fs, 4, ufp);
return Advpc;
case DMFC1:
/*
* store an fp register pair, (F(s), F(s+1)),
* into cpu registers (rt, rt+1)
*/
iprint("fpemu: 64-bit DMFC1 may have words backward\n");
fst((uintptr)&vl, fs & ~1, 8, ufp);
rt &= ~1;
REG(ur, rt) = (ulong)vl;
REG(ur, rt+1) = vl>>32;
return Advpc;
case CFC1:
switch (fs) {
case Fpimp: /* MOVW FCR0,Rn */
REG(ur, rt) = 0x500; /* claim to be r4k */
break;
case Fpcsr:
REG(ur, rt) = ufp->fpcontrol;
break;
}
if(DBG(Dbgbasic))
iprint("MOVW FCR%d, R%d\n", fs, rt);
return Advpc;
case CTC1:
switch (fs) {
case Fpcsr:
ufp->fpcontrol = REG(ur, rt);
break;
}
if(DBG(Dbgbasic))
iprint("MOVW R%d, FCR%d\n", rt, fs);
return Advpc;
}
return Nomatch; /* not a load or store; keep looking */
}
static char *
decodefmt(int fmt)
{
if (fmtnames[fmt])
return fmtnames[fmt];
else
return "GOK";
}
static char *
predname(int pred) /* predicate name */
{
if (prednames[pred])
return prednames[pred];
else
return "GOK";
}
static int
fcmpf(Internal m, Internal n, int, int cond)
{
int i;
if(IsWeird(&m) || IsWeird(&n)){
/* BUG: should trap if not masked */
return 0;
}
fpiround(&n);
fpiround(&m);
i = fpicmp(&m, &n); /* returns -1, 0, or 1 */
switch (cond) {
case 0: /* F - false */
case 1: /* UN - unordered */
return 0;
case 2: /* EQ */
case 3: /* UEQ */
return i == 0;
case 4: /* OLT */
case 5: /* ULT */
return i < 0;
case 6: /* OLE */
case 7: /* ULE */
return i <= 0;
case 8: /* SF */
case 9: /* NGLE - not >, < or = */
return 0;
case 10: /* SEQ */
return i == 0;
case 11: /* NGL */
return i != 0;
case 12: /* LT */
case 13: /* NGE */
return i < 0;
case 14: /* LE */
case 15: /* NGT */
return i <= 0;
}
return 0;
}
/*
* assuming that ur->pc points to a branch instruction,
* change it to point to the branch's target and return it.
*/
static uintptr
followbr(Ureg *ur)
{
uintptr npc;
npc = branch(ur, up->fpsave.fpstatus);
if(npc == 0)
panic("fpemu: branch expected but not seen at %#p", ur->pc);
ur->pc = npc;
return npc;
}
/* emulate COP1 instruction in branch delay slot */
static void
dsemu(Instr *ip, ulong dsinsn, Ureg *ur, FPsave *ufp)
{
uintptr npc;
npc = ur->pc; /* save ur->pc since fpemu will change it */
if(DBG(Dbgdelay))
iprint(">>> emulating br delay slot\n");
fpimips(ip->pc + 4, dsinsn, ur, ufp);
if(DBG(Dbgdelay))
iprint("<<< done emulating br delay slot\n");
ur->pc = npc;
}
/*
* execute non-COP1 instruction in branch delay slot, in user mode with
* user registers, then trap so we can finish up and take the branch.
*/
static void
dsexec(Instr *ip, Ureg *ur, FPsave *ufp)
{
ulong dsaddr, wpaddr;
Tos *tos;
/*
* copy delay slot, EHB, EHB, EHB to tos->kscr, flush caches,
* point pc there, set watch point on tos->kscr[2], return.
* this is safe since we've already checked for branches (and FP
* instructions) in the delay slot, so the instruction can be
* executed at any address.
*/
dsaddr = ip->pc + 4;
tos = (Tos*)(USTKTOP-sizeof(Tos));
tos->kscr[0] = *(ulong *)dsaddr;
tos->kscr[1] = 0xc0; /* EHB; we could use some trap instead */
tos->kscr[2] = 0xc0; /* EHB */
tos->kscr[3] = 0xc0; /* EHB */
dcflush(tos->kscr, sizeof tos->kscr);
icflush(tos->kscr, sizeof tos->kscr);
wpaddr = (ulong)&tos->kscr[2] & ~7; /* clear I/R/W bits */
ufp->fpdelayexec = 1;
ufp->fpdelaypc = ip->pc; /* remember branch ip->pc */
ufp->fpdelaysts = ufp->fpstatus; /* remember state of FPCOND */
ur->pc = (ulong)tos->kscr; /* restart in tos */
qlock(&watchlock); /* wait for first watchpoint */
setwatchlo0(wpaddr | 1<<2); /* doubleword addr(!); i-fetches only */
setwatchhi0(TLBPID(tlbvirt())<<16); /* asid; see mmu.c */
if (DBG(Dbgdelay))
iprint("fpemu: set %s watch point at %#lux, after br ds %#lux...",
up->text, wpaddr, *(ulong *)dsaddr);
/* return to user mode, await fpwatch() trap */
}
void
fpwatch(Ureg *ur) /* called on watch-point trap */
{
FPsave *ufp;
ufp = &up->fpsave;
if(ufp->fpdelayexec == 0)
panic("fpwatch: unexpected watch trap");
/* assume we got here after branch-delay-slot execution */
ufp->fpdelayexec = 0;
setwatchlo0(0);
setwatchhi0(0);
qunlock(&watchlock);
ur->pc = ufp->fpdelaypc; /* pc of fp branch */
ur->cause &= BD; /* take no chances */
ufp->fpstatus = ufp->fpdelaysts;
followbr(ur); /* sets ur->pc to fp branch target */
if (DBG(Dbgdelay))
iprint("delay slot executed; resuming at %#lux\n", ur->pc);
}
static ulong
validiw(uintptr pc)
{
validaddr(pc, 4, 0);
return *(ulong*)pc;
}
/*
* COP1 (6) | BRANCH (5) | cc (3) | likely | true | offset(16)
* cc = ip->rn >> 2; // assume cc == 0
*/
static int
bremu(Instr *ip)
{
int off, taken;
ulong dsinsn;
FPsave *ufp;
Ureg *ur;
if (ip->iw & (1<<17))
error("fpuemu: `likely' fp branch (obs)");
ufp = ip->ufp;
if (ufp->fpstatus & FPCOND)
taken = ip->iw & (1<<16); /* taken iff BCT */
else
taken = !(ip->iw & (1<<16)); /* taken iff BCF */
dsinsn = validiw(ip->pc + 4); /* delay slot addressible? */
if(DBG(Dbgdelay)){
off = (short)(ip->iw & MASK(16));
iprint("BFP%c\t%d(PC): %staken\n", (ip->iw & (1<<16)? 'T': 'F'),
off, taken? "": "not ");
iprint("\tdelay slot: %08lux\n", dsinsn);
delay(75);
}
ur = ip->ur;
assert(ur->pc == ip->pc);
if(!taken)
return Advpc; /* didn't branch, so return to delay slot */
/*
* fp branch taken; emulate or execute the delay slot, then jump.
*/
if(dsinsn == NOP || dsinsn == MIPSNOP){
; /* delay slot does nothing */
}else if(isbranch((ulong *)(ip->pc + 4)))
error("fpuemu: branch in fp branch delay slot");
else if (isfpop(dsinsn))
dsemu(ip, dsinsn, ur, ufp); /* emulate delay slot */
else{
/*
* The hard case: we need to execute the delay slot
* in user mode with user registers. Set a watch point,
* return to user mode, await fpwatch() trap.
*/
dsexec(ip, ur, ufp);
return Leavepcret;
}
followbr(ur);
return Leavepc;
}
/* interpret fp reg as fmt (float or double) and convert to Internal */
static void
reg2intern(Internal *i, int reg, int fmt, Ureg *ur)
{
Double d;
FPsave *ufp;
/* we may see other fmt types on conversion or unary ops; ignore */
ufp = &up->fpsave;
switch (fmt) {
case Ffloat:
fpis2i(i, &FREG(ufp, reg));
internsane(i, ur);
break;
case Fdouble:
dreg2dbl(&d, reg, ufp);
fpid2i(i, &d);
internsane(i, ur);
break;
default:
SetQNaN(i); /* cause trouble if we try to use i */
break;
}
}
/* convert Internal to fp reg as fmt (float or double) */
static void
intern2reg(int reg, Internal *i, int fmt, Ureg *ur)
{
Double d;
FPsave *ufp;
Internal tmp;
ufp = &up->fpsave;
tmp = *i; /* make a disposable copy */
internsane(&tmp, ur);
switch (fmt) {
case Ffloat:
fpii2s(&FREG(ufp, reg), &tmp);
break;
case Fdouble:
fpii2d(&d, &tmp);
dbl2dreg(reg, &d, ufp);
break;
default:
panic("intern2reg: bad fmt %d", fmt);
}
}
/*
* comparisons - encoded slightly differently than arithmetic:
* COP1 (6) | fmt(5) | ft (5) | fs (5) | # same
* cc (3) | 0 | A=0 | # diff, was REGD
* FC=11 | cond (4) # FUNC
*/
static int
cmpemu(Instr *ip)
{
int cc, cond;
cc = ip->rd >> 2;
cond = ip->o & MASK(4);
reg2intern(ip->fn, ip->rn, ip->fmt, ip->ur);
/* fpicmp args are swapped, so this is `n compare m' */
if (fcmpf(*ip->fm, *ip->fn, cc, cond))
ip->ufp->fpstatus |= FPCOND;
else
ip->ufp->fpstatus &= ~FPCOND;
if(DBG(Dbgbasic))
iprint("CMP%s.%s F%d,F%d =%d\n", predname(cond), ip->dfmt,
ip->rm, ip->rn, (ip->ufp->fpstatus & FPCOND? 1: 0));
if(DBG(Dbgregs)) {
intpr(ip->fm, ip->rm, ip->fmt, ip->ufp);
intpr(ip->fn, ip->rn, ip->fmt, ip->ufp);
delay(75);
}
return Advpc;
}
static int
binemu(Instr *ip)
{
FP2 *fp;
Internal fd, prfd;
Internal *fn;
fp = &optab2[ip->o];
if(fp->f == nil)
unimp(ip->pc, ip->iw, "missing binary op");
/* convert the second operand */
fn = ip->fn;
reg2intern(fn, ip->rn, ip->fmt, ip->ur);
if(DBG(Dbgregs))
intpr(fn, ip->rn, ip->fmt, ip->ufp);
if(DBG(Dbgbasic)){
iprint("%s.%s\tF%d,F%d,F%d\n", fp->name, ip->dfmt,
ip->rm, ip->rn, ip->rd);
delay(75);
}
/*
* fn and fm are scratch Internals just for this instruction,
* so it's okay to let the fpi routines trash them in the course
* of operation.
*/
/* NB: fpi routines take m and n (s and t) in reverse order */
(*fp->f)(fn, ip->fm, &fd);
/* convert the result */
if(DBG(Dbgregs))
prfd = fd; /* intern2reg modifies fd */
intern2reg(ip->rd, &fd, ip->fmt, ip->ur);
if(DBG(Dbgregs))
intpr(&prfd, ip->rd, ip->fmt, ip->ufp);
return Advpc;
}
static int
unaryemu(Instr *ip)
{
int o;
FP1 *fp;
FPsave *ufp;
o = ip->o;
fp = &optab1[o];
if(DBG(Dbgbasic)){
iprint("%s.%s\tF%d,F%d\n", fp->name, ip->dfmt, ip->rm, ip->rd);
delay(75);
}
if(o == 6){ /* MOV */
int rm, rd;
ufp = ip->ufp;
rd = ip->rd;
rm = ip->rm;
if(ip->fmt == Fdouble){
rd &= ~1;
rm &= ~1;
FREG(ufp, rd+1) = FREG(ufp, rm+1);
}
FREG(ufp, rd) = FREG(ufp, rm);
}else{
Internal fdint, prfd;
Internal *fd;
switch(o){
case 5: /* ABS */
fd = ip->fm; /* use src Internal as dest */
fd->s = 0;
break;
case 7: /* NEG */
fd = ip->fm; /* use src Internal as dest */
fd->s ^= 1;
break;
default:
if(fp->f == nil)
unimp(ip->pc, ip->iw, "missing unary op");
fd = &fdint;
(*fp->f)(ip->fm, fd);
break;
}
if(DBG(Dbgregs))
prfd = *fd; /* intern2reg modifies fd */
intern2reg(ip->rd, fd, ip->fmt, ip->ur);
if(DBG(Dbgregs))
intpr(&prfd, ip->rd, ip->fmt, ip->ufp);
}
return Advpc;
}
static int
cvtemu(Instr *ip)
{
FPcvt *fp;
fp = &optabcvt[ip->o];
if(fp->f == nil)
unimp(ip->pc, ip->iw, "missing conversion op");
if(DBG(Dbgbasic)){
iprint("%s.%s\tF%d,F%d\n", fp->name, ip->dfmt, ip->rm, ip->rd);
delay(75);
}
(*fp->f)(ip->fmt, ip->rm, ip->rd, ip->ur, ip->ufp);
return Advpc;
}
static void
cop1decode(Instr *ip, ulong iw, ulong pc, Ureg *ur, FPsave *ufp,
Internal *imp, Internal *inp)
{
ip->iw = iw;
ip->pc = pc;
ip->ur = ur;
ip->ufp = ufp;
ip->fmt = FMT(iw);
ip->rm = REGS(iw); /* 1st operand */
ip->rn = REGT(iw); /* 2nd operand (ignored by unary ops) */
ip->rd = REGD(iw); /* destination */
ip->o = FUNC(iw);
ip->fm = imp;
ip->fn = inp;
if (DBG(Dbgbasic))
ip->dfmt = decodefmt(ip->fmt);
}
void
fpstuck(uintptr pc, FPsave *fp)
{
USED(pc);
if(!(DBG(Dbgbasic)))
return;
if (fp->fppc == pc) {
fp->fpcnt++;
if (fp->fpcnt > 4)
panic("fpuemu: cpu%d stuck at pid %ld %s pc %#p "
"instr %#8.8lux", m->machno, up->pid, up->text,
pc, *(ulong *)pc);
} else {
fp->fppc = pc;
fp->fpcnt = 0;
}
}
static void
_dbgstuck(ulong pc, Ureg *ur, FPsave *ufp)
{
fpstuck(pc, ufp);
if (DBG(Dbgdelay) && ur->cause & BD)
iprint("fpuemu: FP in a branch delay slot\n");
}
/* decode the opcode and call common emulation code */
static int
fpimips(ulong pc, ulong op, Ureg *ur, FPsave *ufp)
{
int r, o;
Instr insn;
Instr *ip;
Internal im, in;
/* note: would update fault status here if we noted numeric exceptions */
dummyr0 = 0;
switch (OP(op)) {
case LWC1:
case LDC1:
case SWC1:
case SDC1:
dbgstuck(pc, ur, ufp);
return ldst(op, ur, ufp);
default:
unimp(pc, op, "non-FP instruction");
return Failed;
case COP1:
dbgstuck(pc, ur, ufp);
break;
}
ip = &insn;
cop1decode(ip, op, pc, ur, ufp, &im, &in);
if (ip->fmt == BRANCH) { /* FP conditional branch? */
r = bremu(ip);
if(DBG(Dbgdelay)){
iprint("resuming after br, at %#lux", ur->pc);
if (r == Leavepcret)
iprint("..."); /* we'll be right back */
else
iprint("\n");
}
return r;
}
o = ip->o;
if (o == 0 && ip->rd == 0) { /* *[TF]C1 load or store? */
r = cop1mov(ip);
if (r != Nomatch)
return r;
/* else wasn't a [tf]c1 move */
}
/* don't decode & print rm yet; it might be an integer */
if(o >= 32 && o < 40) /* conversion? */
return cvtemu(ip);
/* decode the mandatory operand, rm */
reg2intern(ip->fm, ip->rm, ip->fmt, ip->ur);
if(DBG(Dbgregs))
intpr(&im, ip->rm, ip->fmt, ip->ufp);
/*
* arithmetic
* all operands must be of the same format
*/
if(o >= 4 && o < 32) /* monadic */
return unaryemu(ip);
if(o < 4) /* the few binary ops */
return binemu(ip);
if(o >= 48 && (ip->rd & MASK(2)) == 0) /* comparison? */
return cmpemu(ip);
/* don't recognise the opcode */
if(DBG(Dbgbasic))
iprint("fp at %#lux: %#8.8lux BOGON\n", pc, op);
unimp(pc, op, "unknown opcode");
return Failed;
}
static FPsave *
fpinit(Ureg *ur)
{
int i, n;
Double d;
FPsave *ufp;
Internal tmp;
/*
* because all the emulated fp state is in the proc structure,
* it need not be saved/restored
*/
ufp = &up->fpsave;
switch(up->fpstate){
case FPactive:
case FPinactive:
error("fpu (in)active but fp is emulated");
case FPinit:
up->fpstate = FPemu;
ufp->fpcontrol = 0;
ufp->fpstatus = 0;
ufp->fpcnt = 0;
ufp->fppc = 0;
for(n = 0; n < Nfpregs-1; n += 2) {
if (fpconst[n].h == 0) /* uninitialised consts */
i = FZERO; /* treated as 0.0 */
else
i = n;
tmp = fpconst[i];
internsane(&tmp, ur);
fpii2d(&d, &tmp);
dbl2dreg(n, &d, ufp);
}
break;
}
return ufp;
}
/*
* called from trap.c's CCPU case, only to deal with user-mode
* instruction faults.
*
* libc/mips/lock.c reads FCR0 to determine what kind of system
* this is (and thus if it can use LL/SC or must use some
* system-dependent method). So we simulate the move from FCR0.
* All modern mips have LL/SC, so just claim to be an r4k.
*/
int
fpuemu(Ureg *ureg)
{
int s;
uintptr pc;
ulong iw, r;
if(waserror()){
postnote(up, 1, up->errstr, NDebug);
return -1;
}
if(up->fpstate & FPillegal)
error("floating point in note handler");
if(up->fpsave.fpdelayexec)
panic("fpuemu: entered with outstanding watch trap");
pc = ureg->pc;
validaddr(pc, 4, 0);
/* only the first instruction can be in a branch delay slot */
if(ureg->cause & BD) {
pc += 4;
validaddr(pc, 4, 0); /* check branch delay slot */
}
iw = *(ulong*)pc;
do {
/* recognise & optimise a common case */
if (iw == 0x44410000){ /* MOVW FCR0,R1 (CFC1) */
ureg->r1 = 0x500; /* claim an r4k */
r = Advpc;
if (DBG(Dbgbasic))
iprint("faked MOVW FCR0,R1\n");
}else{
s = spllo();
if(waserror()){
splx(s);
nexterror();
}
r = fpimips(pc, iw, ureg, fpinit(ureg));
splx(s);
poperror();
if (r == Failed || r == Leavepcret)
break;
}
if (r == Advpc) /* simulation succeeded, advance the pc? */
if(ureg->cause & BD)
followbr(ureg);
else
ureg->pc += 4;
ureg->cause &= ~BD;
pc = ureg->pc;
iw = validiw(pc);
while (iw == NOP || iw == MIPSNOP) { /* skip NOPs */
pc += 4;
ureg->pc = pc;
iw = validiw(pc);
}
/* is next ins'n also FP? */
} while (isfpop(iw));
if (r == Failed){
iprint("fpuemu: fp emulation failed for %#lux"
" at pc %#p in %lud %s\n",
iw, ureg->pc, up->pid, up->text);
unimp(ureg->pc, iw, "no fp instruction");
/* no return */
}
ureg->cause &= ~BD;
poperror();
return 0;
}
int
isbranch(ulong *pc)
{
ulong iw;
iw = *(ulong*)pc;
/*
* Integer unit jumps first
*/
switch(iw>>26){
case 0: /* SPECIAL: JR or JALR */
switch(iw&0x3F){
case 0x09: /* JALR */
case 0x08: /* JR */
return 1;
default:
return 0;
}
case 1: /* BCOND */
switch((iw>>16) & 0x1F){
case 0x10: /* BLTZAL */
case 0x00: /* BLTZ */
case 0x11: /* BGEZAL */
case 0x01: /* BGEZ */
return 1;
default:
return 0;
}
case 3: /* JAL */
case 2: /* JMP */
case 4: /* BEQ */
case 5: /* BNE */
case 6: /* BLEZ */
case 7: /* BGTZ */
return 1;
}
/*
* Floating point unit jumps
*/
if((iw>>26) == COP1)
switch((iw>>16) & 0x3C1){
case 0x101: /* BCT */
case 0x181: /* BCT */
case 0x100: /* BCF */
case 0x180: /* BCF */
return 1;
}
return 0;
}
/*
* if current instruction is a (taken) branch, return new pc and,
* for jump-and-links, set r31.
*/
ulong
branch(Ureg *ur, ulong fcr31)
{
ulong iw, npc, rs, rt, rd, offset, targ, next;
iw = ur->pc;
iw = *(ulong*)iw;
rs = (iw>>21) & 0x1F;
if(rs)
rs = REG(ur, rs);
rt = (iw>>16) & 0x1F;
if(rt)
rt = REG(ur, rt);
offset = iw & ((1<<16)-1);
if(offset & (1<<15)) /* sign extend */
offset |= ~((1<<16)-1);
offset <<= 2;
targ = ur->pc + 4 + offset; /* branch target */
/* ins'n after delay slot (assumes delay slot has already been exec'd) */
next = ur->pc + 8;
/*
* Integer unit jumps first
*/
switch(iw>>26){
case 0: /* SPECIAL: JR or JALR */
switch(iw&0x3F){
case 0x09: /* JALR */
rd = (iw>>11) & 0x1F;
if(rd)
REG(ur, rd) = next;
/* fall through */
case 0x08: /* JR */
return rs;
default:
return 0;
}
case 1: /* BCOND */
switch((iw>>16) & 0x1F){
case 0x10: /* BLTZAL */
ur->r31 = next;
/* fall through */
case 0x00: /* BLTZ */
if((long)rs < 0)
return targ;
return next;
case 0x11: /* BGEZAL */
ur->r31 = next;
/* fall through */
case 0x01: /* BGEZ */
if((long)rs >= 0)
return targ;
return next;
default:
return 0;
}
case 3: /* JAL */
ur->r31 = next;
/* fall through */
case 2: /* JMP */
npc = iw & ((1<<26)-1);
npc <<= 2;
return npc | (ur->pc&0xF0000000);
case 4: /* BEQ */
if(rs == rt)
return targ;
return next;
case 5: /* BNE */
if(rs != rt)
return targ;
return next;
case 6: /* BLEZ */
if((long)rs <= 0)
return targ;
return next;
case 7: /* BGTZ */
if((long)rs > 0)
return targ;
return next;
}
/*
* Floating point unit jumps
*/
if((iw>>26) == COP1)
switch((iw>>16) & 0x3C1){
case 0x101: /* BCT */
case 0x181: /* BCT */
if(fcr31 & FPCOND)
return targ;
return next;
case 0x100: /* BCF */
case 0x180: /* BCF */
if(!(fcr31 & FPCOND))
return targ;
return next;
}
/* shouldn't get here */
return 0;
}
|