summaryrefslogtreecommitdiffstats
path: root/arch
diff options
context:
space:
mode:
authorSteven J. Hill <Steven.Hill@imgtec.com>2013-03-25 19:45:19 +0100
committerRalf Baechle <ralf@linux-mips.org>2013-05-09 17:55:20 +0200
commit8508488fe7028b95bc86c7447c205fbc765cc4cf (patch)
treed1d10ce3f94efb5ade53e3aaa09d1c0c31afd3a4 /arch
parentMIPS: MIPS16e: Add instruction formats. (diff)
downloadlinux-8508488fe7028b95bc86c7447c205fbc765cc4cf.tar.xz
linux-8508488fe7028b95bc86c7447c205fbc765cc4cf.zip
MIPS: MIPS16e: Support handling of delay slots.
Add logic needed to properly calculate exceptions for delay slots when in MIPS16e mode. Signed-off-by: Steven J. Hill <Steven.Hill@imgtec.com>
Diffstat (limited to 'arch')
-rw-r--r--arch/mips/include/asm/branch.h18
-rw-r--r--arch/mips/include/asm/inst.h3
-rw-r--r--arch/mips/kernel/branch.c103
3 files changed, 119 insertions, 5 deletions
diff --git a/arch/mips/include/asm/branch.h b/arch/mips/include/asm/branch.h
index 40bb9ebcc7aa..e28a3e0eb3cb 100644
--- a/arch/mips/include/asm/branch.h
+++ b/arch/mips/include/asm/branch.h
@@ -16,6 +16,7 @@ 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 __microMIPS_compute_return_epc(struct pt_regs *regs);
+extern int __MIPS16e_compute_return_epc(struct pt_regs *regs);
static inline int delay_slot(struct pt_regs *regs)
@@ -41,6 +42,8 @@ static inline int compute_return_epc(struct pt_regs *regs)
if (get_isa16_mode(regs->cp0_epc)) {
if (cpu_has_mmips)
return __microMIPS_compute_return_epc(regs);
+ if (cpu_has_mips16)
+ return __MIPS16e_compute_return_epc(regs);
return regs->cp0_epc;
}
@@ -52,4 +55,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/include/asm/inst.h b/arch/mips/include/asm/inst.h
index b27091e676c1..22912f78401c 100644
--- a/arch/mips/include/asm/inst.h
+++ b/arch/mips/include/asm/inst.h
@@ -82,4 +82,7 @@ struct mm_decoded_insn {
int micro_mips_mode;
};
+/* Recode table from 16-bit register notation to 32-bit GPR. Do NOT export!!! */
+extern const int reg16to32[];
+
#endif /* _ASM_INST_H */
diff --git a/arch/mips/kernel/branch.c b/arch/mips/kernel/branch.c
index a03836b5b683..46c2ad0703a0 100644
--- a/arch/mips/kernel/branch.c
+++ b/arch/mips/kernel/branch.c
@@ -20,13 +20,13 @@
#include <asm/uaccess.h>
/*
- * Calculate and return exception PC in case of branch delay
- * slot for microMIPS. It does not clear the ISA mode bit.
+ * Calculate and return exception PC in case of branch delay slot
+ * for microMIPS and MIPS16e. It does not clear the ISA mode bit.
*/
int __isa_exception_epc(struct pt_regs *regs)
{
- long epc = regs->cp0_epc;
unsigned short inst;
+ long epc = regs->cp0_epc;
/* Calculate exception PC in branch delay slot. */
if (__get_user(inst, (u16 __user *) msk_isa16_mode(epc))) {
@@ -34,8 +34,13 @@ int __isa_exception_epc(struct pt_regs *regs)
force_sig(SIGSEGV, current);
return epc;
}
-
- if (mm_insn_16bit(inst))
+ if (cpu_has_mips16) {
+ if (((union mips16e_instruction)inst).ri.opcode
+ == MIPS16e_jal_op)
+ epc += 4;
+ else
+ epc += 2;
+ } else if (mm_insn_16bit(inst))
epc += 2;
else
epc += 4;
@@ -101,6 +106,94 @@ sigsegv:
return -EFAULT;
}
+/*
+ * Compute return address and emulate branch in MIPS16e mode after an
+ * exception only. It does not handle compact branches/jumps and cannot
+ * be used in interrupt context. (Compact branches/jumps do not cause
+ * exceptions.)
+ */
+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 *)msk_isa16_mode(epc);
+ 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)
+ set_isa16_mode(epc); /* Set ISA mode bit. */
+ 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[reg16to32[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 16-bits.
+ * Branches do not cause an exception.
+ */
+ regs->cp0_epc += 2;
+
+ return 0;
+}
+
/**
* __compute_return_epc_for_insn - Computes the return address and do emulate
* branch simulation, if required.