fix(bootloader): select the MSP before jumping to the application

NuttX 12.12.0 added arm_initialize_stack(), which moves thread mode onto
the process stack pointer whenever CONFIG_ARCH_INTERRUPTSTACK > 7. Every
bootloader config qualifies, so the bootloader now reaches arch_do_jump()
with CONTROL.SPSEL set, where previously it ran on the main stack.

arch_do_jump() writes the MSP and branches, but never selects it. The
application therefore starts on the bootloader's PSP while its own startup
code initialises a stack pointer the CPU is not using. Boards booted far
enough to reach NSH and start MAVLink, then hard faulted once the stale
bootloader stack was overwritten, surfacing as an assertion in Idle_Task
with no useful backtrace.

Clear CONTROL.SPSEL so control passes on the stack the application expects.
The i.MX RT bootloader performs the same jump and its configs also exceed
the interrupt stack threshold, so fix both.

Verified on CubePilot CubeOrange and Pixhawk 6C; the prebuilt bootloaders
for both are regenerated, as the shipped binaries have the defect.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Julian Oes <julian@oes.ch>
This commit is contained in:
Julian Oes
2026-09-02 20:08:30 +12:00
parent 89247b6e5f
commit eac8a3634a
4 changed files with 28 additions and 6 deletions
Binary file not shown.
@@ -684,10 +684,21 @@ arch_do_jump(const uint32_t *app_base)
uint32_t stacktop = app_base[APP_VECTOR_OFFSET_WORDS];
uint32_t entrypoint = app_base[APP_VECTOR_OFFSET_WORDS + 1];
uint32_t scratch;
/* NuttX 12.12 runs thread mode on the PSP (arm_initialize_stack(), enabled
* whenever CONFIG_ARCH_INTERRUPTSTACK > 7). The application expects to start
* on the MSP, so select it again before handing over -- otherwise the app
* keeps running on the bootloader's stack and faults once it is clobbered.
*/
asm volatile(
"msr msp, %0 \n"
"bx %1 \n"
: : "r"(stacktop), "r"(entrypoint) :);
"mrs %0, control \n"
"bic %0, %0, #2 \n"
"msr control, %0 \n"
"isb sy \n"
"msr msp, %1 \n"
"bx %2 \n"
: "=&r"(scratch) : "r"(stacktop), "r"(entrypoint) :);
// just to keep noreturn happy
for (;;) ;
@@ -636,10 +636,21 @@ arch_do_jump(const uint32_t *app_base)
uint32_t stacktop = app_base[0];
uint32_t entrypoint = app_base[1];
uint32_t scratch;
/* NuttX 12.12 runs thread mode on the PSP (arm_initialize_stack(), enabled
* whenever CONFIG_ARCH_INTERRUPTSTACK > 7). The application expects to start
* on the MSP, so select it again before handing over -- otherwise the app
* keeps running on the bootloader's stack and faults once it is clobbered.
*/
asm volatile(
"msr msp, %0 \n"
"bx %1 \n"
: : "r"(stacktop), "r"(entrypoint) :);
"mrs %0, control \n"
"bic %0, %0, #2 \n"
"msr control, %0 \n"
"isb sy \n"
"msr msp, %1 \n"
"bx %2 \n"
: "=&r"(scratch) : "r"(stacktop), "r"(entrypoint) :);
// just to keep noreturn happy
for (;;) ;