[POWERPC] Fix subtle FP state corruption bug in signal return on SMP
[pandora-kernel.git] / arch / powerpc / kernel / signal_64.c
index c2db642..b27e268 100644 (file)
  *  2 of the License, or (at your option) any later version.
  */
 
-#include <linux/config.h>
 #include <linux/sched.h>
 #include <linux/mm.h>
 #include <linux/smp.h>
-#include <linux/smp_lock.h>
 #include <linux/kernel.h>
 #include <linux/signal.h>
 #include <linux/errno.h>
@@ -141,9 +139,7 @@ static long restore_sigcontext(struct pt_regs *regs, sigset_t *set, int sig,
        unsigned long err = 0;
        unsigned long save_r13 = 0;
        elf_greg_t *gregs = (elf_greg_t *)regs;
-#ifdef CONFIG_ALTIVEC
        unsigned long msr;
-#endif
        int i;
 
        /* If this is not a signal return, we preserve the TLS in r13 */
@@ -154,7 +150,12 @@ static long restore_sigcontext(struct pt_regs *regs, sigset_t *set, int sig,
        err |= __copy_from_user(regs, &sc->gp_regs,
                                PT_MSR*sizeof(unsigned long));
 
-       /* skip MSR and SOFTE */
+       /* get MSR separately, transfer the LE bit if doing signal return */
+       err |= __get_user(msr, &sc->gp_regs[PT_MSR]);
+       if (sig)
+               regs->msr = (regs->msr & ~MSR_LE) | (msr & MSR_LE);
+
+       /* skip SOFTE */
        for (i = PT_MSR+1; i <= PT_RESULT; i++) {
                if (i == PT_SOFTE)
                        continue;
@@ -175,11 +176,17 @@ static long restore_sigcontext(struct pt_regs *regs, sigset_t *set, int sig,
         */
        discard_lazy_cpu_state();
 
+       /*
+        * Force reload of FP/VEC.
+        * This has to be done before copying stuff into current->thread.fpr/vr
+        * for the reasons explained in the previous comment.
+        */
+       regs->msr &= ~(MSR_FP | MSR_FE0 | MSR_FE1 | MSR_VEC);
+
        err |= __copy_from_user(&current->thread.fpr, &sc->fp_regs, FP_REGS_SIZE);
 
 #ifdef CONFIG_ALTIVEC
        err |= __get_user(v_regs, &sc->v_regs);
-       err |= __get_user(msr, &sc->gp_regs[PT_MSR]);
        if (err)
                return err;
        if (v_regs && !access_ok(VERIFY_READ, v_regs, 34 * sizeof(vector128)))
@@ -197,9 +204,6 @@ static long restore_sigcontext(struct pt_regs *regs, sigset_t *set, int sig,
                current->thread.vrsave = 0;
 #endif /* CONFIG_ALTIVEC */
 
-       /* Force reload of FP/VEC */
-       regs->msr &= ~(MSR_FP | MSR_FE0 | MSR_FE1 | MSR_VEC);
-
        return err;
 }
 
@@ -396,8 +400,8 @@ static int setup_rt_frame(int signr, struct k_sigaction *ka, siginfo_t *info,
        current->thread.fpscr.val = 0;
 
        /* Set up to return from userspace. */
-       if (vdso64_rt_sigtramp && current->thread.vdso_base) {
-               regs->link = current->thread.vdso_base + vdso64_rt_sigtramp;
+       if (vdso64_rt_sigtramp && current->mm->context.vdso_base) {
+               regs->link = current->mm->context.vdso_base + vdso64_rt_sigtramp;
        } else {
                err |= setup_trampoline(__NR_rt_sigreturn, &frame->tramp[0]);
                if (err)
@@ -412,6 +416,8 @@ static int setup_rt_frame(int signr, struct k_sigaction *ka, siginfo_t *info,
 
        /* Set up "regs" so we "return" to the signal handler. */
        err |= get_user(regs->nip, &funct_desc_ptr->entry);
+       /* enter the signal handler in big-endian mode */
+       regs->msr &= ~MSR_LE;
        regs->gpr[1] = newsp;
        err |= get_user(regs->gpr[2], &funct_desc_ptr->toc);
        regs->gpr[3] = signr;