diff --git a/.github/workflows/test-configs.yml b/.github/workflows/test-configs.yml index a84cde0ccc..3918460111 100644 --- a/.github/workflows/test-configs.yml +++ b/.github/workflows/test-configs.yml @@ -449,6 +449,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 b993abb6dd..e34f2415cf 100644 --- a/Makefile +++ b/Makefile @@ -269,6 +269,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 @@ -647,6 +652,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 10f4c6a0a1..ede4d12ce0 100644 --- a/arch.mk +++ b/arch.mk @@ -263,6 +263,17 @@ ifeq ($(ARCH),ARM) endif + ifeq ($(TARGET),stm32n6) + CORTEX_M33=1 + CFLAGS+=-Ihal -mcpu=cortex-m55 + LDFLAGS+=-mcpu=cortex-m55 + ARCH_FLASH_OFFSET=0x70000000 + WOLFBOOT_ORIGIN=0x34000000 + EXT_FLASH=1 + PART_UPDATE_EXT=1 + PART_SWAP_EXT=1 + endif + ifeq ($(TARGET),rp2350) CORTEX_M33=1 CFLAGS+=-Ihal diff --git a/config/examples/stm32n6.config b/config/examples/stm32n6.config new file mode 100644 index 0000000000..6566f6d216 --- /dev/null +++ b/config/examples/stm32n6.config @@ -0,0 +1,26 @@ +ARCH?=ARM +TARGET?=stm32n6 +SIGN?=ECC256 +HASH?=SHA256 +DEBUG?=0 +VTOR?=1 +CORTEX_M0?=0 +CORTEX_M33?=1 +NO_ASM?=0 +NO_MPU=1 +EXT_FLASH?=1 +PART_UPDATE_EXT?=1 +PART_SWAP_EXT?=1 +SPI_FLASH?=0 +ALLOW_DOWNGRADE?=0 +NVM_FLASH_WRITEONCE?=0 +WOLFBOOT_VERSION?=1 +V?=0 +SPMATH?=1 +RAM_CODE?=0 +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 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 3217652c0d..82052f9eb0 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -41,6 +41,7 @@ This README describes configuration of supported targets. * [STM32F7](#stm32f7) * [STM32G0](#stm32g0) * [STM32H5](#stm32h5) +* [STM32N6](#stm32n6) * [STM32H7](#stm32h7) * [STM32L0](#stm32l0) * [STM32L4](#stm32l4) @@ -1613,6 +1614,123 @@ 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 (0x34000000): + 0x34000000 wolfBoot (loaded to SRAM via SWD or Boot ROM FSBL copy) + 0x34020000 Stack / work area +``` + +### Build and Flash + +Use the example configuration and build: + +```sh +cp config/examples/stm32n6.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 at 0x34000000 +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` +- Boot partition at 0x70020000 (XIP, not marked EXT) +- Update/swap partitions use device-relative offsets +- 4KB sector size (`WOLFBOOT_SECTOR_SIZE=0x1000`) +- ECC256 + SHA256 for signature verification + +### 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. +- Calling `wolfBoot_success()` requires all flash write functions to be placed + in RAM (RAMFUNCTION). The HAL flash functions in `hal/stm32n6.c` need the + RAMFUNCTION attribute for this to work from an XIP application. + +### 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 +load_image wolfboot.bin 0x34000000 bin +reg msplim_s 0x00000000 +reg psplim_s 0x00000000 +reg msp 0x34020000 +mww 0xE000ED08 0x34000000 +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..822a24bb5f --- /dev/null +++ b/hal/stm32n6.c @@ -0,0 +1,534 @@ +/* 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" + +/* 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 + +/* XSPI2 indirect-mode command helper */ +static int RAMFUNCTION xspi_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 ((XSPI2_CR & XSPI_CR_FMODE_MASK) == XSPI_CR_FMODE_MMAP) { + XSPI2_CR |= XSPI_CR_ABORT; + while (XSPI2_CR & XSPI_CR_ABORT) + ; + } + while (XSPI2_SR & XSPI_SR_BUSY) + ; + XSPI2_FCR = XSPI_FCR_CTCF | XSPI_FCR_CTEF | XSPI_FCR_CSMF; + + XSPI2_CR = (XSPI2_CR & ~XSPI_CR_FMODE_MASK) | XSPI_CR_FMODE(fmode); + + if (dataSz > 0) { + XSPI2_DLR = dataSz - 1; + } + + ccr = XSPI_CCR_IMODE(XSPI_MODE_SINGLE) | XSPI_CCR_ISIZE(0); + if (addrMode != XSPI_MODE_NONE) { + ccr |= XSPI_CCR_ADMODE(addrMode) | XSPI_CCR_ADSIZE(3); + } + if (dataMode != XSPI_MODE_NONE) { + ccr |= XSPI_CCR_DMODE(dataMode); + } + XSPI2_CCR = ccr; + XSPI2_TCR = XSPI_TCR_DCYC(dummyCycles); + XSPI2_IR = cmd; + + if (addrMode != XSPI_MODE_NONE) { + XSPI2_AR = addr; + } + + if (dataSz > 0 && data != NULL) { + while (dataSz >= 4) { + if (fmode == 0) { + while (!(XSPI2_SR & (XSPI_SR_FTF | XSPI_SR_TEF))) + ; + if (XSPI2_SR & XSPI_SR_TEF) goto xspi_err; + XSPI2_DR32 = *(uint32_t *)data; + } else { + while (!(XSPI2_SR & (XSPI_SR_FTF | XSPI_SR_TCF | + XSPI_SR_TEF))) + ; + if (XSPI2_SR & XSPI_SR_TEF) goto xspi_err; + *(uint32_t *)data = XSPI2_DR32; + } + data += 4; + dataSz -= 4; + } + while (dataSz > 0) { + if (fmode == 0) { + while (!(XSPI2_SR & (XSPI_SR_FTF | XSPI_SR_TEF))) + ; + if (XSPI2_SR & XSPI_SR_TEF) goto xspi_err; + XSPI2_DR = *data; + } else { + while (!(XSPI2_SR & (XSPI_SR_FTF | XSPI_SR_TCF | + XSPI_SR_TEF))) + ; + if (XSPI2_SR & XSPI_SR_TEF) goto xspi_err; + *data = XSPI2_DR; + } + data++; + dataSz--; + } + } + + while (!(XSPI2_SR & (XSPI_SR_TCF | XSPI_SR_TEF))) + ; + if (XSPI2_SR & XSPI_SR_TEF) goto xspi_err; + XSPI2_FCR = XSPI_FCR_CTCF; + + return 0; + +xspi_err: + XSPI2_FCR = XSPI_FCR_CTEF; + XSPI2_CR |= XSPI_CR_ABORT; + while (XSPI2_CR & XSPI_CR_ABORT) + ; + return -1; +} + +static void RAMFUNCTION xspi_write_enable(void) +{ + xspi_cmd(0, NOR_CMD_WRITE_ENABLE, 0, XSPI_MODE_NONE, + NULL, 0, XSPI_MODE_NONE, 0); +} + +static void RAMFUNCTION xspi_wait_ready(void) +{ + uint8_t sr; + do { + sr = 0; + xspi_cmd(1, NOR_CMD_READ_SR, 0, XSPI_MODE_NONE, + &sr, 1, XSPI_MODE_SINGLE, 0); + } while (sr & NOR_SR_WIP); +} + +static void RAMFUNCTION xspi_enable_mmap(void) +{ + /* Abort first if already in mmap mode (BUSY stays set in mmap) */ + if ((XSPI2_CR & XSPI_CR_FMODE_MASK) == XSPI_CR_FMODE_MMAP) { + XSPI2_CR |= XSPI_CR_ABORT; + while (XSPI2_CR & XSPI_CR_ABORT) + ; + } + while (XSPI2_SR & XSPI_SR_BUSY) + ; + XSPI2_FCR = XSPI_FCR_CTCF | XSPI_FCR_CTEF | XSPI_FCR_CSMF; + + XSPI2_CR = (XSPI2_CR & ~XSPI_CR_FMODE_MASK) | XSPI_CR_FMODE_MMAP; + + /* Fast read: single SPI, 4-byte addr, 8 dummy cycles */ + XSPI2_CCR = XSPI_CCR_IMODE(XSPI_MODE_SINGLE) | + XSPI_CCR_ISIZE(0) | + XSPI_CCR_ADMODE(XSPI_MODE_SINGLE) | + XSPI_CCR_ADSIZE(3) | + XSPI_CCR_DMODE(XSPI_MODE_SINGLE); + XSPI2_TCR = XSPI_TCR_DCYC(8) | XSPI_TCR_SSHIFT; + XSPI2_IR = NOR_CMD_FAST_READ_4B; + + 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) +{ + SCB_CCR &= ~SCB_CCR_DC; + DSB(); + ISB(); +} + +/* XSPI2 GPIO: PN0-PN11 as AF9 (DQS, CLK, NCS, IO0-IO7) */ +static void xspi2_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 xspi2_init(void) +{ + volatile uint32_t delay; + + RCC_AHB5ENR |= RCC_AHB5ENR_XSPI2EN | RCC_AHB5ENR_XSPIMEN; + RCC_MISCENR |= RCC_MISCENR_XSPIPHYCOMPEN; + DMB(); + + XSPI2_CR = 0; + while (XSPI2_SR & XSPI_SR_BUSY) + ; + + XSPI2_DCR1 = XSPI_DCR1_DLYBYP | + XSPI_DCR1_DEVSIZE(NOR_DEVICE_SIZE_LOG2) | + XSPI_DCR1_CSHT(3); + XSPI2_DCR2 = XSPI_DCR2_PRESCALER(16); + while (XSPI2_SR & XSPI_SR_BUSY) + ; + + XSPI2_CR = XSPI_CR_FTHRES(1) | XSPI_CR_EN; + + /* NOR flash software reset */ + xspi_cmd(0, NOR_CMD_RESET_ENABLE, 0, XSPI_MODE_NONE, + NULL, 0, XSPI_MODE_NONE, 0); + xspi_cmd(0, NOR_CMD_RESET_MEMORY, 0, XSPI_MODE_NONE, + NULL, 0, XSPI_MODE_NONE, 0); + for (delay = 0; delay < 100000; delay++) + ; + + xspi_enable_mmap(); +} + +static void clock_config(void) +{ + /* HSI at 64 MHz (PLL configuration deferred) */ + RCC_CR |= RCC_CR_HSION; + while (!(RCC_SR & RCC_SR_HSIRDY)) + ; +} + +#ifdef DEBUG_UART +/* USART1 on PE5 (TX) / PE6 (RX), AF7 */ + +#define UART_CLOCK_FREQ 64000000UL + +static void uart_init(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 */ + USART1_CR1 = 0; + USART1_CR2 = 0; + USART1_CR3 = 0; + USART1_BRR = (UART_CLOCK_FREQ + baud / 2) / baud; + USART1_CR1 = USART_CR1_TE | USART_CR1_RE | USART_CR1_UE; +} + +static void uart_write(const char *buf, int len) +{ + int i; + for (i = 0; i < len; i++) { + while (!(USART1_ISR & USART_ISR_TXE)) + ; + USART1_TDR = buf[i]; + } + while (!(USART1_ISR & USART_ISR_TC)) + ; +} +#endif + +/* Mark VDDIO supplies valid (required for XSPI2 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) +{ + clock_config(); + pwr_enable_io_supply(); + icache_enable(); + dcache_enable(); + xspi2_gpio_init(); + xspi2_init(); + +#ifdef DEBUG_UART + uart_init(115200); + uart_write("wolfBoot Init\n", 14); +#endif +} + +void hal_prepare_boot(void) +{ + xspi_enable_mmap(); + dcache_disable(); + icache_disable(); +} + +int RAMFUNCTION hal_flash_write(uint32_t address, const uint8_t *data, int len) +{ + uint32_t offset; + uint32_t page_off, write_sz; + int total = len; + + if (len <= 0) + return 0; + + offset = address - XSPI2_MEM_BASE; + + while (len > 0) { + page_off = offset & (NOR_PAGE_SIZE - 1); + write_sz = NOR_PAGE_SIZE - page_off; + if ((int)write_sz > len) + write_sz = len; + + xspi_write_enable(); + xspi_cmd(0, NOR_CMD_PAGE_PROG_4B, + offset, XSPI_MODE_SINGLE, + (uint8_t *)data, write_sz, XSPI_MODE_SINGLE, 0); + + xspi_wait_ready(); + + offset += write_sz; + data += write_sz; + len -= write_sz; + } + + xspi_enable_mmap(); + dcache_clean_invalidate_by_addr(address, total); + + return 0; +} + +int RAMFUNCTION hal_flash_erase(uint32_t address, int len) +{ + uint32_t offset; + uint32_t end; + + if (len <= 0) + return -1; + + offset = address - XSPI2_MEM_BASE; + end = offset + len; + + while (offset < end) { + xspi_write_enable(); + xspi_cmd(0, NOR_CMD_SECTOR_ERASE_4B, + offset, XSPI_MODE_SINGLE, + NULL, 0, XSPI_MODE_NONE, 0); + + xspi_wait_ready(); + offset += NOR_SECTOR_SIZE; + } + + xspi_enable_mmap(); + dcache_clean_invalidate_by_addr(address, len); + + return 0; +} + +void RAMFUNCTION hal_flash_unlock(void) +{ +} + +void RAMFUNCTION hal_flash_lock(void) +{ +} + +/* ext_flash API: device-relative offsets (update/swap partitions) */ + +int RAMFUNCTION ext_flash_read(uintptr_t address, uint8_t *data, int len) +{ + if (len <= 0) + return 0; + + xspi_cmd(1, NOR_CMD_FAST_READ_4B, + (uint32_t)address, XSPI_MODE_SINGLE, + data, len, XSPI_MODE_SINGLE, 8); + + xspi_enable_mmap(); + + return len; +} + +int RAMFUNCTION ext_flash_write(uintptr_t address, const uint8_t *data, int len) +{ + uint32_t offset = (uint32_t)address; + uint32_t page_off, write_sz; + const uint8_t *src = data; + int remaining = len; + + if (len <= 0) + return 0; + + while (remaining > 0) { + page_off = offset & (NOR_PAGE_SIZE - 1); + write_sz = NOR_PAGE_SIZE - page_off; + if ((int)write_sz > remaining) + write_sz = remaining; + + xspi_write_enable(); + + xspi_cmd(0, NOR_CMD_PAGE_PROG_4B, + offset, XSPI_MODE_SINGLE, + (uint8_t *)src, write_sz, XSPI_MODE_SINGLE, 0); + + xspi_wait_ready(); + + offset += write_sz; + src += write_sz; + remaining -= write_sz; + } + + xspi_enable_mmap(); + + return 0; +} + +int RAMFUNCTION ext_flash_erase(uintptr_t address, int len) +{ + uint32_t offset = (uint32_t)address; + uint32_t end; + + if (len <= 0) + return -1; + + end = offset + len; + + while (offset < end) { + xspi_write_enable(); + + xspi_cmd(0, NOR_CMD_SECTOR_ERASE_4B, + offset, XSPI_MODE_SINGLE, + NULL, 0, XSPI_MODE_NONE, 0); + + xspi_wait_ready(); + offset += NOR_SECTOR_SIZE; + } + + xspi_enable_mmap(); + + return 0; +} + +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..abf0370e87 --- /dev/null +++ b/hal/stm32n6.h @@ -0,0 +1,444 @@ +/* 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 + +/*** RCC (Reset and Clock Control) — base 0x56028000 (secure) ***/ +#define RCC_BASE (0x56028000UL) + +/* RCC_CR: control register — enable bits */ +#define RCC_CR (*(volatile uint32_t *)(RCC_BASE + 0x00)) +#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: 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 (8) +#define RCC_CFGR2_PPRE4_MASK (0x7 << 8) +#define RCC_CFGR2_PPRE5_SHIFT (12) +#define RCC_CFGR2_PPRE5_MASK (0x7 << 12) + +/* 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_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)) +#define RCC_PLL1CFGR3 (*(volatile uint32_t *)(RCC_BASE + 0x88)) + +/* 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 and bus enable registers */ +#define RCC_DIVENR (*(volatile uint32_t *)(RCC_BASE + 0x240)) +#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) ***/ +#define PWR_BASE (0x56024800UL) + +#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 fields */ +#define PWR_VOSCR_VOS (1 << 0) /* 0=Scale2, 1=Scale1 */ +#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 ***/ +#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) + +/* GPIO register offsets (same as H5/H7) */ +#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_ODR(base) (*(volatile uint32_t *)((base) + 0x14)) +#define GPIO_BSRR(base) (*(volatile uint32_t *)((base) + 0x18)) +#define GPIO_AFRL(base) (*(volatile uint32_t *)((base) + 0x20)) +#define GPIO_AFRH(base) (*(volatile uint32_t *)((base) + 0x24)) + +/* GPIO mode values */ +#define GPIO_MODE_INPUT 0x0 +#define GPIO_MODE_OUTPUT 0x1 +#define GPIO_MODE_AF 0x2 +#define GPIO_MODE_ANALOG 0x3 + +/* GPIO speed values */ +#define GPIO_SPEED_LOW 0x0 +#define GPIO_SPEED_MEDIUM 0x1 +#define GPIO_SPEED_HIGH 0x2 +#define GPIO_SPEED_VERY_HIGH 0x3 + + +/*** XSPI2 (External SPI for NOR flash) ***/ +#define XSPI2_BASE (0x5802A000UL) +#define XSPI2_MEM_BASE (0x70000000UL) + +#define XSPI2_CR (*(volatile uint32_t *)(XSPI2_BASE + 0x00)) +#define XSPI2_DCR1 (*(volatile uint32_t *)(XSPI2_BASE + 0x08)) +#define XSPI2_DCR2 (*(volatile uint32_t *)(XSPI2_BASE + 0x0C)) +#define XSPI2_DCR3 (*(volatile uint32_t *)(XSPI2_BASE + 0x10)) +#define XSPI2_DCR4 (*(volatile uint32_t *)(XSPI2_BASE + 0x14)) +#define XSPI2_SR (*(volatile uint32_t *)(XSPI2_BASE + 0x20)) +#define XSPI2_FCR (*(volatile uint32_t *)(XSPI2_BASE + 0x24)) +#define XSPI2_DLR (*(volatile uint32_t *)(XSPI2_BASE + 0x40)) +#define XSPI2_AR (*(volatile uint32_t *)(XSPI2_BASE + 0x48)) +#define XSPI2_DR (*(volatile uint8_t *)(XSPI2_BASE + 0x50)) +#define XSPI2_DR32 (*(volatile uint32_t *)(XSPI2_BASE + 0x50)) +#define XSPI2_PSMKR (*(volatile uint32_t *)(XSPI2_BASE + 0x80)) +#define XSPI2_PSMAR (*(volatile uint32_t *)(XSPI2_BASE + 0x88)) +#define XSPI2_PIR (*(volatile uint32_t *)(XSPI2_BASE + 0x90)) +#define XSPI2_CCR (*(volatile uint32_t *)(XSPI2_BASE + 0x100)) +#define XSPI2_TCR (*(volatile uint32_t *)(XSPI2_BASE + 0x108)) +#define XSPI2_IR (*(volatile uint32_t *)(XSPI2_BASE + 0x110)) +#define XSPI2_ABR (*(volatile uint32_t *)(XSPI2_BASE + 0x120)) +#define XSPI2_LPTR (*(volatile uint32_t *)(XSPI2_BASE + 0x130)) +#define XSPI2_WCCR (*(volatile uint32_t *)(XSPI2_BASE + 0x180)) +#define XSPI2_WTCR (*(volatile uint32_t *)(XSPI2_BASE + 0x188)) +#define XSPI2_WIR (*(volatile uint32_t *)(XSPI2_BASE + 0x190)) +#define XSPI2_WABR (*(volatile uint32_t *)(XSPI2_BASE + 0x1A0)) + +/* XSPI CR fields */ +#define XSPI_CR_EN (1 << 0) +#define XSPI_CR_ABORT (1 << 1) +#define XSPI_CR_FSEL (1 << 7) +#define XSPI_CR_FTHRES_SHIFT (8) +#define XSPI_CR_FTHRES_MASK (0x3F << 8) +#define XSPI_CR_FTHRES(n) ((((n) - 1) & 0x3F) << 8) +#define XSPI_CR_TCIE (1 << 17) +#define XSPI_CR_FTIE (1 << 18) +#define XSPI_CR_FMODE_SHIFT (28) +#define XSPI_CR_FMODE_MASK (0x3 << 28) +#define XSPI_CR_FMODE(m) (((m) & 0x3) << 28) +#define XSPI_CR_FMODE_IWRITE XSPI_CR_FMODE(0) +#define XSPI_CR_FMODE_IREAD XSPI_CR_FMODE(1) +#define XSPI_CR_FMODE_AUTOPOLL XSPI_CR_FMODE(2) +#define XSPI_CR_FMODE_MMAP XSPI_CR_FMODE(3) + +/* XSPI DCR1 fields */ +#define XSPI_DCR1_CKMODE_3 (1 << 0) +#define XSPI_DCR1_FRCK (1 << 1) +#define XSPI_DCR1_DLYBYP (1 << 3) /* Bypass delay block (DLL) */ +#define XSPI_DCR1_CSHT_SHIFT (8) +#define XSPI_DCR1_CSHT_MASK (0x3F << 8) +#define XSPI_DCR1_CSHT(n) (((n) & 0x3F) << 8) +#define XSPI_DCR1_DEVSIZE_SHIFT (16) +#define XSPI_DCR1_DEVSIZE_MASK (0x1F << 16) +#define XSPI_DCR1_DEVSIZE(n) (((n) & 0x1F) << 16) +#define XSPI_DCR1_MTYP_SHIFT (24) +#define XSPI_DCR1_MTYP_MASK (0x7 << 24) +#define XSPI_DCR1_MTYP(n) (((n) & 0x7) << 24) + +/* XSPI DCR2 fields */ +#define XSPI_DCR2_PRESCALER_SHIFT (0) +#define XSPI_DCR2_PRESCALER_MASK (0xFF) +#define XSPI_DCR2_PRESCALER(n) (((n) - 1) & 0xFF) + +/* XSPI SR fields */ +#define XSPI_SR_TEF (1 << 0) +#define XSPI_SR_TCF (1 << 1) +#define XSPI_SR_FTF (1 << 2) +#define XSPI_SR_SMF (1 << 3) +#define XSPI_SR_BUSY (1 << 5) +#define XSPI_SR_FLEVEL_SHIFT (8) +#define XSPI_SR_FLEVEL_MASK (0x3F << 8) + +/* XSPI FCR fields */ +#define XSPI_FCR_CTEF (1 << 0) +#define XSPI_FCR_CTCF (1 << 1) +#define XSPI_FCR_CSMF (1 << 3) + +/* XSPI CCR fields (Communication Configuration Register) */ +#define XSPI_CCR_IMODE_SHIFT (0) +#define XSPI_CCR_IMODE_MASK (0x7) +#define XSPI_CCR_IMODE(n) (((n) & 0x7) << 0) +#define XSPI_CCR_ISIZE_SHIFT (4) +#define XSPI_CCR_ISIZE(n) (((n) & 0x3) << 4) +#define XSPI_CCR_ADMODE_SHIFT (8) +#define XSPI_CCR_ADMODE(n) (((n) & 0x7) << 8) +#define XSPI_CCR_ADSIZE_SHIFT (12) +#define XSPI_CCR_ADSIZE(n) (((n) & 0x3) << 12) +#define XSPI_CCR_ABMODE_SHIFT (16) +#define XSPI_CCR_ABMODE(n) (((n) & 0x7) << 16) +#define XSPI_CCR_ABSIZE_SHIFT (20) +#define XSPI_CCR_ABSIZE(n) (((n) & 0x3) << 20) +#define XSPI_CCR_DMODE_SHIFT (24) +#define XSPI_CCR_DMODE(n) (((n) & 0x7) << 24) +#define XSPI_CCR_DDTR (1 << 27) +#define XSPI_CCR_SIOO (1 << 31) + +/* XSPI TCR fields */ +#define XSPI_TCR_DCYC_SHIFT (0) +#define XSPI_TCR_DCYC_MASK (0x1F) +#define XSPI_TCR_DCYC(n) (((n) & 0x1F) << 0) +#define XSPI_TCR_DHQC (1 << 28) +#define XSPI_TCR_SSHIFT (1 << 30) + +/* SPI mode values: 0=none, 1=single, 2=dual, 3=quad, 4=octal */ +#define XSPI_MODE_NONE 0 +#define XSPI_MODE_SINGLE 1 +#define XSPI_MODE_DUAL 2 +#define XSPI_MODE_QUAD 3 +#define XSPI_MODE_OCTAL 4 + + +/*** XSPIM (XSPI I/O Manager) ***/ +#define XSPIM_BASE (0x5802B400UL) +#define XSPIM_CR (*(volatile uint32_t *)(XSPIM_BASE + 0x00)) + +/*** NOR Flash Commands (Macronix MX25UM51245G) ***/ +/* Single-SPI mode commands (initial boot) */ +#define NOR_CMD_WRITE_ENABLE 0x06 +#define NOR_CMD_WRITE_DISABLE 0x04 +#define NOR_CMD_READ_SR 0x05 +#define NOR_CMD_READ_ID 0x9F +#define NOR_CMD_FAST_READ_4B 0x0C +#define NOR_CMD_PAGE_PROG_4B 0x12 +#define NOR_CMD_SECTOR_ERASE_4B 0x21 +#define NOR_CMD_BLOCK_ERASE_4B 0xDC +#define NOR_CMD_RESET_ENABLE 0x66 +#define NOR_CMD_RESET_MEMORY 0x99 + +/* NOR flash status register bits */ +#define NOR_SR_WIP (1 << 0) +#define NOR_SR_WEL (1 << 1) + +/* NOR flash geometry */ +#define NOR_PAGE_SIZE 256 +#define NOR_SECTOR_SIZE 0x1000 /* 4KB */ +#define NOR_BLOCK_SIZE 0x10000 /* 64KB */ +#define NOR_DEVICE_SIZE (64 * 1024 * 1024) /* 64MB */ +#define NOR_DEVICE_SIZE_LOG2 26 /* 2^26 = 64MB */ + + +/*** USART1 (Debug UART) ***/ +#define USART1_BASE (0x52001000UL) + +#define USART1_CR1 (*(volatile uint32_t *)(USART1_BASE + 0x00)) +#define USART1_CR2 (*(volatile uint32_t *)(USART1_BASE + 0x04)) +#define USART1_CR3 (*(volatile uint32_t *)(USART1_BASE + 0x08)) +#define USART1_BRR (*(volatile uint32_t *)(USART1_BASE + 0x0C)) +#define USART1_ISR (*(volatile uint32_t *)(USART1_BASE + 0x1C)) +#define USART1_ICR (*(volatile uint32_t *)(USART1_BASE + 0x20)) +#define USART1_RDR (*(volatile uint32_t *)(USART1_BASE + 0x24)) +#define USART1_TDR (*(volatile uint32_t *)(USART1_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..0b8a9229b0 --- /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 + + .edidx : + { + . = 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/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 514f00cf4c..da78940f71 100644 --- a/test-app/Makefile +++ b/test-app/Makefile @@ -364,6 +364,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 @@ -530,6 +538,10 @@ ifeq ($(TARGET),s32k1xx) CFLAGS+=-DRAM_CODE -DDEBUG_UART endif +ifeq ($(TARGET),stm32n6) + CFLAGS+=-DRAM_CODE +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..1d46ff8367 --- /dev/null +++ b/test-app/app_stm32n6.c @@ -0,0 +1,120 @@ +/* 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 "system.h" +#include "hal.h" +#include "wolfboot/wolfboot.h" +#include "target.h" + +#define RCC_BASE (0x56028000UL) +#define RCC_AHB4ENR (*(volatile uint32_t *)(RCC_BASE + 0x25C)) +#define RCC_AHB4ENR_GPIOGEN (1 << 6) +#define RCC_AHB4ENR_PWREN (1 << 18) + +/* PWR I/O supply valid bits (required for Port G output) */ +#define PWR_BASE (0x56024800UL) +#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_OSPEEDR(base) (*(volatile uint32_t *)((base) + 0x08)) +#define GPIO_PUPDR(base) (*(volatile uint32_t *)((base) + 0x0C)) +#define GPIO_BSRR(base) (*(volatile uint32_t *)((base) + 0x18)) + +#define GPIOG_BASE (0x56021800UL) + +/* 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(); + + /* Mark I/O supply valid for Port G */ + 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; +} + +/* Active LOW: BSRR reset = ON, BSRR set = OFF */ +static void led_on(uint32_t gpio_base, int pin) +{ + GPIO_BSRR(gpio_base) = (1 << (pin + 16)); +} + +static void led_off(uint32_t gpio_base, int pin) +{ + GPIO_BSRR(gpio_base) = (1 << pin); +} + +static void delay(volatile uint32_t count) +{ + while (count--) + ; +} + +volatile uint32_t app_running __attribute__((section(".data"))) = 0; + +void main(void) +{ + /* hal_init() not called — XSPI2 already configured by wolfBoot for XIP */ + led_init(); + + app_running = 0xCAFEBEEF; + (void)wolfBoot_current_firmware_version(); + + led_on(GPIOG_BASE, LED_GREEN_PIN); + + /* Test flash erase from XIP (RAMFUNCTION verification) */ + hal_flash_unlock(); + hal_flash_erase(0x70010000, 0x1000); + hal_flash_lock(); + + app_running = 0xF1A5F1A5; + + /* Mark firmware stable (flash ops are RAMFUNCTION, safe from XIP) */ + wolfBoot_success(); + + while (1) { + led_on(GPIOG_BASE, LED_BLUE_PIN); + delay(2000000); + led_off(GPIOG_BASE, LED_BLUE_PIN); + delay(2000000); + } +} diff --git a/tools/scripts/stm32n6_flash.sh b/tools/scripts/stm32n6_flash.sh new file mode 100755 index 0000000000..47bc7bbb3d --- /dev/null +++ b/tools/scripts/stm32n6_flash.sh @@ -0,0 +1,135 @@ +#!/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 + +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 -9 openocd 2>/dev/null || true +sleep 1 + +OPENOCD_CMDS="reset init; " + +if [ $APP_ONLY -eq 0 ]; then + echo -e "${CYAN} wolfboot.bin -> SRAM 0x34000000${NC}" + OPENOCD_CMDS+="load_image ${WOLFBOOT_ROOT}/wolfboot.bin 0x34000000 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}; " +fi + +# Boot wolfBoot from SRAM (reset would clear SRAM, so we jump directly) +if [ $APP_ONLY -eq 0 ]; then + ENTRY_ADDR=$(od -A n -t x4 -N 8 "${WOLFBOOT_ROOT}/wolfboot.bin" | awk '{print "0x"$2}') + ENTRY_THUMB=$(printf "0x%08x" $(( ${ENTRY_ADDR} | 1 ))) + echo -e "${CYAN} Booting wolfBoot (entry: ${ENTRY_THUMB})...${NC}" + OPENOCD_CMDS+="reg msplim_s 0x00000000; " + OPENOCD_CMDS+="reg psplim_s 0x00000000; " + OPENOCD_CMDS+="reg msp 0x34020000; " + OPENOCD_CMDS+="mww 0xE000ED08 0x34000000; " # 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}"