fedora core 6 1.2949 + vserver 2.2.0
[linux-2.6.git] / arch / arm / vfp / vfpmodule.c
index 4178f6c..f1e5951 100644 (file)
@@ -28,7 +28,7 @@ void vfp_testing_entry(void);
 void vfp_support_entry(void);
 
 void (*vfp_vector)(void) = vfp_testing_entry;
-union vfp_state *last_VFP_context;
+union vfp_state *last_VFP_context[NR_CPUS];
 
 /*
  * Dual-use variable.
@@ -40,45 +40,59 @@ unsigned int VFP_arch;
 static int vfp_notifier(struct notifier_block *self, unsigned long cmd, void *v)
 {
        struct thread_info *thread = v;
-       union vfp_state *vfp = &thread->vfpstate;
+       union vfp_state *vfp;
+       __u32 cpu = thread->cpu;
 
-       switch (cmd) {
-       case THREAD_NOTIFY_FLUSH:
+       if (likely(cmd == THREAD_NOTIFY_SWITCH)) {
+               u32 fpexc = fmrx(FPEXC);
+
+#ifdef CONFIG_SMP
                /*
-                * Per-thread VFP initialisation.
+                * On SMP, if VFP is enabled, save the old state in
+                * case the thread migrates to a different CPU. The
+                * restoring is done lazily.
                 */
-               memset(vfp, 0, sizeof(union vfp_state));
-
-               vfp->hard.fpexc = FPEXC_ENABLE;
-               vfp->hard.fpscr = FPSCR_ROUND_NEAREST;
-
+               if ((fpexc & FPEXC_ENABLE) && last_VFP_context[cpu]) {
+                       vfp_save_state(last_VFP_context[cpu], fpexc);
+                       last_VFP_context[cpu]->hard.cpu = cpu;
+               }
                /*
-                * Disable VFP to ensure we initialise it first.
+                * Thread migration, just force the reloading of the
+                * state on the new CPU in case the VFP registers
+                * contain stale data.
                 */
-               fmxr(FPEXC, fmrx(FPEXC) & ~FPEXC_ENABLE);
+               if (thread->vfpstate.hard.cpu != cpu)
+                       last_VFP_context[cpu] = NULL;
+#endif
 
                /*
-                * FALLTHROUGH: Ensure we don't try to overwrite our newly
-                * initialised state information on the first fault.
+                * Always disable VFP so we can lazily save/restore the
+                * old state.
                 */
+               fmxr(FPEXC, fpexc & ~FPEXC_ENABLE);
+               return NOTIFY_DONE;
+       }
 
-       case THREAD_NOTIFY_RELEASE:
+       vfp = &thread->vfpstate;
+       if (cmd == THREAD_NOTIFY_FLUSH) {
                /*
-                * Per-thread VFP cleanup.
+                * Per-thread VFP initialisation.
                 */
-               if (last_VFP_context == vfp)
-                       last_VFP_context = NULL;
-               break;
+               memset(vfp, 0, sizeof(union vfp_state));
+
+               vfp->hard.fpexc = FPEXC_ENABLE;
+               vfp->hard.fpscr = FPSCR_ROUND_NEAREST;
 
-       case THREAD_NOTIFY_SWITCH:
                /*
-                * Always disable VFP so we can lazily save/restore the
-                * old state.
+                * Disable VFP to ensure we initialise it first.
                 */
                fmxr(FPEXC, fmrx(FPEXC) & ~FPEXC_ENABLE);
-               break;
        }
 
+       /* flush and release case: Per-thread VFP cleanup. */
+       if (last_VFP_context[cpu] == vfp)
+               last_VFP_context[cpu] = NULL;
+
        return NOTIFY_DONE;
 }
 
@@ -98,7 +112,7 @@ void vfp_raise_sigfpe(unsigned int sicode, struct pt_regs *regs)
 
        info.si_signo = SIGFPE;
        info.si_code = sicode;
-       info.si_addr = (void *)(instruction_pointer(regs) - 4);
+       info.si_addr = (void __user *)(instruction_pointer(regs) - 4);
 
        /*
         * This is the same as NWFPE, because it's not clear what
@@ -156,6 +170,7 @@ static void vfp_raise_exceptions(u32 exceptions, u32 inst, u32 fpscr, struct pt_
        /*
         * These are arranged in priority order, least to highest.
         */
+       RAISE(FPSCR_DZC, FPSCR_DZE, FPE_FLTDIV);
        RAISE(FPSCR_IXC, FPSCR_IXE, FPE_FLTRES);
        RAISE(FPSCR_UFC, FPSCR_UFE, FPE_FLTUND);
        RAISE(FPSCR_OFC, FPSCR_OFE, FPE_FLTOVF);
@@ -270,13 +285,36 @@ void VFP9_bounce(u32 trigger, u32 fpexc, struct pt_regs *regs)
        if (exceptions)
                vfp_raise_exceptions(exceptions, trigger, orig_fpscr, regs);
 }
+
+static void vfp_enable(void *unused)
+{
+       u32 access = get_copro_access();
+
+       /*
+        * Enable full access to VFP (cp10 and cp11)
+        */
+       set_copro_access(access | CPACC_FULL(10) | CPACC_FULL(11));
+}
+
+#include <linux/smp.h>
+
 /*
  * VFP support code initialisation.
  */
 static int __init vfp_init(void)
 {
        unsigned int vfpsid;
+       unsigned int cpu_arch = cpu_architecture();
+       u32 access = 0;
+
+       if (cpu_arch >= CPU_ARCH_ARMv6) {
+               access = get_copro_access();
+
+               /*
+                * Enable full access to VFP (cp10 and cp11)
+                */
+               set_copro_access(access | CPACC_FULL(10) | CPACC_FULL(11));
+       }
 
        /*
         * First check that there is a VFP that we can use.
@@ -284,13 +322,22 @@ static int __init vfp_init(void)
         * we just need to read the VFPSID register.
         */
        vfpsid = fmrx(FPSID);
+       barrier();
 
        printk(KERN_INFO "VFP support v0.3: ");
        if (VFP_arch) {
                printk("not present\n");
+
+               /*
+                * Restore the copro access register.
+                */
+               if (cpu_arch >= CPU_ARCH_ARMv6)
+                       set_copro_access(access);
        } else if (vfpsid & FPSID_NODOUBLE) {
                printk("no double precision support\n");
        } else {
+               smp_call_function(vfp_enable, NULL, 1, 1);
+
                VFP_arch = (vfpsid & FPSID_ARCH_MASK) >> FPSID_ARCH_BIT;  /* Extract the architecture version */
                printk("implementor %02x architecture %d part %02x variant %x rev %x\n",
                        (vfpsid & FPSID_IMPLEMENTER_MASK) >> FPSID_IMPLEMENTER_BIT,
@@ -298,9 +345,16 @@ static int __init vfp_init(void)
                        (vfpsid & FPSID_PART_MASK) >> FPSID_PART_BIT,
                        (vfpsid & FPSID_VARIANT_MASK) >> FPSID_VARIANT_BIT,
                        (vfpsid & FPSID_REV_MASK) >> FPSID_REV_BIT);
+
                vfp_vector = vfp_support_entry;
 
                thread_register_notifier(&vfp_notifier_block);
+
+               /*
+                * We detected VFP, and the support code is
+                * in place; report VFP support to userspace.
+                */
+               elf_hwcap |= HWCAP_VFP;
        }
        return 0;
 }