/* * This file is subject to the terms and conditions of the GNU General Public * License. See the file "COPYING" in the main directory of this archive * for more details. * * Copyright (c) 2014 Imagination Technologies Ltd. * Author: Leonid Yegoshin <Leonid.Yegoshin@imgtec.com> * Author: Markos Chandras <markos.chandras@imgtec.com> * * MIPS R2 user space instruction emulator for MIPS R6 *
*/ #include <linux/bug.h> #include <linux/compiler.h> #include <linux/debugfs.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/ptrace.h> #include <linux/seq_file.h>
/** * mipsr6_emul - Emulate some frequent R2/R5/R6 instructions in delay slot * for performance instead of the traditional way of using a stack trampoline * which is rather slow. * @regs: Process register set * @ir: Instruction
*/ staticinlineint mipsr6_emul(struct pt_regs *regs, u32 ir)
{ switch (MIPSInst_OPCODE(ir)) { case addiu_op: if (MIPSInst_RT(ir))
regs->regs[MIPSInst_RT(ir)] =
(s32)regs->regs[MIPSInst_RS(ir)] +
(s32)MIPSInst_SIMM(ir); return 0; case daddiu_op: if (IS_ENABLED(CONFIG_32BIT)) break;
if (MIPSInst_RT(ir))
regs->regs[MIPSInst_RT(ir)] =
(s64)regs->regs[MIPSInst_RS(ir)] +
(s64)MIPSInst_SIMM(ir); return 0; case lwc1_op: case swc1_op: case cop1_op: case cop1x_op: /* FPU instructions in delay slot */ return -SIGFPE; case spec_op: switch (MIPSInst_FUNC(ir)) { case or_op: if (MIPSInst_RD(ir))
regs->regs[MIPSInst_RD(ir)] =
regs->regs[MIPSInst_RS(ir)] |
regs->regs[MIPSInst_RT(ir)]; return 0; case sll_op: if (MIPSInst_RS(ir)) break;
if (MIPSInst_RD(ir))
regs->regs[MIPSInst_RD(ir)] =
(s32)(((u32)regs->regs[MIPSInst_RT(ir)]) <<
MIPSInst_FD(ir)); return 0; case srl_op: if (MIPSInst_RS(ir)) break;
if (MIPSInst_RD(ir))
regs->regs[MIPSInst_RD(ir)] =
(s32)(((u32)regs->regs[MIPSInst_RT(ir)]) >>
MIPSInst_FD(ir)); return 0; case addu_op: if (MIPSInst_FD(ir)) break;
if (MIPSInst_RD(ir))
regs->regs[MIPSInst_RD(ir)] =
(s32)((u32)regs->regs[MIPSInst_RS(ir)] +
(u32)regs->regs[MIPSInst_RT(ir)]); return 0; case subu_op: if (MIPSInst_FD(ir)) break;
if (MIPSInst_RD(ir))
regs->regs[MIPSInst_RD(ir)] =
(s32)((u32)regs->regs[MIPSInst_RS(ir)] -
(u32)regs->regs[MIPSInst_RT(ir)]); return 0; case dsll_op: if (IS_ENABLED(CONFIG_32BIT) || MIPSInst_RS(ir)) break;
if (MIPSInst_RD(ir))
regs->regs[MIPSInst_RD(ir)] =
(s64)(((u64)regs->regs[MIPSInst_RT(ir)]) <<
MIPSInst_FD(ir)); return 0; case dsrl_op: if (IS_ENABLED(CONFIG_32BIT) || MIPSInst_RS(ir)) break;
if (MIPSInst_RD(ir))
regs->regs[MIPSInst_RD(ir)] =
(s64)(((u64)regs->regs[MIPSInst_RT(ir)]) >>
MIPSInst_FD(ir)); return 0; case daddu_op: if (IS_ENABLED(CONFIG_32BIT) || MIPSInst_FD(ir)) break;
if (MIPSInst_RD(ir))
regs->regs[MIPSInst_RD(ir)] =
(u64)regs->regs[MIPSInst_RS(ir)] +
(u64)regs->regs[MIPSInst_RT(ir)]; return 0; case dsubu_op: if (IS_ENABLED(CONFIG_32BIT) || MIPSInst_FD(ir)) break;
if (MIPSInst_RD(ir))
regs->regs[MIPSInst_RD(ir)] =
(s64)((u64)regs->regs[MIPSInst_RS(ir)] -
(u64)regs->regs[MIPSInst_RT(ir)]); return 0;
} break; default:
pr_debug("No fastpath BD emulation for instruction 0x%08x (op: %02x)\n",
ir, MIPSInst_OPCODE(ir));
}
return SIGILL;
}
/** * movf_func - Emulate a MOVF instruction * @regs: Process register set * @ir: Instruction * * Returns 0 since it always succeeds.
*/ staticint movf_func(struct pt_regs *regs, u32 ir)
{
u32 csr;
u32 cond;
/** * jr_func - Emulate a JR instruction. * @pt_regs: Process register set * @ir: Instruction * * Returns SIGILL if JR was in delay slot, SIGEMT if we * can't compute the EPC, SIGSEGV if we can't access the * userland instruction or 0 on success.
*/ staticint jr_func(struct pt_regs *regs, u32 ir)
{ int err; unsignedlong cepc, epc, nepc;
u32 nir;
if (delay_slot(regs)) return SIGILL;
/* EPC after the RI/JR instruction */
nepc = regs->cp0_epc; /* Roll back to the reserved R2 JR instruction */
regs->cp0_epc -= 4;
epc = regs->cp0_epc;
err = __compute_return_epc(regs);
if (err < 0) return SIGEMT;
/* Computed EPC */
cepc = regs->cp0_epc;
/* Get DS instruction */
err = __get_user(nir, (u32 __user *)nepc); if (err) return SIGSEGV;
MIPS_R2BR_STATS(jrs);
/* If nir == 0(NOP), then nothing else to do */ if (nir) { /* * Negative err means FPU instruction in BD-slot, * Zero err means 'BD-slot emulation done' * For anything else we go back to trampoline emulation.
*/
err = mipsr6_emul(regs, nir); if (err > 0) {
regs->cp0_epc = nepc;
err = mips_dsemul(regs, nir, epc, cepc); if (err == SIGILL)
err = SIGEMT;
MIPS_R2_STATS(dsemul);
}
}
return err;
}
/** * movz_func - Emulate a MOVZ instruction * @regs: Process register set * @ir: Instruction * * Returns 0 since it always succeeds.
*/ staticint movz_func(struct pt_regs *regs, u32 ir)
{ if (((regs->regs[MIPSInst_RT(ir)]) == 0) && MIPSInst_RD(ir))
regs->regs[MIPSInst_RD(ir)] = regs->regs[MIPSInst_RS(ir)];
MIPS_R2_STATS(movs);
return 0;
}
/** * movn_func - Emulate a MOVZ instruction * @regs: Process register set * @ir: Instruction * * Returns 0 since it always succeeds.
*/ staticint movn_func(struct pt_regs *regs, u32 ir)
{ if (((regs->regs[MIPSInst_RT(ir)]) != 0) && MIPSInst_RD(ir))
regs->regs[MIPSInst_RD(ir)] = regs->regs[MIPSInst_RS(ir)];
MIPS_R2_STATS(movs);
return 0;
}
/** * mfhi_func - Emulate a MFHI instruction * @regs: Process register set * @ir: Instruction * * Returns 0 since it always succeeds.
*/ staticint mfhi_func(struct pt_regs *regs, u32 ir)
{ if (MIPSInst_RD(ir))
regs->regs[MIPSInst_RD(ir)] = regs->hi;
MIPS_R2_STATS(hilo);
return 0;
}
/** * mthi_func - Emulate a MTHI instruction * @regs: Process register set * @ir: Instruction * * Returns 0 since it always succeeds.
*/ staticint mthi_func(struct pt_regs *regs, u32 ir)
{
regs->hi = regs->regs[MIPSInst_RS(ir)];
MIPS_R2_STATS(hilo);
return 0;
}
/** * mflo_func - Emulate a MFLO instruction * @regs: Process register set * @ir: Instruction * * Returns 0 since it always succeeds.
*/ staticint mflo_func(struct pt_regs *regs, u32 ir)
{ if (MIPSInst_RD(ir))
regs->regs[MIPSInst_RD(ir)] = regs->lo;
MIPS_R2_STATS(hilo);
return 0;
}
/** * mtlo_func - Emulate a MTLO instruction * @regs: Process register set * @ir: Instruction * * Returns 0 since it always succeeds.
*/ staticint mtlo_func(struct pt_regs *regs, u32 ir)
{
regs->lo = regs->regs[MIPSInst_RS(ir)];
MIPS_R2_STATS(hilo);
return 0;
}
/** * mult_func - Emulate a MULT instruction * @regs: Process register set * @ir: Instruction * * Returns 0 since it always succeeds.
*/ staticint mult_func(struct pt_regs *regs, u32 ir)
{
s64 res;
s32 rt, rs;
/** * mipsr2_decoder: Decode and emulate a MIPS R2 instruction * @regs: Process register set * @inst: Instruction to decode and emulate * @fcr31: Floating Point Control and Status Register Cause bits returned
*/ int mipsr2_decoder(struct pt_regs *regs, u32 inst, unsignedlong *fcr31)
{ int err = 0; unsignedlong vaddr;
u32 nir; unsignedlong cpc, epc, nepc, r31, res, rs, rt;
switch (MIPSInst_OPCODE(inst)) { case spec_op:
err = mipsr2_find_op_func(regs, inst, spec_op_table); if (err < 0) { /* FPU instruction under JR */
regs->cp0_cause |= CAUSEF_BD; goto fpu_emul;
} break; case spec2_op:
err = mipsr2_find_op_func(regs, inst, spec2_op_table); break; case bcond_op:
rt = MIPSInst_RT(inst);
rs = MIPSInst_RS(inst); switch (rt) { case tgei_op: if ((long)regs->regs[rs] >= MIPSInst_SIMM(inst))
do_trap_or_bp(regs, 0, 0, "TGEI");
MIPS_R2_STATS(traps);
break; case tgeiu_op: if (regs->regs[rs] >= MIPSInst_UIMM(inst))
do_trap_or_bp(regs, 0, 0, "TGEIU");
MIPS_R2_STATS(traps);
break; case tlti_op: if ((long)regs->regs[rs] < MIPSInst_SIMM(inst))
do_trap_or_bp(regs, 0, 0, "TLTI");
MIPS_R2_STATS(traps);
break; case tltiu_op: if (regs->regs[rs] < MIPSInst_UIMM(inst))
do_trap_or_bp(regs, 0, 0, "TLTIU");
MIPS_R2_STATS(traps);
break; case teqi_op: if (regs->regs[rs] == MIPSInst_SIMM(inst))
do_trap_or_bp(regs, 0, 0, "TEQI");
MIPS_R2_STATS(traps);
break; case tnei_op: if (regs->regs[rs] != MIPSInst_SIMM(inst))
do_trap_or_bp(regs, 0, 0, "TNEI");
MIPS_R2_STATS(traps);
break; case bltzl_op: case bgezl_op: case bltzall_op: case bgezall_op: if (delay_slot(regs)) {
err = SIGILL; break;
}
regs->regs[31] = r31;
regs->cp0_epc = epc;
err = __compute_return_epc(regs); if (err < 0) return SIGEMT; if (err != BRANCH_LIKELY_TAKEN) break;
cpc = regs->cp0_epc;
nepc = epc + 4;
err = __get_user(nir, (u32 __user *)nepc); if (err) {
err = SIGSEGV; break;
} /* * This will probably be optimized away when * CONFIG_DEBUG_FS is not enabled
*/ switch (rt) { case bltzl_op:
MIPS_R2BR_STATS(bltzl); break; case bgezl_op:
MIPS_R2BR_STATS(bgezl); break; case bltzall_op:
MIPS_R2BR_STATS(bltzall); break; case bgezall_op:
MIPS_R2BR_STATS(bgezall); break;
}
switch (MIPSInst_OPCODE(nir)) { case cop1_op: case cop1x_op: case lwc1_op: case swc1_op:
regs->cp0_cause |= CAUSEF_BD; goto fpu_emul;
} if (nir) {
err = mipsr6_emul(regs, nir); if (err > 0) {
err = mips_dsemul(regs, nir, epc, cpc); if (err == SIGILL)
err = SIGEMT;
MIPS_R2_STATS(dsemul);
}
} break; case bltzal_op: case bgezal_op: if (delay_slot(regs)) {
err = SIGILL; break;
}
regs->regs[31] = r31;
regs->cp0_epc = epc;
err = __compute_return_epc(regs); if (err < 0) return SIGEMT;
cpc = regs->cp0_epc;
nepc = epc + 4;
err = __get_user(nir, (u32 __user *)nepc); if (err) {
err = SIGSEGV; break;
} /* * This will probably be optimized away when * CONFIG_DEBUG_FS is not enabled
*/ switch (rt) { case bltzal_op:
MIPS_R2BR_STATS(bltzal); break; case bgezal_op:
MIPS_R2BR_STATS(bgezal); break;
}
switch (MIPSInst_OPCODE(nir)) { case cop1_op: case cop1x_op: case lwc1_op: case swc1_op:
regs->cp0_cause |= CAUSEF_BD; goto fpu_emul;
} if (nir) {
err = mipsr6_emul(regs, nir); if (err > 0) {
err = mips_dsemul(regs, nir, epc, cpc); if (err == SIGILL)
err = SIGEMT;
MIPS_R2_STATS(dsemul);
}
} break; default:
regs->regs[31] = r31;
regs->cp0_epc = epc;
err = SIGILL; break;
} break;
case blezl_op: case bgtzl_op: /* * For BLEZL and BGTZL, rt field must be set to 0. If this * is not the case, this may be an encoding of a MIPS R6 * instruction, so return to CPU execution if this occurs
*/ if (MIPSInst_RT(inst)) {
err = SIGILL; break;
}
fallthrough; case beql_op: case bnel_op: if (delay_slot(regs)) {
err = SIGILL; break;
}
regs->regs[31] = r31;
regs->cp0_epc = epc;
err = __compute_return_epc(regs); if (err < 0) return SIGEMT; if (err != BRANCH_LIKELY_TAKEN) break;
cpc = regs->cp0_epc;
nepc = epc + 4;
err = __get_user(nir, (u32 __user *)nepc); if (err) {
err = SIGSEGV; break;
} /* * This will probably be optimized away when * CONFIG_DEBUG_FS is not enabled
*/ switch (MIPSInst_OPCODE(inst)) { case beql_op:
MIPS_R2BR_STATS(beql); break; case bnel_op:
MIPS_R2BR_STATS(bnel); break; case blezl_op:
MIPS_R2BR_STATS(blezl); break; case bgtzl_op:
MIPS_R2BR_STATS(bgtzl); break;
}
switch (MIPSInst_OPCODE(nir)) { case cop1_op: case cop1x_op: case lwc1_op: case swc1_op:
regs->cp0_cause |= CAUSEF_BD; goto fpu_emul;
} if (nir) {
err = mipsr6_emul(regs, nir); if (err > 0) {
err = mips_dsemul(regs, nir, epc, cpc); if (err == SIGILL)
err = SIGEMT;
MIPS_R2_STATS(dsemul);
}
} break; case lwc1_op: case swc1_op: case cop1_op: case cop1x_op:
fpu_emul:
regs->regs[31] = r31;
regs->cp0_epc = epc;
/* * We can't allow the emulated instruction to leave any * enabled Cause bits set in $fcr31.
*/
*fcr31 = res = mask_fcr31_x(current->thread.fpu.fcr31);
current->thread.fpu.fcr31 &= ~res;
/* * this is a tricky issue - lose_fpu() uses LL/SC atomics * if FPU is owned and effectively cancels user level LL/SC. * So, it could be logical to don't restore FPU ownership here. * But the sequence of multiple FPU instructions is much much * more often than LL-FPU-SC and I prefer loop here until * next scheduler cycle cancels FPU ownership
*/
own_fpu(1); /* Restore FPU state. */
if (err)
current->thread.cp0_baduaddr = (unsignedlong)fault_addr;
if (!cpu_has_rw_llb) { /* * An LL/SC block can't be safely emulated without * a Config5/LLB availability. So it's probably time to * kill our process before things get any worse. This is * because Config5/LLB allows us to use ERETNC so that * the LLAddr/LLB bit is not cleared when we return from * an exception. MIPS R2 LL/SC instructions trap with an * RI exception so once we emulate them here, we return * back to userland with ERETNC. That preserves the * LLAddr/LLB so the subsequent SC instruction will * succeed preserving the atomic semantics of the LL/SC * block. Without that, there is no safe way to emulate * an LL/SC block in MIPSR2 userland.
*/
pr_err("Can't emulate MIPSR2 LL/SC without Config5/LLB\n");
err = SIGKILL; break;
}
if (!cpu_has_rw_llb) { /* * An LL/SC block can't be safely emulated without * a Config5/LLB availability. So it's probably time to * kill our process before things get any worse. This is * because Config5/LLB allows us to use ERETNC so that * the LLAddr/LLB bit is not cleared when we return from * an exception. MIPS R2 LL/SC instructions trap with an * RI exception so once we emulate them here, we return * back to userland with ERETNC. That preserves the * LLAddr/LLB so the subsequent SC instruction will * succeed preserving the atomic semantics of the LL/SC * block. Without that, there is no safe way to emulate * an LL/SC block in MIPSR2 userland.
*/
pr_err("Can't emulate MIPSR2 LL/SC without Config5/LLB\n");
err = SIGKILL; break;
}
if (!cpu_has_rw_llb) { /* * An LL/SC block can't be safely emulated without * a Config5/LLB availability. So it's probably time to * kill our process before things get any worse. This is * because Config5/LLB allows us to use ERETNC so that * the LLAddr/LLB bit is not cleared when we return from * an exception. MIPS R2 LL/SC instructions trap with an * RI exception so once we emulate them here, we return * back to userland with ERETNC. That preserves the * LLAddr/LLB so the subsequent SC instruction will * succeed preserving the atomic semantics of the LL/SC * block. Without that, there is no safe way to emulate * an LL/SC block in MIPSR2 userland.
*/
pr_err("Can't emulate MIPSR2 LL/SC without Config5/LLB\n");
err = SIGKILL; break;
}
if (!cpu_has_rw_llb) { /* * An LL/SC block can't be safely emulated without * a Config5/LLB availability. So it's probably time to * kill our process before things get any worse. This is * because Config5/LLB allows us to use ERETNC so that * the LLAddr/LLB bit is not cleared when we return from * an exception. MIPS R2 LL/SC instructions trap with an * RI exception so once we emulate them here, we return * back to userland with ERETNC. That preserves the * LLAddr/LLB so the subsequent SC instruction will * succeed preserving the atomic semantics of the LL/SC * block. Without that, there is no safe way to emulate * an LL/SC block in MIPSR2 userland.
*/
pr_err("Can't emulate MIPSR2 LL/SC without Config5/LLB\n");
err = SIGKILL; break;
}
if (MIPSInst_RT(inst) && !err)
regs->regs[MIPSInst_RT(inst)] = res;
MIPS_R2_STATS(llsc);
break; case pref_op: /* skip it */ break; default:
err = SIGILL;
}
/* * Let's not return to userland just yet. It's costly and * it's likely we have more R2 instructions to emulate
*/ if (!err && (pass++ < MIPS_R2_EMUL_TOTAL_PASS)) {
regs->cp0_cause &= ~CAUSEF_BD;
err = get_user(inst, (u32 __user *)regs->cp0_epc); if (!err) goto repeat;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.