Skip to content

Commit

Permalink
MIPS Kprobes: Refactor branch emulation
Browse files Browse the repository at this point in the history
This patch refactors MIPS branch emulation code so as to allow skipping
delay slot instruction in case of branch likely instructions when branch is
not taken. This is useful for keeping the code common for use cases like
kprobes where one would like to handle the branch instructions keeping the
delay slot instuction also in picture for branch likely instructions. Also
allow emulation when instruction to be decoded is not at pt_regs->cp0_epc
as in case of kprobes where pt_regs->cp0_epc points to the breakpoint
instruction.

The patch also exports the function for modules.

Signed-off-by: Maneesh Soni <manesoni@cisco.com>
Signed-off-by: Victor Kamensky <kamensky@cisco.com>
Cc: David Daney <david.daney@cavium.com>
Cc: ananth@in.ibm.com
Cc: linux-kernel@vger.kernel.org
Cc: linux-mips@linux-mips.org
Patchwork: https://patchwork.linux-mips.org/patch/2913/
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
  • Loading branch information
Maneesh Soni authored and Ralf Baechle committed Dec 7, 2011
1 parent 9233c1e commit d8d4e3a
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 45 deletions.
5 changes: 5 additions & 0 deletions arch/mips/include/asm/branch.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#define _ASM_BRANCH_H

#include <asm/ptrace.h>
#include <asm/inst.h>

static inline int delay_slot(struct pt_regs *regs)
{
Expand All @@ -23,7 +24,11 @@ static inline unsigned long exception_epc(struct pt_regs *regs)
return regs->cp0_epc + 4;
}

#define BRANCH_LIKELY_TAKEN 0x0001

extern int __compute_return_epc(struct pt_regs *regs);
extern int __compute_return_epc_for_insn(struct pt_regs *regs,
union mips_instruction insn);

static inline int compute_return_epc(struct pt_regs *regs)
{
Expand Down
128 changes: 84 additions & 44 deletions arch/mips/kernel/branch.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/signal.h>
#include <linux/module.h>
#include <asm/branch.h>
#include <asm/cpu.h>
#include <asm/cpu-features.h>
Expand All @@ -17,28 +18,22 @@
#include <asm/ptrace.h>
#include <asm/uaccess.h>

/*
* Compute the return address and do emulate branch simulation, if required.
/**
* __compute_return_epc_for_insn - Computes the return address and do emulate
* branch simulation, if required.
*
* @regs: Pointer to pt_regs
* @insn: branch instruction to decode
* @returns: -EFAULT on error and forces SIGBUS, and on success
* returns 0 or BRANCH_LIKELY_TAKEN as appropriate after
* evaluating the branch.
*/
int __compute_return_epc(struct pt_regs *regs)
int __compute_return_epc_for_insn(struct pt_regs *regs,
union mips_instruction insn)
{
unsigned int __user *addr;
unsigned int bit, fcr31, dspcontrol;
long epc;
union mips_instruction insn;

epc = regs->cp0_epc;
if (epc & 3)
goto unaligned;

/*
* Read the instruction
*/
addr = (unsigned int __user *) epc;
if (__get_user(insn.word, addr)) {
force_sig(SIGSEGV, current);
return -EFAULT;
}
long epc = regs->cp0_epc;
int ret = 0;

switch (insn.i_format.opcode) {
/*
Expand All @@ -64,41 +59,50 @@ int __compute_return_epc(struct pt_regs *regs)
switch (insn.i_format.rt) {
case bltz_op:
case bltzl_op:
if ((long)regs->regs[insn.i_format.rs] < 0)
if ((long)regs->regs[insn.i_format.rs] < 0) {
epc = epc + 4 + (insn.i_format.simmediate << 2);
else
if (insn.i_format.rt == bltzl_op)
ret = BRANCH_LIKELY_TAKEN;
} else
epc += 8;
regs->cp0_epc = epc;
break;

case bgez_op:
case bgezl_op:
if ((long)regs->regs[insn.i_format.rs] >= 0)
if ((long)regs->regs[insn.i_format.rs] >= 0) {
epc = epc + 4 + (insn.i_format.simmediate << 2);
else
if (insn.i_format.rt == bgezl_op)
ret = BRANCH_LIKELY_TAKEN;
} else
epc += 8;
regs->cp0_epc = epc;
break;

case bltzal_op:
case bltzall_op:
regs->regs[31] = epc + 8;
if ((long)regs->regs[insn.i_format.rs] < 0)
if ((long)regs->regs[insn.i_format.rs] < 0) {
epc = epc + 4 + (insn.i_format.simmediate << 2);
else
if (insn.i_format.rt == bltzall_op)
ret = BRANCH_LIKELY_TAKEN;
} else
epc += 8;
regs->cp0_epc = epc;
break;

case bgezal_op:
case bgezall_op:
regs->regs[31] = epc + 8;
if ((long)regs->regs[insn.i_format.rs] >= 0)
if ((long)regs->regs[insn.i_format.rs] >= 0) {
epc = epc + 4 + (insn.i_format.simmediate << 2);
else
if (insn.i_format.rt == bgezall_op)
ret = BRANCH_LIKELY_TAKEN;
} else
epc += 8;
regs->cp0_epc = epc;
break;

case bposge32_op:
if (!cpu_has_dsp)
goto sigill;
Expand Down Expand Up @@ -133,39 +137,47 @@ int __compute_return_epc(struct pt_regs *regs)
case beq_op:
case beql_op:
if (regs->regs[insn.i_format.rs] ==
regs->regs[insn.i_format.rt])
regs->regs[insn.i_format.rt]) {
epc = epc + 4 + (insn.i_format.simmediate << 2);
else
if (insn.i_format.rt == beql_op)
ret = BRANCH_LIKELY_TAKEN;
} else
epc += 8;
regs->cp0_epc = epc;
break;

case bne_op:
case bnel_op:
if (regs->regs[insn.i_format.rs] !=
regs->regs[insn.i_format.rt])
regs->regs[insn.i_format.rt]) {
epc = epc + 4 + (insn.i_format.simmediate << 2);
else
if (insn.i_format.rt == bnel_op)
ret = BRANCH_LIKELY_TAKEN;
} else
epc += 8;
regs->cp0_epc = epc;
break;

case blez_op: /* not really i_format */
case blezl_op:
/* rt field assumed to be zero */
if ((long)regs->regs[insn.i_format.rs] <= 0)
if ((long)regs->regs[insn.i_format.rs] <= 0) {
epc = epc + 4 + (insn.i_format.simmediate << 2);
else
if (insn.i_format.rt == bnel_op)
ret = BRANCH_LIKELY_TAKEN;
} else
epc += 8;
regs->cp0_epc = epc;
break;

case bgtz_op:
case bgtzl_op:
/* rt field assumed to be zero */
if ((long)regs->regs[insn.i_format.rs] > 0)
if ((long)regs->regs[insn.i_format.rs] > 0) {
epc = epc + 4 + (insn.i_format.simmediate << 2);
else
if (insn.i_format.rt == bnel_op)
ret = BRANCH_LIKELY_TAKEN;
} else
epc += 8;
regs->cp0_epc = epc;
break;
Expand All @@ -187,18 +199,22 @@ int __compute_return_epc(struct pt_regs *regs)
switch (insn.i_format.rt & 3) {
case 0: /* bc1f */
case 2: /* bc1fl */
if (~fcr31 & (1 << bit))
if (~fcr31 & (1 << bit)) {
epc = epc + 4 + (insn.i_format.simmediate << 2);
else
if (insn.i_format.rt == 2)
ret = BRANCH_LIKELY_TAKEN;
} else
epc += 8;
regs->cp0_epc = epc;
break;

case 1: /* bc1t */
case 3: /* bc1tl */
if (fcr31 & (1 << bit))
if (fcr31 & (1 << bit)) {
epc = epc + 4 + (insn.i_format.simmediate << 2);
else
if (insn.i_format.rt == 3)
ret = BRANCH_LIKELY_TAKEN;
} else
epc += 8;
regs->cp0_epc = epc;
break;
Expand Down Expand Up @@ -239,15 +255,39 @@ int __compute_return_epc(struct pt_regs *regs)
#endif
}

return 0;
return ret;

unaligned:
printk("%s: unaligned epc - sending SIGBUS.\n", current->comm);
sigill:
printk("%s: DSP branch but not DSP ASE - sending SIGBUS.\n", current->comm);
force_sig(SIGBUS, current);
return -EFAULT;
}
EXPORT_SYMBOL_GPL(__compute_return_epc_for_insn);

sigill:
printk("%s: DSP branch but not DSP ASE - sending SIGBUS.\n", current->comm);
int __compute_return_epc(struct pt_regs *regs)
{
unsigned int __user *addr;
long epc;
union mips_instruction insn;

epc = regs->cp0_epc;
if (epc & 3)
goto unaligned;

/*
* Read the instruction
*/
addr = (unsigned int __user *) epc;
if (__get_user(insn.word, addr)) {
force_sig(SIGSEGV, current);
return -EFAULT;
}

return __compute_return_epc_for_insn(regs, insn);

unaligned:
printk("%s: unaligned epc - sending SIGBUS.\n", current->comm);
force_sig(SIGBUS, current);
return -EFAULT;

}
2 changes: 1 addition & 1 deletion arch/mips/math-emu/cp1emu.c
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ static int cop1Emulate(struct pt_regs *xcp, struct mips_fpu_struct *ctx,
*/
emulpc = xcp->cp0_epc + 4; /* Snapshot emulation target */

if (__compute_return_epc(xcp)) {
if (__compute_return_epc(xcp) < 0) {
#ifdef CP1DBG
printk("failed to emulate branch at %p\n",
(void *) (xcp->cp0_epc));
Expand Down

0 comments on commit d8d4e3a

Please sign in to comment.