|
|
|
[PATCH 4/9] MIPS: Support microMIPS/MIPS16e handling of delay slots. | |
| [Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] | |
From: "Steven J. Hill" <sjhill@xxxxxxxx>
Add logic needed to properly calculate exceptions for delay
slots when in microMIPS or MIPS16e mode.
Signed-off-by: Leonid Yegoshin <yegoshin@xxxxxxxx>
Signed-off-by: Steven J. Hill <sjhill@xxxxxxxx>
---
arch/mips/include/asm/branch.h | 33 +++++++-
arch/mips/kernel/branch.c | 183 +++++++++++++++++++++++++++++++++++++++-
2 files changed, 213 insertions(+), 3 deletions(-)
diff --git a/arch/mips/include/asm/branch.h b/arch/mips/include/asm/branch.h
index 888766a..ccc938a 100644
--- a/arch/mips/include/asm/branch.h
+++ b/arch/mips/include/asm/branch.h
@@ -16,11 +16,16 @@ static inline int delay_slot(struct pt_regs *regs)
return regs->cp0_cause & CAUSEF_BD;
}
+extern int __isa_exception_epc(struct pt_regs *regs);
+
static inline unsigned long exception_epc(struct pt_regs *regs)
{
- if (!delay_slot(regs))
+ if (likely(!delay_slot(regs)))
return regs->cp0_epc;
+ if (is16mode(regs))
+ return __isa_exception_epc(regs);
+
return regs->cp0_epc + 4;
}
@@ -29,9 +34,20 @@ static inline unsigned long exception_epc(struct pt_regs *regs)
extern int __compute_return_epc(struct pt_regs *regs);
extern int __compute_return_epc_for_insn(struct pt_regs *regs,
union mips_instruction insn);
+extern int __MIPS16e_compute_return_epc(struct pt_regs *regs);
+extern int __microMIPS_compute_return_epc(struct pt_regs *regs);
+/* only for MIPS32/64 but not 16bits variants */
static inline int compute_return_epc(struct pt_regs *regs)
{
+ if (is16mode(regs)) {
+ if (cpu_has_mips16)
+ return __MIPS16e_compute_return_epc(regs);
+ if (cpu_has_mmips)
+ return __microMIPS_compute_return_epc(regs);
+ return regs->cp0_epc;
+ }
+
if (!delay_slot(regs)) {
regs->cp0_epc += 4;
return 0;
@@ -40,4 +56,19 @@ static inline int compute_return_epc(struct pt_regs *regs)
return __compute_return_epc(regs);
}
+static inline int MIPS16e_compute_return_epc(struct pt_regs *regs,
+ union mips16e_instruction *inst)
+{
+ if (likely(!delay_slot(regs))) {
+ if (inst->ri.opcode == MIPS16e_extend_op) {
+ regs->cp0_epc += 4;
+ return 0;
+ }
+ regs->cp0_epc += 2;
+ return 0;
+ }
+
+ return __MIPS16e_compute_return_epc(regs);
+}
+
#endif /* _ASM_BRANCH_H */
diff --git a/arch/mips/kernel/branch.c b/arch/mips/kernel/branch.c
index 4d735d0..01b4e91 100644
--- a/arch/mips/kernel/branch.c
+++ b/arch/mips/kernel/branch.c
@@ -14,10 +14,188 @@
#include <asm/cpu.h>
#include <asm/cpu-features.h>
#include <asm/fpu.h>
+#include <asm/fpu_emulator.h>
#include <asm/inst.h>
#include <asm/ptrace.h>
#include <asm/uaccess.h>
+/*
+ * Calculate and return exception epc in case of
+ * branch delay slot for microMIPS/MIPS16e
+ * It doesn't clear ISA mode bit.
+ */
+int __isa_exception_epc(struct pt_regs *regs)
+{
+ long epc;
+ union mips16e_instruction inst;
+
+ /* calc exception pc in branch delay slot */
+ epc = regs->cp0_epc;
+ if (__get_user(inst.full, (u16 __user *) (epc & ~MIPS_ISA_MODE))) {
+ /* it should never happens... because delay slot was checked */
+ force_sig(SIGSEGV, current);
+ return epc;
+ }
+ if (cpu_has_mips16) {
+ if (inst.ri.opcode == MIPS16e_jal_op)
+ epc += 4;
+ else
+ epc += 2;
+ } else if (mm_is16bit(inst.full))
+ epc += 2;
+ else
+ epc += 4;
+
+ return epc;
+}
+
+/*
+ * Compute the return address and do emulate branch simulation in MIPS16e mode,
+ * if required.
+ * After exception only - doesn't do 'compact' branch/jumps and can't be used
+ * during interrupt (compact B/J doesn't do exception)
+ */
+int __MIPS16e_compute_return_epc(struct pt_regs *regs)
+{
+ u16 __user *addr;
+ union mips16e_instruction inst;
+ u16 inst2;
+ u32 fullinst;
+ long epc;
+
+ epc = regs->cp0_epc;
+ /*
+ * Read the instruction
+ */
+ addr = (u16 __user *) (epc & ~MIPS_ISA_MODE);
+ if (__get_user(inst.full, addr)) {
+ force_sig(SIGSEGV, current);
+ return -EFAULT;
+ }
+
+ switch (inst.ri.opcode) {
+ case MIPS16e_extend_op:
+ regs->cp0_epc += 4;
+ return 0;
+
+ /*
+ * JAL and JALX in MIPS16e mode
+ */
+ case MIPS16e_jal_op:
+ addr += 1;
+ if (__get_user(inst2, addr)) {
+ force_sig(SIGSEGV, current);
+ return -EFAULT;
+ }
+ fullinst = ((unsigned)inst.full << 16) | inst2;
+ regs->regs[31] = epc + 6;
+ epc += 4;
+ epc >>= 28;
+ epc <<= 28;
+ /*
+ * JAL:5 X:1 TARGET[20-16]:5 TARGET[25:21]:5 TARGET[15:0]:16
+ *
+ * ......TARGET[15:0].................TARGET[20:16]...........
+ * ......TARGET[25:21]
+ */
+ epc |=
+ ((fullinst & 0xffff) << 2) | ((fullinst & 0x3e00000) >> 3) |
+ ((fullinst & 0x1f0000) << 7);
+ if (!inst.jal.x)
+ epc |= MIPS_ISA_MODE; /* set ISA mode 1 */
+ regs->cp0_epc = epc;
+ return 0;
+
+ /*
+ * J(AL)R(C)
+ */
+ case MIPS16e_rr_op:
+ if (inst.rr.func == MIPS16e_jr_func) {
+
+ if (inst.rr.ra)
+ regs->cp0_epc = regs->regs[31];
+ else
+ regs->cp0_epc =
+ regs->regs[mips16e_reg2gpr[inst.rr.rx]];
+
+ if (inst.rr.l) {
+ if (inst.rr.nd)
+ regs->regs[31] = epc + 2;
+ else
+ regs->regs[31] = epc + 4;
+ }
+ return 0;
+ }
+ break;
+ }
+
+ /* all other cases have no branch delay slot and are 16bits,
+ and branches do not do exception */
+ regs->cp0_epc += 2;
+
+ return 0;
+}
+
+/*
+ * Compute the return address and do emulate branch simulation in
+ * microMIPS mode, if required.
+ * After exception only - doesn't do 'compact' branch/jumps and can't be used
+ * during interrupt (compact B/J doesn't do exception)
+ */
+int __microMIPS_compute_return_epc(struct pt_regs *regs)
+{
+ u16 __user *pc16;
+ u16 halfword;
+ unsigned int word;
+ unsigned long contpc;
+ struct decoded_instn mminst = { 0 };
+
+ mminst.micro_mips_mode = 1;
+
+ /*
+ * This load never faults.
+ */
+ pc16 = (unsigned short __user *)(regs->cp0_epc & ~MIPS_ISA_MODE);
+ __get_user(halfword, pc16);
+ pc16++;
+ contpc = regs->cp0_epc + 2;
+ word = ((unsigned int)halfword << 16);
+ mminst.pc_inc = 2;
+
+ if (!mm_is16bit(halfword)) {
+ __get_user(halfword, pc16);
+ pc16++;
+ contpc = regs->cp0_epc + 4;
+ mminst.pc_inc = 4;
+ word |= halfword;
+ }
+ mminst.insn = word;
+
+ if (get_user(halfword, pc16))
+ goto sigsegv;
+ mminst.next_pc_inc = 2;
+ word = ((unsigned int)halfword << 16);
+
+ if (!mm_is16bit(halfword)) {
+ pc16++;
+ if (get_user(halfword, pc16))
+ goto sigsegv;
+ mminst.next_pc_inc = 4;
+ word |= halfword;
+ }
+ mminst.next_insn = word;
+
+ mm_isBranchInstr(regs, mminst, &contpc);
+
+ regs->cp0_epc = contpc;
+
+ return 0;
+
+sigsegv:
+ force_sig(SIGSEGV, current);
+ return -EFAULT;
+}
+
/**
* __compute_return_epc_for_insn - Computes the return address and do emulate
* branch simulation, if required.
@@ -57,7 +235,7 @@ int __compute_return_epc_for_insn(struct pt_regs *regs,
*/
case bcond_op:
switch (insn.i_format.rt) {
- case bltz_op:
+ case bltz_op:
case bltzl_op:
if ((long)regs->regs[insn.i_format.rs] < 0) {
epc = epc + 4 + (insn.i_format.simmediate << 2);
@@ -129,6 +307,8 @@ int __compute_return_epc_for_insn(struct pt_regs *regs,
epc <<= 28;
epc |= (insn.j_format.target << 2);
regs->cp0_epc = epc;
+ if (insn.i_format.opcode == jalx_op)
+ regs->cp0_epc |= MIPS_ISA_MODE;
break;
/*
@@ -289,5 +469,4 @@ unaligned:
printk("%s: unaligned epc - sending SIGBUS.\n", current->comm);
force_sig(SIGBUS, current);
return -EFAULT;
-
}
--
1.7.10
[Linux MIPS Home] [LKML Archive] [Linux ARM] [Linux] [Git] [Photo] [Yosemite News] [Linux SCSI] [Linux Hams]
![]() |
![]() |