diff --git a/.github/workflows/test-configs.yml b/.github/workflows/test-configs.yml index 5f3ada4350..f52e9dacc2 100644 --- a/.github/workflows/test-configs.yml +++ b/.github/workflows/test-configs.yml @@ -443,6 +443,18 @@ jobs: arch: arm config-file: ./config/examples/stm32h5-dualbank.config + stm32n6_test: + uses: ./.github/workflows/test-build.yml + with: + arch: arm + config-file: ./config/examples/stm32n6.config + + stm32n6_tz_test: + uses: ./.github/workflows/test-build.yml + with: + arch: arm + config-file: ./config/examples/stm32n6-tz.config + stm32h5_tz_test: uses: ./.github/workflows/test-build.yml with: @@ -473,6 +485,12 @@ jobs: arch: arm config-file: ./config/examples/stm32h5-tz-dualbank-otp-lms.config + stm32n6_test: + uses: ./.github/workflows/test-build.yml + with: + arch: arm + config-file: ./config/examples/stm32n6.config + stm32h7_test: uses: ./.github/workflows/test-build.yml with: diff --git a/Makefile b/Makefile index 321322a9fe..7b18df9ba5 100644 --- a/Makefile +++ b/Makefile @@ -274,6 +274,11 @@ ifeq ($(TARGET),sama5d3) MAIN_TARGET:=wolfboot.bin test-app/image_v1_signed.bin endif +ifeq ($(TARGET),stm32n6) + # wolfBoot runs from SRAM, app from XIP on external NOR - no contiguous factory.bin + MAIN_TARGET:=wolfboot.bin test-app/image_v1_signed.bin +endif + ifeq ($(TARGET),rp2350) MAIN_TARGET:=include/target.h keytools wolfboot_signing_private_key.der pico-sdk-info endif @@ -653,6 +658,12 @@ stack-usage: wolfboot.bin image-header-size: wolfboot.bin $(Q)echo $(IMAGE_HEADER_SIZE) > .image_header_size +## Target-specific flash targets +ifeq ($(TARGET),stm32n6) +flash: wolfboot.bin test-app/image_v1_signed.bin + $(Q)tools/scripts/stm32n6_flash.sh --skip-build +endif + cppcheck: cppcheck -f --enable=warning --enable=portability \ diff --git a/arch.mk b/arch.mk index a9de6d9e0f..647da4f7f0 100644 --- a/arch.mk +++ b/arch.mk @@ -270,6 +270,20 @@ ifeq ($(ARCH),ARM) endif + ifeq ($(TARGET),stm32n6) + CORTEX_M55=1 + CFLAGS+=-Ihal + ARCH_FLASH_OFFSET=0x70000000 + ifeq ($(TZEN),1) + WOLFBOOT_ORIGIN=0x24000000 + else + WOLFBOOT_ORIGIN=0x34000000 + endif + EXT_FLASH=1 + PART_UPDATE_EXT=1 + PART_SWAP_EXT=1 + endif + ifeq ($(TARGET),rp2350) CORTEX_M33=1 CFLAGS+=-Ihal @@ -352,12 +366,22 @@ else CORTEXM_ARM_EXTRA_CFLAGS+=-DWOLFSSL_ARMASM -DWOLFSSL_ARMASM_NO_HW_CRYPTO \ -DWOLFSSL_ARMASM_NO_NEON -DWOLFSSL_ARMASM_THUMB2 endif + ifeq ($(CORTEX_M55),1) + CORTEX_M33=1 + CFLAGS+=-mcpu=cortex-m55 -DCORTEX_M55 + LDFLAGS+=-mcpu=cortex-m55 + endif ifeq ($(CORTEX_M33),1) - CFLAGS+=-mcpu=cortex-m33 -DCORTEX_M33 - LDFLAGS+=-mcpu=cortex-m33 + CFLAGS+=-DCORTEX_M33 + ifneq ($(CORTEX_M55),1) + CFLAGS+=-mcpu=cortex-m33 + LDFLAGS+=-mcpu=cortex-m33 + endif ifeq ($(TZEN),1) ifneq (,$(findstring stm32,$(TARGET))) - OBJS+=hal/stm32_tz.o + ifneq ($(TARGET),stm32n6) + OBJS+=hal/stm32_tz.o + endif endif CFLAGS+=-mcmse ifeq ($(WOLFCRYPT_TZ),1) diff --git a/config/examples/stm32n6-tz.config b/config/examples/stm32n6-tz.config new file mode 100644 index 0000000000..f98cd6d54a --- /dev/null +++ b/config/examples/stm32n6-tz.config @@ -0,0 +1,24 @@ +ARCH?=ARM +TARGET?=stm32n6 +TZEN?=1 +SIGN?=ECC256 +HASH?=SHA256 +DEBUG?=0 +DEBUG_UART?=1 +VTOR?=1 +NO_ASM?=0 +NO_MPU=1 +SPI_FLASH?=0 +ALLOW_DOWNGRADE?=0 +NVM_FLASH_WRITEONCE?=0 +WOLFBOOT_VERSION?=1 +V?=0 +SPMATH?=1 +RAM_CODE?=1 +WOLFBOOT_SECTOR_SIZE?=0x1000 +WOLFBOOT_PARTITION_SIZE?=0x100000 +WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x70020000 +WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x00120000 +WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x00010000 +IMAGE_HEADER_SIZE?=1024 +CFLAGS_EXTRA+=-DPART_BOOT_EXT diff --git a/config/examples/stm32n6.config b/config/examples/stm32n6.config new file mode 100644 index 0000000000..2643526dc8 --- /dev/null +++ b/config/examples/stm32n6.config @@ -0,0 +1,27 @@ +ARCH?=ARM +TARGET?=stm32n6 +SIGN?=ECC256 +HASH?=SHA256 +DEBUG?=0 +DEBUG_UART?=1 +VTOR?=1 +NO_ASM?=0 +NO_MPU=1 +SPI_FLASH?=0 +ALLOW_DOWNGRADE?=0 +NVM_FLASH_WRITEONCE?=0 +WOLFBOOT_VERSION?=1 +V?=0 +SPMATH?=1 +RAM_CODE?=1 +WOLFBOOT_SECTOR_SIZE?=0x1000 +WOLFBOOT_PARTITION_SIZE?=0x100000 +WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x70020000 +WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x00120000 +WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x00010000 +IMAGE_HEADER_SIZE?=1024 +# Boot partition is on the same XSPI2 NOR flash as update/swap. +# PART_BOOT_EXT ensures boot partition reads go through ext_flash API +# (SPI commands) instead of XIP, avoiding bus faults when XSPI2 toggles +# between memory-mapped and command mode during firmware updates. +CFLAGS_EXTRA+=-DPART_BOOT_EXT diff --git a/config/openocd/openocd_stm32n6.cfg b/config/openocd/openocd_stm32n6.cfg new file mode 100644 index 0000000000..33d149e90a --- /dev/null +++ b/config/openocd/openocd_stm32n6.cfg @@ -0,0 +1,108 @@ +# OpenOCD config for NUCLEO-N657X0-Q with MX25UM51245G NOR on XSPI2 + +source [find interface/stlink.cfg] +transport select swd + +set CHIPNAME stm32n6x +set WORKAREASIZE 0x10000 + +source [find target/stm32n6x.cfg] + +# Work-area above wolfBoot SRAM region +$_TARGETNAME configure -work-area-phys 0x34020000 -work-area-size $WORKAREASIZE -work-area-backup 0 + +# XSPI2 NOR flash bank (memory-mapped at 0x70000000, regs at 0x5802A000) +set XSPI2_BANK_ID [llength [flash list]] +flash bank $CHIPNAME.xspi2 stmqspi 0x70000000 0 0 0 $CHIPNAME.cpu 0x5802A000 + +# Mark VDDIO supplies valid (required for XSPI2 GPIO) +proc pwr_enable_io_supply {} { + mmw 0x5602825C 0x00040000 0 ;# RCC_AHB4ENR: PWR clock + mmw 0x56024834 0x00000100 0 ;# SVMCR1: VDDIO4SV + mmw 0x56024838 0x00000100 0 ;# SVMCR2: VDDIO5SV + mmw 0x5602483C 0x00000300 0 ;# SVMCR3: VDDIO2SV + VDDIO3SV +} + +# Port N GPIO for XSPI2 (PN0-PN11, AF9, very high speed) +proc xspi2_gpio_init {} { + mmw 0x5602825C 0x00002000 0 ;# RCC_AHB4ENR: GPION clock + sleep 1 + mmw 0x56023400 0x00AAAAAA 0x00555555 ;# MODER: AF mode + mmw 0x56023408 0x00FFFFFF 0 ;# OSPEEDR: very high + mmw 0x5602340C 0 0x00FFFFFF ;# PUPDR: no pull + mww 0x56023420 0x99999999 ;# AFRL: AF9 + mww 0x56023424 0x00009999 ;# AFRH: AF9 +} + +# XSPI2 init: single-SPI, /16 prescaler, NOR reset, enter mmap mode +proc xspi2_init {} { + mmw 0x56028260 0x00003000 0 ;# RCC_AHB5ENR: XSPI2 + XSPIM clocks + mmw 0x56028248 0x00000008 0 ;# RCC_MISCENR: XSPI PHY comp clock + sleep 1 + + mww 0x5802A000 0x00000000 ;# CR: disable + sleep 1 + mww 0x5802A008 0x001A0308 ;# DCR1: DLYBYP, DEVSIZE=26, CSHT=3 + mww 0x5802A00C 0x0000000F ;# DCR2: prescaler /16 + sleep 1 + mww 0x5802A000 0x00000001 ;# CR: enable + + # NOR flash software reset (0x66 + 0x99) + mmw 0x5802A000 0x00000002 0 ;# abort + sleep 1 + mww 0x5802A024 0x0000000B ;# FCR: clear flags + mww 0x5802A100 0x00000001 ;# CCR: IMODE=single + mww 0x5802A108 0x00000000 ;# TCR: no dummy + mww 0x5802A110 0x00000066 ;# IR: Reset Enable + sleep 1 + + mmw 0x5802A000 0x00000002 0 ;# abort + sleep 1 + mww 0x5802A024 0x0000000B + mww 0x5802A100 0x00000001 + mww 0x5802A108 0x00000000 + mww 0x5802A110 0x00000099 ;# IR: Reset Memory + sleep 10 + + xspi2_mem_mapped +} + +# Memory-mapped fast-read mode (single-SPI, 4-byte addr, 8 dummy cycles) +proc xspi2_mem_mapped {} { + mmw 0x5802A000 0x00000002 0 ;# abort + sleep 1 + mww 0x5802A000 0x30000001 ;# CR: mmap + enable + mww 0x5802A100 0x01003101 ;# CCR: IMODE=1, ADMODE=1, ADSIZE=3, DMODE=1 + mww 0x5802A108 0x40000008 ;# TCR: DCYC=8, SSHIFT + mww 0x5802A110 0x0000000C ;# IR: Fast Read 4B +} + +# Set NOR flash params manually (SFDP not readable in single-SPI mode) +proc xspi2_flash_set {} { + global XSPI2_BANK_ID + stmqspi set $XSPI2_BANK_ID MX25UM51245G 0x4000000 0x100 0x13 0 0x12 0x60 0x1000 0x21 +} + +# Full reinit for use when XSPI2 may already be configured +proc xspi2_reinit {} { + global XSPI2_BANK_ID + pwr_enable_io_supply + xspi2_gpio_init + xspi2_init + xspi2_flash_set + flash probe $XSPI2_BANK_ID + xspi2_flash_set +} + +$_TARGETNAME configure -event reset-init { + global XSPI2_BANK_ID + pwr_enable_io_supply + xspi2_gpio_init + xspi2_init + xspi2_flash_set + flash probe $XSPI2_BANK_ID + # Re-set after probe (stmqspi driver quirk) + xspi2_flash_set +} + +init diff --git a/docs/Targets.md b/docs/Targets.md index 0a7ed2eeec..a6b870ef0c 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -43,6 +43,7 @@ This README describes configuration of supported targets. * [STM32F7](#stm32f7) * [STM32G0](#stm32g0) * [STM32H5](#stm32h5) +* [STM32N6](#stm32n6) * [STM32H7](#stm32h7) * [STM32L0](#stm32l0) * [STM32L4](#stm32l4) @@ -1819,6 +1820,200 @@ c ``` +## STM32N6 + +The STM32N6 (Cortex-M55) has no internal flash — all firmware resides on external +NOR flash (Macronix MX25UM51245G, 64MB) connected via XSPI2. The on-chip Boot ROM +copies the FSBL (First Stage Boot Loader) from external flash to internal SRAM and +jumps to it. wolfBoot serves as the FSBL, performing image verification and +chain-loading the application from external flash in XIP (Execute-In-Place) mode. + +Tested on: **NUCLEO-N657X0-Q** (STM32N657X0H, MB1940) + +### Memory Layout + +``` +XSPI2 NOR Flash (memory-mapped at 0x70000000): + 0x70000000 FSBL header area (128KB, future autonomous boot) + 0x70010000 Swap partition (64KB, device-relative: 0x00010000) + 0x70020000 Boot partition (1MB, app runs from here via XIP) + 0x70120000 Update partition (1MB, device-relative: 0x00120000) + +AXISRAM — without TrustZone (non-secure alias): + 0x34000000 wolfBoot (loaded to SRAM via SWD or Boot ROM FSBL copy) + 0x34020000 Stack / work area + +AXISRAM — with TrustZone (TZEN=1, secure alias): + 0x24000000 wolfBoot (secure SRAM — IDAU marks 0x24xxxxxx as secure) + 0x24020000 Stack / work area (secure) + 0x34000000 Application SRAM (SAU region: non-secure) +``` + +### Build and Flash + +Use the example configuration and build: + +```sh +# Without TrustZone: +cp config/examples/stm32n6.config .config +make +make flash + +# With TrustZone: +cp config/examples/stm32n6-tz.config .config +make +make flash +``` + +`make flash` uses OpenOCD with the stmqspi driver to: +1. Program the signed application to NOR flash at 0x70020000 +2. Load wolfBoot to SRAM (0x24000000 for TZEN=1, 0x34000000 otherwise) +3. Start wolfBoot, which verifies and boots the application via XIP + +Prerequisites: +- OpenOCD 0.12+ with stm32n6x target support (build from source if needed) +- ST-Link connected to the Nucleo board +- arm-none-eabi toolchain in PATH + +### Build Options + +```sh +make TARGET=stm32n6 SIGN=ECC256 +``` + +The example config uses: +- `EXT_FLASH=1` with `PART_UPDATE_EXT=1` and `PART_SWAP_EXT=1` +- `PART_BOOT_EXT` — boot partition reads use ext_flash API during updates + (required because boot and update share the same XSPI2 NOR flash) +- `RAM_CODE=1` — flash functions placed in `.ramcode` for XIP safety +- `DEBUG_UART=1` — USART1 output for boot messages and test-app +- Boot partition at 0x70020000 (XIP for app execution) +- Update/swap partitions use device-relative offsets +- 4KB sector size (`WOLFBOOT_SECTOR_SIZE=0x1000`) +- ECC256 + SHA256 for signature verification + +### TrustZone Support (TZEN=1) + +The STM32N6 Cortex-M55 always boots in secure mode. Unlike older STM32 parts +(H5, L5, U5), there is no `TZEN` option byte — TrustZone is always available +via the hardware IDAU (Implementation-Defined Attribution Unit). + +wolfBoot supports two modes: + +**Without TrustZone** (`stm32n6.config`): wolfBoot runs from the non-secure SRAM +alias at `0x34000000`. A blanket SAU NSC region covers the entire address space, +allowing access to all peripherals and memory. The application boots in the same +(non-secure) state. + +**With TrustZone** (`stm32n6-tz.config`, `TZEN=1`): wolfBoot runs from the secure +SRAM alias at `0x24000000`. The SAU is configured with specific regions: + +| SAU Region | Address Range | Type | Purpose | +|------------|---------------|------|---------| +| 0 (NSC) | 0x24010000–0x2401FFFF | Non-Secure Callable | Gateway veneers | +| 1 (NS) | 0x70000000–0x7FFFFFFF | Non-Secure | XSPI2 flash (app XIP) | +| 2 (NS) | 0x34000000–0x343FFFFF | Non-Secure | App SRAM | +| 3 (NS) | 0x40000000–0x4FFFFFFF | Non-Secure | Peripheral NS aliases | +| *default* | *all other* | Secure | wolfBoot SRAM, secure peripherals | + +wolfBoot uses secure peripheral aliases (0x56xxx RCC, 0x52xxx USART, 0x58xxx XSPI2). +The application runs in non-secure state and uses non-secure aliases (0x46xxx, 0x42xxx). +The flash script automatically selects the correct SRAM load address based on the +`TZEN` setting in `.config`. + +### SAU Configuration (non-TrustZone) + +Without `TZEN=1`, wolfBoot configures SAU region 0 to cover the entire 4GB address +space as Non-Secure Callable (NSC), allowing the CPU to access all peripherals and +memory regions regardless of IDAU attribution. + +### Shared Flash: PART_BOOT_EXT + +The boot and update partitions reside on the same XSPI2 NOR flash. During +firmware updates, wolfBoot must read boot partition data while also issuing SPI +commands to write the update/swap partitions. Since the XSPI2 cannot be in +memory-mapped (XIP) and SPI command mode simultaneously, the boot partition +must be accessed via SPI commands during updates. + +The config sets `PART_BOOT_EXT` so all boot partition reads during the update +swap use `ext_flash_read()` (SPI commands) instead of XIP. The `ext_flash_*` +functions in `hal/stm32n6.c` accept both absolute memory-mapped addresses +(0x70xxxxxx) and device-relative offsets, converting automatically. + +The application still boots via XIP — `do_boot()` jumps to the memory-mapped +address at 0x70020400. + +### XIP Constraints + +Since the application executes directly from NOR flash via XSPI2 memory-mapped +mode, the following constraints apply: + +- The application must NOT call `hal_init()` — XSPI2 is already configured by + wolfBoot for memory-mapped mode. Reinitializing XSPI2 would disable XIP and + crash the CPU. +- `RAM_CODE=1` must be set so that flash write functions are tagged RAMFUNCTION + and placed in `.ramcode`. The test-app's startup code copies `.ramcode` to + RAM, allowing `wolfBoot_success()` and other flash operations to execute + from RAM while XSPI2 is in SPI command mode. +- The `nor_flash_write()` function buffers data to a stack-local array before + issuing SPI commands, since the source data pointer may reference XIP flash + that becomes inaccessible when XSPI2 leaves memory-mapped mode. + +### UART Clock + +USART1 kernel clock defaults to PCLK2. With the PLL1 configuration +(IC2 = 400 MHz, AHB prescaler /2, APB2 prescaler /1), PCLK2 = 200 MHz. +The BRR is calculated accordingly for 115200 baud. + +### Flash Script Options + +The flash script supports several modes: + +```sh +./tools/scripts/stm32n6_flash.sh # Build and flash all +./tools/scripts/stm32n6_flash.sh --skip-build # Flash only (existing binaries) +./tools/scripts/stm32n6_flash.sh --app-only # Flash signed app only +./tools/scripts/stm32n6_flash.sh --test-update # Flash v1 boot + v2 update +./tools/scripts/stm32n6_flash.sh --halt # Leave OpenOCD running +``` + +### Debugging + +OpenOCD: + +```sh +openocd -f config/openocd/openocd_stm32n6.cfg +``` + +After OpenOCD starts, connect via telnet (port 4444). To manually load wolfBoot +and start it: + +```sh +reset halt +# Use 0x24000000 for TZEN=1, 0x34000000 for non-TZ +load_image wolfboot.bin 0x24000000 bin +reg msplim_s 0x00000000 +reg psplim_s 0x00000000 +reg msp 0x24020000 +mww 0xE000ED08 0x24000000 +resume +``` + +The entry address can be found with: +```sh +arm-none-eabi-nm wolfboot.elf | grep isr_reset +``` + +GDB: + +```sh +arm-none-eabi-gdb wolfboot.elf +target remote :3333 +mon halt +add-symbol-file test-app/image.elf 0x70020400 +``` + + ## STM32H7 The STM32H7 flash geometry must be defined beforehand. diff --git a/hal/stm32n6.c b/hal/stm32n6.c new file mode 100644 index 0000000000..47f680a6f7 --- /dev/null +++ b/hal/stm32n6.c @@ -0,0 +1,694 @@ +/* stm32n6.c + * + * Copyright (C) 2025 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include +#include +#include +#include +#include "hal.h" +#include "hal/stm32n6.h" +#include "printf.h" +#ifdef TZEN +#include "hal/armv8m_tz.h" +#endif + +/* OCTOSPI register definitions come from hal/spi/spi_drv_stm32.h (included + * via stm32n6.h). STM32N6 XSPI2 uses the same IP block as OCTOSPI. + * + * Note: We cannot reuse qspi_transfer() from spi_drv_stm32.c because it + * disables/enables the peripheral on each transfer. The N6 boots via XIP + * (memory-mapped mode on XSPI2), so we need custom transfer code that: + * 1) Aborts memory-mapped mode before indirect access + * 2) Restores memory-mapped mode after each operation + * 3) Runs from SRAM (RAMFUNCTION) when called from XIP code + */ + +/* SPI mode values for OCTOSPI CCR fields */ +#define SPI_MODE_NONE 0 +#define SPI_MODE_SINGLE 1 + +/* RAMFUNCTION override for test-app (XIP needs flash ops in SRAM) */ +#if defined(RAM_CODE) && !defined(__WOLFBOOT) + #undef RAMFUNCTION + #define RAMFUNCTION __attribute__((used,section(".ramcode"),long_call)) +#endif + +/* OCTOSPI indirect-mode command helper. + * Handles memory-mapped mode abort/restore and TEF error detection. */ +static int RAMFUNCTION octospi_cmd(uint8_t fmode, uint8_t cmd, + uint32_t addr, uint32_t addrMode, + uint8_t *data, uint32_t dataSz, uint32_t dataMode, + uint32_t dummyCycles) +{ + uint32_t ccr; + + /* Abort memory-mapped mode if active */ + if ((OCTOSPI_CR & OCTOSPI_CR_FMODE_MASK) == OCTOSPI_CR_FMODE_MMAP) { + OCTOSPI_CR |= OCTOSPI_CR_ABORT; + while (OCTOSPI_CR & OCTOSPI_CR_ABORT) + ; + } + while (OCTOSPI_SR & OCTOSPI_SR_BUSY) + ; + OCTOSPI_FCR = OCTOSPI_FCR_CTCF | OCTOSPI_FCR_CTEF | OCTOSPI_FCR_CSMF; + + OCTOSPI_CR = (OCTOSPI_CR & ~OCTOSPI_CR_FMODE_MASK) | + OCTOSPI_CR_FMODE(fmode); + + if (dataSz > 0) { + OCTOSPI_DLR = dataSz - 1; + } + + ccr = OCTOSPI_CCR_IMODE(SPI_MODE_SINGLE) | OCTOSPI_CCR_ISIZE(0); + if (addrMode != SPI_MODE_NONE) { + ccr |= OCTOSPI_CCR_ADMODE(addrMode) | OCTOSPI_CCR_ADSIZE(3); + } + if (dataMode != SPI_MODE_NONE) { + ccr |= OCTOSPI_CCR_DMODE(dataMode); + } + OCTOSPI_CCR = ccr; + OCTOSPI_TCR = OCTOSPI_TCR_DCYC(dummyCycles); + OCTOSPI_IR = cmd; + + if (addrMode != SPI_MODE_NONE) { + OCTOSPI_AR = addr; + } + + if (dataSz > 0 && data != NULL) { + while (dataSz >= 4) { + if (fmode == 0) { + while (!(OCTOSPI_SR & (OCTOSPI_SR_FTF | OCTOSPI_SR_TEF))) + ; + if (OCTOSPI_SR & OCTOSPI_SR_TEF) goto octospi_err; + OCTOSPI_DR32 = *(uint32_t *)data; + } else { + while (!(OCTOSPI_SR & (OCTOSPI_SR_FTF | OCTOSPI_SR_TCF | + OCTOSPI_SR_TEF))) + ; + if (OCTOSPI_SR & OCTOSPI_SR_TEF) goto octospi_err; + *(uint32_t *)data = OCTOSPI_DR32; + } + data += 4; + dataSz -= 4; + } + while (dataSz > 0) { + if (fmode == 0) { + while (!(OCTOSPI_SR & (OCTOSPI_SR_FTF | OCTOSPI_SR_TEF))) + ; + if (OCTOSPI_SR & OCTOSPI_SR_TEF) goto octospi_err; + OCTOSPI_DR = *data; + } else { + while (!(OCTOSPI_SR & (OCTOSPI_SR_FTF | OCTOSPI_SR_TCF | + OCTOSPI_SR_TEF))) + ; + if (OCTOSPI_SR & OCTOSPI_SR_TEF) goto octospi_err; + *data = OCTOSPI_DR; + } + data++; + dataSz--; + } + } + + while (!(OCTOSPI_SR & (OCTOSPI_SR_TCF | OCTOSPI_SR_TEF))) + ; + if (OCTOSPI_SR & OCTOSPI_SR_TEF) goto octospi_err; + OCTOSPI_FCR = OCTOSPI_FCR_CTCF; + + return 0; + +octospi_err: + OCTOSPI_FCR = OCTOSPI_FCR_CTEF; + OCTOSPI_CR |= OCTOSPI_CR_ABORT; + while (OCTOSPI_CR & OCTOSPI_CR_ABORT) + ; + return -1; +} + +static void RAMFUNCTION octospi_write_enable(void) +{ + octospi_cmd(0, WRITE_ENABLE_CMD, 0, SPI_MODE_NONE, + NULL, 0, SPI_MODE_NONE, 0); +} + +static void RAMFUNCTION octospi_wait_ready(void) +{ + uint8_t sr; + do { + sr = 0; + octospi_cmd(1, READ_SR_CMD, 0, SPI_MODE_NONE, + &sr, 1, SPI_MODE_SINGLE, 0); + } while (sr & FLASH_SR_BUSY); +} + +static void RAMFUNCTION octospi_enable_mmap(void) +{ + /* Abort first if already in mmap mode (BUSY stays set in mmap) */ + if ((OCTOSPI_CR & OCTOSPI_CR_FMODE_MASK) == OCTOSPI_CR_FMODE_MMAP) { + OCTOSPI_CR |= OCTOSPI_CR_ABORT; + while (OCTOSPI_CR & OCTOSPI_CR_ABORT) + ; + } + while (OCTOSPI_SR & OCTOSPI_SR_BUSY) + ; + OCTOSPI_FCR = OCTOSPI_FCR_CTCF | OCTOSPI_FCR_CTEF | OCTOSPI_FCR_CSMF; + + OCTOSPI_CR = (OCTOSPI_CR & ~OCTOSPI_CR_FMODE_MASK) | + OCTOSPI_CR_FMODE_MMAP; + + /* Fast read: single SPI, 4-byte addr, 8 dummy cycles */ + OCTOSPI_CCR = OCTOSPI_CCR_IMODE(SPI_MODE_SINGLE) | + OCTOSPI_CCR_ISIZE(0) | + OCTOSPI_CCR_ADMODE(SPI_MODE_SINGLE) | + OCTOSPI_CCR_ADSIZE(3) | + OCTOSPI_CCR_DMODE(SPI_MODE_SINGLE); + OCTOSPI_TCR = OCTOSPI_TCR_DCYC(8) | OCTOSPI_TCR_SSHIFT; + OCTOSPI_IR = FAST_READ_4B_CMD; + + DSB(); + ISB(); +} + +static void RAMFUNCTION dcache_clean_invalidate_by_addr(uint32_t addr, + uint32_t size) +{ + uint32_t line; + for (line = addr & ~0x1FUL; line < addr + size; line += 32) { + SCB_DCCIMVAC = line; + } + DSB(); + ISB(); +} + +static void icache_enable(void) +{ + DSB(); + ISB(); + SCB_ICIALLU = 0; + DSB(); + ISB(); + SCB_CCR |= SCB_CCR_IC; + DSB(); + ISB(); +} + +static void dcache_enable(void) +{ + DSB(); + SCB_CCR |= SCB_CCR_DC; + DSB(); + ISB(); +} + +static void icache_disable(void) +{ + DSB(); + ISB(); + SCB_CCR &= ~SCB_CCR_IC; + SCB_ICIALLU = 0; + DSB(); + ISB(); +} + +static void dcache_disable(void) +{ + /* Clean+invalidate all lines by set/way before disabling */ + uint32_t sets, ways, set, way, way_shift; + uint32_t ccsidr; + + CSSELR = 0; /* select L1 data cache */ + DSB(); + ccsidr = CCSIDR; + sets = ((ccsidr >> 13) & 0x7FFF) + 1; + ways = ((ccsidr >> 3) & 0x3FF) + 1; + + /* Calculate way shift: 32 - log2(ways) */ + way_shift = 32; + { uint32_t tmp = ways - 1; while (tmp) { way_shift--; tmp >>= 1; } } + + for (way = 0; way < ways; way++) { + for (set = 0; set < sets; set++) { + SCB_DCCISW = (way << way_shift) | (set << 5); + } + } + + DSB(); + SCB_CCR &= ~SCB_CCR_DC; + DSB(); + ISB(); +} + +/* OCTOSPI GPIO: PN0-PN11 as AF9 (DQS, CLK, NCS, IO0-IO7) */ +static void octospi_gpio_init(void) +{ + uint32_t reg; + int pin; + + RCC_AHB4ENR |= RCC_AHB4ENR_GPIONEN; + DMB(); + + /* AF mode, very high speed, no pull */ + reg = GPIO_MODER(GPION_BASE); + for (pin = 0; pin <= 11; pin++) { + reg &= ~(0x3 << (pin * 2)); + reg |= (GPIO_MODE_AF << (pin * 2)); + } + GPIO_MODER(GPION_BASE) = reg; + + reg = GPIO_OSPEEDR(GPION_BASE); + for (pin = 0; pin <= 11; pin++) { + reg &= ~(0x3 << (pin * 2)); + reg |= (GPIO_SPEED_VERY_HIGH << (pin * 2)); + } + GPIO_OSPEEDR(GPION_BASE) = reg; + + reg = GPIO_PUPDR(GPION_BASE); + for (pin = 0; pin <= 11; pin++) { + reg &= ~(0x3 << (pin * 2)); + } + GPIO_PUPDR(GPION_BASE) = reg; + + /* AF9 for PN0-PN7 (AFRL) and PN8-PN11 (AFRH) */ + reg = 0; + for (pin = 0; pin <= 7; pin++) { + reg |= (9 << (pin * 4)); + } + GPIO_AFRL(GPION_BASE) = reg; + + reg = 0; + for (pin = 0; pin <= 3; pin++) { + reg |= (9 << (pin * 4)); + } + GPIO_AFRH(GPION_BASE) = reg; +} + +static void octospi_init(void) +{ + volatile uint32_t delay; + + RCC_AHB5ENR |= RCC_AHB5ENR_XSPI2EN | RCC_AHB5ENR_XSPIMEN; + RCC_MISCENR |= RCC_MISCENR_XSPIPHYCOMPEN; + DMB(); + + OCTOSPI_CR = 0; + while (OCTOSPI_SR & OCTOSPI_SR_BUSY) + ; + + OCTOSPI_DCR1 = OCTOSPI_DCR1_DLYBYP | + OCTOSPI_DCR1_DEVSIZE(FLASH_DEVICE_SIZE_LOG2) | + OCTOSPI_DCR1_CSHT(3); + OCTOSPI_DCR2 = OCTOSPI_DCR2_PRESCALER(16); + while (OCTOSPI_SR & OCTOSPI_SR_BUSY) + ; + + OCTOSPI_CR = OCTOSPI_CR_FTHRES(1) | OCTOSPI_CR_EN; + + /* NOR flash software reset */ + octospi_cmd(0, RESET_ENABLE_CMD, 0, SPI_MODE_NONE, + NULL, 0, SPI_MODE_NONE, 0); + octospi_cmd(0, RESET_MEMORY_CMD, 0, SPI_MODE_NONE, + NULL, 0, SPI_MODE_NONE, 0); + for (delay = 0; delay < 100000; delay++) + ; + + octospi_enable_mmap(); +} + +/* Configure clocks: PLL1 → 600 MHz CPU (Voltage Scale 1). + * STM32N6 supports up to 800 MHz at Voltage Scale 0 (PWR_VOSCR_VOS=1). + * + * Clock tree: + * HSI 64 MHz → PLL1 (M=4, N=75) → VCO 1200 MHz → PDIV1=1 → 1200 MHz + * IC1 /2 = 600 MHz → CPU (CPUSW=IC1) + * IC2 /3 = 400 MHz → AXI bus (SYSSW=IC2) + * IC6 /4 = 300 MHz → system bus C (SYSSW=IC6) + * IC11/3 = 400 MHz → system bus D (SYSSW=IC11) + * AHB prescaler /2 → HCLK = 300 MHz + */ +static void clock_config(void) +{ + uint32_t reg; + + /* Enable HSI 64 MHz */ + RCC_CSR = RCC_CR_HSION; + while (!(RCC_SR & RCC_SR_HSIRDY)) + ; + + /* Disable PLL1 before reconfiguring */ + RCC_CCR = RCC_CR_PLL1ON; + while (RCC_SR & RCC_SR_PLL1RDY) + ; + + /* PLL1: HSI / 4 * 75 = 1200 MHz VCO. + * Clear BYP (bypass) — Boot ROM leaves it set, which routes HSI + * directly to PLL output, skipping the VCO entirely. */ + reg = RCC_PLL1CFGR1; + reg &= ~(RCC_PLL1CFGR1_SEL_MASK | RCC_PLL1CFGR1_DIVM_MASK | + RCC_PLL1CFGR1_DIVN_MASK | RCC_PLL1CFGR1_BYP); + reg |= RCC_PLL1CFGR1_SEL_HSI | + (4 << RCC_PLL1CFGR1_DIVM_SHIFT) | + (75 << RCC_PLL1CFGR1_DIVN_SHIFT); + RCC_PLL1CFGR1 = reg; + + RCC_PLL1CFGR2 = 0; /* no fractional */ + + /* PDIV1=1, PDIV2=1 → PLL output = VCO = 1200 MHz. + * MODSSDIS: disable spread spectrum. PDIVEN: enable PLL output. */ + RCC_PLL1CFGR3 = (1 << RCC_PLL1CFGR3_PDIV1_SHIFT) | + (1 << RCC_PLL1CFGR3_PDIV2_SHIFT) | + RCC_PLL1CFGR3_MODSSDIS | + RCC_PLL1CFGR3_MODSSRST | + RCC_PLL1CFGR3_PDIVEN; + + /* Enable PLL1, wait for lock */ + RCC_CSR = RCC_CR_PLL1ON; + while (!(RCC_SR & RCC_SR_PLL1RDY)) + ; + + /* Configure IC dividers: disable → configure → re-enable. + * IC divider = INT + 1, SEL: 0=PLL1 (per ST LL driver). */ + RCC_DIVENCR = RCC_DIVENR_IC1EN; + RCC_IC1CFGR = RCC_ICCFGR_SEL_PLL1 | ((2 - 1) << RCC_ICCFGR_INT_SHIFT); + RCC_DIVENSR = RCC_DIVENR_IC1EN; + + RCC_DIVENCR = RCC_DIVENR_IC2EN; + RCC_IC2CFGR = RCC_ICCFGR_SEL_PLL1 | ((3 - 1) << RCC_ICCFGR_INT_SHIFT); + RCC_DIVENSR = RCC_DIVENR_IC2EN; + + RCC_DIVENCR = RCC_DIVENR_IC6EN; + RCC_IC6CFGR = RCC_ICCFGR_SEL_PLL1 | ((4 - 1) << RCC_ICCFGR_INT_SHIFT); + RCC_DIVENSR = RCC_DIVENR_IC6EN; + + RCC_DIVENCR = RCC_DIVENR_IC11EN; + RCC_IC11CFGR = RCC_ICCFGR_SEL_PLL1 | ((3 - 1) << RCC_ICCFGR_INT_SHIFT); + RCC_DIVENSR = RCC_DIVENR_IC11EN; + + /* AHB prescaler /2 → HCLK = 300 MHz */ + reg = RCC_CFGR2; + reg &= ~RCC_CFGR2_HPRE_MASK; + reg |= (1 << RCC_CFGR2_HPRE_SHIFT); + RCC_CFGR2 = reg; + + /* Switch CPU to IC1, system bus to IC2/IC6/IC11 */ + reg = RCC_CFGR1; + reg &= ~(RCC_CFGR1_CPUSW_MASK | RCC_CFGR1_SYSSW_MASK); + reg |= (0x3 << RCC_CFGR1_CPUSW_SHIFT) | + (0x3 << RCC_CFGR1_SYSSW_SHIFT); + RCC_CFGR1 = reg; + while ((RCC_CFGR1 & RCC_CFGR1_CPUSWS_MASK) != + (0x3 << RCC_CFGR1_CPUSWS_SHIFT)) + ; + while ((RCC_CFGR1 & RCC_CFGR1_SYSSWS_MASK) != + (0x3 << RCC_CFGR1_SYSSWS_SHIFT)) + ; +} + +#ifdef DEBUG_UART +/* USART1 on PE5 (TX) / PE6 (RX), AF7 */ + +#define UART_BASE USART1_BASE +#define UART_CLOCK_FREQ 200000000UL /* PCLK2 = IC2(400MHz) / AHB(2) / APB2(1) */ + +static void uart_init_baud(uint32_t baud) +{ + uint32_t reg; + + RCC_APB2ENR |= RCC_APB2ENR_USART1EN; + RCC_AHB4ENR |= RCC_AHB4ENR_GPIOEEN; + DMB(); + + /* PE5/PE6 AF mode */ + reg = GPIO_MODER(GPIOE_BASE); + reg &= ~((0x3 << (5 * 2)) | (0x3 << (6 * 2))); + reg |= (GPIO_MODE_AF << (5 * 2)) | (GPIO_MODE_AF << (6 * 2)); + GPIO_MODER(GPIOE_BASE) = reg; + + /* AF7 */ + reg = GPIO_AFRL(GPIOE_BASE); + reg &= ~((0xF << (5 * 4)) | (0xF << (6 * 4))); + reg |= (7 << (5 * 4)) | (7 << (6 * 4)); + GPIO_AFRL(GPIOE_BASE) = reg; + + reg = GPIO_OSPEEDR(GPIOE_BASE); + reg &= ~((0x3 << (5 * 2)) | (0x3 << (6 * 2))); + reg |= (GPIO_SPEED_HIGH << (5 * 2)) | (GPIO_SPEED_HIGH << (6 * 2)); + GPIO_OSPEEDR(GPIOE_BASE) = reg; + + /* 8N1 */ + UART_CR1(UART_BASE) = 0; + UART_CR2(UART_BASE) = 0; + UART_CR3(UART_BASE) = 0; + UART_BRR(UART_BASE) = (UART_CLOCK_FREQ + baud / 2) / baud; + UART_CR1(UART_BASE) = USART_CR1_TE | USART_CR1_RE | USART_CR1_UE; +} + +void uart_write(const char *buf, unsigned int len) +{ + unsigned int i; + for (i = 0; i < len; i++) { + while (!(UART_ISR(UART_BASE) & USART_ISR_TXE)) + ; + UART_TDR(UART_BASE) = buf[i]; + } + while (!(UART_ISR(UART_BASE) & USART_ISR_TC)) + ; +} +#endif + +/* Mark VDDIO supplies valid (required for OCTOSPI GPIO) */ +static void pwr_enable_io_supply(void) +{ + RCC_AHB4ENR |= RCC_AHB4ENR_PWREN; + DMB(); + PWR_SVMCR1 |= PWR_SVMCR1_VDDIO4SV; + PWR_SVMCR2 |= PWR_SVMCR2_VDDIO5SV; + PWR_SVMCR3 |= PWR_SVMCR3_VDDIO2SV | PWR_SVMCR3_VDDIO3SV; + DMB(); +} + +void hal_init(void) +{ +#ifdef TZEN + /* TrustZone enabled: wolfBoot runs Secure, app runs Non-Secure. + * Configure SAU to define non-secure regions for the application. */ + { + SAU_CTRL = 0; + DSB(); + + /* Region 0: NSC - reserved for secure gateway veneers */ + sau_init_region(0, 0x24010000, 0x2401FFFF, 1); + + /* Region 1: NS - XSPI2 memory-mapped flash (app XIP + data) */ + sau_init_region(1, 0x70000000, 0x7FFFFFFF, 0); + + /* Region 2: NS - app SRAM (non-secure AXISRAM alias) */ + sau_init_region(2, 0x34000000, 0x343FFFFF, 0); + + /* Region 3: NS - peripheral non-secure aliases (for app) */ + sau_init_region(3, 0x40000000, 0x4FFFFFFF, 0); + + SAU_CTRL = SAU_INIT_CTRL_ENABLE; + DSB(); + ISB(); + + /* Enable SecureFault handler */ + SCB_SHCSR |= SCB_SHCSR_SECUREFAULT_EN; + } +#else + /* No TrustZone: blanket NSC region allows secure CPU to access all + * memory regardless of IDAU attribution. */ + { + #define SAU_CTRL (*(volatile uint32_t *)(0xE000EDD0UL)) + #define SAU_RNR (*(volatile uint32_t *)(0xE000EDD8UL)) + #define SAU_RBAR (*(volatile uint32_t *)(0xE000EDDCUL)) + #define SAU_RLAR (*(volatile uint32_t *)(0xE000EDE0UL)) + SAU_CTRL = 0; + DSB(); + SAU_RNR = 0; + SAU_RBAR = 0x00000000; + SAU_RLAR = 0xFFFFFFE1; /* full range, NSC, enabled */ + DSB(); + SAU_CTRL = 1; + DSB(); + ISB(); + } +#endif + clock_config(); + pwr_enable_io_supply(); + icache_enable(); + octospi_gpio_init(); + octospi_init(); + dcache_enable(); + +#ifdef DEBUG_UART + uart_init_baud(115200); + uart_write("wolfBoot Init\n", 14); + wolfBoot_printf("TrustZone: %s\n", + #if TZ_SECURE() + "Secure" + #else + "Off" + #endif + ); +#endif +} + +void hal_prepare_boot(void) +{ + octospi_enable_mmap(); + dcache_disable(); + icache_disable(); +} + +/* Shared NOR flash helpers used by both hal_flash_* and ext_flash_* */ +static int RAMFUNCTION nor_flash_write(uint32_t offset, const uint8_t *data, + int len) +{ + uint32_t page_off, write_sz; + int remaining = len; + int ret = 0; + /* Buffer for data that may reside in XIP flash (memory-mapped XSPI2). + * The source pointer becomes invalid once XSPI2 leaves mmap mode for + * the SPI page-program command, so we copy to RAM first. */ + uint8_t page_buf[FLASH_PAGE_SIZE]; + + if (len <= 0) + return 0; + + while (remaining > 0) { + page_off = offset & (FLASH_PAGE_SIZE - 1); + write_sz = FLASH_PAGE_SIZE - page_off; + if ((int)write_sz > remaining) + write_sz = remaining; + + memcpy(page_buf, data, write_sz); + + octospi_write_enable(); + ret = octospi_cmd(0, PAGE_PROG_4B_CMD, + offset, SPI_MODE_SINGLE, + page_buf, write_sz, SPI_MODE_SINGLE, 0); + if (ret < 0) + break; + + octospi_wait_ready(); + + offset += write_sz; + data += write_sz; + remaining -= write_sz; + } + + octospi_enable_mmap(); + return ret; +} + +static int RAMFUNCTION nor_flash_erase(uint32_t offset, int len) +{ + uint32_t end; + int ret = 0; + + if (len <= 0) + return -1; + + end = offset + len; + + while (offset < end) { + octospi_write_enable(); + ret = octospi_cmd(0, SEC_ERASE_4B_CMD, + offset, SPI_MODE_SINGLE, + NULL, 0, SPI_MODE_NONE, 0); + if (ret < 0) + break; + + octospi_wait_ready(); + offset += FLASH_SECTOR_SIZE; + } + + octospi_enable_mmap(); + return ret; +} + +int RAMFUNCTION hal_flash_write(uint32_t address, const uint8_t *data, int len) +{ + int ret = nor_flash_write(address - OCTOSPI_MEM_BASE, data, len); + if (ret == 0 && len > 0) + dcache_clean_invalidate_by_addr(address, len); + return ret; +} + +int RAMFUNCTION hal_flash_erase(uint32_t address, int len) +{ + int ret = nor_flash_erase(address - OCTOSPI_MEM_BASE, len); + if (ret == 0 && len > 0) + dcache_clean_invalidate_by_addr(address, len); + return ret; +} + +void RAMFUNCTION hal_flash_unlock(void) +{ +} + +void RAMFUNCTION hal_flash_lock(void) +{ +} + +/* ext_flash API: accepts both device-relative offsets (update/swap) and + * absolute memory-mapped addresses (boot partition with PART_BOOT_EXT). */ + +static uint32_t RAMFUNCTION ext_flash_addr(uintptr_t address) +{ + if (address >= OCTOSPI_MEM_BASE) + return (uint32_t)(address - OCTOSPI_MEM_BASE); + return (uint32_t)address; +} + +int RAMFUNCTION ext_flash_read(uintptr_t address, uint8_t *data, int len) +{ + int ret; + + if (len <= 0) + return 0; + + ret = octospi_cmd(1, FAST_READ_4B_CMD, + ext_flash_addr(address), SPI_MODE_SINGLE, + data, len, SPI_MODE_SINGLE, 8); + + octospi_enable_mmap(); + + return (ret < 0) ? ret : len; +} + +int RAMFUNCTION ext_flash_write(uintptr_t address, const uint8_t *data, + int len) +{ + return nor_flash_write(ext_flash_addr(address), data, len); +} + +int RAMFUNCTION ext_flash_erase(uintptr_t address, int len) +{ + return nor_flash_erase(ext_flash_addr(address), len); +} + +void RAMFUNCTION ext_flash_lock(void) +{ +} + +void RAMFUNCTION ext_flash_unlock(void) +{ +} diff --git a/hal/stm32n6.h b/hal/stm32n6.h new file mode 100644 index 0000000000..e08d1bb1ce --- /dev/null +++ b/hal/stm32n6.h @@ -0,0 +1,399 @@ +/* stm32n6.h + * + * Copyright (C) 2025 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifndef STM32N6_DEF_INCLUDED +#define STM32N6_DEF_INCLUDED + +/* Assembly helpers */ +#ifndef DMB +#define DMB() __asm__ volatile ("dmb") +#endif +#ifndef ISB +#define ISB() __asm__ volatile ("isb") +#endif +#ifndef DSB +#define DSB() __asm__ volatile ("dsb") +#endif + +/* TrustZone secure/non-secure detection. + * When compiled with -mcmse (TZEN=1) and not building the non-secure app, + * TZ_SECURE() is 1 and we use secure peripheral aliases (0x5xxxxxxx). + * Otherwise we use non-secure aliases (0x4xxxxxxx). */ +#if (defined(__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3U) && \ + !defined(NONSECURE_APP)) +# define TZ_SECURE() (1) +#else +# define TZ_SECURE() (0) +#endif + +/*** RCC (Reset and Clock Control) ***/ +#if TZ_SECURE() +#define RCC_BASE (0x56028000UL) /* AHB4 secure */ +#else +#define RCC_BASE (0x46028000UL) /* AHB4 non-secure */ +#endif + +/* RCC_CR: control register (read), CSR: set register, CCR: clear register */ +#define RCC_CR (*(volatile uint32_t *)(RCC_BASE + 0x000)) +#define RCC_CSR (*(volatile uint32_t *)(RCC_BASE + 0x800)) +#define RCC_CCR (*(volatile uint32_t *)(RCC_BASE + 0x1000)) +#define RCC_CR_LSION (1 << 0) +#define RCC_CR_LSEON (1 << 1) +#define RCC_CR_MSION (1 << 2) +#define RCC_CR_HSION (1 << 3) +#define RCC_CR_HSEON (1 << 4) +#define RCC_CR_PLL1ON (1 << 8) +#define RCC_CR_PLL2ON (1 << 9) +#define RCC_CR_PLL3ON (1 << 10) +#define RCC_CR_PLL4ON (1 << 11) + +/* RCC_SR: status register — ready flags */ +#define RCC_SR (*(volatile uint32_t *)(RCC_BASE + 0x04)) +#define RCC_SR_LSIRDY (1 << 0) +#define RCC_SR_LSERDY (1 << 1) +#define RCC_SR_MSIRDY (1 << 2) +#define RCC_SR_HSIRDY (1 << 3) +#define RCC_SR_HSERDY (1 << 4) +#define RCC_SR_PLL1RDY (1 << 8) +#define RCC_SR_PLL2RDY (1 << 9) +#define RCC_SR_PLL3RDY (1 << 10) +#define RCC_SR_PLL4RDY (1 << 11) + +/* RCC_CFGR1: clock switching */ +#define RCC_CFGR1 (*(volatile uint32_t *)(RCC_BASE + 0x20)) +#define RCC_CFGR1_CPUSW_SHIFT (16) +#define RCC_CFGR1_CPUSW_MASK (0x3 << 16) +#define RCC_CFGR1_CPUSWS_SHIFT (20) +#define RCC_CFGR1_CPUSWS_MASK (0x3 << 20) +#define RCC_CFGR1_SYSSW_SHIFT (24) +#define RCC_CFGR1_SYSSW_MASK (0x3 << 24) +#define RCC_CFGR1_SYSSWS_SHIFT (28) +#define RCC_CFGR1_SYSSWS_MASK (0x3 << 28) + +/* RCC_CFGR2: AHB/APB prescalers */ +#define RCC_CFGR2 (*(volatile uint32_t *)(RCC_BASE + 0x24)) +#define RCC_CFGR2_PPRE1_SHIFT (0) +#define RCC_CFGR2_PPRE1_MASK (0x7 << 0) +#define RCC_CFGR2_PPRE2_SHIFT (4) +#define RCC_CFGR2_PPRE2_MASK (0x7 << 4) +#define RCC_CFGR2_PPRE4_SHIFT (12) +#define RCC_CFGR2_PPRE4_MASK (0x7 << 12) +#define RCC_CFGR2_PPRE5_SHIFT (16) +#define RCC_CFGR2_PPRE5_MASK (0x7 << 16) +#define RCC_CFGR2_HPRE_SHIFT (20) +#define RCC_CFGR2_HPRE_MASK (0x7 << 20) + +/* PLL1 Configuration registers */ +#define RCC_PLL1CFGR1 (*(volatile uint32_t *)(RCC_BASE + 0x80)) +#define RCC_PLL1CFGR1_DIVN_SHIFT (8) /* bits [19:8]: VCO multiplication */ +#define RCC_PLL1CFGR1_DIVN_MASK (0xFFF << 8) +#define RCC_PLL1CFGR1_DIVM_SHIFT (20) /* bits [25:20]: reference divider */ +#define RCC_PLL1CFGR1_DIVM_MASK (0x3F << 20) +#define RCC_PLL1CFGR1_SEL_SHIFT (28) /* bits [30:28]: PLL source */ +#define RCC_PLL1CFGR1_SEL_MASK (0x7 << 28) +#define RCC_PLL1CFGR1_BYP (1 << 27) /* PLL bypass (ref clock passthrough) */ +#define RCC_PLL1CFGR1_SEL_HSI (0x0 << 28) +#define RCC_PLL1CFGR1_SEL_HSE (0x1 << 28) +#define RCC_PLL1CFGR1_SEL_MSI (0x2 << 28) + +#define RCC_PLL1CFGR2 (*(volatile uint32_t *)(RCC_BASE + 0x84)) +/* PLL1CFGR2: fractional DIVN only (bits [23:0]) */ + +#define RCC_PLL1CFGR3 (*(volatile uint32_t *)(RCC_BASE + 0x88)) +#define RCC_PLL1CFGR3_PDIV2_SHIFT (24) /* bits [26:24]: output post-divider 2 (1-7) */ +#define RCC_PLL1CFGR3_PDIV2_MASK (0x7 << 24) +#define RCC_PLL1CFGR3_PDIV1_SHIFT (27) /* bits [29:27]: output post-divider 1 (1-7) */ +#define RCC_PLL1CFGR3_PDIV1_MASK (0x7 << 27) +#define RCC_PLL1CFGR3_MODSSRST (1 << 0) /* spread spectrum modulation reset */ +#define RCC_PLL1CFGR3_MODSSDIS (1 << 2) /* spread spectrum disable */ +#define RCC_PLL1CFGR3_PDIVEN (1 << 30) /* post-divider + PLL output enable */ + +/* IC (Interconnect Clock) dividers */ +#define RCC_IC1CFGR (*(volatile uint32_t *)(RCC_BASE + 0xC4)) +#define RCC_IC2CFGR (*(volatile uint32_t *)(RCC_BASE + 0xC8)) +#define RCC_IC3CFGR (*(volatile uint32_t *)(RCC_BASE + 0xCC)) +#define RCC_IC4CFGR (*(volatile uint32_t *)(RCC_BASE + 0xD0)) +#define RCC_IC5CFGR (*(volatile uint32_t *)(RCC_BASE + 0xD4)) +#define RCC_IC6CFGR (*(volatile uint32_t *)(RCC_BASE + 0xD8)) +#define RCC_IC7CFGR (*(volatile uint32_t *)(RCC_BASE + 0xDC)) +#define RCC_IC8CFGR (*(volatile uint32_t *)(RCC_BASE + 0xE0)) +#define RCC_IC9CFGR (*(volatile uint32_t *)(RCC_BASE + 0xE4)) +#define RCC_IC10CFGR (*(volatile uint32_t *)(RCC_BASE + 0xE8)) +#define RCC_IC11CFGR (*(volatile uint32_t *)(RCC_BASE + 0xEC)) + +/* IC divider register fields: + * ICxINT [23:16] = integer division factor + * ICxSEL [29:28] = source: 0=PLL1, 1=PLL2, 2=PLL3, 3=PLL4 + */ +#define RCC_ICCFGR_INT_SHIFT (16) +#define RCC_ICCFGR_INT_MASK (0xFF << 16) +#define RCC_ICCFGR_SEL_SHIFT (28) +#define RCC_ICCFGR_SEL_MASK (0x3 << 28) +#define RCC_ICCFGR_SEL_PLL1 (0x0 << 28) +#define RCC_ICCFGR_SEL_PLL2 (0x1 << 28) +#define RCC_ICCFGR_SEL_PLL3 (0x2 << 28) +#define RCC_ICCFGR_SEL_PLL4 (0x3 << 28) + +/* Divider enable: direct, set (+0x800), clear (+0x1000) */ +#define RCC_DIVENR (*(volatile uint32_t *)(RCC_BASE + 0x240)) +#define RCC_DIVENSR (*(volatile uint32_t *)(RCC_BASE + 0xA40)) +#define RCC_DIVENCR (*(volatile uint32_t *)(RCC_BASE + 0x1240)) +#define RCC_DIVENR_IC1EN (1 << 0) +#define RCC_DIVENR_IC2EN (1 << 1) +#define RCC_DIVENR_IC3EN (1 << 2) +#define RCC_DIVENR_IC4EN (1 << 3) +#define RCC_DIVENR_IC5EN (1 << 4) +#define RCC_DIVENR_IC6EN (1 << 5) +#define RCC_DIVENR_IC11EN (1 << 10) + +/* Clock enable registers */ +#define RCC_MISCENR (*(volatile uint32_t *)(RCC_BASE + 0x248)) +#define RCC_AHB1ENR (*(volatile uint32_t *)(RCC_BASE + 0x250)) +#define RCC_AHB2ENR (*(volatile uint32_t *)(RCC_BASE + 0x254)) +#define RCC_AHB3ENR (*(volatile uint32_t *)(RCC_BASE + 0x258)) +#define RCC_AHB4ENR (*(volatile uint32_t *)(RCC_BASE + 0x25C)) +#define RCC_AHB5ENR (*(volatile uint32_t *)(RCC_BASE + 0x260)) +#define RCC_APB1ENR1 (*(volatile uint32_t *)(RCC_BASE + 0x264)) +#define RCC_APB1ENR2 (*(volatile uint32_t *)(RCC_BASE + 0x268)) +#define RCC_APB2ENR (*(volatile uint32_t *)(RCC_BASE + 0x26C)) +#define RCC_APB4ENR1 (*(volatile uint32_t *)(RCC_BASE + 0x274)) +#define RCC_APB5ENR (*(volatile uint32_t *)(RCC_BASE + 0x27C)) + +/* GPIO clock enable bits in RCC_AHB4ENR */ +#define RCC_AHB4ENR_GPIOAEN (1 << 0) +#define RCC_AHB4ENR_GPIOBEN (1 << 1) +#define RCC_AHB4ENR_GPIOCEN (1 << 2) +#define RCC_AHB4ENR_GPIODEN (1 << 3) +#define RCC_AHB4ENR_GPIOEEN (1 << 4) +#define RCC_AHB4ENR_GPIOFEN (1 << 5) +#define RCC_AHB4ENR_GPIOGEN (1 << 6) +#define RCC_AHB4ENR_GPIOHEN (1 << 7) +#define RCC_AHB4ENR_GPIONEN (1 << 13) +#define RCC_AHB4ENR_GPIOOEN (1 << 14) +#define RCC_AHB4ENR_GPIOPEN (1 << 15) +#define RCC_AHB4ENR_GPIOQEN (1 << 16) +#define RCC_AHB4ENR_PWREN (1 << 18) + +/* XSPI clock enable in RCC_AHB5ENR */ +#define RCC_AHB5ENR_XSPI1EN (1 << 5) +#define RCC_AHB5ENR_XSPI2EN (1 << 12) +#define RCC_AHB5ENR_XSPIMEN (1 << 13) + +/* XSPI PHY compensation clock in RCC_MISCENR */ +#define RCC_MISCENR_XSPIPHYCOMPEN (1 << 3) + +/* USART clock enable */ +#define RCC_APB2ENR_USART1EN (1 << 4) + + +/*** PWR (Power Control) — base 0x56024800 (secure) ***/ +#if TZ_SECURE() +#define PWR_BASE (0x56024800UL) /* secure */ +#else +#define PWR_BASE (0x46024800UL) /* non-secure */ +#endif + +#define PWR_CR1 (*(volatile uint32_t *)(PWR_BASE + 0x00)) +#define PWR_CR2 (*(volatile uint32_t *)(PWR_BASE + 0x04)) +#define PWR_CR3 (*(volatile uint32_t *)(PWR_BASE + 0x08)) +#define PWR_CR4 (*(volatile uint32_t *)(PWR_BASE + 0x0C)) +#define PWR_VOSCR (*(volatile uint32_t *)(PWR_BASE + 0x20)) + +/* PWR_VOSCR: Voltage scaling controls max CPU frequency. + * Scale 1 (VOS=0, default): up to 600 MHz CPU. + * Scale 0 (VOS=1): up to 800 MHz CPU. */ +#define PWR_VOSCR_VOS (1 << 0) +#define PWR_VOSCR_VOSRDY (1 << 1) + +/* PWR Supply Voltage Monitoring Control Registers */ +#define PWR_SVMCR1 (*(volatile uint32_t *)(PWR_BASE + 0x34)) +#define PWR_SVMCR2 (*(volatile uint32_t *)(PWR_BASE + 0x38)) +#define PWR_SVMCR3 (*(volatile uint32_t *)(PWR_BASE + 0x3C)) + +/* SVMCR1: VDDIO4 supply valid (bit 8) */ +#define PWR_SVMCR1_VDDIO4SV (1 << 8) +/* SVMCR2: VDDIO5 supply valid (bit 8) */ +#define PWR_SVMCR2_VDDIO5SV (1 << 8) +/* SVMCR3: VDDIO2 supply valid (bit 8), VDDIO3 supply valid (bit 9) */ +#define PWR_SVMCR3_VDDIO2SV (1 << 8) +#define PWR_SVMCR3_VDDIO3SV (1 << 9) + + +/*** GPIO ***/ +#if TZ_SECURE() +#define GPIOA_BASE (0x56020000UL) +#define GPIOB_BASE (0x56020400UL) +#define GPIOC_BASE (0x56020800UL) +#define GPIOD_BASE (0x56020C00UL) +#define GPIOE_BASE (0x56021000UL) +#define GPIOF_BASE (0x56021400UL) +#define GPIOG_BASE (0x56021800UL) +#define GPIOH_BASE (0x56021C00UL) +#define GPION_BASE (0x56023400UL) +#define GPIOO_BASE (0x56023800UL) +#define GPIOP_BASE (0x56023C00UL) +#define GPIOQ_BASE (0x56024000UL) +#else +#define GPIOA_BASE (0x46020000UL) +#define GPIOB_BASE (0x46020400UL) +#define GPIOC_BASE (0x46020800UL) +#define GPIOD_BASE (0x46020C00UL) +#define GPIOE_BASE (0x46021000UL) +#define GPIOF_BASE (0x46021400UL) +#define GPIOG_BASE (0x46021800UL) +#define GPIOH_BASE (0x46021C00UL) +#define GPION_BASE (0x46023400UL) +#define GPIOO_BASE (0x46023800UL) +#define GPIOP_BASE (0x46023C00UL) +#define GPIOQ_BASE (0x46024000UL) +#endif + +/* GPIO register offsets — GPIO_ODR, GPIO_BSRR, GPIO_MODE_* come from + * spi_drv_stm32.h. Define the rest that aren't in the shared header. */ +#define GPIO_MODER(base) (*(volatile uint32_t *)((base) + 0x00)) +#define GPIO_OTYPER(base) (*(volatile uint32_t *)((base) + 0x04)) +#define GPIO_OSPEEDR(base) (*(volatile uint32_t *)((base) + 0x08)) +#define GPIO_PUPDR(base) (*(volatile uint32_t *)((base) + 0x0C)) +#define GPIO_IDR(base) (*(volatile uint32_t *)((base) + 0x10)) +#define GPIO_AFRL(base) (*(volatile uint32_t *)((base) + 0x20)) +#define GPIO_AFRH(base) (*(volatile uint32_t *)((base) + 0x24)) + +/* GPIO speed values */ +#define GPIO_SPEED_LOW 0x0 +#define GPIO_SPEED_MEDIUM 0x1 +#define GPIO_SPEED_HIGH 0x2 +#define GPIO_SPEED_VERY_HIGH 0x3 + + +/*** OCTOSPI (XSPI2) — register definitions from hal/spi/spi_drv_stm32.h ***/ +/* Set base address before including shared OCTOSPI register macros. + * STM32N6 XSPI2 uses the same IP block as OCTOSPI (identical register layout). + */ +#if TZ_SECURE() +#define OCTOSPI_BASE (0x5802A000UL) /* AHB5 secure */ +#else +#define OCTOSPI_BASE (0x4802A000UL) /* AHB5 non-secure */ +#endif +#define OCTOSPI_MEM_BASE (0x70000000UL) /* XSPI2 memory-mapped region */ + +#include "hal/spi/spi_drv_stm32.h" + +/* OCTOSPI bits not defined in spi_drv_stm32.h */ +#define OCTOSPI_CR_FMODE_MMAP OCTOSPI_CR_FMODE(3) /* Memory-mapped mode */ +#define OCTOSPI_SR_TEF (1 << 0) /* Transfer Error Flag */ +#define OCTOSPI_FCR_CTEF (1 << 0) /* Clear Transfer Error Flag */ +#define OCTOSPI_FCR_CTCF (1 << 1) /* Clear Transfer Complete Flag */ +#define OCTOSPI_FCR_CSMF (1 << 3) /* Clear Status Match Flag */ +#define OCTOSPI_DCR1_DLYBYP (1 << 3) /* Bypass delay block (N6-specific) */ + +/*** XSPIM (XSPI I/O Manager) ***/ +#define XSPIM_BASE (0x5802B400UL) +#define XSPIM_CR (*(volatile uint32_t *)(XSPIM_BASE + 0x00)) + +/*** NOR Flash Commands (Macronix MX25UM51245G) ***/ +/* Same commands as src/qspi_flash.c, using 4-byte address variants + * (0x0C/0x12/0x21) instead of entering 4-byte address mode. */ +#define WRITE_ENABLE_CMD 0x06U +#define READ_SR_CMD 0x05U +#define FAST_READ_4B_CMD 0x0CU +#define PAGE_PROG_4B_CMD 0x12U +#define SEC_ERASE_4B_CMD 0x21U /* 4KB sector erase, 4-byte addr */ +#define RESET_ENABLE_CMD 0x66U +#define RESET_MEMORY_CMD 0x99U + +/* NOR flash status register bits */ +#define FLASH_SR_BUSY (1 << 0) /* Write-in-progress */ +#define FLASH_SR_WRITE_EN (1 << 1) /* Write enable latch */ + +/* NOR flash geometry */ +#define FLASH_PAGE_SIZE 256 +#define FLASH_SECTOR_SIZE 0x1000 /* 4KB */ +#define FLASH_DEVICE_SIZE (64 * 1024 * 1024) /* 64MB */ +#define FLASH_DEVICE_SIZE_LOG2 26 /* DEVSIZE: 2^26 = 64MB */ + + +/*** USART — parameterized by base address ***/ +#if TZ_SECURE() +#define USART1_BASE (0x52001000UL) /* APB2 secure */ +#else +#define USART1_BASE (0x42001000UL) /* APB2 non-secure */ +#endif + +#define UART_CR1(base) (*(volatile uint32_t *)((base) + 0x00)) +#define UART_CR2(base) (*(volatile uint32_t *)((base) + 0x04)) +#define UART_CR3(base) (*(volatile uint32_t *)((base) + 0x08)) +#define UART_BRR(base) (*(volatile uint32_t *)((base) + 0x0C)) +#define UART_ISR(base) (*(volatile uint32_t *)((base) + 0x1C)) +#define UART_ICR(base) (*(volatile uint32_t *)((base) + 0x20)) +#define UART_RDR(base) (*(volatile uint32_t *)((base) + 0x24)) +#define UART_TDR(base) (*(volatile uint32_t *)((base) + 0x28)) + +#define USART_CR1_UE (1 << 0) +#define USART_CR1_RE (1 << 2) +#define USART_CR1_TE (1 << 3) +#define USART_CR1_OVER8 (1 << 15) +#define USART_ISR_TXE (1 << 7) +#define USART_ISR_RXNE (1 << 5) +#define USART_ISR_TC (1 << 6) + + +/*** SCB (System Control Block) — Cortex-M55 cache control ***/ +#define SCB_BASE (0xE000ED00UL) +#define SCB_CCR (*(volatile uint32_t *)(SCB_BASE + 0x14)) +#define SCB_CCR_IC (1 << 17) +#define SCB_CCR_DC (1 << 16) + +/* Cache maintenance (Cortex-M55 uses standard ARM CMSIS-like registers) */ +#define SCB_ICIALLU (*(volatile uint32_t *)(0xE000EF50UL)) +#define SCB_DCIMVAC (*(volatile uint32_t *)(0xE000EF5CUL)) +#define SCB_DCISW (*(volatile uint32_t *)(0xE000EF60UL)) +#define SCB_DCCMVAU (*(volatile uint32_t *)(0xE000EF64UL)) +#define SCB_DCCMVAC (*(volatile uint32_t *)(0xE000EF68UL)) +#define SCB_DCCSW (*(volatile uint32_t *)(0xE000EF6CUL)) +#define SCB_DCCIMVAC (*(volatile uint32_t *)(0xE000EF70UL)) +#define SCB_DCCISW (*(volatile uint32_t *)(0xE000EF74UL)) + +/* Cache size ID registers */ +#define CCSIDR (*(volatile uint32_t *)(0xE000ED80UL)) +#define CSSELR (*(volatile uint32_t *)(0xE000ED84UL)) + +/*** AIRCR (Application Interrupt and Reset Control) ***/ +#define AIRCR (*(volatile uint32_t *)(0xE000ED0CUL)) +#define AIRCR_VKEY (0x05FA << 16) +#define AIRCR_SYSRESETREQ (1 << 2) + +/*** SysTick ***/ +#define SYSTICK_BASE (0xE000E010UL) +#define SYSTICK_CSR (*(volatile uint32_t *)(SYSTICK_BASE + 0x00)) +#define SYSTICK_RVR (*(volatile uint32_t *)(SYSTICK_BASE + 0x04)) +#define SYSTICK_CVR (*(volatile uint32_t *)(SYSTICK_BASE + 0x08)) + + +/*** SRAM regions ***/ +#define AXISRAM1_BASE (0x34000000UL) +#define AXISRAM2_BASE (0x34180400UL) +#define AXISRAM3_BASE (0x34200000UL) +#define AXISRAM4_BASE (0x34270000UL) +#define AXISRAM5_BASE (0x342E0000UL) +#define AXISRAM6_BASE (0x34350000UL) + + +#endif /* STM32N6_DEF_INCLUDED */ diff --git a/hal/stm32n6.ld b/hal/stm32n6.ld new file mode 100644 index 0000000000..4447d41454 --- /dev/null +++ b/hal/stm32n6.ld @@ -0,0 +1,56 @@ +/* wolfBoot linker script for STM32N6 + * + * wolfBoot runs from SRAM (Boot ROM copies FSBL from external NOR flash). + * FLASH region is actually AXISRAM1 at 0x34000000. + * RAM is placed higher in SRAM for stack/heap. + */ + +MEMORY +{ + FLASH (rwx) : ORIGIN = @WOLFBOOT_ORIGIN@, LENGTH = @BOOTLOADER_PARTITION_SIZE@ +} + +SECTIONS +{ + .text : + { + _start_text = .; + KEEP(*(.isr_vector)) + *(.text*) + *(.rodata*) + . = ALIGN(4); + _end_text = .; + } > FLASH + + .ARM.exidx : + { + . = ALIGN(4); + *(.ARM.exidx*) + } > FLASH + + _stored_data = .; + .data : + { + _start_data = .; + KEEP(*(.data*)) + . = ALIGN(4); + KEEP(*(.ramcode)) + . = ALIGN(4); + _end_data = .; + } > FLASH + + .bss (NOLOAD) : + { + _start_bss = .; + __bss_start__ = .; + *(.bss*) + *(COMMON) + . = ALIGN(4); + _end_bss = .; + __bss_end__ = .; + _end = .; + } > FLASH + . = ALIGN(4); +} + +END_STACK = ORIGIN(FLASH) + LENGTH(FLASH); diff --git a/src/boot_arm.c b/src/boot_arm.c index 43a65babae..778878a523 100644 --- a/src/boot_arm.c +++ b/src/boot_arm.c @@ -419,9 +419,10 @@ void isr_empty(void) * */ -#ifdef TZEN +#if defined(__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3U) && \ + defined(TZEN) && !defined(CORTEX_M55) #include "hal.h" -#define VTOR (*(volatile uint32_t *)(0xE002ED08)) +#define VTOR (*(volatile uint32_t *)(0xE002ED08)) /* Non-secure VTOR */ #else #define VTOR (*(volatile uint32_t *)(0xE000ED08)) #endif @@ -440,7 +441,7 @@ void RAMFUNCTION do_boot(const uint32_t *app_offset) asm volatile("do_boot_r5:\n" " mov pc, r0\n"); -#elif defined(CORTEX_M33) /* Armv8 boot procedure */ +#elif defined(CORTEX_M33) || defined(CORTEX_M55) /* Armv8 boot procedure */ /* Get stack pointer, entry point */ app_end_stack = (*((uint32_t *)(app_offset))); @@ -451,7 +452,8 @@ void RAMFUNCTION do_boot(const uint32_t *app_offset) /* 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) +# 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)); diff --git a/test-app/ARM-stm32n6.ld b/test-app/ARM-stm32n6.ld new file mode 100644 index 0000000000..0a52577e67 --- /dev/null +++ b/test-app/ARM-stm32n6.ld @@ -0,0 +1,57 @@ +MEMORY +{ + FLASH (rx) : ORIGIN = @WOLFBOOT_TEST_APP_ADDRESS@, LENGTH = @WOLFBOOT_TEST_APP_SIZE@ + RAM (rwx) : ORIGIN = 0x34010000, LENGTH = 64K +} + +SECTIONS +{ + .text : + { + _start_text = .; + KEEP(*(.isr_vector)) + *(.init) + *(.fini) + *(.text*) + KEEP(*(.rodata*)) + . = ALIGN(4); + _end_text = .; + } > FLASH + + .ARM : + { + __exidx_start = .; + *(.ARM.exidx*) + __exidx_end = .; + } > FLASH + + _stored_data = .; + + .data : AT (_stored_data) + { + _start_data = .; + KEEP(*(.data*)) + . = ALIGN(4); + KEEP(*(.ramcode)) + . = ALIGN(4); + _end_data = .; + } > RAM + + .bss : + { + _start_bss = .; + *(.bss*) + *(COMMON) + . = ALIGN(4); + _end_bss = .; + _end = .; + } > RAM +} + +_wolfboot_partition_boot_address = @WOLFBOOT_PARTITION_BOOT_ADDRESS@; +_wolfboot_partition_size = @WOLFBOOT_PARTITION_SIZE@; +_wolfboot_partition_update_address = @WOLFBOOT_PARTITION_UPDATE_ADDRESS@; +_wolfboot_partition_swap_address = @WOLFBOOT_PARTITION_SWAP_ADDRESS@; + +PROVIDE(_start_heap = _end); +PROVIDE(_end_stack = ORIGIN(RAM) + LENGTH(RAM)); diff --git a/test-app/Makefile b/test-app/Makefile index 1d0b1e5ad5..656cee2d27 100644 --- a/test-app/Makefile +++ b/test-app/Makefile @@ -417,6 +417,14 @@ ifeq ($(TARGET),stm32h5) endif endif +ifeq ($(TARGET),stm32n6) + LSCRIPT_TEMPLATE=ARM-stm32n6.ld + CFLAGS+=-mcpu=cortex-m55 -ffunction-sections -fdata-sections -fno-common + LDFLAGS+=-mcpu=cortex-m55 + LDFLAGS+=-Wl,-gc-sections -Wl,-Map=image.map + CFLAGS+=-I.. +endif + ifeq ($(TARGET),stm32u5) ifeq ($(TZEN),1) LSCRIPT_TEMPLATE=ARM-stm32u5-ns.ld @@ -607,6 +615,11 @@ ifeq ($(TARGET),s32k1xx) CFLAGS+=-DRAM_CODE -DDEBUG_UART endif +ifeq ($(TARGET),stm32n6) + CFLAGS+=-DRAM_CODE -DDEBUG_UART + LDFLAGS+=--specs=nosys.specs +endif + ifeq ($(TARGET),mcxw) ifeq ($(TZEN),1) LSCRIPT_TEMPLATE=ARM-mcxw-ns.ld diff --git a/test-app/app_stm32n6.c b/test-app/app_stm32n6.c new file mode 100644 index 0000000000..aa8fbb091d --- /dev/null +++ b/test-app/app_stm32n6.c @@ -0,0 +1,270 @@ +/* app_stm32n6.c + * + * Test bare-metal application for NUCLEO-N657X0-Q. + * + * Copyright (C) 2025 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include +#include +#include "system.h" +#include "hal.h" +#include "wolfboot/wolfboot.h" +#include "target.h" + +/* UART: uart_write() provided by hal/stm32n6.c (linked as stm32n6_ns.o) */ +extern void uart_write(const char *buf, unsigned int len); + +/* --- GPIO / LEDs --- */ +/* With TZEN=1, test-app is compiled with -DNONSECURE_APP and runs in + * non-secure state. Use non-secure peripheral aliases (0x46xxx). */ +#ifdef NONSECURE_APP +#define RCC_BASE (0x46028000UL) +#define PWR_BASE (0x46024800UL) +#define GPIOG_BASE (0x46021800UL) +#else +#define RCC_BASE (0x56028000UL) +#define PWR_BASE (0x56024800UL) +#define GPIOG_BASE (0x56021800UL) +#endif + +#define RCC_AHB4ENR (*(volatile uint32_t *)(RCC_BASE + 0x25C)) +#define RCC_AHB4ENR_GPIOGEN (1 << 6) +#define RCC_AHB4ENR_PWREN (1 << 18) + +#define PWR_SVMCR3 (*(volatile uint32_t *)(PWR_BASE + 0x3C)) +#define PWR_SVMCR3_VDDIO2SV (1 << 8) +#define PWR_SVMCR3_VDDIO3SV (1 << 9) + +#define GPIO_MODER(base) (*(volatile uint32_t *)((base) + 0x00)) +#define GPIO_BSRR(base) (*(volatile uint32_t *)((base) + 0x18)) + +/* User LEDs: active LOW on Port G (LD6=PG0 green, LD7=PG8 blue, LD5=PG10 red) */ +#define LED_GREEN_PIN 0 +#define LED_BLUE_PIN 8 +#define LED_RED_PIN 10 + +static void led_init(void) +{ + uint32_t reg; + + RCC_AHB4ENR |= RCC_AHB4ENR_GPIOGEN | RCC_AHB4ENR_PWREN; + DMB(); + + PWR_SVMCR3 |= PWR_SVMCR3_VDDIO2SV | PWR_SVMCR3_VDDIO3SV; + DMB(); + + /* Set PG0, PG8, PG10 to output mode */ + reg = GPIO_MODER(GPIOG_BASE); + reg &= ~(0x3 << (LED_GREEN_PIN * 2)); + reg |= (0x1 << (LED_GREEN_PIN * 2)); + reg &= ~(0x3 << (LED_BLUE_PIN * 2)); + reg |= (0x1 << (LED_BLUE_PIN * 2)); + reg &= ~(0x3 << (LED_RED_PIN * 2)); + reg |= (0x1 << (LED_RED_PIN * 2)); + GPIO_MODER(GPIOG_BASE) = reg; +} + +static void led_on(uint32_t gpio_base, int pin) +{ + GPIO_BSRR(gpio_base) = (1 << (pin + 16)); /* active LOW */ +} + +static void led_off(uint32_t gpio_base, int pin) +{ + GPIO_BSRR(gpio_base) = (1 << pin); +} + +/* --- SysTick delay --- */ +#define SYSTICK_BASE (0xE000E010UL) +#define SYSTICK_CSR (*(volatile uint32_t *)(SYSTICK_BASE + 0x00)) +#define SYSTICK_RVR (*(volatile uint32_t *)(SYSTICK_BASE + 0x04)) +#define SYSTICK_CVR (*(volatile uint32_t *)(SYSTICK_BASE + 0x08)) +#define HCLK_FREQ 200000000UL /* IC2(400MHz) / AHB prescaler 2 */ +#define SYSTICK_COUNTFLAG (1 << 16) + +static void systick_init(void) +{ + SYSTICK_RVR = (HCLK_FREQ / 1000) - 1; + SYSTICK_CVR = 0; + SYSTICK_CSR = 0x5; /* enable, processor clock, no interrupt */ +} + +static void delay_ms(uint32_t ms) +{ + while (ms > 0) { + while (!(SYSTICK_CSR & SYSTICK_COUNTFLAG)) + ; + ms--; + } +} + +/* --- Helpers --- */ +static const char* state_name(uint8_t state) +{ + switch (state) { + case IMG_STATE_NEW: return "NEW"; + case IMG_STATE_UPDATING: return "UPDATING"; + case IMG_STATE_TESTING: return "TESTING"; + case IMG_STATE_SUCCESS: return "SUCCESS"; + default: return "UNKNOWN"; + } +} + +static void print_partition_info(void) +{ + uint32_t boot_ver, update_ver; + uint8_t boot_state = 0, update_state = 0; + + boot_ver = wolfBoot_current_firmware_version(); + update_ver = wolfBoot_update_firmware_version(); + wolfBoot_get_partition_state(PART_BOOT, &boot_state); + wolfBoot_get_partition_state(PART_UPDATE, &update_state); + + printf("Partition Info\r\n"); + printf(" Boot: version %lu, state %s\r\n", + (unsigned long)boot_ver, state_name(boot_state)); + printf(" Update: version %lu, state %s\r\n", + (unsigned long)update_ver, state_name(update_state)); +} + +/* Note: keystore API (keystore_num_pubkeys, etc.) is in src/keystore.o + * which is not linked into the test-app. Keystore info is printed by + * wolfBoot itself when DEBUG=1. */ + +/* --- Main --- */ +void main(void) +{ + uint32_t version; + uint8_t boot_state = 0; + + /* hal_init() not called -- XSPI2 already configured by wolfBoot for XIP */ + led_init(); + led_on(GPIOG_BASE, LED_GREEN_PIN); + + version = wolfBoot_current_firmware_version(); + + printf("\r\n=== STM32N6 wolfBoot Test App ===\r\n"); + printf("Firmware Version: %lu\r\n", (unsigned long)version); + print_partition_info(); + + /* Auto-handle boot state */ + wolfBoot_get_partition_state(PART_BOOT, &boot_state); + if (boot_state == IMG_STATE_TESTING) { + printf("State TESTING -> marking success\r\n"); + wolfBoot_success(); + } else if (boot_state != IMG_STATE_SUCCESS) { + printf("Calling wolfBoot_success()\r\n"); + wolfBoot_success(); + } + printf("Boot OK (state: %s)\r\n", state_name(boot_state)); + + /* Enable icache for XIP performance (hal_prepare_boot disables it) */ + { + #define SCB_CCR_REG (*(volatile uint32_t *)(0xE000ED14UL)) + #define SCB_ICIALLU_REG (*(volatile uint32_t *)(0xE000EF50UL)) + __asm__ volatile("dsb; isb"); + SCB_ICIALLU_REG = 0; + __asm__ volatile("dsb; isb"); + SCB_CCR_REG |= (1 << 17); /* IC bit */ + __asm__ volatile("dsb; isb"); + } + + systick_init(); + + /* Blink LED based on version: blue for v1, red for v>1 */ + printf("Blinking %s LED\r\n", (version > 1) ? "red" : "blue"); + { + int led_pin = (version > 1) ? LED_RED_PIN : LED_BLUE_PIN; + while (1) { + led_on(GPIOG_BASE, led_pin); + delay_ms(500); + led_off(GPIOG_BASE, led_pin); + delay_ms(500); + } + } +} + +/* --- Syscalls for printf --- */ +int _write(int file, char *ptr, int len) +{ + (void)file; + uart_write(ptr, (unsigned int)len); + return len; +} + +int _read(int file, char *ptr, int len) +{ + (void)file; (void)ptr; (void)len; + return -1; +} + +int _close(int file) +{ + (void)file; + return -1; +} + +int _lseek(int file, int ptr, int dir) +{ + (void)file; (void)ptr; (void)dir; + return 0; +} + +int _isatty(int file) +{ + (void)file; + return 1; +} + +int _fstat(int file, void *st) +{ + (void)file; (void)st; + return 0; +} + +void *_sbrk(int incr) +{ + extern char _end; /* defined by linker */ + static char *heap_end = 0; + char *prev; + + if (heap_end == 0) + heap_end = &_end; + prev = heap_end; + heap_end += incr; + return prev; +} + +int _getpid(void) +{ + return 1; +} + +int _kill(int pid, int sig) +{ + (void)pid; (void)sig; + return -1; +} + +void _exit(int status) +{ + (void)status; + while (1) {} +} diff --git a/tools/config.mk b/tools/config.mk index 034ac550c6..a33beb722a 100644 --- a/tools/config.mk +++ b/tools/config.mk @@ -20,6 +20,7 @@ ifeq ($(ARCH),) VTOR?=1 CORTEX_M0?=0 CORTEX_M33?=0 + CORTEX_M55?=0 CORTEX_M7?=0 CORTEX_M3?=0 NO_ASM?=0 @@ -93,7 +94,7 @@ endif CONFIG_VARS:= ARCH TARGET SIGN HASH MCUXSDK MCUXPRESSO MCUXPRESSO_CPU MCUXPRESSO_DRIVERS \ MCUXPRESSO_CMSIS FREEDOM_E_SDK STM32CUBE CYPRESS_PDL CYPRESS_CORE_LIB CYPRESS_TARGET_LIB DEBUG VTOR \ - CORTEX_M0 CORTEX_M7 CORTEX_M33 NO_ASM EXT_FLASH SPI_FLASH NO_XIP UART_FLASH ALLOW_DOWNGRADE NVM_FLASH_WRITEONCE \ + CORTEX_M0 CORTEX_M7 CORTEX_M33 CORTEX_M55 NO_ASM EXT_FLASH SPI_FLASH NO_XIP UART_FLASH ALLOW_DOWNGRADE NVM_FLASH_WRITEONCE \ DISABLE_BACKUP WOLFBOOT_VERSION V NO_MPU ENCRYPT FLAGS_HOME FLAGS_INVERT \ SPMATH SPMATHALL RAM_CODE DUALBANK_SWAP IMAGE_HEADER_SIZE PKA TZEN PSOC6_CRYPTO \ WOLFTPM WOLFBOOT_TPM_VERIFY MEASURED_BOOT WOLFBOOT_TPM_SEAL WOLFBOOT_TPM_KEYSTORE \ diff --git a/tools/scripts/stm32n6_flash.sh b/tools/scripts/stm32n6_flash.sh new file mode 100755 index 0000000000..2d719c5bc9 --- /dev/null +++ b/tools/scripts/stm32n6_flash.sh @@ -0,0 +1,156 @@ +#!/bin/bash +# +# STM32N6 Flash Script for NUCLEO-N657X0-Q +# Programs NOR flash (MX25UM51245G on XSPI2) and loads wolfBoot to SRAM. +# +# Usage: +# ./tools/scripts/stm32n6_flash.sh # Build and flash all +# ./tools/scripts/stm32n6_flash.sh --skip-build # Flash only +# ./tools/scripts/stm32n6_flash.sh --app-only # Flash app to NOR only +# ./tools/scripts/stm32n6_flash.sh --test-update # Flash v1 + v2 update +# ./tools/scripts/stm32n6_flash.sh --halt # Leave OpenOCD running + +set -e + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +NC='\033[0m' + +SKIP_BUILD=0 +APP_ONLY=0 +TEST_UPDATE=0 +LEAVE_RUNNING=0 + +while [[ $# -gt 0 ]]; do + case $1 in + --skip-build) SKIP_BUILD=1; shift ;; + --app-only) APP_ONLY=1; shift ;; + --test-update) TEST_UPDATE=1; shift ;; + --halt) LEAVE_RUNNING=1; shift ;; + -h|--help) + sed -n '2,/^$/p' "$0" | sed 's/^# \?//' + exit 0 ;; + *) echo -e "${RED}Unknown option: $1${NC}"; exit 1 ;; + esac +done + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +WOLFBOOT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +cd "${WOLFBOOT_ROOT}" + +OPENOCD_CFG="${WOLFBOOT_ROOT}/config/openocd/openocd_stm32n6.cfg" +BOOT_ADDR=0x70020000 +UPDATE_ADDR=0x70120000 + +# Determine SRAM load address from TZEN config +TZEN_CHECK=$(grep -E '^TZEN\?*=' "${WOLFBOOT_ROOT}/.config" 2>/dev/null | head -1 | sed 's/.*=//;s/[[:space:]]//g') +if [ "$TZEN_CHECK" = "1" ]; then + SRAM_ADDR=0x24000000 + SRAM_WORK=0x24020000 +else + SRAM_ADDR=0x34000000 + SRAM_WORK=0x34020000 +fi + +check_tool() { + command -v "$1" &>/dev/null || { echo -e "${RED}Error: $1 not found${NC}"; exit 1; } +} + +check_tool openocd +[ $SKIP_BUILD -eq 0 ] && check_tool arm-none-eabi-gcc +[ -f "${OPENOCD_CFG}" ] || { echo -e "${RED}Error: ${OPENOCD_CFG} not found${NC}"; exit 1; } + +echo -e "${GREEN}=== STM32N6 Flash Script ===${NC}" + +# Build +if [ $SKIP_BUILD -eq 0 ]; then + echo -e "${GREEN}[1/2] Building...${NC}" + + if [ ! -f .config ]; then + [ -f config/examples/stm32n6.config ] || { echo -e "${RED}No .config found${NC}"; exit 1; } + cp config/examples/stm32n6.config .config + fi + + TARGET_CHECK=$(grep -E '^TARGET\?*=' .config | head -1 | sed 's/.*=//;s/[[:space:]]//g') + [ "$TARGET_CHECK" = "stm32n6" ] || { echo -e "${RED}TARGET is '${TARGET_CHECK}', expected 'stm32n6'${NC}"; exit 1; } + + make clean && make wolfboot.bin + echo -e "${GREEN}wolfboot.bin: $(stat -c%s wolfboot.bin 2>/dev/null || stat -f%z wolfboot.bin) bytes${NC}" + + make test-app/image_v1_signed.bin + echo -e "${GREEN}image_v1_signed.bin: $(stat -c%s test-app/image_v1_signed.bin 2>/dev/null || stat -f%z test-app/image_v1_signed.bin) bytes${NC}" + + if [ $TEST_UPDATE -eq 1 ]; then + SIGN_VALUE=$(grep -E '^SIGN\?*=' .config | head -1 | sed 's/.*=//;s/[[:space:]]//g') + HASH_VALUE=$(grep -E '^HASH\?*=' .config | head -1 | sed 's/.*=//;s/[[:space:]]//g') + [ -f tools/keytools/sign ] || make -C tools/keytools + ./tools/keytools/sign \ + --$(echo "$SIGN_VALUE" | tr '[:upper:]' '[:lower:]') \ + --$(echo "$HASH_VALUE" | tr '[:upper:]' '[:lower:]') \ + test-app/image.bin wolfboot_signing_private_key.der 2 + echo -e "${GREEN}image_v2_signed.bin built${NC}" + fi +else + echo -e "${YELLOW}[1/2] Skipping build${NC}" +fi + +# Verify binaries +[ $APP_ONLY -eq 0 ] && [ ! -f wolfboot.bin ] && { echo -e "${RED}wolfboot.bin not found${NC}"; exit 1; } +[ -f test-app/image_v1_signed.bin ] || { echo -e "${RED}image_v1_signed.bin not found${NC}"; exit 1; } +[ $TEST_UPDATE -eq 1 ] && [ ! -f test-app/image_v2_signed.bin ] && { echo -e "${RED}image_v2_signed.bin not found${NC}"; exit 1; } + +# Flash via OpenOCD +echo -e "${GREEN}[2/2] Programming via OpenOCD...${NC}" +pkill -x openocd 2>/dev/null || true +sleep 1 + +OPENOCD_CMDS="reset init; " + +if [ $APP_ONLY -eq 0 ]; then + echo -e "${CYAN} wolfboot.bin -> SRAM ${SRAM_ADDR}${NC}" + OPENOCD_CMDS+="load_image ${WOLFBOOT_ROOT}/wolfboot.bin ${SRAM_ADDR} bin; " +fi + +echo -e "${CYAN} image_v1_signed.bin -> NOR ${BOOT_ADDR}${NC}" +OPENOCD_CMDS+="flash write_image erase ${WOLFBOOT_ROOT}/test-app/image_v1_signed.bin ${BOOT_ADDR}; " + +if [ $TEST_UPDATE -eq 1 ]; then + echo -e "${CYAN} image_v2_signed.bin -> NOR ${UPDATE_ADDR}${NC}" + OPENOCD_CMDS+="flash write_image erase ${WOLFBOOT_ROOT}/test-app/image_v2_signed.bin ${UPDATE_ADDR}; " + + # Write update trigger: "pBOOT" (0x70 0x42 0x4F 0x4F 0x54) at end of + # update partition. wolfBoot reads this trailer to detect a pending update. + # Address: UPDATE_ADDR + PARTITION_SIZE - 5 + PART_SIZE=$(grep -E '^WOLFBOOT_PARTITION_SIZE' "${WOLFBOOT_ROOT}/.config" | head -1 | sed 's/.*=//;s/[[:space:]]//g') + TRIGGER_ADDR=$(printf "0x%08x" $(( ${UPDATE_ADDR} + ${PART_SIZE} - 5 ))) + echo -e "${CYAN} Update trigger -> NOR ${TRIGGER_ADDR}${NC}" + printf 'pBOOT' > /tmp/trigger_magic.bin + OPENOCD_CMDS+="flash write_image /tmp/trigger_magic.bin ${TRIGGER_ADDR}; " +fi + +# Boot wolfBoot from SRAM (reset would clear SRAM, so we jump directly) +if [ $APP_ONLY -eq 0 ]; then + # Extract initial SP (word 0) and entry point (word 1) from vector table + INIT_SP=$(od -A n -t x4 -N 4 "${WOLFBOOT_ROOT}/wolfboot.bin" | awk '{print "0x"$1}') + ENTRY_ADDR=$(od -A n -j 4 -t x4 -N 4 "${WOLFBOOT_ROOT}/wolfboot.bin" | awk '{print "0x"$1}') + ENTRY_THUMB=$(printf "0x%08x" $(( ${ENTRY_ADDR} | 1 ))) + echo -e "${CYAN} Booting wolfBoot (SP: ${INIT_SP}, entry: ${ENTRY_THUMB})...${NC}" + OPENOCD_CMDS+="reg msplim_s 0x00000000; " + OPENOCD_CMDS+="reg psplim_s 0x00000000; " + OPENOCD_CMDS+="reg msp ${INIT_SP}; " + OPENOCD_CMDS+="mww 0xE000ED08 ${SRAM_ADDR}; " # VTOR + OPENOCD_CMDS+="mww 0xE000ED28 0xFFFFFFFF; " # Clear CFSR + OPENOCD_CMDS+="resume ${ENTRY_THUMB}; " +fi + +if [ $LEAVE_RUNNING -eq 0 ]; then + OPENOCD_CMDS+="shutdown" +else + OPENOCD_CMDS+="echo {OpenOCD running. Connect via: telnet localhost 4444}" +fi + +openocd -f "${OPENOCD_CFG}" -c "${OPENOCD_CMDS}" + +echo -e "${GREEN}=== Done ===${NC}"