arm64: defer reloading a task's FPSIMD state to userland resume
authorArd Biesheuvel <ard.biesheuvel@linaro.org>
Thu, 8 May 2014 09:20:23 +0000 (11:20 +0200)
committerArd Biesheuvel <ard.biesheuvel@linaro.org>
Thu, 8 May 2014 09:31:57 +0000 (11:31 +0200)
If a task gets scheduled out and back in again and nothing has touched
its FPSIMD state in the mean time, there is really no reason to reload
it from memory. Similarly, repeated calls to kernel_neon_begin() and
kernel_neon_end() will preserve and restore the FPSIMD state every time.

This patch defers the FPSIMD state restore to the last possible moment,
i.e., right before the task returns to userland. If a task does not return to
userland at all (for any reason), the existing FPSIMD state is preserved
and may be reused by the owning task if it gets scheduled in again on the
same CPU.

This patch adds two more functions to abstract away from straight FPSIMD
register file saves and restores:
- fpsimd_restore_current_state -> ensure current's FPSIMD state is loaded
- fpsimd_flush_task_state -> invalidate live copies of a task's FPSIMD state

Signed-off-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>
arch/arm64/include/asm/fpsimd.h
arch/arm64/include/asm/thread_info.h
arch/arm64/kernel/entry.S
arch/arm64/kernel/fpsimd.c
arch/arm64/kernel/ptrace.c
arch/arm64/kernel/signal.c

index f4e524b67e912296ef7c539e535987b1f3405322..7a900142dbc89436e7c639f82ee5244ee64769fb 100644 (file)
@@ -37,6 +37,8 @@ struct fpsimd_state {
                        u32 fpcr;
                };
        };
+       /* the id of the last cpu to have restored this state */
+       unsigned int cpu;
 };
 
 #if defined(__KERNEL__) && defined(CONFIG_COMPAT)
@@ -59,8 +61,11 @@ extern void fpsimd_thread_switch(struct task_struct *next);
 extern void fpsimd_flush_thread(void);
 
 extern void fpsimd_preserve_current_state(void);
+extern void fpsimd_restore_current_state(void);
 extern void fpsimd_update_current_state(struct fpsimd_state *state);
 
+extern void fpsimd_flush_task_state(struct task_struct *target);
+
 #endif
 
 #endif
index 720e70b66ffdcf6eff60efc964c32e9a0584325b..4a1ca1cfb2f8ef9d3502299c699835854ff85356 100644 (file)
@@ -100,6 +100,7 @@ static inline struct thread_info *current_thread_info(void)
 #define TIF_SIGPENDING         0
 #define TIF_NEED_RESCHED       1
 #define TIF_NOTIFY_RESUME      2       /* callback before returning to user */
+#define TIF_FOREIGN_FPSTATE    3       /* CPU's FP state is not current's */
 #define TIF_SYSCALL_TRACE      8
 #define TIF_POLLING_NRFLAG     16
 #define TIF_MEMDIE             18      /* is terminating due to OOM killer */
@@ -112,10 +113,11 @@ static inline struct thread_info *current_thread_info(void)
 #define _TIF_SIGPENDING                (1 << TIF_SIGPENDING)
 #define _TIF_NEED_RESCHED      (1 << TIF_NEED_RESCHED)
 #define _TIF_NOTIFY_RESUME     (1 << TIF_NOTIFY_RESUME)
+#define _TIF_FOREIGN_FPSTATE   (1 << TIF_FOREIGN_FPSTATE)
 #define _TIF_32BIT             (1 << TIF_32BIT)
 
 #define _TIF_WORK_MASK         (_TIF_NEED_RESCHED | _TIF_SIGPENDING | \
-                                _TIF_NOTIFY_RESUME)
+                                _TIF_NOTIFY_RESUME | _TIF_FOREIGN_FPSTATE)
 
 #endif /* __KERNEL__ */
 #endif /* __ASM_THREAD_INFO_H */
index 39ac630d83de77fcba77c61ca2e6817c2c8239b8..80464e2fb1a59e2fc1159c262ebcee9aa9ac0f7c 100644 (file)
@@ -576,7 +576,7 @@ fast_work_pending:
        str     x0, [sp, #S_X0]                 // returned x0
 work_pending:
        tbnz    x1, #TIF_NEED_RESCHED, work_resched
-       /* TIF_SIGPENDING or TIF_NOTIFY_RESUME case */
+       /* TIF_SIGPENDING, TIF_NOTIFY_RESUME or TIF_FOREIGN_FPSTATE case */
        ldr     x2, [sp, #S_PSTATE]
        mov     x0, sp                          // 'regs'
        tst     x2, #PSR_MODE_MASK              // user mode regs?
index 8a97163debc77e9e9c0d7aaf7bfbda0ec6e392fe..5ae89303c3ab16d9e963922fe217e6a14fe2b18b 100644 (file)
 #define FPEXC_IXF      (1 << 4)
 #define FPEXC_IDF      (1 << 7)
 
+/*
+ * In order to reduce the number of times the FPSIMD state is needlessly saved
+ * and restored, we need to keep track of two things:
+ * (a) for each task, we need to remember which CPU was the last one to have
+ *     the task's FPSIMD state loaded into its FPSIMD registers;
+ * (b) for each CPU, we need to remember which task's userland FPSIMD state has
+ *     been loaded into its FPSIMD registers most recently, or whether it has
+ *     been used to perform kernel mode NEON in the meantime.
+ *
+ * For (a), we add a 'cpu' field to struct fpsimd_state, which gets updated to
+ * the id of the current CPU everytime the state is loaded onto a CPU. For (b),
+ * we add the per-cpu variable 'fpsimd_last_state' (below), which contains the
+ * address of the userland FPSIMD state of the task that was loaded onto the CPU
+ * the most recently, or NULL if kernel mode NEON has been performed after that.
+ *
+ * With this in place, we no longer have to restore the next FPSIMD state right
+ * when switching between tasks. Instead, we can defer this check to userland
+ * resume, at which time we verify whether the CPU's fpsimd_last_state and the
+ * task's fpsimd_state.cpu are still mutually in sync. If this is the case, we
+ * can omit the FPSIMD restore.
+ *
+ * As an optimization, we use the thread_info flag TIF_FOREIGN_FPSTATE to
+ * indicate whether or not the userland FPSIMD state of the current task is
+ * present in the registers. The flag is set unless the FPSIMD registers of this
+ * CPU currently contain the most recent userland FPSIMD state of the current
+ * task.
+ *
+ * For a certain task, the sequence may look something like this:
+ * - the task gets scheduled in; if both the task's fpsimd_state.cpu field
+ *   contains the id of the current CPU, and the CPU's fpsimd_last_state per-cpu
+ *   variable points to the task's fpsimd_state, the TIF_FOREIGN_FPSTATE flag is
+ *   cleared, otherwise it is set;
+ *
+ * - the task returns to userland; if TIF_FOREIGN_FPSTATE is set, the task's
+ *   userland FPSIMD state is copied from memory to the registers, the task's
+ *   fpsimd_state.cpu field is set to the id of the current CPU, the current
+ *   CPU's fpsimd_last_state pointer is set to this task's fpsimd_state and the
+ *   TIF_FOREIGN_FPSTATE flag is cleared;
+ *
+ * - the task executes an ordinary syscall; upon return to userland, the
+ *   TIF_FOREIGN_FPSTATE flag will still be cleared, so no FPSIMD state is
+ *   restored;
+ *
+ * - the task executes a syscall which executes some NEON instructions; this is
+ *   preceded by a call to kernel_neon_begin(), which copies the task's FPSIMD
+ *   register contents to memory, clears the fpsimd_last_state per-cpu variable
+ *   and sets the TIF_FOREIGN_FPSTATE flag;
+ *
+ * - the task gets preempted after kernel_neon_end() is called; as we have not
+ *   returned from the 2nd syscall yet, TIF_FOREIGN_FPSTATE is still set so
+ *   whatever is in the FPSIMD registers is not saved to memory, but discarded.
+ */
+static DEFINE_PER_CPU(struct fpsimd_state *, fpsimd_last_state);
+
 /*
  * Trapped FP/ASIMD access.
  */
@@ -72,41 +126,96 @@ void do_fpsimd_exc(unsigned int esr, struct pt_regs *regs)
 
 void fpsimd_thread_switch(struct task_struct *next)
 {
-       /* check if not kernel threads */
-       if (current->mm)
+       /*
+        * Save the current FPSIMD state to memory, but only if whatever is in
+        * the registers is in fact the most recent userland FPSIMD state of
+        * 'current'.
+        */
+       if (current->mm && !test_thread_flag(TIF_FOREIGN_FPSTATE))
                fpsimd_save_state(&current->thread.fpsimd_state);
-       if (next->mm)
-               fpsimd_load_state(&next->thread.fpsimd_state);
+
+       if (next->mm) {
+               /*
+                * If we are switching to a task whose most recent userland
+                * FPSIMD state is already in the registers of *this* cpu,
+                * we can skip loading the state from memory. Otherwise, set
+                * the TIF_FOREIGN_FPSTATE flag so the state will be loaded
+                * upon the next return to userland.
+                */
+               struct fpsimd_state *st = &next->thread.fpsimd_state;
+
+               if (__this_cpu_read(fpsimd_last_state) == st
+                   && st->cpu == smp_processor_id())
+                       clear_ti_thread_flag(task_thread_info(next),
+                                            TIF_FOREIGN_FPSTATE);
+               else
+                       set_ti_thread_flag(task_thread_info(next),
+                                          TIF_FOREIGN_FPSTATE);
+       }
 }
 
 void fpsimd_flush_thread(void)
 {
-       preempt_disable();
        memset(&current->thread.fpsimd_state, 0, sizeof(struct fpsimd_state));
-       fpsimd_load_state(&current->thread.fpsimd_state);
-       preempt_enable();
+       set_thread_flag(TIF_FOREIGN_FPSTATE);
 }
 
 /*
- * Save the userland FPSIMD state of 'current' to memory
+ * Save the userland FPSIMD state of 'current' to memory, but only if the state
+ * currently held in the registers does in fact belong to 'current'
  */
 void fpsimd_preserve_current_state(void)
 {
        preempt_disable();
-       fpsimd_save_state(&current->thread.fpsimd_state);
+       if (!test_thread_flag(TIF_FOREIGN_FPSTATE))
+               fpsimd_save_state(&current->thread.fpsimd_state);
        preempt_enable();
 }
 
 /*
- * Load an updated userland FPSIMD state for 'current' from memory
+ * Load the userland FPSIMD state of 'current' from memory, but only if the
+ * FPSIMD state already held in the registers is /not/ the most recent FPSIMD
+ * state of 'current'
+ */
+void fpsimd_restore_current_state(void)
+{
+       preempt_disable();
+       if (test_and_clear_thread_flag(TIF_FOREIGN_FPSTATE)) {
+               struct fpsimd_state *st = &current->thread.fpsimd_state;
+
+               fpsimd_load_state(st);
+               this_cpu_write(fpsimd_last_state, st);
+               st->cpu = smp_processor_id();
+       }
+       preempt_enable();
+}
+
+/*
+ * Load an updated userland FPSIMD state for 'current' from memory and set the
+ * flag that indicates that the FPSIMD register contents are the most recent
+ * FPSIMD state of 'current'
  */
 void fpsimd_update_current_state(struct fpsimd_state *state)
 {
        preempt_disable();
        fpsimd_load_state(state);
+       if (test_and_clear_thread_flag(TIF_FOREIGN_FPSTATE)) {
+               struct fpsimd_state *st = &current->thread.fpsimd_state;
+
+               this_cpu_write(fpsimd_last_state, st);
+               st->cpu = smp_processor_id();
+       }
        preempt_enable();
 }
 
+/*
+ * Invalidate live CPU copies of task t's FPSIMD state
+ */
+void fpsimd_flush_task_state(struct task_struct *t)
+{
+       t->thread.fpsimd_state.cpu = NR_CPUS;
+}
+
 #ifdef CONFIG_KERNEL_MODE_NEON
 
 /*
@@ -118,16 +227,19 @@ void kernel_neon_begin(void)
        BUG_ON(in_interrupt());
        preempt_disable();
 
-       if (current->mm)
+       /*
+        * Save the userland FPSIMD state if we have one and if we haven't done
+        * so already. Clear fpsimd_last_state to indicate that there is no
+        * longer userland FPSIMD state in the registers.
+        */
+       if (current->mm && !test_and_set_thread_flag(TIF_FOREIGN_FPSTATE))
                fpsimd_save_state(&current->thread.fpsimd_state);
+       this_cpu_write(fpsimd_last_state, NULL);
 }
 EXPORT_SYMBOL(kernel_neon_begin);
 
 void kernel_neon_end(void)
 {
-       if (current->mm)
-               fpsimd_load_state(&current->thread.fpsimd_state);
-
        preempt_enable();
 }
 EXPORT_SYMBOL(kernel_neon_end);
@@ -140,12 +252,12 @@ static int fpsimd_cpu_pm_notifier(struct notifier_block *self,
 {
        switch (cmd) {
        case CPU_PM_ENTER:
-               if (current->mm)
+               if (current->mm && !test_thread_flag(TIF_FOREIGN_FPSTATE))
                        fpsimd_save_state(&current->thread.fpsimd_state);
                break;
        case CPU_PM_EXIT:
                if (current->mm)
-                       fpsimd_load_state(&current->thread.fpsimd_state);
+                       set_thread_flag(TIF_FOREIGN_FPSTATE);
                break;
        case CPU_PM_ENTER_FAILED:
        default:
index 6a8928bba03c9e8135c4481b497268f7018ca393..f8700eca24e781fe548786da280218be6403c331 100644 (file)
@@ -517,6 +517,7 @@ static int fpr_set(struct task_struct *target, const struct user_regset *regset,
                return ret;
 
        target->thread.fpsimd_state.user_fpsimd = newstate;
+       fpsimd_flush_task_state(target);
        return ret;
 }
 
@@ -764,6 +765,7 @@ static int compat_vfp_set(struct task_struct *target,
                uregs->fpcr = fpscr & VFP_FPSCR_CTRL_MASK;
        }
 
+       fpsimd_flush_task_state(target);
        return ret;
 }
 
index 06448a77ff5351a18ad98409311675144453462a..882f01774365671f5a8fbe096514db81805c9286 100644 (file)
@@ -413,4 +413,8 @@ asmlinkage void do_notify_resume(struct pt_regs *regs,
                clear_thread_flag(TIF_NOTIFY_RESUME);
                tracehook_notify_resume(regs);
        }
+
+       if (thread_flags & _TIF_FOREIGN_FPSTATE)
+               fpsimd_restore_current_state();
+
 }