Skip to content

Add wolfBoot port for STM32N6 (NUCLEO-N657X0-Q)#720

Merged
danielinux merged 1 commit into
masterfrom
stm32n6-port-wolfboot
May 28, 2026
Merged

Add wolfBoot port for STM32N6 (NUCLEO-N657X0-Q)#720
danielinux merged 1 commit into
masterfrom
stm32n6-port-wolfboot

Conversation

@aidangarske
Copy link
Copy Markdown
Member

@aidangarske aidangarske commented Mar 11, 2026

Summary

  • Cortex-M55 support added to arch.mk (inherits Cortex-M33 settings, adds -mcpu=cortex-m55).
  • HAL in hal/stm32n6.{c,h,ld}: clock tree to 600 MHz (HSI -> PLL1 -> IC1/IC2/IC6/IC11), OCTOSPI/XSPI2 in single-SPI mmap mode with NOR FAST_READ_4B, I/D-cache control, SAU (TZ and non-TZ), USART1 for wolfBoot_printf, and the hal_flash_* / ext_flash_* API on top of an octospi_cmd page-program/sector-erase helper.
  • wolfBoot reset path (src/boot_arm.c): naked isr_reset for STM32N6 that sets SCB->VTOR = WOLFBOOT_ORIGIN, reloads MSP from word 0 of the FSBL vector table, then tail-calls into isr_reset_c. Also writes MSPLIM_S to _end_bss after BSS init (raw T2 MSR encoding because -mcmse is not in the build) so a future stack overflow trips a clean UsageFault.STKOF.
  • Linker scripts: hal/stm32n6.ld (wolfBoot in AXISRAM2 at WOLFBOOT_ORIGIN=0x34180400, 256 KB region) and test-app/ARM-stm32n6.ld (app XIP at 0x70020400).
  • Test app (test-app/app_stm32n6.c): bare-metal LED blinker that reads partition state, calls wolfBoot_success(), blinks blue (v1) or red (v>=2).
  • Flash script (tools/scripts/stm32n6_flash.sh): wraps STM32_SigningTool_CLI and OpenOCD for one-shot programming. Supports --skip-build, --app-only, --test-update (signs a v2 with IMAGE_HEADER_SIZE matching the wolfBoot build), --halt.
  • OpenOCD config (config/openocd/openocd_stm32n6.cfg): brings up XSPI2 single-SPI mmap on reset-init so flash programming via stmqspi works without the wolfBoot HAL.
  • Documentation (docs/Targets.md): full STM32N6 section with memory layout, OTP fuse requirements, jumper table, build/flash/debug instructions, TrustZone notes.
  • CI (.github/workflows/test-configs.yml): adds stm32n6.config to the build matrix.

Non-obvious gotchas captured in the port

  1. Boot ROM hands off with CPU on PLL1. clock_config() parks CPUSW/SYSSW on HSI before clearing PLL1ON. Disabling PLL1 while the CPU runs on it stops the CPU clock. OpenOCD-loaded boot masks this because reset_halt leaves the CPU on reset-state HSI; cold boot was the only path that hit it.
  2. STM32_SigningTool_CLI -ep <addr> writes the address verbatim with no Thumb bit. The flash script never passes -ep/-la; the tool reads the entry point with the Thumb bit set from the binary's vector table at offset 0x04. Wrong settings result in a silent hardfault on the first instruction.
  3. XSPIM_CR swaps XSPI1/XSPI2 port routing. Boot ROM leaves XSPIM_CR.MODE=1, sending XSPI2 traffic to Port 1 (nothing wired there). octospi_init() disables XSPI1, RCC-resets XSPI2, then writes XSPIM_CR=0 so XSPI2 reaches Port 2 (where MX25UM51245G is wired).
  4. MX25UM51245G stays in single-SPI mode after Boot ROM hand-off. The earlier theory that the Boot ROM left the chip in OPI/DTR was wrong; single-SPI FAST_READ_4B works directly.
  5. XIP mmap needs D-cache. Single-word XIP reads or non-cacheable mapping stall OCTOSPI with BUSY=1 and full FIFO. octospi_init() ends with one warmup XIP read of WOLFBOOT_PARTITION_BOOT_ADDRESS; without it the first XIP access from wolfBoot_start stalls (looks like a prefetch/AXI pipeline cold-start quirk).
  6. Boot ROM does NOT load MSP from the FSBL vector table. It just BXes the entry point with MSP still pointing at the Boot ROM's tiny scratch stack (~0x341037f4). Naked isr_reset in src/boot_arm.c sets VTOR and reloads MSP before any C prologue. Without this, wolfBoot's signature-verify path overflows the scratch stack, corrupts a saved LR into a FNC_RETURN signature (0xFEFFFFFx), the next BX LR does an S->NS state transition with garbage PC, the resulting MemManage fault is also stack-busted, double-fault -> LOCKUP.
  7. octospi_cmd data pump uses byte-wide DR access driven by FLEVEL. The DR32+FTHRES polling path stalls on transfers larger than the FIFO depth (32 bytes). FLEVEL is correct for any transfer size and threshold.
  8. ext_flash_write / ext_flash_erase must invalidate the D-cache for the XIP alias. Without it, reads of just-modified regions return stale data. The A/B swap and signature re-verify after swap depend on this.
  9. Sign tool reads IMAGE_HEADER_SIZE from env. The flash script's --test-update invocation now exports it from .config so v2 has the same 1024-byte manifest header as v1 (default is 256, which causes hash verification to fail at update time).

Validation

  • Cold boot from QSPI NOR, no pending update: wolfBoot Init -> Verifying signature...done -> app banner, blue/red LED.
  • Cold boot from QSPI NOR with pending v2 update: 3-way A/B swap completes, v2 boots, TESTING -> SUCCESS.
  • Subsequent cold boots run v2 with state SUCCESS.
  • Non-TZ build (config/examples/stm32n6.config): builds and runs end-to-end.
  • TZ build (config/examples/stm32n6-tz.config): config in place but the test-app linker script needs a separate fix; pre-existing issue, not part of this work.

@aidangarske aidangarske self-assigned this Mar 11, 2026
Copilot AI review requested due to automatic review settings March 11, 2026 01:17

This comment was marked as resolved.

@aidangarske aidangarske force-pushed the stm32n6-port-wolfboot branch from fedaf00 to 1416f2f Compare March 11, 2026 18:37
@aidangarske aidangarske requested a review from Copilot March 11, 2026 18:40

This comment was marked as resolved.

@aidangarske aidangarske marked this pull request as ready for review March 11, 2026 18:58
Copy link
Copy Markdown
Member

@dgarske dgarske left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! I haven't tested on hardware yet, but have looked over each line.

Comment thread hal/stm32n6.h Outdated
Comment thread hal/stm32n6.h Outdated
Comment thread hal/stm32n6.h Outdated
Comment thread hal/stm32n6.h Outdated
Comment thread arch.mk
Comment thread Makefile
@dgarske dgarske assigned dgarske and wolfSSL-Bot and unassigned aidangarske Mar 16, 2026
@dgarske dgarske self-requested a review March 16, 2026 23:31
Comment thread docs/release-stm32n6.md Outdated
dgarske
dgarske previously approved these changes Mar 18, 2026
@dgarske dgarske assigned danielinux and unassigned dgarske Mar 18, 2026
Copilot AI review requested due to automatic review settings March 18, 2026 19:54
@dgarske dgarske force-pushed the stm32n6-port-wolfboot branch from 4100805 to cc789ae Compare March 18, 2026 19:54
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@dgarske dgarske assigned dgarske and unassigned danielinux Mar 18, 2026
@dgarske dgarske force-pushed the stm32n6-port-wolfboot branch from cc789ae to 896c2a7 Compare March 18, 2026 20:14
Copilot AI review requested due to automatic review settings March 18, 2026 23:58

This comment was marked as off-topic.

@dgarske dgarske added the Later It won't be fixed in the upcoming release label Apr 15, 2026
@dgarske dgarske assigned aidangarske and unassigned aidangarske May 4, 2026
@dgarske
Copy link
Copy Markdown
Member

dgarske commented May 4, 2026

@aidangarske how's this going? If you won't have time in the next week let me know and I can finish it up.

@dgarske dgarske self-assigned this May 4, 2026
@danielinux danielinux removed the Later It won't be fixed in the upcoming release label May 7, 2026
@dgarske dgarske force-pushed the stm32n6-port-wolfboot branch from 6585a0b to aa524f9 Compare May 15, 2026 20:18
@dgarske dgarske requested a review from Copilot May 15, 2026 20:27
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 9 comments.

Comments suppressed due to low confidence (1)

src/boot_arm.c:519

  • do_boot() disables the CMSE/TZEN non-secure handoff path when CORTEX_M55 is defined (&& !defined(CORTEX_M55)). That means for stm32n6-tz.config wolfBoot will not set VTOR_NS / MSP_NS and will jump to app_entry in the current security state instead of doing a blxns. This looks inconsistent with the STM32N6 TrustZone model (secure bootloader, non-secure app) and likely breaks the TZ configuration at runtime. The CMSE/TZEN branch should handle Cortex-M55 as well (or the docs/config should be adjusted to reflect that non-secure apps aren’t supported).
#if defined(__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3U) && \
    defined(TZEN) && !defined(CORTEX_M55)
#include "hal.h"
#define VTOR (*(volatile uint32_t *)(0xE002ED08)) /* Non-secure VTOR */
#else
#define VTOR (*(volatile uint32_t *)(0xE000ED08))
#endif


static void  *app_entry;
static uint32_t app_end_stack;


void RAMFUNCTION do_boot(const uint32_t *app_offset)
{
#if defined(CORTEX_R5)
    (void)app_entry;
    (void)app_end_stack;
  /* limitations with TI arm compiler requires assembly */
    asm volatile("do_boot_r5:\n"
                 "  mov     pc, r0\n");

#elif defined(CORTEX_M33) || defined(CORTEX_M55) /* Armv8 boot procedure */

    /* Get stack pointer, entry point */
    app_end_stack = (*((uint32_t *)(app_offset)));
    app_entry = (void *)(*((uint32_t *)(app_offset + 1)));
    /* Disable interrupts */
    asm volatile("cpsid i");

    /* Update IV */
    VTOR = ((uint32_t)app_offset);
    asm volatile("msr msplim, %0" ::"r"(0));
#   if defined (__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3U) && \
       defined(TZEN) && !defined(CORTEX_M55)
    asm volatile("msr msp_ns, %0" ::"r"(app_end_stack));
    /* Jump to non secure app_entry */
    asm volatile("mov r7, %0" ::"r"(app_entry));
    asm volatile("bic.w   r7, r7, #1");
    /* Re-enable interrupts to allow non-secure OS handlers */
    asm volatile("cpsie i");
    asm volatile("blxns   r7" );
#   else
    asm volatile("msr msp, %0" ::"r"(app_end_stack));
    asm volatile("mov pc, %0":: "r"(app_entry));
#   endif

Comment thread tools/scripts/stm32n6_flash.sh
Comment thread tools/scripts/stm32n6_flash.sh Outdated
Comment thread arch.mk
Comment thread arch.mk
Comment thread src/boot_arm.c Outdated
Comment thread config/openocd/openocd_stm32n6.cfg
Comment thread docs/Targets.md
Comment thread docs/Targets.md
Comment thread hal/stm32n6.h
@dgarske dgarske force-pushed the stm32n6-port-wolfboot branch 5 times, most recently from c6fa180 to b4fd37e Compare May 19, 2026 06:22
@dgarske dgarske force-pushed the stm32n6-port-wolfboot branch 2 times, most recently from 3377112 to 74d150b Compare May 27, 2026 17:06
Co-authored-by: Aidan Garske <aidan@wolfssl.com>
@dgarske dgarske assigned danielinux and wolfSSL-Bot and unassigned dgarske May 27, 2026
danielinux
danielinux previously approved these changes May 27, 2026
dgarske
dgarske previously approved these changes May 27, 2026
@dgarske dgarske dismissed stale reviews from danielinux and themself via 30de6b2 May 28, 2026 04:16
@dgarske dgarske force-pushed the stm32n6-port-wolfboot branch from 74d150b to 30de6b2 Compare May 28, 2026 04:16
@danielinux danielinux self-requested a review May 28, 2026 14:48
@danielinux danielinux merged commit 9786f56 into master May 28, 2026
764 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants