int mips_dsemul(struct pt_regs *regs, mips_instruction ir, gpreg_t cpc) { extern asmlinkage void handle_dsemulret(void); mips_instruction *dsemul_insns; struct emuframe *fr; int err; if (ir == 0) { /* a nop is easy */ regs->cp0_epc = cpc; regs->cp0_cause &= ~CAUSEF_BD; return 0; } #ifdef DSEMUL_TRACE printk("dsemul %lx %lx\n", regs->cp0_epc, cpc); #endif /* * The strategy is to push the instruction onto the user stack * and put a trap after it which we can catch and jump to * the required address any alternative apart from full * instruction emulation!!. * * Algorithmics used a system call instruction, and * borrowed that vector. MIPS/Linux version is a bit * more heavyweight in the interests of portability and * multiprocessor support. For Linux we generate a * an unaligned access and force an address error exception. * * For embedded systems (stand-alone) we prefer to use a * non-existing CP1 instruction. This prevents us from emulating * branches, but gives us a cleaner interface to the exception * handler (single entry point). */ /* Ensure that the two instructions are in the same cache line */ dsemul_insns = (mips_instruction *) REG_TO_VA ((regs->regs[29] - sizeof(struct emuframe)) & ~0x7); fr = (struct emuframe *) dsemul_insns; /* Verify that the stack pointer is not competely insane */ if (unlikely(!access_ok(VERIFY_WRITE, fr, sizeof(struct emuframe)))) return SIGBUS; err = __put_user(ir, &fr->emul); err |= __put_user((mips_instruction)BADINST, &fr->badinst); err |= __put_user((mips_instruction)BD_COOKIE, &fr->cookie); err |= __put_user(cpc, &fr->epc); if (unlikely(err)) { fpuemuprivate.stats.errors++; return SIGBUS; } regs->cp0_epc = VA_TO_REG & fr->emul; flush_cache_sigtramp((unsigned long)&fr->badinst); return SIGILL; /* force out of emulation loop */ }
static int cop1Emulate(int xcptno, struct pt_regs *xcp, struct mips_fpu_soft_struct *ctx) { mips_instruction ir; vaddr_t emulpc; vaddr_t contpc; unsigned int cond; int err = 0; ir = mips_get_word(xcp, REG_TO_VA xcp->cp0_epc, &err); if (err) { fpuemuprivate.stats.errors++; return SIGBUS; } /* XXX NEC Vr54xx bug workaround */ if ((xcp->cp0_cause & CAUSEF_BD) && !isBranchInstr(&ir)) xcp->cp0_cause &= ~CAUSEF_BD; if (xcp->cp0_cause & CAUSEF_BD) { /* * The instruction to be emulated is in a branch delay slot * which means that we have to emulate the branch instruction * BEFORE we do the cop1 instruction. * * This branch could be a COP1 branch, but in that case we * would have had a trap for that instruction, and would not * come through this route. * * Linux MIPS branch emulator operates on context, updating the * cp0_epc. */ emulpc = REG_TO_VA(xcp->cp0_epc + 4); /* Snapshot emulation target */ if (__compute_return_epc(xcp)) { #ifdef CP1DBG printk("failed to emulate branch at %p\n", REG_TO_VA(xcp->cp0_epc)); #endif return SIGILL;; } ir = mips_get_word(xcp, emulpc, &err); if (err) { fpuemuprivate.stats.errors++; return SIGBUS; } contpc = REG_TO_VA xcp->cp0_epc; } else { emulpc = REG_TO_VA xcp->cp0_epc; contpc = REG_TO_VA xcp->cp0_epc + 4; } emul: fpuemuprivate.stats.emulated++; switch (MIPSInst_OPCODE(ir)) { #ifdef CP0_STATUS_FR_SUPPORT /* R4000+ 64-bit fpu registers */ #ifndef SINGLE_ONLY_FPU case ldc1_op: { void *va = REG_TO_VA(xcp->regs[MIPSInst_RS(ir)]) + MIPSInst_SIMM(ir); int ft = MIPSInst_RT(ir); if (!(xcp->cp0_status & ST0_FR)) ft &= ~1; ctx->regs[ft] = mips_get_dword(xcp, va, &err); fpuemuprivate.stats.loads++; if (err) { fpuemuprivate.stats.errors++; return SIGBUS; } } break; case sdc1_op: { void *va = REG_TO_VA(xcp->regs[MIPSInst_RS(ir)]) + MIPSInst_SIMM(ir); int ft = MIPSInst_RT(ir); if (!(xcp->cp0_status & ST0_FR)) ft &= ~1; fpuemuprivate.stats.stores++; if (mips_put_dword(xcp, va, ctx->regs[ft])) { fpuemuprivate.stats.errors++; return SIGBUS; } } break; #endif case lwc1_op: { void *va = REG_TO_VA(xcp->regs[MIPSInst_RS(ir)]) + MIPSInst_SIMM(ir); fpureg_t val; int ft = MIPSInst_RT(ir); fpuemuprivate.stats.loads++; val = mips_get_word(xcp, va, &err); if (err) { fpuemuprivate.stats.errors++; return SIGBUS; } if (xcp->cp0_status & ST0_FR) { /* load whole register */ ctx->regs[ft] = val; } else if (ft & 1) { /* load to m.s. 32 bits */ #ifdef SINGLE_ONLY_FPU /* illegal register in single-float mode */ return SIGILL; #else ctx->regs[(ft & ~1)] &= 0xffffffff; ctx->regs[(ft & ~1)] |= val << 32; #endif } else { /* load to l.s. 32 bits */ ctx->regs[ft] &= ~0xffffffffLL; ctx->regs[ft] |= val; } } break; case swc1_op: { void *va = REG_TO_VA(xcp->regs[MIPSInst_RS(ir)]) + MIPSInst_SIMM(ir); unsigned int val; int ft = MIPSInst_RT(ir); fpuemuprivate.stats.stores++; if (xcp->cp0_status & ST0_FR) { /* store whole register */ val = ctx->regs[ft]; } else if (ft & 1) { #ifdef SINGLE_ONLY_FPU /* illegal register in single-float mode */ return SIGILL; #else /* store from m.s. 32 bits */ val = ctx->regs[(ft & ~1)] >> 32; #endif } else { /* store from l.s. 32 bits */ val = ctx->regs[ft]; } if (mips_put_word(xcp, va, val)) { fpuemuprivate.stats.errors++; return SIGBUS; } } break; #else /* old 32-bit fpu registers */ case lwc1_op: { void *va = REG_TO_VA(xcp->regs[MIPSInst_RS(ir)]) + MIPSInst_SIMM(ir); ctx->regs[MIPSInst_RT(ir)] = mips_get_word(xcp, va, &err); fpuemuprivate.stats.loads++; if (err) { fpuemuprivate.stats.errors++; return SIGBUS; } } break; case swc1_op: { void *va = REG_TO_VA(xcp->regs[MIPSInst_RS(ir)]) + MIPSInst_SIMM(ir); fpuemuprivate.stats.stores++; if (mips_put_word (xcp, va, ctx->regs[MIPSInst_RT(ir)])) { fpuemuprivate.stats.errors++; return SIGBUS; } } break; case ldc1_op: { void *va = REG_TO_VA(xcp->regs[MIPSInst_RS(ir)]) + MIPSInst_SIMM(ir); unsigned int rt = MIPSInst_RT(ir) & ~1; int errs = 0; fpuemuprivate.stats.loads++; #if (defined(BYTE_ORDER) && BYTE_ORDER == BIG_ENDIAN) || defined(__MIPSEB__) ctx->regs[rt + 1] = mips_get_word(xcp, va + 0, &err); errs += err; ctx->regs[rt + 0] = mips_get_word(xcp, va + 4, &err); errs += err; #else ctx->regs[rt + 0] = mips_get_word(xcp, va + 0, &err); errs += err; ctx->regs[rt + 1] = mips_get_word(xcp, va + 4, &err); errs += err; #endif if (err) return SIGBUS; } break; case sdc1_op: { void *va = REG_TO_VA(xcp->regs[MIPSInst_RS(ir)]) + MIPSInst_SIMM(ir); unsigned int rt = MIPSInst_RT(ir) & ~1; fpuemuprivate.stats.stores++; #if (defined(BYTE_ORDER) && BYTE_ORDER == BIG_ENDIAN) || defined(__MIPSEB__) if (mips_put_word(xcp, va + 0, ctx->regs[rt + 1])) return SIGBUS; if (mips_put_word(xcp, va + 4, ctx->regs[rt + 0])) return SIGBUS; #else if (mips_put_word(xcp, va + 0, ctx->regs[rt + 0])) return SIGBUS; if (mips_put_word(xcp, va + 4, ctx->regs[rt + 1])) return SIGBUS; #endif } break; #endif case cop1_op: switch (MIPSInst_RS(ir)) { #ifdef CP0_STATUS_FR_SUPPORT #if __mips64 && !defined(SINGLE_ONLY_FPU) case dmfc_op: /* copregister fs -> gpr[rt] */ if (MIPSInst_RT(ir) != 0) { int fs = MIPSInst_RD(ir); if (!(xcp->cp0_status & ST0_FR)) fs &= ~1; xcp->regs[MIPSInst_RT(ir)] = ctx->regs[fs]; } break; case dmtc_op: /* copregister fs <- rt */ { fpureg_t value; int fs = MIPSInst_RD(ir); if (!(xcp->cp0_status & ST0_FR)) fs &= ~1; value = (MIPSInst_RT(ir) == 0) ? 0 : xcp->regs[MIPSInst_RT(ir)]; ctx->regs[fs] = value; } break; #endif case mfc_op: /* copregister rd -> gpr[rt] */ if (MIPSInst_RT(ir) != 0) { /* default value from l.s. 32 bits */ int value = ctx->regs[MIPSInst_RD(ir)]; if (MIPSInst_RD(ir) & 1) { #ifdef SINGLE_ONLY_FPU /* illegal register in single-float mode */ return SIGILL; #else if (!(xcp->cp0_status & ST0_FR)) { /* move from m.s. 32 bits */ value = ctx-> regs[MIPSInst_RD(ir) & ~1] >> 32; } #endif } xcp->regs[MIPSInst_RT(ir)] = value; }