Commit 005f78cd authored by Ard Biesheuvel's avatar Ard Biesheuvel

arm64: defer reloading a task's FPSIMD state to userland resume

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: default avatarArd Biesheuvel <ard.biesheuvel@linaro.org>
parent c51f9269
...@@ -37,6 +37,8 @@ struct fpsimd_state { ...@@ -37,6 +37,8 @@ struct fpsimd_state {
u32 fpcr; u32 fpcr;
}; };
}; };
/* the id of the last cpu to have restored this state */
unsigned int cpu;
}; };
#if defined(__KERNEL__) && defined(CONFIG_COMPAT) #if defined(__KERNEL__) && defined(CONFIG_COMPAT)
...@@ -59,8 +61,11 @@ extern void fpsimd_thread_switch(struct task_struct *next); ...@@ -59,8 +61,11 @@ extern void fpsimd_thread_switch(struct task_struct *next);
extern void fpsimd_flush_thread(void); extern void fpsimd_flush_thread(void);
extern void fpsimd_preserve_current_state(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_update_current_state(struct fpsimd_state *state);
extern void fpsimd_flush_task_state(struct task_struct *target);
#endif #endif
#endif #endif
...@@ -100,6 +100,7 @@ static inline struct thread_info *current_thread_info(void) ...@@ -100,6 +100,7 @@ static inline struct thread_info *current_thread_info(void)
#define TIF_SIGPENDING 0 #define TIF_SIGPENDING 0
#define TIF_NEED_RESCHED 1 #define TIF_NEED_RESCHED 1
#define TIF_NOTIFY_RESUME 2 /* callback before returning to user */ #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_SYSCALL_TRACE 8
#define TIF_POLLING_NRFLAG 16 #define TIF_POLLING_NRFLAG 16
#define TIF_MEMDIE 18 /* is terminating due to OOM killer */ #define TIF_MEMDIE 18 /* is terminating due to OOM killer */
...@@ -112,10 +113,11 @@ static inline struct thread_info *current_thread_info(void) ...@@ -112,10 +113,11 @@ static inline struct thread_info *current_thread_info(void)
#define _TIF_SIGPENDING (1 << TIF_SIGPENDING) #define _TIF_SIGPENDING (1 << TIF_SIGPENDING)
#define _TIF_NEED_RESCHED (1 << TIF_NEED_RESCHED) #define _TIF_NEED_RESCHED (1 << TIF_NEED_RESCHED)
#define _TIF_NOTIFY_RESUME (1 << TIF_NOTIFY_RESUME) #define _TIF_NOTIFY_RESUME (1 << TIF_NOTIFY_RESUME)
#define _TIF_FOREIGN_FPSTATE (1 << TIF_FOREIGN_FPSTATE)
#define _TIF_32BIT (1 << TIF_32BIT) #define _TIF_32BIT (1 << TIF_32BIT)
#define _TIF_WORK_MASK (_TIF_NEED_RESCHED | _TIF_SIGPENDING | \ #define _TIF_WORK_MASK (_TIF_NEED_RESCHED | _TIF_SIGPENDING | \
_TIF_NOTIFY_RESUME) _TIF_NOTIFY_RESUME | _TIF_FOREIGN_FPSTATE)
#endif /* __KERNEL__ */ #endif /* __KERNEL__ */
#endif /* __ASM_THREAD_INFO_H */ #endif /* __ASM_THREAD_INFO_H */
...@@ -576,7 +576,7 @@ fast_work_pending: ...@@ -576,7 +576,7 @@ fast_work_pending:
str x0, [sp, #S_X0] // returned x0 str x0, [sp, #S_X0] // returned x0
work_pending: work_pending:
tbnz x1, #TIF_NEED_RESCHED, work_resched 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] ldr x2, [sp, #S_PSTATE]
mov x0, sp // 'regs' mov x0, sp // 'regs'
tst x2, #PSR_MODE_MASK // user mode regs? tst x2, #PSR_MODE_MASK // user mode regs?
......
...@@ -34,6 +34,60 @@ ...@@ -34,6 +34,60 @@
#define FPEXC_IXF (1 << 4) #define FPEXC_IXF (1 << 4)
#define FPEXC_IDF (1 << 7) #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. * Trapped FP/ASIMD access.
*/ */
...@@ -72,41 +126,96 @@ void do_fpsimd_exc(unsigned int esr, struct pt_regs *regs) ...@@ -72,41 +126,96 @@ void do_fpsimd_exc(unsigned int esr, struct pt_regs *regs)
void fpsimd_thread_switch(struct task_struct *next) 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); 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) void fpsimd_flush_thread(void)
{ {
preempt_disable();
memset(&current->thread.fpsimd_state, 0, sizeof(struct fpsimd_state)); memset(&current->thread.fpsimd_state, 0, sizeof(struct fpsimd_state));
fpsimd_load_state(&current->thread.fpsimd_state); set_thread_flag(TIF_FOREIGN_FPSTATE);
preempt_enable();
} }
/* /*
* 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) void fpsimd_preserve_current_state(void)
{ {
preempt_disable(); preempt_disable();
if (!test_thread_flag(TIF_FOREIGN_FPSTATE))
fpsimd_save_state(&current->thread.fpsimd_state); fpsimd_save_state(&current->thread.fpsimd_state);
preempt_enable(); 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) void fpsimd_update_current_state(struct fpsimd_state *state)
{ {
preempt_disable(); preempt_disable();
fpsimd_load_state(state); 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(); 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 #ifdef CONFIG_KERNEL_MODE_NEON
/* /*
...@@ -118,16 +227,19 @@ void kernel_neon_begin(void) ...@@ -118,16 +227,19 @@ void kernel_neon_begin(void)
BUG_ON(in_interrupt()); BUG_ON(in_interrupt());
preempt_disable(); 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); fpsimd_save_state(&current->thread.fpsimd_state);
this_cpu_write(fpsimd_last_state, NULL);
} }
EXPORT_SYMBOL(kernel_neon_begin); EXPORT_SYMBOL(kernel_neon_begin);
void kernel_neon_end(void) void kernel_neon_end(void)
{ {
if (current->mm)
fpsimd_load_state(&current->thread.fpsimd_state);
preempt_enable(); preempt_enable();
} }
EXPORT_SYMBOL(kernel_neon_end); EXPORT_SYMBOL(kernel_neon_end);
...@@ -140,12 +252,12 @@ static int fpsimd_cpu_pm_notifier(struct notifier_block *self, ...@@ -140,12 +252,12 @@ static int fpsimd_cpu_pm_notifier(struct notifier_block *self,
{ {
switch (cmd) { switch (cmd) {
case CPU_PM_ENTER: case CPU_PM_ENTER:
if (current->mm) if (current->mm && !test_thread_flag(TIF_FOREIGN_FPSTATE))
fpsimd_save_state(&current->thread.fpsimd_state); fpsimd_save_state(&current->thread.fpsimd_state);
break; break;
case CPU_PM_EXIT: case CPU_PM_EXIT:
if (current->mm) if (current->mm)
fpsimd_load_state(&current->thread.fpsimd_state); set_thread_flag(TIF_FOREIGN_FPSTATE);
break; break;
case CPU_PM_ENTER_FAILED: case CPU_PM_ENTER_FAILED:
default: default:
......
...@@ -517,6 +517,7 @@ static int fpr_set(struct task_struct *target, const struct user_regset *regset, ...@@ -517,6 +517,7 @@ static int fpr_set(struct task_struct *target, const struct user_regset *regset,
return ret; return ret;
target->thread.fpsimd_state.user_fpsimd = newstate; target->thread.fpsimd_state.user_fpsimd = newstate;
fpsimd_flush_task_state(target);
return ret; return ret;
} }
...@@ -764,6 +765,7 @@ static int compat_vfp_set(struct task_struct *target, ...@@ -764,6 +765,7 @@ static int compat_vfp_set(struct task_struct *target,
uregs->fpcr = fpscr & VFP_FPSCR_CTRL_MASK; uregs->fpcr = fpscr & VFP_FPSCR_CTRL_MASK;
} }
fpsimd_flush_task_state(target);
return ret; return ret;
} }
......
...@@ -413,4 +413,8 @@ asmlinkage void do_notify_resume(struct pt_regs *regs, ...@@ -413,4 +413,8 @@ asmlinkage void do_notify_resume(struct pt_regs *regs,
clear_thread_flag(TIF_NOTIFY_RESUME); clear_thread_flag(TIF_NOTIFY_RESUME);
tracehook_notify_resume(regs); tracehook_notify_resume(regs);
} }
if (thread_flags & _TIF_FOREIGN_FPSTATE)
fpsimd_restore_current_state();
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment