in math-emu/cp1emu.c [971:1388]
static int cop1Emulate(struct pt_regs *xcp, struct mips_fpu_struct *ctx,
struct mm_decoded_insn dec_insn, void __user **fault_addr)
{
unsigned long contpc = xcp->cp0_epc + dec_insn.pc_inc;
unsigned int cond, cbit, bit0;
mips_instruction ir;
int likely, pc_inc;
union fpureg *fpr;
u32 __user *wva;
u64 __user *dva;
u32 wval;
u64 dval;
int sig;
/*
* These are giving gcc a gentle hint about what to expect in
* dec_inst in order to do better optimization.
*/
if (!cpu_has_mmips && dec_insn.micro_mips_mode)
unreachable();
/* XXX NEC Vr54xx bug workaround */
if (delay_slot(xcp)) {
if (dec_insn.micro_mips_mode) {
if (!mm_isBranchInstr(xcp, dec_insn, &contpc))
clear_delay_slot(xcp);
} else {
if (!isBranchInstr(xcp, dec_insn, &contpc))
clear_delay_slot(xcp);
}
}
if (delay_slot(xcp)) {
/*
* 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.
*/
ir = dec_insn.next_insn; /* process delay slot instr */
pc_inc = dec_insn.next_pc_inc;
} else {
ir = dec_insn.insn; /* process current instr */
pc_inc = dec_insn.pc_inc;
}
/*
* Since microMIPS FPU instructios are a subset of MIPS32 FPU
* instructions, we want to convert microMIPS FPU instructions
* into MIPS32 instructions so that we could reuse all of the
* FPU emulation code.
*
* NOTE: We cannot do this for branch instructions since they
* are not a subset. Example: Cannot emulate a 16-bit
* aligned target address with a MIPS32 instruction.
*/
if (dec_insn.micro_mips_mode) {
/*
* If next instruction is a 16-bit instruction, then it
* it cannot be a FPU instruction. This could happen
* since we can be called for non-FPU instructions.
*/
if ((pc_inc == 2) ||
(microMIPS32_to_MIPS32((union mips_instruction *)&ir)
== SIGILL))
return SIGILL;
}
emul:
perf_sw_event(PERF_COUNT_SW_EMULATION_FAULTS, 1, xcp, 0);
MIPS_FPU_EMU_INC_STATS(emulated);
switch (MIPSInst_OPCODE(ir)) {
case ldc1_op:
dva = (u64 __user *) (xcp->regs[MIPSInst_RS(ir)] +
MIPSInst_SIMM(ir));
MIPS_FPU_EMU_INC_STATS(loads);
if (!access_ok(dva, sizeof(u64))) {
MIPS_FPU_EMU_INC_STATS(errors);
*fault_addr = dva;
return SIGBUS;
}
if (__get_user(dval, dva)) {
MIPS_FPU_EMU_INC_STATS(errors);
*fault_addr = dva;
return SIGSEGV;
}
DITOREG(dval, MIPSInst_RT(ir));
break;
case sdc1_op:
dva = (u64 __user *) (xcp->regs[MIPSInst_RS(ir)] +
MIPSInst_SIMM(ir));
MIPS_FPU_EMU_INC_STATS(stores);
DIFROMREG(dval, MIPSInst_RT(ir));
if (!access_ok(dva, sizeof(u64))) {
MIPS_FPU_EMU_INC_STATS(errors);
*fault_addr = dva;
return SIGBUS;
}
if (__put_user(dval, dva)) {
MIPS_FPU_EMU_INC_STATS(errors);
*fault_addr = dva;
return SIGSEGV;
}
break;
case lwc1_op:
wva = (u32 __user *) (xcp->regs[MIPSInst_RS(ir)] +
MIPSInst_SIMM(ir));
MIPS_FPU_EMU_INC_STATS(loads);
if (!access_ok(wva, sizeof(u32))) {
MIPS_FPU_EMU_INC_STATS(errors);
*fault_addr = wva;
return SIGBUS;
}
if (__get_user(wval, wva)) {
MIPS_FPU_EMU_INC_STATS(errors);
*fault_addr = wva;
return SIGSEGV;
}
SITOREG(wval, MIPSInst_RT(ir));
break;
case swc1_op:
wva = (u32 __user *) (xcp->regs[MIPSInst_RS(ir)] +
MIPSInst_SIMM(ir));
MIPS_FPU_EMU_INC_STATS(stores);
SIFROMREG(wval, MIPSInst_RT(ir));
if (!access_ok(wva, sizeof(u32))) {
MIPS_FPU_EMU_INC_STATS(errors);
*fault_addr = wva;
return SIGBUS;
}
if (__put_user(wval, wva)) {
MIPS_FPU_EMU_INC_STATS(errors);
*fault_addr = wva;
return SIGSEGV;
}
break;
case cop1_op:
switch (MIPSInst_RS(ir)) {
case dmfc_op:
if (!cpu_has_mips_3_4_5 && !cpu_has_mips64)
return SIGILL;
/* copregister fs -> gpr[rt] */
if (MIPSInst_RT(ir) != 0) {
DIFROMREG(xcp->regs[MIPSInst_RT(ir)],
MIPSInst_RD(ir));
}
break;
case dmtc_op:
if (!cpu_has_mips_3_4_5 && !cpu_has_mips64)
return SIGILL;
/* copregister fs <- rt */
DITOREG(xcp->regs[MIPSInst_RT(ir)], MIPSInst_RD(ir));
break;
case mfhc_op:
if (!cpu_has_mips_r2_r6)
return SIGILL;
/* copregister rd -> gpr[rt] */
if (MIPSInst_RT(ir) != 0) {
SIFROMHREG(xcp->regs[MIPSInst_RT(ir)],
MIPSInst_RD(ir));
}
break;
case mthc_op:
if (!cpu_has_mips_r2_r6)
return SIGILL;
/* copregister rd <- gpr[rt] */
SITOHREG(xcp->regs[MIPSInst_RT(ir)], MIPSInst_RD(ir));
break;
case mfc_op:
/* copregister rd -> gpr[rt] */
if (MIPSInst_RT(ir) != 0) {
SIFROMREG(xcp->regs[MIPSInst_RT(ir)],
MIPSInst_RD(ir));
}
break;
case mtc_op:
/* copregister rd <- rt */
SITOREG(xcp->regs[MIPSInst_RT(ir)], MIPSInst_RD(ir));
break;
case cfc_op:
/* cop control register rd -> gpr[rt] */
cop1_cfc(xcp, ctx, ir);
break;
case ctc_op:
/* copregister rd <- rt */
cop1_ctc(xcp, ctx, ir);
if ((ctx->fcr31 >> 5) & ctx->fcr31 & FPU_CSR_ALL_E) {
return SIGFPE;
}
break;
case bc1eqz_op:
case bc1nez_op:
if (!cpu_has_mips_r6 || delay_slot(xcp))
return SIGILL;
likely = 0;
cond = 0;
fpr = ¤t->thread.fpu.fpr[MIPSInst_RT(ir)];
bit0 = get_fpr32(fpr, 0) & 0x1;
switch (MIPSInst_RS(ir)) {
case bc1eqz_op:
MIPS_FPU_EMU_INC_STATS(bc1eqz);
cond = bit0 == 0;
break;
case bc1nez_op:
MIPS_FPU_EMU_INC_STATS(bc1nez);
cond = bit0 != 0;
break;
}
goto branch_common;
case bc_op:
if (delay_slot(xcp))
return SIGILL;
if (cpu_has_mips_4_5_r)
cbit = fpucondbit[MIPSInst_RT(ir) >> 2];
else
cbit = FPU_CSR_COND;
cond = ctx->fcr31 & cbit;
likely = 0;
switch (MIPSInst_RT(ir) & 3) {
case bcfl_op:
if (cpu_has_mips_2_3_4_5_r)
likely = 1;
fallthrough;
case bcf_op:
cond = !cond;
break;
case bctl_op:
if (cpu_has_mips_2_3_4_5_r)
likely = 1;
fallthrough;
case bct_op:
break;
}
branch_common:
MIPS_FPU_EMU_INC_STATS(branches);
set_delay_slot(xcp);
if (cond) {
/*
* Branch taken: emulate dslot instruction
*/
unsigned long bcpc;
/*
* Remember EPC at the branch to point back
* at so that any delay-slot instruction
* signal is not silently ignored.
*/
bcpc = xcp->cp0_epc;
xcp->cp0_epc += dec_insn.pc_inc;
contpc = MIPSInst_SIMM(ir);
ir = dec_insn.next_insn;
if (dec_insn.micro_mips_mode) {
contpc = (xcp->cp0_epc + (contpc << 1));
/* If 16-bit instruction, not FPU. */
if ((dec_insn.next_pc_inc == 2) ||
(microMIPS32_to_MIPS32((union mips_instruction *)&ir) == SIGILL)) {
/*
* Since this instruction will
* be put on the stack with
* 32-bit words, get around
* this problem by putting a
* NOP16 as the second one.
*/
if (dec_insn.next_pc_inc == 2)
ir = (ir & (~0xffff)) | MM_NOP16;
/*
* Single step the non-CP1
* instruction in the dslot.
*/
sig = mips_dsemul(xcp, ir,
bcpc, contpc);
if (sig < 0)
break;
if (sig)
xcp->cp0_epc = bcpc;
/*
* SIGILL forces out of
* the emulation loop.
*/
return sig ? sig : SIGILL;
}
} else
contpc = (xcp->cp0_epc + (contpc << 2));
switch (MIPSInst_OPCODE(ir)) {
case lwc1_op:
case swc1_op:
goto emul;
case ldc1_op:
case sdc1_op:
if (cpu_has_mips_2_3_4_5_r)
goto emul;
goto bc_sigill;
case cop1_op:
goto emul;
case cop1x_op:
if (cpu_has_mips_4_5_64_r2_r6)
/* its one of ours */
goto emul;
goto bc_sigill;
case spec_op:
switch (MIPSInst_FUNC(ir)) {
case movc_op:
if (cpu_has_mips_4_5_r)
goto emul;
goto bc_sigill;
}
break;
bc_sigill:
xcp->cp0_epc = bcpc;
return SIGILL;
}
/*
* Single step the non-cp1
* instruction in the dslot
*/
sig = mips_dsemul(xcp, ir, bcpc, contpc);
if (sig < 0)
break;
if (sig)
xcp->cp0_epc = bcpc;
/* SIGILL forces out of the emulation loop. */
return sig ? sig : SIGILL;
} else if (likely) { /* branch not taken */
/*
* branch likely nullifies
* dslot if not taken
*/
xcp->cp0_epc += dec_insn.pc_inc;
contpc += dec_insn.pc_inc;
/*
* else continue & execute
* dslot as normal insn
*/
}
break;
default:
if (!(MIPSInst_RS(ir) & 0x10))
return SIGILL;
/* a real fpu computation instruction */
sig = fpu_emu(xcp, ctx, ir);
if (sig)
return sig;
}
break;
case cop1x_op:
if (!cpu_has_mips_4_5_64_r2_r6)
return SIGILL;
sig = fpux_emu(xcp, ctx, ir, fault_addr);
if (sig)
return sig;
break;
case spec_op:
if (!cpu_has_mips_4_5_r)
return SIGILL;
if (MIPSInst_FUNC(ir) != movc_op)
return SIGILL;
cond = fpucondbit[MIPSInst_RT(ir) >> 2];
if (((ctx->fcr31 & cond) != 0) == ((MIPSInst_RT(ir) & 1) != 0))
xcp->regs[MIPSInst_RD(ir)] =
xcp->regs[MIPSInst_RS(ir)];
break;
default:
return SIGILL;
}
/* we did it !! */
xcp->cp0_epc = contpc;
clear_delay_slot(xcp);
return 0;
}