Merge branches 'work.misc' and 'work.dcache' of git://git.kernel.org/pub/scm/linux...
[sfrench/cifs-2.6.git] / arch / mips / kernel / process.c
index 9670e70139fd971d00bf7a06c46b06cec7ff1e35..8fc69891e1173a91da5e972a5feaadb3e8547775 100644 (file)
@@ -30,6 +30,7 @@
 #include <linux/random.h>
 #include <linux/prctl.h>
 #include <linux/nmi.h>
+#include <linux/cpu.h>
 
 #include <asm/asm.h>
 #include <asm/bootinfo.h>
@@ -706,19 +707,25 @@ int mips_get_process_fp_mode(struct task_struct *task)
        return value;
 }
 
-static void prepare_for_fp_mode_switch(void *info)
+static long prepare_for_fp_mode_switch(void *unused)
 {
-       struct mm_struct *mm = info;
-
-       if (current->mm == mm)
-               lose_fpu(1);
+       /*
+        * This is icky, but we use this to simply ensure that all CPUs have
+        * context switched, regardless of whether they were previously running
+        * kernel or user code. This ensures that no CPU currently has its FPU
+        * enabled, or is about to attempt to enable it through any path other
+        * than enable_restore_fp_context() which will wait appropriately for
+        * fp_mode_switching to be zero.
+        */
+       return 0;
 }
 
 int mips_set_process_fp_mode(struct task_struct *task, unsigned int value)
 {
        const unsigned int known_bits = PR_FP_MODE_FR | PR_FP_MODE_FRE;
        struct task_struct *t;
-       int max_users;
+       struct cpumask process_cpus;
+       int cpu;
 
        /* If nothing to change, return right away, successfully.  */
        if (value == mips_get_process_fp_mode(task))
@@ -751,35 +758,7 @@ int mips_set_process_fp_mode(struct task_struct *task, unsigned int value)
        if (!(value & PR_FP_MODE_FR) && raw_cpu_has_fpu && cpu_has_mips_r6)
                return -EOPNOTSUPP;
 
-       /* Proceed with the mode switch */
-       preempt_disable();
-
-       /* Save FP & vector context, then disable FPU & MSA */
-       if (task->signal == current->signal)
-               lose_fpu(1);
-
-       /* Prevent any threads from obtaining live FP context */
-       atomic_set(&task->mm->context.fp_mode_switching, 1);
-       smp_mb__after_atomic();
-
-       /*
-        * If there are multiple online CPUs then force any which are running
-        * threads in this process to lose their FPU context, which they can't
-        * regain until fp_mode_switching is cleared later.
-        */
-       if (num_online_cpus() > 1) {
-               /* No need to send an IPI for the local CPU */
-               max_users = (task->mm == current->mm) ? 1 : 0;
-
-               if (atomic_read(&current->mm->mm_users) > max_users)
-                       smp_call_function(prepare_for_fp_mode_switch,
-                                         (void *)current->mm, 1);
-       }
-
-       /*
-        * There are now no threads of the process with live FP context, so it
-        * is safe to proceed with the FP mode switch.
-        */
+       /* Indicate the new FP mode in each thread */
        for_each_thread(task, t) {
                /* Update desired FP register width */
                if (value & PR_FP_MODE_FR) {
@@ -796,9 +775,34 @@ int mips_set_process_fp_mode(struct task_struct *task, unsigned int value)
                        clear_tsk_thread_flag(t, TIF_HYBRID_FPREGS);
        }
 
-       /* Allow threads to use FP again */
-       atomic_set(&task->mm->context.fp_mode_switching, 0);
-       preempt_enable();
+       /*
+        * We need to ensure that all threads in the process have switched mode
+        * before returning, in order to allow userland to not worry about
+        * races. We can do this by forcing all CPUs that any thread in the
+        * process may be running on to schedule something else - in this case
+        * prepare_for_fp_mode_switch().
+        *
+        * We begin by generating a mask of all CPUs that any thread in the
+        * process may be running on.
+        */
+       cpumask_clear(&process_cpus);
+       for_each_thread(task, t)
+               cpumask_set_cpu(task_cpu(t), &process_cpus);
+
+       /*
+        * Now we schedule prepare_for_fp_mode_switch() on each of those CPUs.
+        *
+        * The CPUs may have rescheduled already since we switched mode or
+        * generated the cpumask, but that doesn't matter. If the task in this
+        * process is scheduled out then our scheduling
+        * prepare_for_fp_mode_switch() will simply be redundant. If it's
+        * scheduled in then it will already have picked up the new FP mode
+        * whilst doing so.
+        */
+       get_online_cpus();
+       for_each_cpu_and(cpu, &process_cpus, cpu_online_mask)
+               work_on_cpu(cpu, prepare_for_fp_mode_switch, NULL);
+       put_online_cpus();
 
        wake_up_var(&task->mm->context.fp_mode_switching);