* Copyright (C) 1999 Silicon Graphics, Inc.
  * Kevin D. Kissell, kevink@mips.com and Carsten Langgaard, carstenl@mips.com
  * Copyright (C) 2000, 01 MIPS Technologies, Inc.
- * Copyright (C) 2002, 2003, 2004, 2005  Maciej W. Rozycki
+ * Copyright (C) 2002, 2003, 2004, 2005, 2007  Maciej W. Rozycki
  */
 #include <linux/bug.h>
+#include <linux/compiler.h>
 #include <linux/init.h>
 #include <linux/mm.h>
 #include <linux/module.h>
 }
 
 /*
- * ll/sc emulation
+ * ll/sc, rdhwr, sync emulation
  */
 
 #define OPCODE 0xfc000000
 #define OFFSET 0x0000ffff
 #define LL     0xc0000000
 #define SC     0xe0000000
+#define SPEC0  0x00000000
 #define SPEC3  0x7c000000
 #define RD     0x0000f800
 #define FUNC   0x0000003f
+#define SYNC   0x0000000f
 #define RDHWR  0x0000003b
 
 /*
 
 static struct task_struct *ll_task = NULL;
 
-static inline void simulate_ll(struct pt_regs *regs, unsigned int opcode)
+static inline int simulate_ll(struct pt_regs *regs, unsigned int opcode)
 {
        unsigned long value, __user *vaddr;
        long offset;
-       int signal = 0;
 
        /*
         * analyse the ll instruction that just caused a ri exception
        vaddr = (unsigned long __user *)
                ((unsigned long)(regs->regs[(opcode & BASE) >> 21]) + offset);
 
-       if ((unsigned long)vaddr & 3) {
-               signal = SIGBUS;
-               goto sig;
-       }
-       if (get_user(value, vaddr)) {
-               signal = SIGSEGV;
-               goto sig;
-       }
+       if ((unsigned long)vaddr & 3)
+               return SIGBUS;
+       if (get_user(value, vaddr))
+               return SIGSEGV;
 
        preempt_disable();
 
 
        preempt_enable();
 
-       compute_return_epc(regs);
-
        regs->regs[(opcode & RT) >> 16] = value;
 
-       return;
-
-sig:
-       force_sig(signal, current);
+       return 0;
 }
 
-static inline void simulate_sc(struct pt_regs *regs, unsigned int opcode)
+static inline int simulate_sc(struct pt_regs *regs, unsigned int opcode)
 {
        unsigned long __user *vaddr;
        unsigned long reg;
        long offset;
-       int signal = 0;
 
        /*
         * analyse the sc instruction that just caused a ri exception
                ((unsigned long)(regs->regs[(opcode & BASE) >> 21]) + offset);
        reg = (opcode & RT) >> 16;
 
-       if ((unsigned long)vaddr & 3) {
-               signal = SIGBUS;
-               goto sig;
-       }
+       if ((unsigned long)vaddr & 3)
+               return SIGBUS;
 
        preempt_disable();
 
        if (ll_bit == 0 || ll_task != current) {
-               compute_return_epc(regs);
                regs->regs[reg] = 0;
                preempt_enable();
-               return;
+               return 0;
        }
 
        preempt_enable();
 
-       if (put_user(regs->regs[reg], vaddr)) {
-               signal = SIGSEGV;
-               goto sig;
-       }
+       if (put_user(regs->regs[reg], vaddr))
+               return SIGSEGV;
 
-       compute_return_epc(regs);
        regs->regs[reg] = 1;
 
-       return;
-
-sig:
-       force_sig(signal, current);
+       return 0;
 }
 
 /*
  * few processors such as NEC's VR4100 throw reserved instruction exceptions
  * instead, so we're doing the emulation thing in both exception handlers.
  */
-static inline int simulate_llsc(struct pt_regs *regs)
+static int simulate_llsc(struct pt_regs *regs, unsigned int opcode)
 {
-       unsigned int opcode;
-
-       if (get_user(opcode, (unsigned int __user *) exception_epc(regs)))
-               goto out_sigsegv;
-
-       if ((opcode & OPCODE) == LL) {
-               simulate_ll(regs, opcode);
-               return 0;
-       }
-       if ((opcode & OPCODE) == SC) {
-               simulate_sc(regs, opcode);
-               return 0;
-       }
-
-       return -EFAULT;                 /* Strange things going on ... */
+       if ((opcode & OPCODE) == LL)
+               return simulate_ll(regs, opcode);
+       if ((opcode & OPCODE) == SC)
+               return simulate_sc(regs, opcode);
 
-out_sigsegv:
-       force_sig(SIGSEGV, current);
-       return -EFAULT;
+       return -1;                      /* Must be something else ... */
 }
 
 /*
  * registers not implemented in hardware.  The only current use of this
  * is the thread area pointer.
  */
-static inline int simulate_rdhwr(struct pt_regs *regs)
+static int simulate_rdhwr(struct pt_regs *regs, unsigned int opcode)
 {
        struct thread_info *ti = task_thread_info(current);
-       unsigned int opcode;
-
-       if (get_user(opcode, (unsigned int __user *) exception_epc(regs)))
-               goto out_sigsegv;
-
-       if (unlikely(compute_return_epc(regs)))
-               return -EFAULT;
 
        if ((opcode & OPCODE) == SPEC3 && (opcode & FUNC) == RDHWR) {
                int rd = (opcode & RD) >> 11;
                                regs->regs[rt] = ti->tp_value;
                                return 0;
                        default:
-                               return -EFAULT;
+                               return -1;
                }
        }
 
        /* Not ours.  */
-       return -EFAULT;
+       return -1;
+}
 
-out_sigsegv:
-       force_sig(SIGSEGV, current);
-       return -EFAULT;
+static int simulate_sync(struct pt_regs *regs, unsigned int opcode)
+{
+       if ((opcode & OPCODE) == SPEC0 && (opcode & FUNC) == SYNC)
+               return 0;
+
+       return -1;                      /* Must be something else ... */
 }
 
 asmlinkage void do_ov(struct pt_regs *regs)
 
 asmlinkage void do_ri(struct pt_regs *regs)
 {
-       die_if_kernel("Reserved instruction in kernel code", regs);
+       unsigned int __user *epc = (unsigned int __user *)exception_epc(regs);
+       unsigned long old_epc = regs->cp0_epc;
+       unsigned int opcode = 0;
+       int status = -1;
 
-       if (!cpu_has_llsc)
-               if (!simulate_llsc(regs))
-                       return;
+       die_if_kernel("Reserved instruction in kernel code", regs);
 
-       if (!simulate_rdhwr(regs))
+       if (unlikely(compute_return_epc(regs) < 0))
                return;
 
-       force_sig(SIGILL, current);
+       if (unlikely(get_user(opcode, epc) < 0))
+               status = SIGSEGV;
+
+       if (!cpu_has_llsc && status < 0)
+               status = simulate_llsc(regs, opcode);
+
+       if (status < 0)
+               status = simulate_rdhwr(regs, opcode);
+
+       if (status < 0)
+               status = simulate_sync(regs, opcode);
+
+       if (status < 0)
+               status = SIGILL;
+
+       if (unlikely(status > 0)) {
+               regs->cp0_epc = old_epc;                /* Undo skip-over.  */
+               force_sig(status, current);
+       }
 }
 
 /*
 
 asmlinkage void do_cpu(struct pt_regs *regs)
 {
+       unsigned int __user *epc;
+       unsigned long old_epc;
+       unsigned int opcode;
        unsigned int cpid;
+       int status;
 
        die_if_kernel("do_cpu invoked from kernel context!", regs);
 
 
        switch (cpid) {
        case 0:
-               if (!cpu_has_llsc)
-                       if (!simulate_llsc(regs))
-                               return;
+               epc = (unsigned int __user *)exception_epc(regs);
+               old_epc = regs->cp0_epc;
+               opcode = 0;
+               status = -1;
 
-               if (!simulate_rdhwr(regs))
+               if (unlikely(compute_return_epc(regs) < 0))
                        return;
 
-               break;
+               if (unlikely(get_user(opcode, epc) < 0))
+                       status = SIGSEGV;
+
+               if (!cpu_has_llsc && status < 0)
+                       status = simulate_llsc(regs, opcode);
+
+               if (status < 0)
+                       status = simulate_rdhwr(regs, opcode);
+
+               if (status < 0)
+                       status = SIGILL;
+
+               if (unlikely(status > 0)) {
+                       regs->cp0_epc = old_epc;        /* Undo skip-over.  */
+                       force_sig(status, current);
+               }
+
+               return;
 
        case 1:
                if (used_math())        /* Using the FPU again.  */