diff mbox

[v2] arm64: fpsimd: improve stacking logic in non-interruptible context

Message ID 1481105648-19775-1-git-send-email-ard.biesheuvel@linaro.org
State Superseded
Headers show

Commit Message

Ard Biesheuvel Dec. 7, 2016, 10:14 a.m. UTC
Currently, we allow kernel mode NEON in softirq or hardirq context by
stacking and unstacking a slice of the NEON register file for each call
to kernel_neon_begin() and kernel_neon_end(), respectively.

Given that
a) a CPU typically spends most of its time in userland, during which time
   no kernel mode NEON in process context is in progress,
b) a CPU spends most of its time in the kernel doing other things than
   kernel mode NEON when it gets interrupted to perform kernel mode NEON
   in softirq context

the stacking and subsequent unstacking is only necessary if we are
interrupting a thread while it is performing kernel mode NEON in process
context, which means that in all other cases, we can simply preserve the
userland FPSIMD state once, and only restore it upon return to userland,
even if we are being invoked from softirq or hardirq context.

So instead of checking whether we are running in interrupt context, keep
track of the level of nested kernel mode NEON calls in progress, and only
perform the eager stack/unstack if the level exceeds 1.

Signed-off-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>

---
v2:
- BUG() on unexpected values of the nesting level
- relax the BUG() on num_regs>32 to a WARN, given that nothing actually
  breaks in that case

 arch/arm64/kernel/fpsimd.c | 48 ++++++++++++++------
 1 file changed, 34 insertions(+), 14 deletions(-)

-- 
2.7.4


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

Comments

Catalin Marinas Dec. 8, 2016, 3:50 p.m. UTC | #1
Hi Ard,

On Wed, Dec 07, 2016 at 10:14:08AM +0000, Ard Biesheuvel wrote:
>  void kernel_neon_begin_partial(u32 num_regs)

>  {

> -	if (in_interrupt()) {

> -		struct fpsimd_partial_state *s = this_cpu_ptr(

> -			in_irq() ? &hardirq_fpsimdstate : &softirq_fpsimdstate);

> +	struct fpsimd_partial_state *s;

> +	int level;

> +

> +	preempt_disable();

> +

> +	level = this_cpu_read(kernel_neon_nesting_level);

> +	BUG_ON(level > 2);

> +

> +	if (level > 0) {

> +		s = this_cpu_ptr(nested_fpsimdstate);

>  

> -		BUG_ON(num_regs > 32);

> -		fpsimd_save_partial_state(s, roundup(num_regs, 2));

> +		WARN_ON_ONCE(num_regs > 32);

> +		num_regs = min(roundup(num_regs, 2), 32U);

> +

> +		fpsimd_save_partial_state(&s[level - 1], num_regs);

>  	} else {

>  		/*

>  		 * Save the userland FPSIMD state if we have one and if we

> @@ -241,24 +256,29 @@ void kernel_neon_begin_partial(u32 num_regs)

>  		 * that there is no longer userland FPSIMD state in the

>  		 * registers.

>  		 */

> -		preempt_disable();

>  		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);

>  	}

> +	this_cpu_write(kernel_neon_nesting_level, level + 1);

>  }


I'm slightly confused with the potential race with an interrupt here.
Let's say the above is running in the process context, sets the
TIF_FOREIGN_FPSTATE but is interrupted before fpsimd_save_state(). The
interrupt handler calling kernel_neon_begin_partial() is seeing level 0
and TIF_FOREIGN_FPSTATE and decides that it is safe to corrupt the Neon
state without any further saving.

I think the kernel_neon_nesting_level should be incremented early on in
this function.

-- 
Catalin

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
Ard Biesheuvel Dec. 8, 2016, 3:53 p.m. UTC | #2
On 8 December 2016 at 15:50, Catalin Marinas <catalin.marinas@arm.com> wrote:
> Hi Ard,

>

> On Wed, Dec 07, 2016 at 10:14:08AM +0000, Ard Biesheuvel wrote:

>>  void kernel_neon_begin_partial(u32 num_regs)

>>  {

>> -     if (in_interrupt()) {

>> -             struct fpsimd_partial_state *s = this_cpu_ptr(

>> -                     in_irq() ? &hardirq_fpsimdstate : &softirq_fpsimdstate);

>> +     struct fpsimd_partial_state *s;

>> +     int level;

>> +

>> +     preempt_disable();

>> +

>> +     level = this_cpu_read(kernel_neon_nesting_level);

>> +     BUG_ON(level > 2);

>> +

>> +     if (level > 0) {

>> +             s = this_cpu_ptr(nested_fpsimdstate);

>>

>> -             BUG_ON(num_regs > 32);

>> -             fpsimd_save_partial_state(s, roundup(num_regs, 2));

>> +             WARN_ON_ONCE(num_regs > 32);

>> +             num_regs = min(roundup(num_regs, 2), 32U);

>> +

>> +             fpsimd_save_partial_state(&s[level - 1], num_regs);

>>       } else {

>>               /*

>>                * Save the userland FPSIMD state if we have one and if we

>> @@ -241,24 +256,29 @@ void kernel_neon_begin_partial(u32 num_regs)

>>                * that there is no longer userland FPSIMD state in the

>>                * registers.

>>                */

>> -             preempt_disable();

>>               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);

>>       }

>> +     this_cpu_write(kernel_neon_nesting_level, level + 1);

>>  }

>

> I'm slightly confused with the potential race with an interrupt here.

> Let's say the above is running in the process context, sets the

> TIF_FOREIGN_FPSTATE but is interrupted before fpsimd_save_state(). The

> interrupt handler calling kernel_neon_begin_partial() is seeing level 0

> and TIF_FOREIGN_FPSTATE and decides that it is safe to corrupt the Neon

> state without any further saving.

>

> I think the kernel_neon_nesting_level should be incremented early on in

> this function.

>


Good point, I hadn't considered that.

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
diff mbox

Patch

diff --git a/arch/arm64/kernel/fpsimd.c b/arch/arm64/kernel/fpsimd.c
index 394c61db5566..ae2c1bf569db 100644
--- a/arch/arm64/kernel/fpsimd.c
+++ b/arch/arm64/kernel/fpsimd.c
@@ -220,20 +220,35 @@  void fpsimd_flush_task_state(struct task_struct *t)
 
 #ifdef CONFIG_KERNEL_MODE_NEON
 
-static DEFINE_PER_CPU(struct fpsimd_partial_state, hardirq_fpsimdstate);
-static DEFINE_PER_CPU(struct fpsimd_partial_state, softirq_fpsimdstate);
+/*
+ * Although unlikely, it is possible for three kernel mode NEON contexts to
+ * be live at the same time: process context, softirq context and hardirq
+ * context. So while the userland context is stashed in the thread's fpsimd
+ * state structure, we need two additional levels of storage.
+ */
+static DEFINE_PER_CPU(struct fpsimd_partial_state, nested_fpsimdstate[2]);
+static DEFINE_PER_CPU(int, kernel_neon_nesting_level);
 
 /*
  * Kernel-side NEON support functions
  */
 void kernel_neon_begin_partial(u32 num_regs)
 {
-	if (in_interrupt()) {
-		struct fpsimd_partial_state *s = this_cpu_ptr(
-			in_irq() ? &hardirq_fpsimdstate : &softirq_fpsimdstate);
+	struct fpsimd_partial_state *s;
+	int level;
+
+	preempt_disable();
+
+	level = this_cpu_read(kernel_neon_nesting_level);
+	BUG_ON(level > 2);
+
+	if (level > 0) {
+		s = this_cpu_ptr(nested_fpsimdstate);
 
-		BUG_ON(num_regs > 32);
-		fpsimd_save_partial_state(s, roundup(num_regs, 2));
+		WARN_ON_ONCE(num_regs > 32);
+		num_regs = min(roundup(num_regs, 2), 32U);
+
+		fpsimd_save_partial_state(&s[level - 1], num_regs);
 	} else {
 		/*
 		 * Save the userland FPSIMD state if we have one and if we
@@ -241,24 +256,29 @@  void kernel_neon_begin_partial(u32 num_regs)
 		 * that there is no longer userland FPSIMD state in the
 		 * registers.
 		 */
-		preempt_disable();
 		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);
 	}
+	this_cpu_write(kernel_neon_nesting_level, level + 1);
 }
 EXPORT_SYMBOL(kernel_neon_begin_partial);
 
 void kernel_neon_end(void)
 {
-	if (in_interrupt()) {
-		struct fpsimd_partial_state *s = this_cpu_ptr(
-			in_irq() ? &hardirq_fpsimdstate : &softirq_fpsimdstate);
-		fpsimd_load_partial_state(s);
-	} else {
-		preempt_enable();
+	struct fpsimd_partial_state *s;
+	int level;
+
+	level = this_cpu_read(kernel_neon_nesting_level) - 1;
+	BUG_ON(level < 0);
+
+	if (level > 0) {
+		s = this_cpu_ptr(nested_fpsimdstate);
+		fpsimd_load_partial_state(&s[level - 1]);
 	}
+	this_cpu_write(kernel_neon_nesting_level, level);
+	preempt_enable();
 }
 EXPORT_SYMBOL(kernel_neon_end);