This series collects the various SME related fixes that were previously posted separately. These should address all the issues I am aware of so a patch which reenables the SME configuration option is also included.
Signed-off-by: Mark Brown broonie@kernel.org --- Changes in v2: - Pull simplification of the signal restore code after the SME reenablement, it's not a fix but there's some code overlap. - Comment updates. - Link to v1: https://lore.kernel.org/r/20241203-arm64-sme-reenable-v1-0-d853479d1b77@kern...
--- Mark Brown (6): arm64/sme: Flush foreign register state in do_sme_acc() arm64/fp: Don't corrupt FPMR when streaming mode changes arm64/ptrace: Zero FPMR on streaming mode entry/exit arm64/signal: Avoid corruption of SME state when entering signal handler arm64/sme: Reenable SME arm64/signal: Consistently invalidate the in register FP state in restore
arch/arm64/Kconfig | 1 - arch/arm64/include/asm/fpsimd.h | 1 + arch/arm64/kernel/fpsimd.c | 57 ++++++++++++++++++++++---- arch/arm64/kernel/ptrace.c | 12 +++++- arch/arm64/kernel/signal.c | 89 +++++++++++------------------------------ 5 files changed, 84 insertions(+), 76 deletions(-) --- base-commit: 40384c840ea1944d7c5a392e8975ed088ecf0b37 change-id: 20241202-arm64-sme-reenable-98e64c161a8e
Best regards,
When do_sme_acc() runs with foreign FP state it does not do any updates of the task structure, relying on the next return to userspace to reload the register state appropriately, but leaves the task's last loaded CPU untouched. This means that if the task returns to userspace on the last CPU it ran on then the checks in fpsimd_bind_task_to_cpu() will incorrectly determine that the register state on the CPU is current and suppress reload of the floating point register state before returning to userspace. This will result in spurious warnings due to SME access traps occuring for the task after TIF_SME is set.
Call fpsimd_flush_task_state() to invalidate the last loaded CPU recorded in the task, forcing detection of the task as foreign.
Fixes: 8bd7f91c03d8 ("arm64/sme: Implement traps and syscall handling for SME") Reported-by: Mark Rutlamd mark.rutland@arm.com Signed-off-by: Mark Brown broonie@kernel.org Cc: stable@vger.kernel.org --- arch/arm64/kernel/fpsimd.c | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/arch/arm64/kernel/fpsimd.c b/arch/arm64/kernel/fpsimd.c index 8c4c1a2186cc510a7826d15ec36225857c07ed71..eca0b6a2fc6fa25d8c850a5b9e109b4d58809f54 100644 --- a/arch/arm64/kernel/fpsimd.c +++ b/arch/arm64/kernel/fpsimd.c @@ -1460,6 +1460,8 @@ void do_sme_acc(unsigned long esr, struct pt_regs *regs) sme_set_vq(vq_minus_one);
fpsimd_bind_task_to_cpu(); + } else { + fpsimd_flush_task_state(current); }
put_cpu_fpsimd_context();
When FPMR and SME are both present then entering and exiting streaming mode clears FPMR in the same manner as it clears the V/Z and P registers. Since entering and exiting streaming mode via ptrace is expected to have the same effect as doing so via SMSTART/SMSTOP it should clear FPMR too but this was missed when FPMR support was added. Add the required reset of FPMR.
Since changing the vector length resets SVCR a SME vector length change implemented via a write to ZA can trigger an exit of streaming mode and we need to check when writing to ZA as well.
Fixes: 4035c22ef7d4 ("arm64/ptrace: Expose FPMR via ptrace") Signed-off-by: Mark Brown broonie@kernel.org Cc: stable@vger.kernel.org --- arch/arm64/kernel/ptrace.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/kernel/ptrace.c b/arch/arm64/kernel/ptrace.c index e4437f62a2cda93734052c44b48886db83d75b3e..43a9397d5903ff87b608befdcaed3f9a7e48f976 100644 --- a/arch/arm64/kernel/ptrace.c +++ b/arch/arm64/kernel/ptrace.c @@ -877,6 +877,7 @@ static int sve_set_common(struct task_struct *target, const void *kbuf, const void __user *ubuf, enum vec_type type) { + u64 old_svcr = target->thread.svcr; int ret; struct user_sve_header header; unsigned int vq; @@ -908,8 +909,6 @@ static int sve_set_common(struct task_struct *target,
/* Enter/exit streaming mode */ if (system_supports_sme()) { - u64 old_svcr = target->thread.svcr; - switch (type) { case ARM64_VEC_SVE: target->thread.svcr &= ~SVCR_SM_MASK; @@ -1008,6 +1007,10 @@ static int sve_set_common(struct task_struct *target, start, end);
out: + /* If we entered or exited streaming mode then reset FPMR */ + if ((target->thread.svcr & SVCR_SM) != (old_svcr & SVCR_SM)) + target->thread.uw.fpmr = 0; + fpsimd_flush_task_state(target); return ret; } @@ -1104,6 +1107,7 @@ static int za_set(struct task_struct *target, unsigned int pos, unsigned int count, const void *kbuf, const void __user *ubuf) { + u64 old_svcr = target->thread.svcr; int ret; struct user_za_header header; unsigned int vq; @@ -1184,6 +1188,10 @@ static int za_set(struct task_struct *target, target->thread.svcr |= SVCR_ZA_MASK;
out: + /* If we entered or exited streaming mode then reset FPMR */ + if ((target->thread.svcr & SVCR_SM) != (old_svcr & SVCR_SM)) + target->thread.uw.fpmr = 0; + fpsimd_flush_task_state(target); return ret; }
We intend that signal handlers are entered with PSTATE.{SM,ZA}={0,0}. The logic for this in setup_return() manipulates the saved state and live CPU state in an unsafe manner, and consequently, when a task enters a signal handler:
* The task entering the signal handler might not have its PSTATE.{SM,ZA} bits cleared, and other register state that is affected by changes to PSTATE.{SM,ZA} might not be zeroed as expected.
* An unrelated task might have its PSTATE.{SM,ZA} bits cleared unexpectedly, potentially zeroing other register state that is affected by changes to PSTATE.{SM,ZA}.
Tasks which do not set PSTATE.{SM,ZA} (i.e. those only using plain FPSIMD or non-streaming SVE) are not affected, as there is no resulting change to PSTATE.{SM,ZA}.
Consider for example two tasks on one CPU:
A: Begins signal entry in kernel mode, is preempted prior to SMSTOP. B: Using SM and/or ZA in userspace with register state current on the CPU, is preempted. A: Scheduled in, no register state changes made as in kernel mode. A: Executes SMSTOP, modifying live register state. A: Scheduled out. B: Scheduled in, fpsimd_thread_switch() sees the register state on the CPU is tracked as being that for task B so the state is not reloaded prior to returning to userspace.
Task B is now running with SM and ZA incorrectly cleared.
Fix this by:
* Checking TIF_FOREIGN_FPSTATE, and only updating the saved or live state as appropriate.
* Using {get,put}_cpu_fpsimd_context() to ensure mutual exclusion against other code which manipulates this state. To allow their use, the logic is moved into a new fpsimd_enter_sighandler() helper in fpsimd.c.
This race has been observed intermittently with fp-stress, especially with preempt disabled, commonly but not exclusively reporting "Bad SVCR: 0".
While we're at it also fix a discrepancy between in register and in memory entries. When operating on the register state we issue a SMSTOP, exiting streaming mode if we were in it. This clears the V/Z and P register and FPMR, and resets FPSR to 0x800009f but does not change ZA, ZT or FPCR. The in memory version clears all the user FPSIMD state including FPCR and FPSR but does not clear FPMR. Update the code to implement the changes the hardware implements.
Fixes: 40a8e87bb3285 ("arm64/sme: Disable ZA and streaming mode when handling signals") Signed-off-by: Mark Brown broonie@kernel.org Cc: stable@vger.kernel.org --- arch/arm64/include/asm/fpsimd.h | 1 + arch/arm64/kernel/fpsimd.c | 40 ++++++++++++++++++++++++++++++++++++++++ arch/arm64/kernel/signal.c | 19 +------------------ 3 files changed, 42 insertions(+), 18 deletions(-)
diff --git a/arch/arm64/include/asm/fpsimd.h b/arch/arm64/include/asm/fpsimd.h index f2a84efc361858d4deda99faf1967cc7cac386c1..09af7cfd9f6c2cec26332caa4c254976e117b1bf 100644 --- a/arch/arm64/include/asm/fpsimd.h +++ b/arch/arm64/include/asm/fpsimd.h @@ -76,6 +76,7 @@ extern void fpsimd_load_state(struct user_fpsimd_state *state); extern void fpsimd_thread_switch(struct task_struct *next); extern void fpsimd_flush_thread(void);
+extern void fpsimd_enter_sighandler(void); extern void fpsimd_signal_preserve_current_state(void); extern void fpsimd_preserve_current_state(void); extern void fpsimd_restore_current_state(void); diff --git a/arch/arm64/kernel/fpsimd.c b/arch/arm64/kernel/fpsimd.c index a3bb17c88942eba031d26e9f75ad46f37b6dc621..2b045d4d8f71ace5bf01a596dda279285a0998a5 100644 --- a/arch/arm64/kernel/fpsimd.c +++ b/arch/arm64/kernel/fpsimd.c @@ -1696,6 +1696,46 @@ void fpsimd_signal_preserve_current_state(void) sve_to_fpsimd(current); }
+/* + * Called by the signal handling code when preparing current to enter + * a signal handler. Currently this only needs to take care of exiting + * streaming mode and clearing ZA on SME systems. + */ +void fpsimd_enter_sighandler(void) +{ + if (!system_supports_sme()) + return; + + get_cpu_fpsimd_context(); + + if (test_thread_flag(TIF_FOREIGN_FPSTATE)) { + /* + * Exiting streaming mode zeros the V/Z and P + * registers and FPMR. Zero FPMR and the V registers, + * marking the state as FPSIMD only to force a clear + * of the remaining bits during reload if needed. + */ + if (current->thread.svcr & SVCR_SM_MASK) { + memset(¤t->thread.uw.fpsimd_state.vregs, 0, + sizeof(current->thread.uw.fpsimd_state.vregs)); + current->thread.uw.fpsimd_state.fpsr = 0x800009f; + current->thread.uw.fpmr = 0; + current->thread.fp_type = FP_STATE_FPSIMD; + } + + current->thread.svcr &= ~(SVCR_ZA_MASK | + SVCR_SM_MASK); + + /* Ensure any copies on other CPUs aren't reused */ + fpsimd_flush_task_state(current); + } else { + /* The register state is current, just update it. */ + sme_smstop(); + } + + put_cpu_fpsimd_context(); +} + /* * Called by KVM when entering the guest. */ diff --git a/arch/arm64/kernel/signal.c b/arch/arm64/kernel/signal.c index 14ac6fdb872b9672e4b16a097f1b577aae8dec50..79c9c5cd0802149b3cde20b398617437d79181f2 100644 --- a/arch/arm64/kernel/signal.c +++ b/arch/arm64/kernel/signal.c @@ -1487,24 +1487,7 @@ static int setup_return(struct pt_regs *regs, struct ksignal *ksig, /* TCO (Tag Check Override) always cleared for signal handlers */ regs->pstate &= ~PSR_TCO_BIT;
- /* Signal handlers are invoked with ZA and streaming mode disabled */ - if (system_supports_sme()) { - /* - * If we were in streaming mode the saved register - * state was SVE but we will exit SM and use the - * FPSIMD register state - flush the saved FPSIMD - * register state in case it gets loaded. - */ - if (current->thread.svcr & SVCR_SM_MASK) { - memset(¤t->thread.uw.fpsimd_state, 0, - sizeof(current->thread.uw.fpsimd_state)); - current->thread.fp_type = FP_STATE_FPSIMD; - } - - current->thread.svcr &= ~(SVCR_ZA_MASK | - SVCR_SM_MASK); - sme_smstop(); - } + fpsimd_enter_sighandler();
if (ksig->ka.sa.sa_flags & SA_RESTORER) sigtramp = ksig->ka.sa.sa_restorer;
On Wed, Dec 04, 2024 at 03:20:48PM +0000, Mark Brown wrote:
This series collects the various SME related fixes that were previously posted separately. These should address all the issues I am aware of so a patch which reenables the SME configuration option is also included.
Signed-off-by: Mark Brown broonie@kernel.org
Changes in v2:
- Pull simplification of the signal restore code after the SME reenablement, it's not a fix but there's some code overlap.
- Comment updates.
- Link to v1: https://lore.kernel.org/r/20241203-arm64-sme-reenable-v1-0-d853479d1b77@kern...
Mark (R), are you happy with this? I know you were digging into some other issues in this area but I'm not sure whether they invalidate the fixes here or not.
Cheers,
Will
linux-stable-mirror@lists.linaro.org