arch: arm: cortex_m: do not re-enable LSPEN on FP context switch (ES0182 fix) - #11
Conversation
On Cortex-M4 with CONFIG_FPU_SHARING=y, Zephyr's PRE_KERNEL_1 hook
(in the OKSbot application) clears FPCCR.LSPEN at boot to force eager
FPU stacking, working around Cortex-M4 errata ES0182.
However z_arm_mpu_stack_guard_and_fpu_adjust() was unconditionally
re-enabling LSPEN (FPU->FPCCR |= FPU_FPCCR_LSPEN_Msk) on every
context switch to a K_FP_REGS thread, immediately undoing the
boot-time fix.
ES0182 root cause:
- FPCCR.LSPEN=1 (lazy stacking enabled)
- Thread executes any FPU instruction → LSPEN triggers deferred save
- A nested ISR fires before the lazy-stacking write completes
- Hardware saves s0-s15/fpscr to FPCAR, which points at the wrong
exception-frame offset (basic frame sized, not extended)
- Stacked r0/r1/r2/r3/r12/lr/pc/xpsr are overwritten with FP
register values
- Exception return loads a corrupted PC → cascading
INVSTATE / INVPC / MemManage faults → reboot loop
Observed on OKSbot (STM32F405 @ 168 MHz, jiri_board) where a 400 Hz
SPI ISR from the Jetson fired during a LOG_ERR call in the fault
handler. Crash signature: code-segment addresses appearing in
xpsr/fpscr exception-frame slots, PC pointing to garbage.
Fix: remove the FPU->FPCCR |= FPU_FPCCR_LSPEN_Msk line from the
K_FP_REGS branch of z_arm_mpu_stack_guard_and_fpu_adjust(). LSPEN
is kept at 0 (eager stacking) permanently. Eager stacking costs one
extra exception-entry cycle (~2.5 µs on Cortex-M4 at 168 MHz when
the FPU has been used) but is unconditionally safe and eliminates
the ES0182 race entirely.
The companion app-side fix (PRE_KERNEL_1 clearing LSPEN, plus a
PRE_KERNEL_2 hook tagging z_main_thread with K_FP_REGS) remains
necessary to keep LSPEN=0 from early boot through to the first
context switch.
Relates to Zephyr upstream issue zephyrproject-rtos#108793.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
|
Closing in favour of #10, which is a cleaner fix for the same root cause. Why #10 is better than this PR: This PR keeps PR #10 fixes the root cause at the right layer — |
Problem
On Cortex-M4 with
CONFIG_FPU_SHARING=y, the OKSbot application clearsFPCCR.LSPENat boot via aPRE_KERNEL_1hook to force eager FPU stacking, working around Cortex-M4 errata ES0182.However
z_arm_mpu_stack_guard_and_fpu_adjust()was re-enabling LSPEN (FPU->FPCCR |= FPU_FPCCR_LSPEN_Msk) on every context switch to aK_FP_REGSthread — immediately undoing the boot-time fix.ES0182 crash mechanism
FPCCR.LSPEN = 1(lazy stacking re-enabled by this function)s0–s15 + fpscrto the address inFPCAR, which was set for the basic (non-FPU) exception frame — 8 bytes too earlyr0/r1/r2/r3/r12/lr/**pc**/xpsrwith FPU register valuesINVSTATE/INVPC/MemManagefaults → reboot loopObserved crash signature
Seen on OKSbot B1087 (
STM32F405 @ 168 MHz,jiri_board, Zephyr v4.2.0). The crash was a deterministic reboot loop starting within the first 200 ms of boot. CAN harness removal confirmed the trigger was NOT CAN — the 400 Hz SPI ISR from the Jetson host is sufficient.Fix
Remove the single line
FPU->FPCCR |= FPU_FPCCR_LSPEN_Msk;from theK_FP_REGSbranch inz_arm_mpu_stack_guard_and_fpu_adjust().With this change, once the application disables LSPEN at boot, it stays disabled (eager stacking) through every subsequent context switch. Eager stacking costs one additional ISR-entry cycle (~2.5 µs on Cortex-M4 at 168 MHz when the FPU has been used) but is unconditionally safe and eliminates the ES0182 race window entirely.
The note in the existing comment about "activate lazy stacking" is removed because we are intentionally not doing that.
Companion fix (application side)
The application (
rr_oks_robot_base_controller / oksbot_base_ctrl_fw) must still provide twoSYS_INIThooks:Without the PRE_KERNEL_1 hook, LSPEN is still enabled during early boot (before the first K_FP_REGS context switch).
Testing
jiri_board(STM32F405RGTx, 168 MHz, 128 KB RAM,CONFIG_FPU=y,CONFIG_FPU_SHARING=y,CONFIG_HW_STACK_PROTECTION=y)References