diff options
Diffstat (limited to 'arch/mips/kernel')
-rw-r--r-- | arch/mips/kernel/traps.c | 164 |
1 files changed, 86 insertions, 78 deletions
diff --git a/arch/mips/kernel/traps.c b/arch/mips/kernel/traps.c index 9c0c478d71a..bbf01b81a4f 100644 --- a/arch/mips/kernel/traps.c +++ b/arch/mips/kernel/traps.c @@ -9,9 +9,10 @@ * 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> @@ -410,7 +411,7 @@ asmlinkage void do_be(struct pt_regs *regs) } /* - * ll/sc emulation + * ll/sc, rdhwr, sync emulation */ #define OPCODE 0xfc000000 @@ -419,9 +420,11 @@ asmlinkage void do_be(struct pt_regs *regs) #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 /* @@ -432,11 +435,10 @@ unsigned long ll_bit; 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 @@ -451,14 +453,10 @@ static inline void simulate_ll(struct pt_regs *regs, unsigned int opcode) 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(); @@ -471,22 +469,16 @@ static inline void simulate_ll(struct pt_regs *regs, unsigned int opcode) 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 @@ -502,34 +494,25 @@ static inline void simulate_sc(struct pt_regs *regs, unsigned int opcode) ((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; } /* @@ -539,27 +522,14 @@ sig: * 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 ... */ } /* @@ -567,16 +537,9 @@ out_sigsegv: * 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; @@ -586,16 +549,20 @@ static inline int simulate_rdhwr(struct pt_regs *regs) 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) @@ -767,16 +734,35 @@ out_sigsegv: 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); + } } /* @@ -808,7 +794,11 @@ static void mt_ase_fp_affinity(void) 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); @@ -816,14 +806,32 @@ asmlinkage void do_cpu(struct pt_regs *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. */ |