/* * 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 #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; }