diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index 36ef4d6521..3325566d6a 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -50,6 +50,7 @@ jobs: if: | inputs.config-file == './config/examples/stm32h7.config' || inputs.config-file == './config/examples/stm32h7-octospi.config' || + inputs.config-file == './config/examples/stm32u3.config' || inputs.config-file == './config/examples/stm32u5.config' || inputs.config-file == './config/examples/stm32u5-wolfcrypt-tz.config' || inputs.config-file == './config/examples/stm32u5-nonsecure-dualbank.config' || diff --git a/.github/workflows/test-configs.yml b/.github/workflows/test-configs.yml index 11159bf3c2..606d34e9d7 100644 --- a/.github/workflows/test-configs.yml +++ b/.github/workflows/test-configs.yml @@ -530,6 +530,12 @@ jobs: arch: arm config-file: ./config/examples/stm32l5-wolfcrypt-tz.config + stm32u3_test: + uses: ./.github/workflows/test-build.yml + with: + arch: arm + config-file: ./config/examples/stm32u3.config + stm32u5_nonsecure_dualbank_test: uses: ./.github/workflows/test-build.yml with: diff --git a/arch.mk b/arch.mk index f56e3444f7..ced25271c9 100644 --- a/arch.mk +++ b/arch.mk @@ -255,6 +255,15 @@ ifeq ($(ARCH),ARM) SPI_TARGET=stm32 endif + ifeq ($(TARGET),stm32u3) + CORTEX_M33=1 + CFLAGS+=-Ihal + ARCH_FLASH_OFFSET=0x08000000 + WOLFBOOT_ORIGIN=0x08000000 + LSCRIPT_IN=hal/$(TARGET).ld + SPI_TARGET=stm32 + endif + ifeq ($(TARGET),stm32h5) CORTEX_M33=1 CFLAGS+=-Ihal diff --git a/config/examples/stm32u3.config b/config/examples/stm32u3.config new file mode 100644 index 0000000000..f35e84a9be --- /dev/null +++ b/config/examples/stm32u3.config @@ -0,0 +1,32 @@ +ARCH?=ARM +TZEN?=0 +TARGET?=stm32u3 +SIGN?=ECC384 +HASH?=SHA384 +DEBUG?=0 +VTOR?=1 +CORTEX_M0?=0 +CORTEX_M33?=1 +NO_ASM?=0 +NO_MPU=1 +EXT_FLASH?=0 +SPI_FLASH?=0 +ALLOW_DOWNGRADE?=0 +NVM_FLASH_WRITEONCE?=1 +WOLFBOOT_VERSION?=1 +V?=0 +SPMATH?=1 +RAM_CODE?=1 +DUALBANK_SWAP?=0 +# Flash layout for dual-bank (2x512KB, 4KB pages): +# Bank 1 (0x08000000): wolfBoot (64KB) + BOOT partition (448KB) +# Bank 2 (0x08080000): UPDATE partition (448KB) + SWAP (4KB) +# Future: DUALBANK_SWAP=1 eliminates swap and uses HW bank-swap +WOLFBOOT_SECTOR_SIZE?=0x1000 +WOLFBOOT_PARTITION_SIZE?=0x70000 +WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x08010000 +WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x08080000 +WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x080F0000 +FLAGS_HOME=0 +DISABLE_BACKUP=0 +DEBUG_UART=1 diff --git a/docs/Targets.md b/docs/Targets.md index fbc02e6181..8b6237868b 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -1685,6 +1685,76 @@ arm-none-eabi-gdb ``` +## STM32U3 + +The STM32U3 family (for example the STM32U385RG on NUCLEO-U385RG-Q) is a +Cortex-M33 part **without TrustZone**, so the port is single-image only +(no `-tz` or `-ns` variants). 1 MB internal flash, 256 KB SRAM, 4 KB +pages, 64-bit (double-word) flash write quantum. + +### Flash layout (stm32u3.config) + +Dual-bank flash (2 x 512 KB, 4 KB pages). Bank 1 holds wolfBoot + BOOT, +bank 2 holds UPDATE + SWAP: + +``` +Bank 1: + 0x08000000 - 0x0800FFFF wolfBoot bootloader (64 KB) + 0x08010000 - 0x0807FFFF BOOT partition (0x70000, 448 KB) +Bank 2: + 0x08080000 - 0x080EFFFF UPDATE partition (0x70000, 448 KB) + 0x080F0000 - 0x080F0FFF SWAP sector (4 KB) +``` + +### Clock and UART + +UART is always available in the test-app and enabled in wolfBoot via +`DEBUG_UART=1` (on by default in the example config). USART1 on PA9 +(TX) / PA10 (RX), AF7, 115200 8N1 — the ST-LINK VCP on NUCLEO-U385RG-Q. + +### Building + +```sh +cp config/examples/stm32u3.config .config +make clean +make +``` + +`DEBUG_UART=1` is enabled by default. To also run the flash self-test: + +```sh +make TEST_FLASH=1 +``` + +### Flashing + +Use `STM32_Programmer_CLI` (from STM32CubeIDE or STM32CubeProgrammer). +`st-flash` does not yet support chipid 0x454. + +```sh +STM32_Programmer_CLI -c port=SWD reset=HWrst -e all \ + -d factory.bin 0x08000000 -v -rst +``` + +The test app blinks LD2 (PA5): slow on v1, fast on v2 (post-update). + +### Testing an Update + +Sign the test application as version 2, build the update image with the +`pBOOT` trigger magic, and flash it: + +```sh +./tools/scripts/prepare_update_u3.sh 2 +STM32_Programmer_CLI -c port=SWD reset=HWrst \ + -d update.bin 0x08080000 -v -rst +``` + +Reset the board — wolfBoot verifies v2, swaps partitions, and jumps to +the new image. LD2 transitions from the slow (v1) blink to the fast +(v2) blink; with `DEBUG_UART=1` the UART log shows the v1 → v2 +transition. + + ## STM32H5 Like [STM32L5](#stm32l5) and [STM32U5](#stm32u5), STM32H5 support is also demonstrated diff --git a/hal/stm32u3.c b/hal/stm32u3.c new file mode 100644 index 0000000000..b79c30613b --- /dev/null +++ b/hal/stm32u3.c @@ -0,0 +1,358 @@ +/* stm32u3.c + * + * Copyright (C) 2026 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 + */ + +/* STM32U3 family (e.g. NUCLEO-U385RG-Q). Cortex-M33 without TrustZone. + * Always dual-bank 1 MB flash (2 x 512 KB), 4 KB pages, 64-bit + * (double-word) write quantum. + * No traditional PLL -- MSIS switches directly between MSIRC1 (24 MHz) + * and MSIRC0 (96 MHz). + */ + +#include +#include +#include +#include "hal/stm32u3.h" +#include "hal.h" +#include "printf.h" + + +static void RAMFUNCTION flash_set_waitstates(unsigned int waitstates) +{ + uint32_t reg = FLASH_ACR; + if ((reg & FLASH_ACR_LATENCY_MASK) != waitstates) { + FLASH_ACR = (reg & ~FLASH_ACR_LATENCY_MASK) | waitstates; + /* RM: read-back to confirm LATENCY accepted before clock switch */ + while ((FLASH_ACR & FLASH_ACR_LATENCY_MASK) != waitstates) + ; + } +} + +static RAMFUNCTION void flash_wait_complete(void) +{ + while ((FLASH_NS_SR & (FLASH_SR_BSY | FLASH_SR_WDW)) != 0) + ; +} + +static void RAMFUNCTION flash_clear_errors(void) +{ + uint32_t sr = FLASH_NS_SR; + if (sr & (FLASH_SR_OPERR | FLASH_SR_PROGERR | FLASH_SR_WRPERR | + FLASH_SR_PGAERR | FLASH_SR_SIZERR | FLASH_SR_PGSERR | + FLASH_SR_OPTWERR)) { + /* Write 1 to clear (rc_w1) */ + FLASH_NS_SR = sr; + } +} + +/* RM0487 Section 7.3.7: Flash memory programming sequence (double-word) */ +int RAMFUNCTION hal_flash_write(uint32_t address, const uint8_t *data, int len) +{ + int i = 0; + uint32_t *dst; + + dst = (uint32_t *)address; + + while (i < len) { + uint32_t dword[2]; + int remain = len - i; + + dword[0] = 0xFFFFFFFF; + dword[1] = 0xFFFFFFFF; + memcpy((uint8_t *)dword, data + i, remain < 8 ? remain : 8); + + /* RM steps 2-3: check BSY+WDW clear, clear all error flags */ + flash_wait_complete(); + flash_clear_errors(); + + /* RM step 5: set PG */ + FLASH_NS_CR |= FLASH_CR_PG; + + /* RM step 6: write first word, then second word */ + dst[i >> 2] = dword[0]; + ISB(); + dst[(i >> 2) + 1] = dword[1]; + ISB(); + + /* RM step 8: wait for BSY clear */ + flash_wait_complete(); + + /* RM step 9: clear EOP if set */ + if ((FLASH_NS_SR & FLASH_SR_EOP) != 0) + FLASH_NS_SR |= FLASH_SR_EOP; + + /* RM step 10: clear PG */ + FLASH_NS_CR &= ~FLASH_CR_PG; + i += 8; + } + hal_cache_invalidate(); + return 0; +} + +void RAMFUNCTION hal_flash_unlock(void) +{ + flash_wait_complete(); + /* Unlock NS flash controller (TZEN=0, secure unlock not needed) */ + if ((FLASH_NS_CR & FLASH_CR_LOCK) != 0) { + FLASH_NS_KEYR = FLASH_KEY1; + DMB(); + FLASH_NS_KEYR = FLASH_KEY2; + DMB(); + while ((FLASH_NS_CR & FLASH_CR_LOCK) != 0) + ; + } +} + +void RAMFUNCTION hal_flash_lock(void) +{ + flash_wait_complete(); + if ((FLASH_NS_CR & FLASH_CR_LOCK) == 0) + FLASH_NS_CR |= FLASH_CR_LOCK; +} + +void RAMFUNCTION hal_flash_opt_unlock(void) +{ + flash_wait_complete(); + if ((FLASH_NS_CR & FLASH_CR_OPTLOCK) != 0) { + FLASH_NS_OPTKEYR = FLASH_OPTKEY1; + DMB(); + FLASH_NS_OPTKEYR = FLASH_OPTKEY2; + DMB(); + while ((FLASH_NS_CR & FLASH_CR_OPTLOCK) != 0) + ; + } +} + +void RAMFUNCTION hal_flash_opt_lock(void) +{ + FLASH_NS_CR |= FLASH_CR_OPTSTRT; + flash_wait_complete(); + FLASH_NS_CR |= FLASH_CR_OBL_LAUNCH; + if ((FLASH_NS_CR & FLASH_CR_OPTLOCK) == 0) + FLASH_NS_CR |= FLASH_CR_OPTLOCK; +} + +/* Erase — matches STM32U5 hal pattern exactly (same Cortex-M33 flash controller) */ +int RAMFUNCTION hal_flash_erase(uint32_t address, int len) +{ + uint32_t end_address; + uint32_t p; + + flash_clear_errors(); + if (len == 0) + return -1; + if (address < ARCH_FLASH_OFFSET) + return -1; + + end_address = address + len - 1; + for (p = address; p < end_address; p += FLASH_PAGE_SIZE) { + uint32_t reg; + uint32_t bker = 0; + uint32_t base; + + if (p > FLASH_TOP) { + FLASH_NS_CR &= ~FLASH_CR_PER; + return 0; + } + + if (p >= FLASH_BANK2_BASE) { + bker = FLASH_CR_BKER; + base = FLASH_BANK2_BASE; + } else { + base = FLASHMEM_ADDRESS_SPACE; + } + + /* Single MODIFY_REG: clear PNB+BKER, set page+PER+bker */ + reg = FLASH_NS_CR & ~((FLASH_CR_PNB_MASK << FLASH_CR_PNB_SHIFT) | + FLASH_CR_BKER); + reg |= (((p - base) >> 12) << FLASH_CR_PNB_SHIFT) | + FLASH_CR_PER | bker; + FLASH_NS_CR = reg; + DMB(); + FLASH_NS_CR |= FLASH_CR_STRT; + flash_wait_complete(); + } + FLASH_NS_CR &= ~FLASH_CR_PER; + hal_cache_invalidate(); + return 0; +} + +/* --- UART: USART1 on PA9 (TX) / PA10 (RX), AF7 --- */ + +#define USART1_BASE (0x40013800U) +#define USART1_CR1 (*(volatile uint32_t *)(USART1_BASE + 0x00)) +#define USART1_BRR (*(volatile uint32_t *)(USART1_BASE + 0x0C)) +#define USART1_ISR (*(volatile uint32_t *)(USART1_BASE + 0x1C)) +#define USART1_TDR (*(volatile uint32_t *)(USART1_BASE + 0x28)) + +#define UART_CR1_UE (1 << 0) +#define UART_CR1_RE (1 << 2) +#define UART_CR1_TE (1 << 3) +#define UART_ISR_TXE (1 << 7) + +#define UART_TX_PIN (9) +#define UART_RX_PIN (10) +#define UART_PIN_AF (7) + +#define GPIOA_MODER (*(volatile uint32_t *)(GPIOA_BASE + 0x00)) +#define GPIOA_PUPDR (*(volatile uint32_t *)(GPIOA_BASE + 0x0C)) +#define GPIOA_AFRH (*(volatile uint32_t *)(GPIOA_BASE + 0x24)) + +#define USART1_PCLK (96000000U) + +/* Available when: wolfBoot with DEBUG_UART, or test-app (always) */ +#if defined(DEBUG_UART) || !defined(__WOLFBOOT) + +static void uart1_pins_setup(void) +{ + uint32_t reg; + + RCC_AHB2ENR1 |= RCC_AHB2ENR1_GPIOAEN; + reg = RCC_AHB2ENR1; + (void)reg; + + reg = GPIOA_MODER & ~(0x3u << (UART_TX_PIN * 2)); + GPIOA_MODER = reg | (0x2u << (UART_TX_PIN * 2)); + reg = GPIOA_MODER & ~(0x3u << (UART_RX_PIN * 2)); + GPIOA_MODER = reg | (0x2u << (UART_RX_PIN * 2)); + + reg = GPIOA_AFRH & ~(0xFu << ((UART_TX_PIN - 8) * 4)); + GPIOA_AFRH = reg | (UART_PIN_AF << ((UART_TX_PIN - 8) * 4)); + reg = GPIOA_AFRH & ~(0xFu << ((UART_RX_PIN - 8) * 4)); + GPIOA_AFRH = reg | (UART_PIN_AF << ((UART_RX_PIN - 8) * 4)); + + GPIOA_PUPDR &= ~(0x3u << (UART_TX_PIN * 2)); + GPIOA_PUPDR &= ~(0x3u << (UART_RX_PIN * 2)); +} + +void uart_init(void) +{ + uint32_t reg; + + uart1_pins_setup(); + + RCC_APB2ENR |= RCC_APB2ENR_USART1EN; + reg = RCC_APB2ENR; + (void)reg; + + USART1_CR1 &= ~UART_CR1_UE; + USART1_BRR = USART1_PCLK / 115200; + USART1_CR1 |= UART_CR1_TE | UART_CR1_RE | UART_CR1_UE; +} + +void uart_write(const char *buf, unsigned int sz) +{ + while (sz-- > 0) { + while ((USART1_ISR & UART_ISR_TXE) == 0) + ; + USART1_TDR = *buf++; + } +} + +#endif /* DEBUG_UART || !__WOLFBOOT */ + +/* Clock: MSIS MSIRC0 (96 MHz) with SMPS + EPOD booster + VOS range 1. */ +static void clock_96mhz(void) +{ + uint32_t reg; + + RCC_AHB1ENR2 |= RCC_AHB1ENR2_PWREN; + reg = RCC_AHB1ENR2; + (void)reg; + + if ((PWR_SVMSR & PWR_SVMSR_REGS) == 0) { + PWR_CR3 |= PWR_CR3_REGSEL; + while ((PWR_SVMSR & PWR_SVMSR_REGS) == 0) + ; + } + + reg = RCC_CFGR4; + reg &= ~(RCC_CFGR4_BOOSTSEL_MASK | RCC_CFGR4_BOOSTDIV_MASK); + reg |= RCC_CFGR4_BOOSTSEL_MSIS; + RCC_CFGR4 = reg; + + if ((PWR_VOSR & PWR_VOSR_BOOSTRDY) == 0) { + PWR_VOSR |= PWR_VOSR_BOOSTEN; + while ((PWR_VOSR & PWR_VOSR_BOOSTRDY) == 0) + ; + } + + if ((PWR_VOSR & PWR_VOSR_R1RDY) == 0) { + reg = PWR_VOSR; + reg &= ~(PWR_VOSR_R1EN | PWR_VOSR_R2EN); + reg |= PWR_VOSR_R1EN; + PWR_VOSR = reg; + while ((PWR_VOSR & PWR_VOSR_R1RDY) == 0) + ; + } + + flash_set_waitstates(2); + FLASH_ACR |= FLASH_ACR_PRFTEN; + + /* MSISSEL: 0=MSIRC0(96MHz), 1=MSIRC1(24MHz). Set MSIRGSEL to use ICSCR1. */ + reg = RCC_ICSCR1; + reg &= ~(RCC_ICSCR1_MSISSEL | RCC_ICSCR1_MSISDIV_MASK); + reg |= RCC_ICSCR1_MSIRGSEL; + RCC_ICSCR1 = reg; + DMB(); + + while ((RCC_CR & RCC_CR_MSISRDY) == 0) + ; + + RCC_CFGR2 = 0; +} + +void hal_init(void) +{ + clock_96mhz(); + hal_cache_enable(1); + +#if defined(DEBUG_UART) && defined(__WOLFBOOT) + uart_init(); + uart_write("wolfBoot HAL Init\n", sizeof("wolfBoot HAL Init\n") - 1); +#endif +} + +void hal_prepare_boot(void) +{ +} + +void RAMFUNCTION hal_cache_enable(int way) +{ + ICACHE_CR |= (way ? ICACHE_CR_2WAYS : ICACHE_CR_1WAY); + ICACHE_CR |= ICACHE_CR_CEN; +} + +void RAMFUNCTION hal_cache_disable(void) +{ + ICACHE_CR &= ~ICACHE_CR_CEN; +} + +void RAMFUNCTION hal_cache_invalidate(void) +{ + if ((ICACHE_CR & ICACHE_CR_CEN) == 0) + return; + if ((ICACHE_SR & ICACHE_SR_BUSYF) == 0) + ICACHE_CR |= ICACHE_CR_CACHEINV; + /* Wait unconditionally for invalidation to complete */ + while ((ICACHE_SR & ICACHE_SR_BSYENDF) == 0) + ; + ICACHE_SR |= ICACHE_SR_BSYENDF; +} diff --git a/hal/stm32u3.h b/hal/stm32u3.h new file mode 100644 index 0000000000..8636c169cc --- /dev/null +++ b/hal/stm32u3.h @@ -0,0 +1,204 @@ +/* stm32u3.h + * + * Copyright (C) 2026 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 + */ + +/* STM32U3 family (e.g. STM32U385RG on NUCLEO-U385RG-Q). + * Cortex-M33, no TrustZone. 1 MB dual-bank flash (2 × 512 KB, 4 KB pages). + * Reference: RM0487, stm32u385xx.h from STM32Cube_FW_U3_V1.3.0. + * + * Peripheral base addresses differ from STM32U5: + * AHB1 peripherals (FLASH, ICACHE, PWR, RCC) at 0x40020000 + offset + * AHB2 peripherals (GPIO) at 0x42020000 + offset + * APB2 peripherals (USART1) at 0x40010000 + offset + * There is no traditional PLL — MSIS RC0 runs natively at 96 MHz. + */ + +#ifndef _STM32U3_H_ +#define _STM32U3_H_ + +#include + +/* Assembly helpers */ +#define DMB() __asm__ volatile ("dmb") +#define ISB() __asm__ volatile ("isb") +#define DSB() __asm__ volatile ("dsb") + +/* -------- RCC (AHB1 + 0x10C00 = 0x40030C00) -------- */ +#define RCC_BASE (0x40030C00) + +#define RCC_CR (*(volatile uint32_t *)(RCC_BASE + 0x00)) +#define RCC_CR_MSISON (1 << 0) +#define RCC_CR_MSISRDY (1 << 2) +#define RCC_CR_MSIPLL1EN (1 << 5) +#define RCC_CR_MSIPLL0EN (1 << 6) +#define RCC_CR_MSIPLL1RDY (1 << 9) +#define RCC_CR_MSIPLL0RDY (1 << 10) +#define RCC_CR_HSION (1 << 11) +#define RCC_CR_HSIRDY (1 << 13) +#define RCC_CR_HSEON (1 << 16) +#define RCC_CR_HSERDY (1 << 17) +#define RCC_CR_HSEBYP (1 << 18) +#define RCC_CR_CSSON (1 << 19) + +#define RCC_ICSCR1 (*(volatile uint32_t *)(RCC_BASE + 0x08)) +/* MSIS source selection: bit 23 MSIRGSEL, bits [31:29] MSISDIV */ +#define RCC_ICSCR1_MSIRGSEL (1 << 23) /* 1 = use ICSCR1 range */ +#define RCC_ICSCR1_MSISDIV_SHIFT (29) +#define RCC_ICSCR1_MSISDIV_MASK (0x3u << 29) +#define RCC_ICSCR1_MSISDIV_1 (0) /* /1 */ +#define RCC_ICSCR1_MSISDIV_2 (1) /* /2 */ +#define RCC_ICSCR1_MSISDIV_4 (2) /* /4 */ +#define RCC_ICSCR1_MSISDIV_8 (3) /* /8 */ +/* MSIS RC source: bit 31 MSISSEL — 0 = MSIRC0 (96 MHz), 1 = MSIRC1 (24 MHz) */ +#define RCC_ICSCR1_MSISSEL (1u << 31) + +#define RCC_CFGR1 (*(volatile uint32_t *)(RCC_BASE + 0x1C)) +#define RCC_CFGR_SW_MSIS 0x0 +#define RCC_CFGR_SW_HSI16 0x1 +#define RCC_CFGR_SW_HSE 0x2 + +#define RCC_CFGR2 (*(volatile uint32_t *)(RCC_BASE + 0x20)) +/* CFGR4: EPOD booster config (offset 0x28) */ +#define RCC_CFGR4 (*(volatile uint32_t *)(RCC_BASE + 0x28)) +#define RCC_CFGR4_BOOSTSEL_SHIFT (0) +#define RCC_CFGR4_BOOSTSEL_MASK (0x3u << 0) +#define RCC_CFGR4_BOOSTSEL_MSIS (0x1u << 0) /* MSIS as booster source */ +#define RCC_CFGR4_BOOSTDIV_SHIFT (12) +#define RCC_CFGR4_BOOSTDIV_MASK (0xFu << 12) + +#define RCC_CIER (*(volatile uint32_t *)(RCC_BASE + 0x50)) + +#define RCC_AHB1ENR1 (*(volatile uint32_t *)(RCC_BASE + 0x88)) +#define RCC_AHB2ENR1 (*(volatile uint32_t *)(RCC_BASE + 0x8C)) +#define RCC_AHB2ENR1_GPIOAEN (1 << 0) +#define RCC_AHB2ENR1_GPIOBEN (1 << 1) +#define RCC_AHB2ENR1_GPIOCEN (1 << 2) +#define RCC_AHB2ENR1_GPIODEN (1 << 3) + +/* AHB1ENR2 at offset 0x094: PWR clock enable is bit 2 */ +#define RCC_AHB1ENR2 (*(volatile uint32_t *)(RCC_BASE + 0x94)) +#define RCC_AHB1ENR2_PWREN (1 << 2) + +#define RCC_APB1ENR1 (*(volatile uint32_t *)(RCC_BASE + 0x9C)) +#define RCC_APB2ENR (*(volatile uint32_t *)(RCC_BASE + 0xA4)) +#define RCC_APB2ENR_USART1EN (1 << 14) + +/* -------- PWR (AHB1 + 0x10800 = 0x40030800) -------- */ +#define PWR_BASE (0x40030800) + +#define PWR_CR3 (*(volatile uint32_t *)(PWR_BASE + 0x08)) +#define PWR_CR3_REGSEL (1 << 1) /* 0=LDO, 1=SMPS */ + +#define PWR_SVMSR (*(volatile uint32_t *)(PWR_BASE + 0x3C)) +#define PWR_SVMSR_REGS (1 << 1) /* regulator status: 0=LDO, 1=SMPS */ + +#define PWR_VOSR (*(volatile uint32_t *)(PWR_BASE + 0x0C)) +#define PWR_VOSR_R1EN (1 << 0) /* VOS range 1 enable */ +#define PWR_VOSR_R2EN (1 << 1) /* VOS range 2 enable */ +#define PWR_VOSR_BOOSTEN (1 << 8) /* EPOD booster enable */ +#define PWR_VOSR_R1RDY (1 << 16) /* range 1 ready */ +#define PWR_VOSR_R2RDY (1 << 17) /* range 2 ready */ +#define PWR_VOSR_BOOSTRDY (1 << 24) /* EPOD booster ready */ + +/* -------- FLASH (AHB1 + 0x2000 = 0x40022000) -------- */ +#define FLASH_BASE (0x40022000) +#define FLASH_ACR (*(volatile uint32_t *)(FLASH_BASE + 0x00)) +#define FLASH_ACR_LATENCY_MASK (0x0F) +#define FLASH_ACR_PRFTEN (1 << 8) +#define FLASH_ACR_LPM (1 << 11) + +/* Non-secure flash registers (fail with PGSERR on programmed pages) */ +#define FLASH_NS_KEYR (*(volatile uint32_t *)(FLASH_BASE + 0x08)) +#define FLASH_NS_OPTKEYR (*(volatile uint32_t *)(FLASH_BASE + 0x10)) +#define FLASH_NS_SR (*(volatile uint32_t *)(FLASH_BASE + 0x20)) +#define FLASH_NS_CR (*(volatile uint32_t *)(FLASH_BASE + 0x28)) + +/* Secure flash registers (unused when TZEN=0, kept for reference) */ +#define FLASH_SKEYR (*(volatile uint32_t *)(FLASH_BASE + 0x0C)) +#define FLASH_SSR (*(volatile uint32_t *)(FLASH_BASE + 0x24)) +#define FLASH_SCR (*(volatile uint32_t *)(FLASH_BASE + 0x2C)) + +#define FLASH_SR_EOP (1 << 0) +#define FLASH_SR_OPERR (1 << 1) +#define FLASH_SR_PROGERR (1 << 3) +#define FLASH_SR_WRPERR (1 << 4) +#define FLASH_SR_PGAERR (1 << 5) +#define FLASH_SR_SIZERR (1 << 6) +#define FLASH_SR_PGSERR (1 << 7) +#define FLASH_SR_OPTWERR (1 << 13) +#define FLASH_SR_BSY (1 << 16) +#define FLASH_SR_WDW (1 << 17) + +#define FLASH_CR_PG (1 << 0) +#define FLASH_CR_PER (1 << 1) +#define FLASH_CR_MER1 (1 << 2) +#define FLASH_CR_PNB_SHIFT 3 +#define FLASH_CR_PNB_MASK 0x7F /* 7 bits: 64 pages per bank */ +#define FLASH_CR_BKER (1 << 11) +#define FLASH_CR_STRT (1 << 16) +#define FLASH_CR_OPTSTRT (1 << 17) +#define FLASH_CR_EOPIE (1 << 24) +#define FLASH_CR_ERRIE (1 << 25) +#define FLASH_CR_OBL_LAUNCH (1 << 27) +#define FLASH_CR_OPTLOCK (1 << 30) +#define FLASH_CR_LOCK (1 << 31) + +#define FLASH_OPTR (*(volatile uint32_t *)(FLASH_BASE + 0x40)) + +#define FLASHMEM_ADDRESS_SPACE (0x08000000) +#define FLASH_PAGE_SIZE (0x1000) /* 4 KB */ +#define FLASH_BANK2_BASE (0x08080000) /* always dual-bank on U3 */ +#define FLASH_TOP (0x080FFFFF) + +#define FLASH_KEY1 (0x45670123) +#define FLASH_KEY2 (0xCDEF89AB) +#define FLASH_OPTKEY1 (0x08192A3BU) +#define FLASH_OPTKEY2 (0x4C5D6E7FU) + +/* -------- GPIO (AHB2, 0x42020000) -------- */ +#define GPIOA_BASE (0x42020000) +#define GPIOB_BASE (0x42020400) +#define GPIOC_BASE (0x42020800) +#define GPIOD_BASE (0x42020C00) + +/* -------- Reset -------- */ +#define AIRCR (*(volatile uint32_t *)(0xE000ED0C)) +#define AIRCR_VKEY (0x05FA << 16) +#define AIRCR_SYSRESETREQ (1 << 2) + +/* -------- ICACHE (AHB1 + 0x10400 = 0x40030400) -------- */ +#define ICACHE_BASE (0x40030400) +#define ICACHE_CR (*(volatile uint32_t *)(ICACHE_BASE + 0x00)) +#define ICACHE_CR_WAYSEL (1 << 2) +#define ICACHE_CR_1WAY 0U +#define ICACHE_CR_2WAYS ICACHE_CR_WAYSEL +#define ICACHE_CR_CACHEINV (1 << 1) +#define ICACHE_CR_CEN (1 << 0) + +#define ICACHE_SR (*(volatile uint32_t *)(ICACHE_BASE + 0x04)) +#define ICACHE_SR_BUSYF (1 << 0) +#define ICACHE_SR_BSYENDF (1 << 1) +#define ICACHE_SR_ERRF (1 << 2) + +void hal_cache_invalidate(void); +void hal_cache_enable(int way); +void hal_cache_disable(void); + +#endif /* _STM32U3_H_ */ diff --git a/hal/stm32u3.ld b/hal/stm32u3.ld new file mode 100644 index 0000000000..0b64a85e16 --- /dev/null +++ b/hal/stm32u3.ld @@ -0,0 +1,56 @@ +MEMORY +{ + FLASH (rx) : ORIGIN = 0x08000000, LENGTH = @BOOTLOADER_PARTITION_SIZE@ + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00040000 /* 256 KB SRAM */ +} + +SECTIONS +{ + .text : + { + _start_text = .; + KEEP(*(.isr_vector)) + *(.text*) + *(.rodata*) + . = ALIGN(4); + _end_text = .; + } > FLASH + + .edidx : + { + . = ALIGN(4); + *(.ARM.exidx*) + } > FLASH + + .keystore : + { + . = ALIGN(4); + KEEP(*(.keystore*)) + } > FLASH + + _stored_data = .; + .data : AT (_stored_data) + { + _start_data = .; + KEEP(*(.data*)) + . = ALIGN(4); + KEEP(*(.ramcode)) + . = ALIGN(4); + _end_data = .; + } > RAM + + .bss (NOLOAD) : + { + _start_bss = .; + __bss_start__ = .; + *(.bss*) + *(COMMON) + . = ALIGN(4); + _end_bss = .; + __bss_end__ = .; + _end = .; + } > RAM + . = ALIGN(4); +} + +END_STACK = ORIGIN(RAM) + LENGTH(RAM); diff --git a/test-app/ARM-stm32u3.ld b/test-app/ARM-stm32u3.ld new file mode 100644 index 0000000000..63564e942f --- /dev/null +++ b/test-app/ARM-stm32u3.ld @@ -0,0 +1,53 @@ +MEMORY +{ + FLASH (rx) : ORIGIN = @WOLFBOOT_TEST_APP_ADDRESS@, LENGTH = @WOLFBOOT_TEST_APP_SIZE@ + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 256K +} + +SECTIONS +{ + .text : + { + _start_text = .; + . = ALIGN(8); + KEEP(*(.isr_vector)) + . = ALIGN(8); + *(.init) + *(.fini) + *(.text*) + *(.rodata*) + . = ALIGN(8); + _end_text = .; + } > FLASH + + .edidx : + { + . = ALIGN(4); + *(.ARM.exidx*) + } > FLASH + + _stored_data = .; + + .data : AT (_stored_data) + { + _start_data = .; + KEEP(*(.data*)) + . = ALIGN(8); + KEEP(*(.ramcode)) + . = ALIGN(8); + _end_data = .; + } > RAM + + .bss : + { + _start_bss = .; + *(.bss*) + *(COMMON) + . = ALIGN(8); + _end_bss = .; + _end = .; + } > RAM +} + +PROVIDE(_start_heap = _end); +PROVIDE(_end_stack = ORIGIN(RAM) + LENGTH(RAM)); diff --git a/test-app/Makefile b/test-app/Makefile index bb50facbab..aa76728b29 100644 --- a/test-app/Makefile +++ b/test-app/Makefile @@ -446,6 +446,14 @@ ifeq ($(TARGET),stm32u5) LDFLAGS+=-mcpu=cortex-m33 endif +ifeq ($(TARGET),stm32u3) + LSCRIPT_TEMPLATE=ARM-stm32u3.ld + CFLAGS+=-mcpu=cortex-m33 -ffunction-sections -fdata-sections -fno-common + LDFLAGS+=-mcpu=cortex-m33 + LDFLAGS+=-Wl,-gc-sections -Wl,-Map=image.map + CFLAGS+=-I.. +endif + ifeq ($(TARGET),nrf5340) ifeq ($(TZEN),1) LSCRIPT_TEMPLATE=ARM-nrf5340-ns.ld diff --git a/test-app/app_stm32u3.c b/test-app/app_stm32u3.c new file mode 100644 index 0000000000..b8116c9be0 --- /dev/null +++ b/test-app/app_stm32u3.c @@ -0,0 +1,124 @@ +/* app_stm32u3.c + * + * Test bare-metal application for the STM32U3 (NUCLEO-U385RG-Q). + * + * Copyright (C) 2026 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 "hal.h" +#include "wolfboot/wolfboot.h" +#include "target.h" + +/* UART always available in test-app (uart_init/uart_write live in hal/stm32u3.c) */ +extern void uart_init(void); +extern void uart_write(const char *buf, unsigned int len); + +static void uart_print(const char *s) +{ + unsigned int n = 0; + while (s[n] != 0) + n++; + uart_write(s, n); +} + +/* NUCLEO-U385RG-Q: user LED LD2 on PA5 (NUCLEO-64 convention). */ +#define LED_USR_PIN (5) + +#define RCC_BASE (0x40030C00) +#define GPIOA_BASE (0x42020000) + +#define RCC_AHB2ENR1 (*(volatile uint32_t *)(RCC_BASE + 0x8C)) +#define GPIOA_EN (1 << 0) + +#define GPIOA_MODER (*(volatile uint32_t *)(GPIOA_BASE + 0x00)) +#define GPIOA_PUPDR (*(volatile uint32_t *)(GPIOA_BASE + 0x0C)) +#define GPIOA_BSRR (*(volatile uint32_t *)(GPIOA_BASE + 0x18)) + +static void led_init(void) +{ + uint32_t reg; + RCC_AHB2ENR1 |= GPIOA_EN; + reg = RCC_AHB2ENR1; + (void)reg; + + reg = GPIOA_MODER & ~(0x03u << (LED_USR_PIN * 2)); + GPIOA_MODER = reg | (0x01u << (LED_USR_PIN * 2)); + GPIOA_PUPDR &= ~(0x03u << (LED_USR_PIN * 2)); +} + +static void led_on(void) +{ + GPIOA_BSRR = (1u << LED_USR_PIN); +} + +static void led_off(void) +{ + GPIOA_BSRR = (1u << (LED_USR_PIN + 16)); +} + +static void busy_delay(uint32_t count) +{ + volatile uint32_t i; + for (i = 0; i < count; i++) + __asm__ volatile ("nop"); +} + +void main(void) +{ + uint32_t version; + uint32_t v; + uint32_t on_ticks, off_ticks; + char num[4]; + int idx = 0; + + hal_init(); + led_init(); + + uart_init(); + uart_print("TEST APP\r\n"); + + version = wolfBoot_current_firmware_version(); + + v = version; + if (v >= 100) { num[idx++] = '0' + (v / 100); v %= 100; } + if (v >= 10 || idx > 0) { num[idx++] = '0' + (v / 10); v %= 10; } + num[idx++] = '0' + v; + num[idx] = '\0'; + uart_write("App version: ", sizeof("App version: ") - 1); + uart_write(num, idx); + uart_write("\r\n", 2); + + /* v1: slow blink. v2+: fast blink. */ + if (version >= 2) { + wolfBoot_success(); + on_ticks = 200000; + off_ticks = 200000; + } else { + on_ticks = 600000; + off_ticks = 600000; + } + + while (1) { + led_on(); + busy_delay(on_ticks); + led_off(); + busy_delay(off_ticks); + } +} diff --git a/test-app/startup_arm.c b/test-app/startup_arm.c index ff65084d34..c2d1c12574 100644 --- a/test-app/startup_arm.c +++ b/test-app/startup_arm.c @@ -197,7 +197,7 @@ void (* const IV[])(void) = isr_empty, // CAN2 isr_empty, // Ethernet isr_empty, // Hibernate -#elif (defined(TARGET_stm32l5) ||defined(TARGET_stm32u5)) /* Fill with extra unused handlers */ +#elif (defined(TARGET_stm32l5) ||defined(TARGET_stm32u5) || defined(TARGET_stm32u3)) /* Fill with extra unused handlers */ isr_empty, // WWDG_IRQHandler isr_empty, // PVD_PVM_IRQHandler isr_empty, // RTC_IRQHandler