[RFC PATCH v2 4/5] MIPS: Add full ISA emulator.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: David Daney <david.daney@xxxxxxxxxx>

Used in follow-on patch to replace FPU delay-slot instruction
execution on stack.

Signed-off-by: David Daney <david.daney@xxxxxxxxxx>
---
 arch/mips/kernel/Makefile    |    2 +-
 arch/mips/kernel/insn-emul.c | 1543 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 1544 insertions(+), 1 deletion(-)
 create mode 100644 arch/mips/kernel/insn-emul.c

diff --git a/arch/mips/kernel/Makefile b/arch/mips/kernel/Makefile
index 92987d1..44c0670 100644
--- a/arch/mips/kernel/Makefile
+++ b/arch/mips/kernel/Makefile
@@ -7,7 +7,7 @@ extra-y		:= head.o vmlinux.lds
 obj-y		+= cpu-probe.o branch.o elf.o entry.o genex.o idle.o irq.o \
 		   process.o prom.o ptrace.o reset.o setup.o signal.o \
 		   syscall.o time.o topology.o traps.o unaligned.o watch.o \
-		   vdso.o
+		   vdso.o insn-emul.o
 
 ifdef CONFIG_FUNCTION_TRACER
 CFLAGS_REMOVE_ftrace.o = -pg
diff --git a/arch/mips/kernel/insn-emul.c b/arch/mips/kernel/insn-emul.c
new file mode 100644
index 0000000..4d484bb
--- /dev/null
+++ b/arch/mips/kernel/insn-emul.c
@@ -0,0 +1,1543 @@
+/*
+ * 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 Cavium, Inc.
+ */
+
+#include <linux/sched.h>
+
+#include <asm/inst.h>
+#include <asm/ptrace.h>
+#include <asm/barrier.h>
+#include <asm/signal.h>
+#include <asm/uaccess.h>
+#include <asm/cacheflush.h>
+
+/*
+  Missing instructions:
+
+  LDL
+  LDR
+  LWL
+  LWR
+  SDL
+  SDR
+  SWL
+  SWR
+  SYSCALL
+
+Cavium/OCTEON-III
+  EXTS
+  EXTS32
+  CINS
+  CINS32
+  DPOP
+  POP
+  SEQ
+  SEQI
+  SNE
+  SNEI
+  SAA
+  SAAD
+  LBX
+  LBUX
+  LHX
+  LHUX
+  LWX
+  LWUX
+  LDX
+  LAA
+  LAAD
+  LAW
+  LAWD
+  LAI
+  LAID
+  LAD
+  LADD
+  LAS
+  LASD
+  LAC
+  LACD
+  ZCB
+  ZCBT
+
+ */
+
+struct mips_fault_info {
+	void *__user *fault_addr;
+	int si_code;
+};
+
+static int mips64_spec_emul(struct pt_regs *regs, mips_instruction ir,
+			    struct mips_fault_info *mfi)
+{
+#ifndef CONFIG_64BIT
+	return SIGILL;
+#else
+	union mips_instruction insn;
+	int sa;
+	u64 rs64, rt64, rd64;
+	s64 rt64s, rd64s;
+	u64 t1_64, t2_64;
+
+	insn.word = ir;
+	sa = 0;
+	rt64 = regs->regs[insn.r_format.rt];
+
+	switch (insn.r_format.func) {
+	case dsll32_op:
+		sa = 32;
+		/* Fall through. */
+	case dsll_op:
+		sa += insn.r_format.re;
+		if (insn.r_format.rs != 0)
+			return SIGILL;
+		if (insn.r_format.rd == 0)
+			return 0; /* ignore result "NOP" */
+		rd64 = rt64 << sa;
+		regs->regs[insn.r_format.rd] = rd64;
+		return 0;
+	case dsllv_op:
+		if (insn.r_format.re != 0)
+			return SIGILL;
+		if (insn.r_format.rd == 0)
+			return 0; /* ignore result "NOP" */
+		rs64 = regs->regs[insn.r_format.rs];
+		sa = rs64 & 0x3f;
+		rd64 = rt64 << sa;
+		regs->regs[insn.r_format.rd] = rd64;
+		return 0;
+	case dsrl32_op:
+		sa = 32;
+		/* Fall through. */
+	case dsrl_op:
+		sa += insn.r_format.re;
+		if (insn.r_format.rs == 0) {
+			/* DSRL(32) */
+			if (insn.r_format.rd == 0)
+				return 0; /* ignore result */
+			rd64 = rt64 >> sa;
+			regs->regs[insn.r_format.rd] = rd64;
+			return 0;
+		} else if (insn.r_format.rs == 1) {
+			/* DROTR(32) */
+			if (insn.r_format.rd == 0)
+				return 0; /* ignore result */
+			rd64 = (rt64 >> sa) | (rt64 << (64 - sa));
+			regs->regs[insn.r_format.rd] = rd64;
+			return 0;
+		}
+		return SIGILL;
+	case dsrlv_op:
+		rs64 = regs->regs[insn.r_format.rs];
+		sa = rs64 & 0x3f;
+		if (insn.r_format.re == 0) {
+			/* DSRLV */
+			if (insn.r_format.rd == 0)
+				return 0; /* ignore result */
+			rd64 = rt64 >> sa;
+			regs->regs[insn.r_format.rd] = rd64;
+			return 0;
+		} else if (insn.r_format.re == 1) {
+			/* DROTRV */
+			if (insn.r_format.rd == 0)
+				return 0; /* ignore result */
+			rd64 = (rt64 >> sa) | (rt64 << (64 - sa));
+			regs->regs[insn.r_format.rd] = rd64;
+			return 0;
+		}
+		return SIGILL;
+	case dsra32_op:
+		sa = 32;
+		/* Fall through. */
+	case dsra_op:
+		sa += insn.r_format.re;
+		if (insn.r_format.rs != 0)
+			return SIGILL;
+		if (insn.r_format.rd == 0)
+			return 0; /* ignore result */
+		rt64s = rt64;
+		rd64s = rt64s >> sa;
+		regs->regs[insn.r_format.rd] = rd64s;
+		return 0;
+	case dsrav_op:
+		if (insn.r_format.re != 0)
+			return SIGILL;
+		if (insn.r_format.rd == 0)
+			return 0; /* ignore result */
+		rs64 = regs->regs[insn.r_format.rs];
+		rt64s = rt64;
+		sa = rs64 & 0x3f;
+		rd64s = rt64s >> sa;
+		regs->regs[insn.r_format.rd] = rd64s;
+		return 0;
+	case dadd_op:
+	case daddu_op:
+		if (insn.r_format.re != 0)
+			return SIGILL;
+		rs64 = regs->regs[insn.r_format.rs];
+		rd64 = rs64 + rt64;
+		if (insn.r_format.func == dadd_op) {
+			/* Check for overflow */
+			int ss = (rs64 >> 63) & 1;
+			int ts = (rt64 >> 63) & 1;
+			int ds = (rd64 >> 63) & 1;
+
+			if (ss == ts && ss != ds) {
+				mfi->si_code = FPE_INTOVF;
+				return SIGFPE;
+			}
+		}
+		if (insn.r_format.rd == 0)
+			return 0; /* ignore result */
+		regs->regs[insn.r_format.rd] = rd64;
+		return 0;
+	case dsub_op:
+	case dsubu_op:
+		if (insn.r_format.re != 0)
+			return SIGILL;
+		rs64 = regs->regs[insn.r_format.rs];
+		rd64 = rs64 - rt64;
+		if (insn.r_format.func == dsub_op) {
+			/* Check for overflow */
+			int ss = (rs64 >> 63) & 1;
+			int ts = (rt64 >> 63) & 1;
+			int ds = (rd64 >> 63) & 1;
+
+			if (ss != ts && ss != ds) {
+				mfi->si_code = FPE_INTOVF;
+				return SIGFPE;
+			}
+		}
+		if (insn.r_format.rd == 0)
+			return 0; /* ignore result */
+		regs->regs[insn.r_format.rd] = rd64;
+		return 0;
+	case dmult_op:
+		if (insn.r_format.re != 0 || insn.r_format.rd != 0)
+			return SIGILL;
+		rs64 = regs->regs[insn.r_format.rs];
+		__asm__ ("dmult	%[rs], %[rt]\n"
+			 "	mfhi	%[hi]\n"
+			 "	mflo	%[lo]"
+			 :
+			 [lo] "=r" (t1_64), [hi] "=r" (t2_64) :
+			 [rs] "r" (rs64), [rt] "r" (rt64) :
+			 "hi", "lo");
+		regs->lo = t1_64;
+		regs->hi = t2_64;
+		return 0;
+	case dmultu_op:
+		if (insn.r_format.re != 0 || insn.r_format.rd != 0)
+			return SIGILL;
+		rs64 = regs->regs[insn.r_format.rs];
+		__asm__ ("dmultu	%[rs], %[rt]\n"
+			 "	mfhi	%[hi]\n"
+			 "	mflo	%[lo]"
+			 :
+			 [lo] "=r" (t1_64), [hi] "=r" (t2_64) :
+			 [rs] "r" (rs64), [rt] "r" (rt64) :
+			 "hi", "lo");
+		regs->lo = t1_64;
+		regs->hi = t2_64;
+		return 0;
+	case ddiv_op:
+		if (insn.r_format.re != 0 || insn.r_format.rd != 0)
+			return SIGILL;
+		rs64 = regs->regs[insn.r_format.rs];
+		__asm__ ("ddiv	%[rs], %[rt]\n"
+			 "	mfhi	%[hi]\n"
+			 "	mflo	%[lo]"
+			 :
+			 [lo] "=r" (t1_64), [hi] "=r" (t2_64) :
+			 [rs] "r" (rs64), [rt] "r" (rt64) :
+			 "hi", "lo");
+		regs->lo = t1_64;
+		regs->hi = t2_64;
+		return 0;
+	case ddivu_op:
+		if (insn.r_format.re != 0 || insn.r_format.rd != 0)
+			return SIGILL;
+		rs64 = regs->regs[insn.r_format.rs];
+		__asm__ ("ddivu	%[rs], %[rt]\n"
+			 "	mfhi	%[hi]\n"
+			 "	mflo	%[lo]"
+			 :
+			 [lo] "=r" (t1_64), [hi] "=r" (t2_64) :
+			 [rs] "r" (rs64), [rt] "r" (rt64) :
+			 "hi", "lo");
+		regs->lo = t1_64;
+		regs->hi = t2_64;
+		return 0;
+	default:
+		return SIGILL;
+	}
+#endif /* CONFIG_64BIT */
+}
+
+static int mips_assert_trap(struct pt_regs *regs, unsigned int code,
+			    struct mips_fault_info *mfi)
+{
+	switch (code) {
+	case BRK_OVERFLOW:
+		mfi->si_code = FPE_INTOVF;
+		*mfi->fault_addr = (void *)regs->cp0_epc;
+		return SIGFPE;
+	case BRK_DIVZERO:
+		mfi->si_code = FPE_INTDIV;
+		*mfi->fault_addr = (void *)regs->cp0_epc;
+		return SIGFPE;
+	default:
+		return SIGTRAP;
+	}
+}
+
+static int mips_spec_emul(struct pt_regs *regs, mips_instruction ir,
+			  struct mips_fault_info *mfi)
+{
+	union mips_instruction insn;
+	u32 rs32, rt32, rd32;
+	u64 t1_64, t2_64;
+	s64 t1_64s, t2_64s;
+	u32 t1_32, t2_32;
+	s32 t1_32s, t2_32s;
+	s32 rs32s, rt32s, rd32s;
+	int sa;
+	long rss, rts;
+	unsigned long rsu, rtu;
+
+	insn.word = ir;
+	switch (insn.r_format.func) {
+	case sll_op:
+		if (insn.r_format.rs != 0)
+			return SIGILL;
+		if (insn.r_format.rd == 0)
+			return 0; /* ignore result "NOP" */
+		rt32 = regs->regs[insn.r_format.rt];
+		sa = insn.r_format.re;
+		rd32 = rt32 << sa;
+		regs->regs[insn.r_format.rd] = (long)(s32)rd32;
+		return 0;
+	case srl_op:
+		if (insn.r_format.rs == 0) {
+			/* SRL */
+			if (insn.r_format.rd == 0)
+				return 0; /* ignore result */
+			rt32 = regs->regs[insn.r_format.rt];
+			rd32 = rt32 >> insn.r_format.re;
+			regs->regs[insn.r_format.rd] = (long)(s32)rd32;
+			return 0;
+		} else if (insn.r_format.rs == 1) {
+			/* ROTR */
+			if (insn.r_format.rd == 0)
+				return 0; /* ignore result */
+			rt32 = regs->regs[insn.r_format.rt];
+			sa = insn.r_format.re;
+			rd32 = (rt32 >> sa) | (rt32 << (32 - sa));
+			regs->regs[insn.r_format.rd] = (long)(s32)rd32;
+			return 0;
+		}
+		return SIGILL;
+	case sra_op:
+		if (insn.r_format.rs != 0)
+			return SIGILL;
+		if (insn.r_format.rd == 0)
+			return 0; /* ignore result */
+		rt32s = regs->regs[insn.r_format.rt];
+		sa = insn.r_format.re;
+		rd32s = rt32s >> sa;
+		regs->regs[insn.r_format.rd] = (long)rd32s;
+		return 0;
+	case sllv_op:
+		if (insn.r_format.re != 0)
+			return SIGILL;
+		if (insn.r_format.rd == 0)
+			return 0; /* ignore result */
+		rs32 = regs->regs[insn.r_format.rs];
+		rt32 = regs->regs[insn.r_format.rt];
+		sa = rs32 & 0x1f;
+		rd32 = rt32 << sa;
+		regs->regs[insn.r_format.rd] = (long)(s32)rd32;
+		return 0;
+	case srlv_op:
+		if (insn.r_format.re == 0) {
+			/* SRLV */
+			if (insn.r_format.rd == 0)
+				return 0; /* ignore result */
+			rs32 = regs->regs[insn.r_format.rs];
+			rt32 = regs->regs[insn.r_format.rt];
+			sa = rs32 & 0x1f;
+			rd32 = rt32 >> sa;
+			regs->regs[insn.r_format.rd] = (long)(s32)rd32;
+			return 0;
+		} else if (insn.r_format.re == 1) {
+			/* ROTRV */
+			if (insn.r_format.rd == 0)
+				return 0; /* ignore result */
+			rs32 = regs->regs[insn.r_format.rs];
+			rt32 = regs->regs[insn.r_format.rt];
+			sa = rs32 & 0x1f;
+			rd32 = (rt32 >> sa) | (rt32 << (32 - sa));
+			regs->regs[insn.r_format.rd] = (long)(s32)rd32;
+			return 0;
+		}
+		return SIGILL;
+	case srav_op:
+		if (insn.r_format.re != 0)
+			return SIGILL;
+		if (insn.r_format.rd == 0)
+			return 0; /* ignore result */
+		rs32 = regs->regs[insn.r_format.rs];
+		rt32s = regs->regs[insn.r_format.rt];
+		sa = rs32 & 0x1f;
+		rd32s = rt32s >> sa;
+		regs->regs[insn.r_format.rd] = (long)rd32s;
+		return 0;
+	case movz_op:
+		if (insn.r_format.rs != 0)
+			return SIGILL;
+		if (insn.r_format.rd == 0)
+			return 0; /* ignore result */
+		if (regs->regs[insn.r_format.rt] == 0)
+			regs->regs[insn.r_format.rd] = regs->regs[insn.r_format.rs];
+		return 0;
+	case movn_op:
+		if (insn.r_format.rs != 0)
+			return SIGILL;
+		if (insn.r_format.rd == 0)
+			return 0; /* ignore result */
+		if (regs->regs[insn.r_format.rt] != 0)
+			regs->regs[insn.r_format.rd] = regs->regs[insn.r_format.rs];
+		return 0;
+	case sync_op:
+		mb(); /* an mb() is a SYNC */
+		return 0;
+	case mfhi_op:
+		if (insn.r_format.rs != 0 || insn.r_format.rt != 0 || insn.r_format.re != 0)
+			return SIGILL;
+		if (insn.r_format.rd == 0)
+			return 0; /* ignore result */
+		regs->regs[insn.r_format.rd] = regs->hi;
+		return 0;
+	case mthi_op:
+		if (insn.r_format.rt != 0 || insn.r_format.rd != 0 || insn.r_format.re != 0)
+			return SIGILL;
+		if (insn.r_format.rd == 0)
+			regs->hi = 0;
+		regs->hi = regs->regs[insn.r_format.rs];
+		return 0;
+	case mflo_op:
+		if (insn.r_format.rs != 0 || insn.r_format.rt != 0 || insn.r_format.re != 0)
+			return SIGILL;
+		if (insn.r_format.rd == 0)
+			return 0; /* ignore result */
+		regs->regs[insn.r_format.rd] = regs->lo;
+		return 0;
+	case mtlo_op:
+		if (insn.r_format.rt != 0 || insn.r_format.rd != 0 || insn.r_format.re != 0)
+			return SIGILL;
+		if (insn.r_format.rd == 0)
+			regs->lo = 0;
+		regs->lo = regs->regs[insn.r_format.rs];
+		return 0;
+	case mult_op:
+		if (insn.r_format.rd != 0 || insn.r_format.re != 0)
+			return SIGILL;
+		rs32s = regs->regs[insn.r_format.rs];
+		rt32s = regs->regs[insn.r_format.rt];
+		t1_64s = rs32s;
+		t2_64s = rt32s;
+		t1_64s *= t2_64s;
+		t1_64 = t1_64s;
+		t1_32 = t1_64 >> 32;
+		t2_32 = t1_64 & 0xffffffffu;
+		regs->lo = (long)(s32)t2_32;
+		regs->hi = (long)(s32)t1_32;
+		return 0;
+	case multu_op:
+		if (insn.r_format.rd != 0 || insn.r_format.re != 0)
+			return SIGILL;
+		rs32 = regs->regs[insn.r_format.rs];
+		rt32 = regs->regs[insn.r_format.rt];
+		t1_64 = rs32;
+		t2_64 = rt32;
+		t1_64 *= t2_64;
+		t1_32 = t1_64 >> 32;
+		t2_32 = t1_64 & 0xffffffffu;
+		regs->lo = (long)(s32)t2_32;
+		regs->hi = (long)(s32)t1_32;
+		return 0;
+	case div_op:
+		if (insn.r_format.rd != 0 || insn.r_format.re != 0)
+			return SIGILL;
+		rs32s = regs->regs[insn.r_format.rs];
+		rt32s = regs->regs[insn.r_format.rt];
+		if (rt32s == 0)
+			return 0; /* Undefined on div/zero, do nothing */
+		t1_32s = rs32s / rt32s;
+		t2_32s = rs32s % rt32s;
+		regs->lo = (long)t1_32s;
+		regs->hi = (long)t2_32s;
+		return 0;
+	case divu_op:
+		if (insn.r_format.rd != 0 || insn.r_format.re != 0)
+			return SIGILL;
+		rs32 = regs->regs[insn.r_format.rs];
+		rt32 = regs->regs[insn.r_format.rt];
+		if (rt32 == 0)
+			return 0; /* Undefined on div/zero, do nothing */
+		t1_32 = rs32 / rt32;
+		t2_32 = rs32 % rt32;
+		regs->lo = (long)(s32)t1_32;
+		regs->hi = (long)(s32)t2_32;
+		return 0;
+	case add_op:
+	case addu_op:
+		if (insn.r_format.re != 0)
+			return SIGILL;
+		rs32 = regs->regs[insn.r_format.rs];
+		rt32 = regs->regs[insn.r_format.rt];
+		rd32 = rs32 + rt32;
+		if (insn.r_format.func == add_op) {
+			/* Check for overflow */
+			int ss = (rs32 >> 31) & 1;
+			int ts = (rt32 >> 31) & 1;
+			int ds = (rd32 >> 31) & 1;
+
+			if (ss == ts && ss != ds) {
+				mfi->si_code = FPE_INTOVF;
+				return SIGFPE;
+			}
+		}
+		if (insn.r_format.rd == 0)
+			return 0; /* ignore result */
+		regs->regs[insn.r_format.rd] = (long)(s32)rd32;
+		return 0;
+	case sub_op:
+	case subu_op:
+		if (insn.r_format.re != 0)
+			return SIGILL;
+		rs32 = regs->regs[insn.r_format.rs];
+		rt32 = regs->regs[insn.r_format.rt];
+		rd32 = rs32 - rt32;
+		if (insn.r_format.func == sub_op) {
+			/* Check for overflow */
+			int ss = (rs32 >> 31) & 1;
+			int ts = (rt32 >> 31) & 1;
+			int ds = (rd32 >> 31) & 1;
+
+			if (ss != ts && ss != ds) {
+				mfi->si_code = FPE_INTOVF;
+				return SIGFPE;
+			}
+		}
+		if (insn.r_format.rd == 0)
+			return 0; /* ignore result */
+		regs->regs[insn.r_format.rd] = (long)(s32)rd32;
+		return 0;
+	case and_op:
+		if (insn.r_format.re != 0)
+			return SIGILL;
+		if (insn.r_format.rd == 0)
+			return 0; /* ignore result */
+		regs->regs[insn.r_format.rd] =
+			regs->regs[insn.r_format.rs] & regs->regs[insn.r_format.rt];
+		return 0;
+	case or_op:
+		if (insn.r_format.re != 0)
+			return SIGILL;
+		if (insn.r_format.rd == 0)
+			return 0; /* ignore result */
+		regs->regs[insn.r_format.rd] =
+			regs->regs[insn.r_format.rs] | regs->regs[insn.r_format.rt];
+		return 0;
+	case xor_op:
+		if (insn.r_format.re != 0)
+			return SIGILL;
+		if (insn.r_format.rd == 0)
+			return 0; /* ignore result */
+		regs->regs[insn.r_format.rd] =
+			regs->regs[insn.r_format.rs] ^ regs->regs[insn.r_format.rt];
+		return 0;
+	case nor_op:
+		if (insn.r_format.re != 0)
+			return SIGILL;
+		if (insn.r_format.rd == 0)
+			return 0; /* ignore result */
+		regs->regs[insn.r_format.rd] =
+			~(regs->regs[insn.r_format.rs] | regs->regs[insn.r_format.rt]);
+		return 0;
+	case slt_op:
+		if (insn.r_format.re != 0)
+			return SIGILL;
+		if (insn.r_format.rd == 0)
+			return 0; /* ignore result */
+		regs->regs[insn.r_format.rd] =
+			((long)regs->regs[insn.r_format.rs]) < ((long)regs->regs[insn.r_format.rt]) ? 1 : 0;
+		return 0;
+	case sltu_op:
+		if (insn.r_format.re != 0)
+			return SIGILL;
+		if (insn.r_format.rd == 0)
+			return 0; /* ignore result */
+		regs->regs[insn.r_format.rd] =
+			regs->regs[insn.r_format.rs] < regs->regs[insn.r_format.rt] ? 1 : 0;
+		return 0;
+	case teq_op:
+		rss = regs->regs[insn.t_format.rs];
+		rts = regs->regs[insn.t_format.rt];
+		if (rss == rts)
+			mips_assert_trap(regs, insn.t_format.code, mfi);
+		else
+			return 0;
+	case tne_op:
+		rss = regs->regs[insn.t_format.rs];
+		rts = regs->regs[insn.t_format.rt];
+		if (rss != rts)
+			mips_assert_trap(regs, insn.t_format.code, mfi);
+		else
+			return 0;
+	case tge_op:
+		rss = regs->regs[insn.t_format.rs];
+		rts = regs->regs[insn.t_format.rt];
+		if (rss >= rts)
+			mips_assert_trap(regs, insn.t_format.code, mfi);
+		else
+			return 0;
+	case tgeu_op:
+		rsu = regs->regs[insn.t_format.rs];
+		rtu = regs->regs[insn.t_format.rt];
+		if (rsu >= rtu)
+			mips_assert_trap(regs, insn.t_format.code, mfi);
+		else
+			return 0;
+	case tlt_op:
+		rss = regs->regs[insn.t_format.rs];
+		rts = regs->regs[insn.t_format.rt];
+		if (rss < rts)
+			mips_assert_trap(regs, insn.t_format.code, mfi);
+		else
+			return 0;
+	case tltu_op:
+		rsu = regs->regs[insn.t_format.rs];
+		rtu = regs->regs[insn.t_format.rt];
+		if (rsu < rtu)
+			mips_assert_trap(regs, insn.t_format.code, mfi);
+		else
+			return 0;
+	case break_op:
+		rsu = insn.b_format.code;
+		/*
+		 * do_bp() in traps.c tells us why we process the
+		 * 'code' like this.
+		 */
+		if (rsu >= (1 << 10))
+			rsu >>= 10;
+		return mips_assert_trap(regs, rsu, mfi);
+	case dsllv_op:
+	case dsrlv_op:
+	case dsrav_op:
+	case dmult_op:
+	case dmultu_op:
+	case ddiv_op:
+	case ddivu_op:
+	case dadd_op:
+	case daddu_op:
+	case dsub_op:
+	case dsubu_op:
+	case dsll_op:
+	case dsrl_op:
+	case dsra_op:
+	case dsll32_op:
+	case dsrl32_op:
+	case dsra32_op:
+		return mips64_spec_emul(regs, ir, mfi);
+	default:
+		return SIGILL;
+	}
+}
+
+static int mips_imm_emul(struct pt_regs *regs, mips_instruction ir,
+			 struct mips_fault_info *mfi)
+{
+	union mips_instruction insn;
+	u32 rs32, rt32;
+	s32 rs32s;
+	u32 t1_32;
+	s32 t1_32s;
+	unsigned long t1;
+
+	insn.word = ir;
+	switch (insn.i_format.opcode) {
+	case addi_op:
+	case addiu_op:
+		rs32 = regs->regs[insn.i_format.rs];
+		t1_32s = insn.i_format.simmediate;
+		t1_32 = t1_32s;
+		rt32 = rs32 + t1_32;
+		if (insn.i_format.opcode == addi_op) {
+			/* Check for overflow */
+			int ss = (rs32 >> 31) & 1;
+			int is = (t1_32 >> 31) & 1;
+			int ts = (rt32 >> 31) & 1;
+
+			if (ss == is && ss != ts) {
+				mfi->si_code = FPE_INTOVF;
+				return SIGFPE;
+			}
+		}
+		if (insn.i_format.rt == 0)
+			return 0; /* ignore result */
+		regs->regs[insn.i_format.rt] = (long)(s32)rt32;
+		return 0;
+	case slti_op:
+		if (insn.i_format.rt == 0)
+			return 0; /* ignore result */
+		rs32s = regs->regs[insn.i_format.rs];
+		t1_32s = insn.i_format.simmediate;
+		regs->regs[insn.i_format.rt] = rs32s > t1_32s ? 1 : 0;
+		return 0;
+	case sltiu_op:
+		if (insn.i_format.rt == 0)
+			return 0; /* ignore result */
+		rs32 = regs->regs[insn.i_format.rs];
+		t1_32s = insn.i_format.simmediate;
+		t1_32 = t1_32s;
+		regs->regs[insn.i_format.rt] = rs32 > t1_32 ? 1 : 0;
+		return 0;
+	case andi_op:
+		if (insn.u_format.rt == 0)
+			return 0; /* ignore result */
+		rs32 = regs->regs[insn.u_format.rs];
+		t1 = insn.u_format.uimmediate;
+		regs->regs[insn.u_format.rt] = t1 & regs->regs[insn.u_format.rs];
+		return 0;
+	case ori_op:
+		if (insn.u_format.rt == 0)
+			return 0; /* ignore result */
+		rs32 = regs->regs[insn.u_format.rs];
+		t1 = insn.u_format.uimmediate;
+		regs->regs[insn.u_format.rt] = t1 | regs->regs[insn.u_format.rs];
+		return 0;
+	case xori_op:
+		if (insn.u_format.rt == 0)
+			return 0; /* ignore result */
+		rs32 = regs->regs[insn.u_format.rs];
+		t1 = insn.u_format.uimmediate;
+		regs->regs[insn.u_format.rt] = t1 ^ regs->regs[insn.u_format.rs];
+		return 0;
+	case lui_op:
+		if (insn.u_format.rs != 0)
+			return SIGILL;
+		if (insn.u_format.rt == 0)
+			return 0; /* ignore result */
+		t1_32 = insn.u_format.uimmediate;
+		t1_32 <<= 16;
+		regs->regs[insn.u_format.rt] = (long)(s32)t1_32;
+		return 0;
+	default:
+		return SIGILL;
+	}
+}
+
+static int mips64_imm_emul(struct pt_regs *regs, mips_instruction ir,
+			   struct mips_fault_info *mfi)
+{
+#ifndef CONFIG_64BIT
+	return SIGILL;
+#else
+	union mips_instruction insn;
+	u64 rs64, rt64;
+	s64 t1_64s;
+	u64 t1_64;
+
+	insn.word = ir;
+	switch (insn.i_format.opcode) {
+	case daddi_op:
+	case daddiu_op:
+		rs64 = regs->regs[insn.i_format.rs];
+		t1_64s = insn.i_format.simmediate;
+		t1_64 = t1_64s;
+		rt64 = rs64 + t1_64;
+		if (insn.i_format.opcode == daddi_op) {
+			/* Check for overflow */
+			int ss = (rs64 >> 63) & 1;
+			int is = (t1_64 >> 63) & 1;
+			int ts = (rt64 >> 63) & 1;
+
+			if (ss == is && ss != ts) {
+				mfi->si_code = FPE_INTOVF;
+				return SIGFPE;
+			}
+		}
+		if (insn.i_format.rt == 0)
+			return 0; /* ignore result */
+		regs->regs[insn.i_format.rt] = rt64;
+		return 0;
+	default:
+		return SIGILL;
+	}
+#endif /* CONFIG_64BIT */
+}
+
+static int mips64_spec2_emul(struct pt_regs *regs, mips_instruction ir)
+{
+#ifndef CONFIG_64BIT
+	return SIGILL;
+#else
+	union mips_instruction insn;
+	u64 t1_64, t2_64;
+	u64 rs64;
+	int i;
+
+	insn.word = ir;
+	switch (insn.r_format.func) {
+	case dclz_op:
+	case dclo_op:
+		t2_64 = (insn.r_format.func == dclz_op) ? 0 : 1;
+		if (insn.r_format.re != 0)
+			return SIGILL;
+		if (insn.r_format.rd == 0)
+			return 0; /* ignore result */
+		rs64 = regs->regs[insn.r_format.rs];
+		t1_64 = 0;
+		for (i = 63; i <= 0; i--) {
+			if (((rs64 >> i) & 1) == t2_64)
+				t1_64++;
+			else
+				break;
+		}
+		regs->regs[insn.r_format.rd] = t1_64;
+		return 0;
+	default:
+		return SIGILL;
+	}
+	return 0;
+#endif /* CONFIG_64BIT */
+}
+
+static int mips_spec2_emul(struct pt_regs *regs, mips_instruction ir)
+{
+	union mips_instruction insn;
+	s32 rs32s, rt32s;
+	u32 rs32, rt32;
+	s64 t1_64s, t2_64s;
+	u64 t1_64, t2_64, t3_64;
+	u32 t1_32, t2_32;
+	int i;
+
+	insn.word = ir;
+	switch (insn.r_format.func) {
+	case madd_op:
+	case msub_op:
+
+		if (insn.r_format.re != 0 || insn.r_format.rd != 0)
+			return SIGILL;
+		rs32s = regs->regs[insn.r_format.rs];
+		rt32s = regs->regs[insn.r_format.rt];
+		t1_64s = rs32s;
+		t2_64s = rt32s;
+		t1_64s = t1_64s * t2_64s;	/* Product */
+		t1_32 = regs->hi;
+		t2_32 = regs->lo;
+		t1_64 = t1_32;
+		t2_64 = t2_32;
+		t1_64 = (t1_64 << 32) | t2_64;
+		t2_64s = t1_64;			/* (hi, lo) */
+		if (insn.r_format.func == madd_op)
+			t2_64s = t2_64s + t1_64s; /* (hi, lo) + Product */
+		else
+			t2_64s = t2_64s - t1_64s; /* (hi, lo) - Product */
+		t1_64 = t2_64s;
+
+		t2_64 = t1_64 & 0xffffffffu;
+		t1_64 = t1_64 >> 32;
+		t1_32 = t1_64;
+		t2_32 = t2_64;
+		regs->hi = (long)(s32)t1_32;
+		regs->lo = (long)(s32)t2_32;
+		return 0;
+
+	case maddu_op:
+	case msubu_op:
+		if (insn.r_format.re != 0 || insn.r_format.rd != 0)
+			return SIGILL;
+		rs32 = regs->regs[insn.r_format.rs];
+		rt32 = regs->regs[insn.r_format.rt];
+		t1_64 = rs32;
+		t2_64 = rt32;
+		t1_64 = t1_64 * t2_64;		/* Product */
+		t1_32 = regs->hi;
+		t2_32 = regs->lo;
+		t2_64 = t1_32;
+		t3_64 = t2_32;
+		t2_64 = (t2_64 << 32) | t3_64;	/* (hi, lo) */
+
+		if (insn.r_format.func == maddu_op)
+			t1_64 = t2_64 + t1_64;	/* (hi, lo) + Product */
+		else
+			t1_64 = t2_64 - t1_64;	/* (hi, lo) - Product */
+
+		t2_64 = t1_64 & 0xffffffffu;
+		t1_64 = t1_64 >> 32;
+		t1_32 = t1_64;
+		t2_32 = t2_64;
+		regs->hi = (long)(s32)t1_32;
+		regs->lo = (long)(s32)t2_32;
+		return 0;
+
+	case mul_op:
+		if (insn.r_format.re != 0)
+			return SIGILL;
+		if (insn.r_format.rd == 0)
+			return 0; /* ignore result */
+		rs32s = regs->regs[insn.r_format.rs];
+		rt32s = regs->regs[insn.r_format.rt];
+		t1_64s = rs32s;
+		t2_64s = rt32s;
+		t1_64s = t1_64s * t2_64s;
+		t1_64 = t1_64s;
+		t1_32 = t1_64;
+		regs->regs[insn.r_format.rd] = (long)(s32)t1_32;
+		return 0;
+
+	case clz_op:
+	case clo_op:
+		t2_32 = (insn.r_format.func == clz_op) ? 0 : 1;
+		if (insn.r_format.re != 0)
+			return SIGILL;
+		if (insn.r_format.rd == 0)
+			return 0; /* ignore result */
+		rs32 = regs->regs[insn.r_format.rs];
+		t1_32 = 0;
+		for (i = 31; i <= 0; i--) {
+			if (((rs32 >> i) & 1) == t2_32)
+				t1_32++;
+			else
+				break;
+		}
+		regs->regs[insn.r_format.rd] = t1_32;
+		return 0;
+
+	case dclz_op:
+	case dclo_op:
+		return mips64_spec2_emul(regs, ir);
+	default:
+		return SIGILL;
+	}
+}
+
+static int mips64_spec3_emul(struct pt_regs *regs, mips_instruction ir)
+{
+#ifndef CONFIG_64BIT
+	return SIGILL;
+#else
+	union mips_instruction insn;
+	u64 rs64, rt64, mask, t64;
+	int pos, size;
+
+	insn.word = ir;
+	rs64 = regs->regs[insn.r_format.rs];
+
+	switch (insn.r_format.func) {
+	case dextm_op:
+		pos = insn.r_format.re;
+		size = insn.r_format.rd + 33;
+		goto extract;
+	case dextu_op:
+		pos = insn.r_format.re + 32;
+		size = insn.r_format.rd + 1;
+		goto extract;
+	case dext_op:
+		pos = insn.r_format.re;
+		size = insn.r_format.rd + 1;
+extract:
+		rt64 = (rs64 >> pos) & (0xffffffffffffffffull >> (64 - size));
+		regs->regs[insn.r_format.rt] = rt64;
+		return 0;
+	case dinsm_op:
+		pos = insn.r_format.re;
+		size = insn.r_format.rd + 33 - pos;
+		goto insert;
+	case dinsu_op:
+		pos = insn.r_format.re + 32;
+		size = insn.r_format.rd + 33 - pos;
+		goto insert;
+	case dins_op:
+		pos = insn.r_format.re;
+		size = insn.r_format.rd + 1 - pos;
+insert:
+		mask = (0xffffffffffffffffull >> (64 - size));
+		t64 = rs64 & mask;
+		mask <<= pos;
+		mask = ~mask;
+		t64 <<= pos;
+		rt64 = regs->regs[insn.r_format.rt];
+		rt64 = (rt64 & mask) | t64;
+		regs->regs[insn.r_format.rt] = rt64;
+		return 0;
+	default:
+		return SIGILL;
+	}
+#endif /* CONFIG_64BIT */
+}
+
+static int mips_bshfl_emul(struct pt_regs *regs, mips_instruction ir)
+{
+	union mips_instruction insn;
+	u32 rt32, rd32;
+	s8 t8s;
+	s16 t16s;
+
+	insn.word = ir;
+	if (insn.r_format.rs != 0)
+		return SIGILL;
+
+	switch (insn.r_format.re) {
+	case wsbh_op:
+		if (insn.r_format.rd == 0)
+			return 0; /* ignore result */
+		rt32 = regs->regs[insn.r_format.rt];
+		rd32 = ((rt32 >> 8) & 0x00ff00ffu) | ((rt32 << 8) & 0xff00ff00u);
+		regs->regs[insn.r_format.rd] = (long)(s32)rd32;
+		return 0;
+	case seb_op:
+		if (insn.r_format.rd == 0)
+			return 0; /* ignore result */
+		t8s = regs->regs[insn.r_format.rt];
+		regs->regs[insn.r_format.rd] = (long)t8s;
+		return 0;
+	case seh_op:
+		if (insn.r_format.rd == 0)
+			return 0; /* ignore result */
+		t16s = regs->regs[insn.r_format.rt];
+		regs->regs[insn.r_format.rd] = (long)t16s;
+		return 0;
+	default:
+		return SIGILL;
+	}
+}
+
+static int mips_dbshfl_emul(struct pt_regs *regs, mips_instruction ir)
+{
+#ifndef CONFIG_64BIT
+	return SIGILL;
+#else
+	u64 rt64, rd64;
+	union mips_instruction insn;
+
+	insn.word = ir;
+	if (insn.r_format.rs != 0)
+		return SIGILL;
+	switch (insn.r_format.re) {
+	case wsbh_op: /* DSBH */
+		if (insn.r_format.rd == 0)
+			return 0; /* ignore result */
+		rt64 = regs->regs[insn.r_format.rt];
+		rd64 =	((rt64 << 8) & 0xff00ff00ff00ff00ull) |
+			((rt64 >> 8) & 0x00ff00ff00ff00ffull);
+		regs->regs[insn.r_format.rd] = rd64;
+		return 0;
+	case dshd_op:
+		if (insn.r_format.rd == 0)
+			return 0; /* ignore result */
+		rt64 = regs->regs[insn.r_format.rt];
+		rd64 =	((rt64 << 48) & 0xffff000000000000ull) |
+			((rt64 << 16) & 0x0000ffff00000000ull) |
+			((rt64 >> 16) & 0x00000000ffff0000ull) |
+			((rt64 >> 48) & 0x000000000000ffffull);
+		regs->regs[insn.r_format.rd] = rd64;
+		return 0;
+	default:
+		return SIGILL;
+	}
+#endif /* CONFIG_64BIT */
+}
+
+static int mips_rdhwr_emul(struct pt_regs *regs, mips_instruction ir)
+{
+	union mips_instruction insn;
+	int rt;
+	unsigned long v32;
+
+	insn.word = ir;
+	if (insn.r_format.re != 0 || insn.r_format.re != 0)
+		return SIGILL;
+	rt = insn.r_format.rt;
+
+	switch (insn.r_format.rd) {
+	case 0: /* EBase[CPUNum]*/
+		if (cpu_has_mips_r2) {
+			regs->regs[rt] = read_c0_ebase() & 0x3ff;
+				return 0;
+		} else {
+			return SIGILL;
+		}
+	case 1: /* SYNCI_Step */
+		if (cpu_has_mips_r2) {
+			__asm__(".set	push\n"
+				"	.set	mips32r2\n"
+				"	rdhwr	%[v], $1\n"
+				"	.set	pop" : [v] "=r" (v32));
+			regs->regs[rt] = v32;
+			return 0;
+		} else {
+			return SIGILL;
+		}
+	case 2: /* Count */
+		regs->regs[rt] = (long)(s32)read_c0_count();
+		return 0;
+	case 3: /* CCRes */
+		if (cpu_has_mips_r2) {
+			__asm__(".set	push\n"
+				"	.set	mips32r2\n"
+				"	rdhwr	%[v], $3\n"
+				"	.set	pop" : [v] "=r" (v32));
+			regs->regs[rt] = v32;
+			return 0;
+		} else {
+			return SIGILL;
+		}
+	case 29: /* UserLocal */
+		regs->regs[rt] = task_thread_info(current)->tp_value;
+		return 0;
+	default:
+		return SIGILL;
+	}
+}
+
+static int mips_spec3_emul(struct pt_regs *regs, mips_instruction ir)
+{
+	union mips_instruction insn;
+	u32 rs32, rt32;
+	u32 t1_32;
+
+	insn.word = ir;
+	switch (insn.r_format.func) {
+	case ext_op:
+		if (insn.r_format.rt == 0)
+			return 0; /* ignore result */
+		rs32 = regs->regs[insn.r_format.rs];
+		rt32 = rs32 >> insn.r_format.re;
+		rt32 = rt32 & (0xffffffffu >> (31 - insn.r_format.rd));
+		regs->regs[insn.r_format.rt] = (long)(s32)rt32;
+		return 0;
+	case ins_op:
+		if (insn.r_format.rt == 0)
+			return 0; /* ignore result */
+		t1_32 = 0xffffffffu >> (31 - insn.r_format.rd + insn.r_format.re);
+		t1_32 = t1_32 << insn.r_format.re; /* Mask */
+		rs32 = regs->regs[insn.r_format.rs];
+		rt32 = regs->regs[insn.r_format.rt];
+		rt32 = (rt32 & ~t1_32);
+		rs32 = (rs32 << insn.r_format.re) & t1_32;
+		rt32 = rt32 | rs32;
+		regs->regs[insn.r_format.rt] = (long)(s32)rt32;
+		return 0;
+	case bshfl_op:
+		return  mips_bshfl_emul(regs, ir);
+	case dbshfl_op:
+		return  mips_dbshfl_emul(regs, ir);
+	case rdhwr_op:
+		return mips_rdhwr_emul(regs, ir);
+	case dextm_op:
+	case dextu_op:
+	case dext_op:
+	case dinsm_op:
+	case dinsu_op:
+	case dins_op:
+		return mips64_spec3_emul(regs, ir);
+	default:
+		return SIGILL;
+	}
+}
+
+static int mips_load_emul(struct pt_regs *regs, mips_instruction ir,
+			  struct mips_fault_info *mfi)
+{
+	s8 __user *va8s;
+	s16 __user *va16s;
+	s32 __user *va32s;
+	u8 __user *va8;
+	u16 __user *va16;
+	union mips_instruction insn;
+	long base;
+	s8 t8s;
+	u8 t8;
+	s16 t16s;
+	u16 t16;
+	s32 t32s;
+
+	insn.word = ir;
+
+	base = regs->regs[insn.i_format.rs];
+	base = base + insn.i_format.simmediate;
+
+	switch (insn.i_format.opcode) {
+	case lb_op:
+		va8s = (s8 __user *)base;
+		if (!access_ok(VERIFY_READ, va8s, sizeof(t8s))) {
+			*mfi->fault_addr = va8s;
+			return SIGBUS;
+		}
+		if (__get_user_nocheck(t8s, va8s, sizeof(t8s))) {
+			*mfi->fault_addr = va8s;
+			return SIGSEGV;
+		}
+		if (insn.i_format.rt == 0)
+			return 0; /* ignore result */
+		regs->regs[insn.i_format.rt] = (long)t8s;
+		return 0;
+	case lh_op:
+		va16s = (s16 __user *)base;
+		if (!access_ok(VERIFY_READ, va16s, sizeof(t16s))) {
+			*mfi->fault_addr = va16s;
+			return SIGBUS;
+		}
+		if (__get_user_nocheck(t16s, va16s, sizeof(t16s))) {
+			*mfi->fault_addr = va16s;
+			return SIGSEGV;
+		}
+		if (insn.i_format.rt == 0)
+			return 0; /* ignore result */
+		regs->regs[insn.i_format.rt] = (long)t16s;
+		return 0;
+	case ll_op:
+		/*
+		 * Treat LL as LW, we are doing an ERET so once in
+		 * user space the result is the same.
+		 *
+		 * Fall through...
+		 */
+	case lw_op:
+		va32s = (s32 __user *)base;
+		if (!access_ok(VERIFY_READ, va32s, sizeof(t32s))) {
+			*mfi->fault_addr = va32s;
+			return SIGBUS;
+		}
+		if (__get_user_nocheck(t32s, va32s, sizeof(t32s))) {
+			*mfi->fault_addr = va32s;
+			return SIGSEGV;
+		}
+		if (insn.i_format.rt == 0)
+			return 0; /* ignore result */
+		regs->regs[insn.i_format.rt] = (long)t32s;
+		return 0;
+	case lbu_op:
+		va8 = (u8 __user *)base;
+		if (!access_ok(VERIFY_READ, va8, sizeof(t8))) {
+			*mfi->fault_addr = va8;
+			return SIGBUS;
+		}
+		if (__get_user_nocheck(t8, va8, sizeof(t8))) {
+			*mfi->fault_addr = va8;
+			return SIGSEGV;
+		}
+		if (insn.i_format.rt == 0)
+			return 0; /* ignore result */
+		regs->regs[insn.i_format.rt] = t8;
+		return 0;
+	case lhu_op:
+		va16 = (u16 __user *)base;
+		if (!access_ok(VERIFY_READ, va16, sizeof(t16))) {
+			*mfi->fault_addr = va16;
+			return SIGBUS;
+		}
+		if (__get_user_nocheck(t16, va16, sizeof(t16))) {
+			*mfi->fault_addr = va16;
+			return SIGSEGV;
+		}
+		if (insn.i_format.rt == 0)
+			return 0; /* ignore result */
+		regs->regs[insn.i_format.rt] = t16;
+		return 0;
+	default:
+		return SIGILL;
+	}
+}
+
+static int mips_store_emul(struct pt_regs *regs, mips_instruction ir,
+			   struct mips_fault_info *mfi)
+{
+	u8 __user *va8;
+	u16 __user *va16;
+	u32 __user *va32;
+	u8 t8;
+	u16 t16;
+	u32 t32;
+	union mips_instruction insn;
+	long base;
+
+	insn.word = ir;
+
+	base = regs->regs[insn.i_format.rs];
+	base = base + insn.i_format.simmediate;
+
+	switch (insn.i_format.opcode) {
+	case sb_op:
+		va8 = (u8 __user *)base;
+		if (!access_ok(VERIFY_WRITE, va8, sizeof(t8))) {
+			*mfi->fault_addr = va8;
+			return SIGBUS;
+		}
+		t8 = (u8)regs->regs[insn.i_format.rt];
+		if (__put_user_nocheck(t8, va8, sizeof(t8))) {
+			*mfi->fault_addr = va8;
+			return SIGSEGV;
+		}
+		return 0;
+	case sh_op:
+		va16 = (u16 __user *)base;
+		if (!access_ok(VERIFY_WRITE, va16, sizeof(t16))) {
+			*mfi->fault_addr = va16;
+			return SIGBUS;
+		}
+		t16 = (u16)regs->regs[insn.i_format.rt];
+		if (__put_user_nocheck(t16, va16, sizeof(t16))) {
+			*mfi->fault_addr = va16;
+			return SIGSEGV;
+		}
+		return 0;
+	case sw_op:
+		va32 = (u32 __user *)base;
+		if (!access_ok(VERIFY_WRITE, va32, sizeof(t32))) {
+			*mfi->fault_addr = va32;
+			return SIGBUS;
+		}
+		t32 = (u32)regs->regs[insn.i_format.rt];
+		if (__put_user_nocheck(t32, va32, sizeof(t32))) {
+			*mfi->fault_addr = va32;
+			return SIGSEGV;
+		}
+		return 0;
+	default:
+		return SIGILL;
+	}
+}
+
+static int mips64_load_emul(struct pt_regs *regs, mips_instruction ir,
+			    struct mips_fault_info *mfi)
+{
+#ifndef CONFIG_64BIT
+	return SIGILL;
+#else
+	union mips_instruction insn;
+	long base;
+	u64 __user *va64;
+	s32 __user *va32s;
+	u64 t64;
+	s32 t32s;
+
+	insn.word = ir;
+
+	base = regs->regs[insn.i_format.rs];
+	base = base + insn.i_format.simmediate;
+
+	switch (insn.i_format.opcode) {
+	case lld_op:
+		/*
+		 * Treat LLD as LD, we are doing an ERET so once in
+		 * user space the result is the same.
+		 *
+		 * Fall through...
+		 */
+	case ld_op:
+		va64 = (u64 __user *)base;
+		if (!access_ok(VERIFY_READ, va64, sizeof(t64))) {
+			*mfi->fault_addr = va64;
+			return SIGBUS;
+		}
+		if (__get_user_nocheck(t64, va64, sizeof(t64))) {
+			*mfi->fault_addr = va64;
+			return SIGSEGV;
+		}
+		if (insn.i_format.rt == 0)
+			return 0; /* ignore result */
+		regs->regs[insn.i_format.rt] = t64;
+		return 0;
+	case lwu_op:
+		va32s = (s32 __user *)base;
+		if (!access_ok(VERIFY_READ, va32s, sizeof(t32s))) {
+			*mfi->fault_addr = va32s;
+			return SIGBUS;
+		}
+		if (__get_user_nocheck(t32s, va32s, sizeof(t32s))) {
+			*mfi->fault_addr = va32s;
+			return SIGSEGV;
+		}
+		if (insn.i_format.rt == 0)
+			return 0; /* ignore result */
+		regs->regs[insn.i_format.rt] = (long)t32s;
+		return 0;
+	default:
+		return SIGILL;
+	}
+#endif /* CONFIG_64BIT */
+}
+
+static int mips64_store_emul(struct pt_regs *regs, mips_instruction ir,
+			     struct mips_fault_info *mfi)
+{
+#ifndef CONFIG_64BIT
+	return SIGILL;
+#else
+	u64 __user *va64;
+	u64 t64;
+	union mips_instruction insn;
+	long base;
+
+	insn.word = ir;
+
+	base = regs->regs[insn.i_format.rs];
+	base = base + insn.i_format.simmediate;
+
+	va64 = (u64 __user *)base;
+	if (!access_ok(VERIFY_WRITE, va64, sizeof(t64))) {
+		*mfi->fault_addr = va64;
+		return SIGBUS;
+	}
+	t64 = regs->regs[insn.i_format.rt];
+	if (__put_user_nocheck(t64, va64, sizeof(t64))) {
+		*mfi->fault_addr = va64;
+		return SIGSEGV;
+	}
+	return 0;
+#endif /* CONFIG_64BIT */
+}
+
+static int mips_sc_emul(struct pt_regs *regs, mips_instruction ir,
+			struct mips_fault_info *mfi)
+{
+	union mips_instruction insn;
+
+	insn.word = ir;
+
+#ifndef CONFIG_64BIT
+	if (insn.i_format.opcode == scd_op)
+		return SIGILL;
+#endif
+	/* All SC and SCD always fail under emulation, and this is fine. */
+	if (insn.i_format.rt)
+		regs->regs[insn.i_format.rt] = 0;
+
+	return 0;
+}
+
+static int mips_regimm_emul(struct pt_regs *regs, mips_instruction ir,
+			struct mips_fault_info *mfi)
+{
+	union mips_instruction insn;
+	long simmediate, sreg;
+	long uimmediate, ureg;
+
+	insn.word = ir;
+	switch (insn.i_format.rt) {
+	case tgei_op:
+		simmediate = insn.i_format.simmediate;
+		sreg = regs->regs[insn.i_format.rs];
+		if (sreg >= simmediate)
+			return mips_assert_trap(regs, 0, mfi);
+		else
+			return 0;
+	case tgeiu_op:
+		simmediate = insn.i_format.simmediate;
+		uimmediate = simmediate;
+		ureg = regs->regs[insn.i_format.rs];
+		if (ureg >= uimmediate)
+			return mips_assert_trap(regs, 0, mfi);
+		else
+			return 0;
+	case tlti_op:
+		simmediate = insn.i_format.simmediate;
+		sreg = regs->regs[insn.i_format.rs];
+		if (sreg < simmediate)
+			return mips_assert_trap(regs, 0, mfi);
+		else
+			return 0;
+	case tltiu_op:
+		simmediate = insn.i_format.simmediate;
+		uimmediate = simmediate;
+		ureg = regs->regs[insn.i_format.rs];
+		if (ureg < uimmediate)
+			return mips_assert_trap(regs, 0, mfi);
+		else
+			return 0;
+	case teqi_op:
+		simmediate = insn.i_format.simmediate;
+		sreg = regs->regs[insn.i_format.rs];
+		if (sreg == simmediate)
+			return mips_assert_trap(regs, 0, mfi);
+		else
+			return 0;
+	case tnei_op:
+		simmediate = insn.i_format.simmediate;
+		sreg = regs->regs[insn.i_format.rs];
+		if (sreg != simmediate)
+			return mips_assert_trap(regs, 0, mfi);
+		else
+			return 0;
+	case op_synci:
+		__flush_cache_all();
+		return 0;
+	default:
+		return SIGILL;
+	}
+}
+
+int mips_insn_emul(struct pt_regs *regs, mips_instruction ir,
+		   void *__user *fault_addr)
+{
+	struct mips_fault_info mfi;
+
+	mfi.fault_addr = fault_addr;
+	mfi.si_code = -1;
+
+	switch (MIPSInst_OPCODE(ir)) {
+	case spec_op:
+		return mips_spec_emul(regs, ir, &mfi);
+	case bcond_op:
+		return mips_regimm_emul(regs, ir, &mfi);
+	case addi_op:
+	case addiu_op:
+	case slti_op:
+	case sltiu_op:
+	case andi_op:
+	case ori_op:
+	case xori_op:
+	case lui_op:
+		return mips_imm_emul(regs, ir, &mfi);
+	case daddi_op:
+	case daddiu_op:
+		return mips64_imm_emul(regs, ir, &mfi);
+	case spec2_op:
+		return mips_spec2_emul(regs, ir);
+	case spec3_op:
+		return mips_spec3_emul(regs, ir);
+	case lb_op:
+	case lh_op:
+	case lw_op:
+	case lbu_op:
+	case lhu_op:
+	case ll_op:
+		return mips_load_emul(regs, ir, &mfi);
+	case sb_op:
+	case sh_op:
+	case sw_op:
+		return mips_store_emul(regs, ir, &mfi);
+	case lwu_op:
+	case ld_op:
+	case lld_op:
+		return mips64_load_emul(regs, ir, &mfi);
+	case sd_op:
+		return mips64_store_emul(regs, ir, &mfi);
+	case pref_op:
+		return 0; /* OK to ignore PREF */
+	case sc_op:
+	case scd_op:
+		return mips_sc_emul(regs, ir, &mfi);
+	default:
+		return SIGILL;
+	}
+}
-- 
1.7.11.7






[Index of Archives]     [Linux MIPS Home]     [LKML Archive]     [Linux ARM Kernel]     [Linux ARM]     [Linux]     [Git]     [Yosemite News]     [Linux SCSI]     [Linux Hams]

  Powered by Linux