diff --git a/.github/workflows/test-configs.yml b/.github/workflows/test-configs.yml index 91175ac56b..4465cc51b9 100644 --- a/.github/workflows/test-configs.yml +++ b/.github/workflows/test-configs.yml @@ -542,6 +542,12 @@ jobs: arch: arm config-file: ./config/examples/stm32u3.config + stm32c5_test: + uses: ./.github/workflows/test-build.yml + with: + arch: arm + config-file: ./config/examples/stm32c5.config + stm32u5_nonsecure_dualbank_test: uses: ./.github/workflows/test-build.yml with: diff --git a/arch.mk b/arch.mk index 261ace49c4..8c3bf7f5b8 100644 --- a/arch.mk +++ b/arch.mk @@ -264,6 +264,15 @@ ifeq ($(ARCH),ARM) SPI_TARGET=stm32 endif + ifeq ($(TARGET),stm32c5) + 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/stm32c5.config b/config/examples/stm32c5.config new file mode 100644 index 0000000000..f2d0e7c95c --- /dev/null +++ b/config/examples/stm32c5.config @@ -0,0 +1,31 @@ +ARCH?=ARM +TZEN?=0 +TARGET?=stm32c5 +SIGN?=ECC256 +HASH?=SHA256 +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, 8KB pages, 1MB total): +# Bank 1 (0x08000000): wolfBoot (64KB) + BOOT partition (448KB) +# Bank 2 (0x08080000): UPDATE partition (448KB) + SWAP (8KB) +WOLFBOOT_SECTOR_SIZE?=0x2000 +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 bbce15e5cb..0d38e0362f 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -39,6 +39,7 @@ This README describes configuration of supported targets. * [Renesas RZN2L](#renesas-rzn2l) * [SiFive HiFive1 RISC-V](#sifive-hifive1-risc-v) * [STM32C0](#stm32c0) +* [STM32C5](#stm32c5) * [STM32F1](#stm32f1) * [STM32F4](#stm32f4) * [STM32F7](#stm32f7) @@ -1779,6 +1780,97 @@ the new image. LD2 transitions from the slow (v1) blink to the fast transition. +## STM32C5 + +The STM32C5 family (for example the STM32C5A3ZGT6 on NUCLEO-C5A3ZG) is +a mainstream Cortex-M33 part **without TrustZone**, so the port is +single-image only (no `-tz` or `-ns` variants). On the -ZG variant: +1 MB internal flash, 256 KB SRAM, 8 KB pages, **128-bit (quad-word) +flash write quantum** with per-quad-word ECC. + +The HAL writes flash in 16-byte aligned quad-words. When wolfBoot +asks for a smaller or unaligned write, the HAL reads the surrounding +flash and merges so each programmed quad-word is a complete ECC +block - sub-quad-word writes leave ECC undefined and reads come back +with bit-flipped "corrected" data. + +### Flash layout (stm32c5.config) + +Dual-bank flash (2 x 512 KB, 8 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 - 0x080F1FFF SWAP sector (8 KB) +``` + +### Clock and UART + +The bootloader runs at the post-reset HSI clock (HSI / 3 = 48 MHz +HCLK; no PLL bring-up). UART is always available in the test-app and +enabled in wolfBoot via `DEBUG_UART=1` (on by default in the example +config). The NUCLEO-C5A3ZG ST-LINK virtual COM port is wired to MCU +pins 36/37 (PA2/PA3) - **USART2** on AF7, 115200 8N1, **not USART1 +on PA9/PA10** (PA9/PA10 only reach the Arduino headers). + +### Building + +```sh +cp config/examples/stm32c5.config .config +make clean +make +``` + +Default signing scheme is ECC256 + SHA256. Produces `wolfboot.bin` +(~25 KB), `test-app/image_v1_signed.bin`, and `factory.bin` (BL + +signed v1). + +### Flashing + +Use `STM32_Programmer_CLI` (from STM32CubeIDE or STM32CubeProgrammer +v2.22+). pyocd has no STM32C5 target as of this writing. The C5 +debug access port is AP2; `mode=UR` (under-reset) is the most +reliable connect mode while a previous image is running. + +```sh +STM32_Programmer_CLI -c port=swd mode=UR -e all \ + -d factory.bin 0x08000000 -v -rst +``` + +The test app blinks LD2 (PG1, **active low**): five slow blinks on +v1 then it triggers an update and resets; v2 blinks fast forever +once `wolfBoot_success()` is acknowledged. + +### Testing an Update + +Sign the test application as version 2 and flash it directly to the +update partition: + +```sh +./tools/keytools/sign --ecc256 --sha256 \ + test-app/image.bin wolfboot_signing_private_key.der 2 +STM32_Programmer_CLI -c port=swd mode=UR \ + -d test-app/image_v2_signed.bin 0x08080000 -v -rst +``` + +On reset wolfBoot detects the staged v2, the v1 test-app calls +`wolfBoot_update_trigger()` after its blink sequence and resets, +wolfBoot performs the bank-to-bank swap, and v2 boots. With +`DEBUG_UART=1` the UART log shows: + +``` +Booting version: 0x1 +TEST APP / App version: 1 / triggering update -> reset +... swap output ... +Booting version: 0x2 +TEST APP / App version: 2 / update OK -- success confirmed +``` + + ## STM32H5 Like [STM32L5](#stm32l5) and [STM32U5](#stm32u5), STM32H5 support is also demonstrated diff --git a/hal/stm32c5.c b/hal/stm32c5.c new file mode 100644 index 0000000000..2a1af75e82 --- /dev/null +++ b/hal/stm32c5.c @@ -0,0 +1,320 @@ +/* stm32c5.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 + */ + +/* STM32C5 family (e.g. STM32C5A3ZGT6 on NUCLEO-C5A3ZG). Cortex-M33 + * without TrustZone in this configuration. Dual-bank 1 MB flash on + * the -ZG variant (2 x 512 KB), 8 KB pages, 64-bit (double-word) write + * quantum. Default sysclk after reset is HSI = 48 MHz (HSIS / 1). + * This minimal port keeps the default clock and only configures + * peripherals. + */ + +#include +#include +#include +#include "hal/stm32c5.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; + while ((FLASH_ACR & FLASH_ACR_LATENCY_MASK) != waitstates) + ; + } +} + +static RAMFUNCTION void flash_wait_complete(void) +{ + while ((FLASH_SR & (FLASH_SR_BSY | FLASH_SR_WBNE | FLASH_SR_DBNE)) != 0) + ; +} + +static void RAMFUNCTION flash_clear_errors(void) +{ + /* On STM32C5, error flags are cleared via the dedicated FLASH_CCR + * register (write 1 to clear). Always clear all known flags so a + * stale error from a prior cycle does not block the next operation. + */ + FLASH_CCR = FLASH_CCR_CLR_EOP | FLASH_CCR_CLR_WRPERR | + FLASH_CCR_CLR_PGSERR | FLASH_CCR_CLR_STRBERR | + FLASH_CCR_CLR_INCERR | FLASH_CCR_CLR_OPTCHANGEERR; +} + +/* C5 flash programming requires 128-bit (quad-word, 16-byte) writes + * to produce valid ECC. Partial writes leave the per-quad-word ECC + * undefined and reads come back with bit-flipped \"corrected\" data. + * For unaligned heads/tails we read the existing flash content and + * merge so that every written quad-word is a complete ECC block. + */ +int RAMFUNCTION hal_flash_write(uint32_t address, const uint8_t *data, int len) +{ + int i = 0; + uint32_t qword[4]; + uint8_t *qword_bytes = (uint8_t *)qword; + uint32_t *src, *dst; + + src = (uint32_t *)data; + dst = (uint32_t *)address; + + while (i < len) { + uint32_t cur_addr = (uint32_t)dst + i; + uint32_t *dst_aligned = (uint32_t *)(cur_addr & 0xFFFFFFF0U); + int byte_offset = cur_addr - (uint32_t)dst_aligned; + int i_aligned = i - byte_offset; + int j; + + if (byte_offset == 0 && i + 16 <= len) { + /* Full aligned 128 bits from caller buffer. */ + for (j = 0; j < 4; j++) { + qword[j] = src[((unsigned int)i >> 2) + j]; + } + } else { + /* Unaligned head, partial tail, or both. Fill the 16-byte + * window from existing flash for bytes outside [i, i+len). + */ + for (j = 0; j < 16; j++) { + if (j < byte_offset || i_aligned + j >= len) + qword_bytes[j] = ((uint8_t *)dst)[i_aligned + j]; + else + qword_bytes[j] = ((uint8_t *)src)[i_aligned + j]; + } + } + + flash_wait_complete(); + flash_clear_errors(); + + FLASH_CR |= FLASH_CR_PG; + for (j = 0; j < 4; j++) { + dst_aligned[j] = qword[j]; + ISB(); + } + flash_wait_complete(); + + if ((FLASH_SR & FLASH_SR_EOP) != 0) + FLASH_CCR = FLASH_CCR_CLR_EOP; + + FLASH_CR &= ~FLASH_CR_PG; + i = i_aligned + 16; + DSB(); + } + hal_cache_invalidate(); + return 0; +} + +void RAMFUNCTION hal_flash_unlock(void) +{ + flash_wait_complete(); + if ((FLASH_CR & FLASH_CR_LOCK) != 0) { + FLASH_KEYR = FLASH_KEY1; + DMB(); + FLASH_KEYR = FLASH_KEY2; + DMB(); + while ((FLASH_CR & FLASH_CR_LOCK) != 0) + ; + } +} + +void RAMFUNCTION hal_flash_lock(void) +{ + flash_wait_complete(); + if ((FLASH_CR & FLASH_CR_LOCK) == 0) + FLASH_CR |= FLASH_CR_LOCK; +} + +void RAMFUNCTION hal_flash_opt_unlock(void) +{ + flash_wait_complete(); + if ((FLASH_OPTCR & FLASH_OPTCR_OPTLOCK) != 0) { + FLASH_OPTKEYR = FLASH_OPTKEY1; + DMB(); + FLASH_OPTKEYR = FLASH_OPTKEY2; + DMB(); + while ((FLASH_OPTCR & FLASH_OPTCR_OPTLOCK) != 0) + ; + } +} + +void RAMFUNCTION hal_flash_opt_lock(void) +{ + FLASH_OPTCR |= FLASH_OPTCR_OPTSTRT; + flash_wait_complete(); + if ((FLASH_OPTCR & FLASH_OPTCR_OPTLOCK) == 0) + FLASH_OPTCR |= FLASH_OPTCR_OPTLOCK; +} + +/* Page erase. PNB[5:0] selects the page within the bank; BKSEL (bit 31) + * selects bank 2 when the address falls into the upper half of flash. + */ +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 bksel = 0; + uint32_t base; + + if (p > FLASH_TOP) { + FLASH_CR &= ~FLASH_CR_PER; + return 0; + } + + if (p >= FLASH_BANK2_BASE) { + bksel = FLASH_CR_BKSEL; + base = FLASH_BANK2_BASE; + } else { + base = FLASHMEM_ADDRESS_SPACE; + } + + reg = FLASH_CR & ~((uint32_t)(FLASH_CR_PNB_MASK << FLASH_CR_PNB_SHIFT) | + FLASH_CR_BKSEL); + reg |= (((p - base) / FLASH_PAGE_SIZE) << FLASH_CR_PNB_SHIFT) | + FLASH_CR_PER | bksel; + FLASH_CR = reg; + DMB(); + FLASH_CR |= FLASH_CR_STRT; + flash_wait_complete(); + } + FLASH_CR &= ~FLASH_CR_PER; + hal_cache_invalidate(); + return 0; +} + +/* --- UART: USART2 on PA2 (TX) / PA3 (RX), AF7 (NUCLEO-C5A3ZG VCP). + * Register/peripheral macros live in stm32c5.h; the values below are + * board/clock specific and stay here. + */ + +#define UART_TX_PIN (2) +#define UART_RX_PIN (3) +#define UART_PIN_AF (7) + +#define USART2_PCLK (48000000U) + +#if defined(DEBUG_UART) || !defined(__WOLFBOOT) + +static void uart2_pins_setup(void) +{ + uint32_t reg; + + RCC_AHB2ENR |= RCC_AHB2ENR_GPIOAEN; + reg = RCC_AHB2ENR; + (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_AFRL & ~(0xFu << (UART_TX_PIN * 4)); + GPIOA_AFRL = reg | (UART_PIN_AF << (UART_TX_PIN * 4)); + reg = GPIOA_AFRL & ~(0xFu << (UART_RX_PIN * 4)); + GPIOA_AFRL = reg | (UART_PIN_AF << (UART_RX_PIN * 4)); + + GPIOA_PUPDR &= ~(0x3u << (UART_TX_PIN * 2)); + GPIOA_PUPDR &= ~(0x3u << (UART_RX_PIN * 2)); +} + +void uart_init(void) +{ + uint32_t reg; + + uart2_pins_setup(); + + RCC_APB1LENR |= RCC_APB1LENR_USART2EN; + reg = RCC_APB1LENR; + (void)reg; + + USART2_CR1 &= ~UART_CR1_UE; + USART2_BRR = USART2_PCLK / 115200; + USART2_CR1 |= UART_CR1_TE | UART_CR1_RE | UART_CR1_UE; +} + +void uart_write(const char *buf, unsigned int sz) +{ + while (sz-- > 0) { + while ((USART2_ISR & UART_ISR_TXE) == 0) + ; + USART2_TDR = *buf++; + } +} + +#endif /* DEBUG_UART || !__WOLFBOOT */ + +/* Default clock: HSIS at 48 MHz remains active after reset. We only + * make sure flash latency is set conservatively for that frequency + * before any high-speed accesses. PLL/HSE bring-up is intentionally + * deferred to a future commit. + */ +static void clock_init(void) +{ + flash_set_waitstates(1); + FLASH_ACR |= FLASH_ACR_PRFTEN; +} + +void hal_init(void) +{ + clock_init(); + 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; + while ((ICACHE_SR & ICACHE_SR_BSYENDF) == 0) + ; + ICACHE_SR |= ICACHE_SR_BSYENDF; +} diff --git a/hal/stm32c5.h b/hal/stm32c5.h new file mode 100644 index 0000000000..09209251d7 --- /dev/null +++ b/hal/stm32c5.h @@ -0,0 +1,187 @@ +/* stm32c5.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 + */ + +/* STM32C5 family (e.g. STM32C5A3ZGT6 on NUCLEO-C5A3ZG). + * Cortex-M33, no TrustZone in this configuration. + * 1 MB dual-bank flash on the -ZG variant (2 x 512 KB, 8 KB pages). + * Reference: stm32c5a3xx.h from STM32CubeC5 v2.0.0. + * + * Bus base addresses (from PERIPH_BASE 0x40000000): + * APB2 at 0x40010000 (USART1) + * AHB1 at 0x40020000 (FLASH controller, ICACHE) + * AHB2 at 0x42020000 (GPIO) + * AHB3 at 0x44020000 (PWR, RCC) + */ + +#ifndef _STM32C5_H_ +#define _STM32C5_H_ + +#include + +/* Assembly helpers */ +#define DMB() __asm__ volatile ("dmb") +#define ISB() __asm__ volatile ("isb") +#define DSB() __asm__ volatile ("dsb") + +/* -------- RCC (AHB3 + 0x0C00 = 0x44020C00) -------- */ +#define RCC_BASE (0x44020C00) + +#define RCC_CR1 (*(volatile uint32_t *)(RCC_BASE + 0x000)) +#define RCC_CR1_HSISON (1 << 0) +#define RCC_CR1_HSIDIV3ON (1 << 1) +#define RCC_CR1_HSISRDY (1 << 4) +#define RCC_CR1_HSIDIV3RDY (1 << 5) + +#define RCC_CFGR1 (*(volatile uint32_t *)(RCC_BASE + 0x01C)) +#define RCC_CFGR2 (*(volatile uint32_t *)(RCC_BASE + 0x020)) + +#define RCC_AHB1ENR (*(volatile uint32_t *)(RCC_BASE + 0x088)) +#define RCC_AHB1ENR_FLASHEN (1 << 8) + +#define RCC_AHB2ENR (*(volatile uint32_t *)(RCC_BASE + 0x08C)) +#define RCC_AHB2ENR_GPIOAEN (1 << 0) +#define RCC_AHB2ENR_GPIOBEN (1 << 1) +#define RCC_AHB2ENR_GPIOCEN (1 << 2) +#define RCC_AHB2ENR_GPIODEN (1 << 3) +#define RCC_AHB2ENR_GPIOGEN (1 << 6) + +#define RCC_AHB4ENR (*(volatile uint32_t *)(RCC_BASE + 0x094)) + +#define RCC_APB1LENR (*(volatile uint32_t *)(RCC_BASE + 0x09C)) +#define RCC_APB1LENR_USART2EN (1 << 17) +#define RCC_APB2ENR (*(volatile uint32_t *)(RCC_BASE + 0x0A4)) +#define RCC_APB2ENR_USART1EN (1 << 14) +#define RCC_APB3ENR (*(volatile uint32_t *)(RCC_BASE + 0x0A8)) + +/* -------- FLASH controller (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_KEYR (*(volatile uint32_t *)(FLASH_BASE + 0x04)) +#define FLASH_OPTKEYR (*(volatile uint32_t *)(FLASH_BASE + 0x0C)) +#define FLASH_OPSR (*(volatile uint32_t *)(FLASH_BASE + 0x18)) +#define FLASH_OPTCR (*(volatile uint32_t *)(FLASH_BASE + 0x1C)) +#define FLASH_SR (*(volatile uint32_t *)(FLASH_BASE + 0x20)) +#define FLASH_CR (*(volatile uint32_t *)(FLASH_BASE + 0x28)) +#define FLASH_CCR (*(volatile uint32_t *)(FLASH_BASE + 0x30)) + +/* FLASH_SR bits */ +#define FLASH_SR_BSY (1 << 0) +#define FLASH_SR_WBNE (1 << 1) +#define FLASH_SR_DBNE (1 << 3) +#define FLASH_SR_EOP (1 << 16) +#define FLASH_SR_WRPERR (1 << 17) +#define FLASH_SR_PGSERR (1 << 18) +#define FLASH_SR_STRBERR (1 << 19) +#define FLASH_SR_INCERR (1 << 20) +#define FLASH_SR_OPTCHANGEERR (1 << 23) + +/* FLASH_CR bits */ +#define FLASH_CR_LOCK (1 << 0) +#define FLASH_CR_PG (1 << 1) +#define FLASH_CR_PER (1 << 2) +#define FLASH_CR_BER (1 << 3) +#define FLASH_CR_FW (1 << 4) +#define FLASH_CR_STRT (1 << 5) +#define FLASH_CR_PNB_SHIFT (6) +#define FLASH_CR_PNB_MASK (0x3F) +#define FLASH_CR_MER (1 << 15) +#define FLASH_CR_BKSEL (1u << 31) + +/* FLASH_OPTCR bits */ +#define FLASH_OPTCR_OPTLOCK (1 << 0) +#define FLASH_OPTCR_OPTSTRT (1 << 1) +#define FLASH_OPTCR_SWAP_BANK (1u << 31) + +/* FLASH_CCR bits (write 1 to clear corresponding SR flag) */ +#define FLASH_CCR_CLR_EOP (1 << 16) +#define FLASH_CCR_CLR_WRPERR (1 << 17) +#define FLASH_CCR_CLR_PGSERR (1 << 18) +#define FLASH_CCR_CLR_STRBERR (1 << 19) +#define FLASH_CCR_CLR_INCERR (1 << 20) +#define FLASH_CCR_CLR_OPTCHANGEERR (1 << 23) + +#define FLASHMEM_ADDRESS_SPACE (0x08000000) +#define FLASH_PAGE_SIZE (0x2000) /* 8 KB */ +#define FLASH_BANK2_BASE (0x08080000) /* 512 KB bank size, 1 MB total */ +#define FLASH_TOP (0x080FFFFF) /* end of 1 MB */ + +#define FLASH_KEY1 (0x45670123) +#define FLASH_KEY2 (0xCDEF89AB) +#define FLASH_OPTKEY1 (0x08192A3BU) +#define FLASH_OPTKEY2 (0x4C5D6E7FU) + +/* -------- GPIO (AHB2 + 0x0000 = 0x42020000) -------- */ +#define GPIOA_BASE (0x42020000) +#define GPIOB_BASE (0x42020400) +#define GPIOC_BASE (0x42020800) +#define GPIOD_BASE (0x42020C00) +#define GPIOG_BASE (0x42021800) + +#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)) +#define GPIOA_AFRL (*(volatile uint32_t *)(GPIOA_BASE + 0x20)) +#define GPIOA_AFRH (*(volatile uint32_t *)(GPIOA_BASE + 0x24)) + +#define GPIOG_MODER (*(volatile uint32_t *)(GPIOG_BASE + 0x00)) +#define GPIOG_PUPDR (*(volatile uint32_t *)(GPIOG_BASE + 0x0C)) +#define GPIOG_BSRR (*(volatile uint32_t *)(GPIOG_BASE + 0x18)) + +/* -------- USART2 (APB1 + 0x4400 = 0x40004400) -------- */ +#define USART2_BASE (0x40004400U) +#define USART2_CR1 (*(volatile uint32_t *)(USART2_BASE + 0x00)) +#define USART2_BRR (*(volatile uint32_t *)(USART2_BASE + 0x0C)) +#define USART2_ISR (*(volatile uint32_t *)(USART2_BASE + 0x1C)) +#define USART2_TDR (*(volatile uint32_t *)(USART2_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) + +/* -------- 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) + +/* -------- Reset -------- */ +#define AIRCR (*(volatile uint32_t *)(0xE000ED0C)) +#define AIRCR_VKEY (0x05FA << 16) +#define AIRCR_SYSRESETREQ (1 << 2) + +void hal_cache_invalidate(void); +void hal_cache_enable(int way); +void hal_cache_disable(void); + +#endif /* _STM32C5_H_ */ diff --git a/hal/stm32c5.ld b/hal/stm32c5.ld new file mode 100644 index 0000000000..4c567dc575 --- /dev/null +++ b/hal/stm32c5.ld @@ -0,0 +1,56 @@ +MEMORY +{ + FLASH (rx) : ORIGIN = 0x08000000, LENGTH = @BOOTLOADER_PARTITION_SIZE@ + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00040000 /* 256 KB SRAM (SRAM1+SRAM2 contiguous) */ +} + +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-stm32c5.ld b/test-app/ARM-stm32c5.ld new file mode 100644 index 0000000000..63564e942f --- /dev/null +++ b/test-app/ARM-stm32c5.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 e5b06108c3..3f3af2bbc2 100644 --- a/test-app/Makefile +++ b/test-app/Makefile @@ -484,6 +484,14 @@ ifeq ($(TARGET),stm32u3) CFLAGS+=-I.. endif +ifeq ($(TARGET),stm32c5) + LSCRIPT_TEMPLATE=ARM-stm32c5.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 @@ -857,6 +865,9 @@ endif ifeq ($(TARGET),stm32c0) CFLAGS+=-DNVM_FLASH_WRITEONCE=1 endif +ifeq ($(TARGET),stm32c5) + CFLAGS+=-DNVM_FLASH_WRITEONCE=1 +endif ifeq ($(TARGET),hifive1.freedom) CFLAGS+=-I$(FREEDOM_E_SDK)/freedom-metal/ -D__METAL_MACHINE_HEADER=\"$(FREEDOM_E_SDK)/bsp/sifive-hifive1/metal.h\" diff --git a/test-app/app_stm32c5.c b/test-app/app_stm32c5.c new file mode 100644 index 0000000000..c854c9c25c --- /dev/null +++ b/test-app/app_stm32c5.c @@ -0,0 +1,150 @@ +/* app_stm32c5.c + * + * Test bare-metal application for the STM32C5 (NUCLEO-C5A3ZG). + * + * 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 "hal/stm32c5.h" +#include "wolfboot/wolfboot.h" +#include "target.h" + +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-C5A3ZG: user LED LD2 (green) on PG1. */ +#define LED_USR_PIN (1) + +static void led_init(void) +{ + uint32_t reg; + RCC_AHB2ENR |= RCC_AHB2ENR_GPIOGEN; + reg = RCC_AHB2ENR; + (void)reg; + + reg = GPIOG_MODER & ~(0x03u << (LED_USR_PIN * 2)); + GPIOG_MODER = reg | (0x01u << (LED_USR_PIN * 2)); + GPIOG_PUPDR &= ~(0x03u << (LED_USR_PIN * 2)); +} + +static void led_on(void) +{ + GPIOG_BSRR = (1u << LED_USR_PIN); +} + +static void led_off(void) +{ + GPIOG_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"); +} + +/* NUCLEO-C5A3ZG LD2 is active LOW: PG1 sourced through R18 to 3V3, + * cathode to PG1. Drive PG1 low to light the LED. + */ +static void led_active_low_on(void) +{ + GPIOG_BSRR = (1u << (LED_USR_PIN + 16)); +} + +static void led_active_low_off(void) +{ + GPIOG_BSRR = (1u << LED_USR_PIN); +} + +static void system_reset(void) +{ + AIRCR = AIRCR_VKEY | AIRCR_SYSRESETREQ; + while (1) + ; +} + +void main(void) +{ + uint32_t version; + uint32_t v; + uint32_t on_ticks, off_ticks; + char num[4]; + int idx = 0; + int blinks; + + hal_init(); + led_init(); + led_active_low_off(); + + 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: blink slowly 5 times, then trigger an update. Once the + * update flag is set, wolfBoot will perform the swap on the next + * reset. + * v2+: confirm success (sticks in bank2) and blink fast forever. + */ + if (version >= 2) { + wolfBoot_success(); + uart_print("update OK -- success confirmed\r\n"); + on_ticks = 200000; + off_ticks = 200000; + while (1) { + led_active_low_on(); + busy_delay(on_ticks); + led_active_low_off(); + busy_delay(off_ticks); + } + } + + on_ticks = 600000; + off_ticks = 600000; + for (blinks = 0; blinks < 5; blinks++) { + led_active_low_on(); + busy_delay(on_ticks); + led_active_low_off(); + busy_delay(off_ticks); + } + + uart_print("triggering update -> reset\r\n"); + wolfBoot_update_trigger(); + system_reset(); +}