Skip to content

Commit af40ae1

Browse files
dgarskedanielinux
authored andcommitted
Add STM32C5 target (NUCLEO-C5A3ZG)
1 parent d69086c commit af40ae1

11 files changed

Lines changed: 1329 additions & 0 deletions

File tree

.github/workflows/test-configs.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,25 @@ jobs:
542542
arch: arm
543543
config-file: ./config/examples/stm32u3.config
544544

545+
stm32c5_test:
546+
uses: ./.github/workflows/test-build.yml
547+
with:
548+
arch: arm
549+
config-file: ./config/examples/stm32c5.config
550+
551+
stm32c5_no_clock_restore_test:
552+
uses: ./.github/workflows/test-build.yml
553+
with:
554+
arch: arm
555+
config-file: ./config/examples/stm32c5.config
556+
make-args: WOLFBOOT_RESTORE_CLOCK=0
557+
558+
stm32c5_dualbank_test:
559+
uses: ./.github/workflows/test-build.yml
560+
with:
561+
arch: arm
562+
config-file: ./config/examples/stm32c5-dualbank.config
563+
545564
stm32u5_nonsecure_dualbank_test:
546565
uses: ./.github/workflows/test-build.yml
547566
with:

arch.mk

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,15 @@ ifeq ($(ARCH),ARM)
264264
SPI_TARGET=stm32
265265
endif
266266

267+
ifeq ($(TARGET),stm32c5)
268+
CORTEX_M33=1
269+
CFLAGS+=-Ihal
270+
ARCH_FLASH_OFFSET=0x08000000
271+
WOLFBOOT_ORIGIN=0x08000000
272+
LSCRIPT_IN=hal/$(TARGET).ld
273+
SPI_TARGET=stm32
274+
endif
275+
267276
ifeq ($(TARGET),stm32h5)
268277
CORTEX_M33=1
269278
CFLAGS+=-Ihal
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
ARCH?=ARM
2+
TZEN?=0
3+
TARGET?=stm32c5
4+
SIGN?=ECC256
5+
HASH?=SHA256
6+
DEBUG?=0
7+
VTOR?=1
8+
CORTEX_M0?=0
9+
CORTEX_M33?=1
10+
NO_ASM?=0
11+
NO_MPU=1
12+
EXT_FLASH?=0
13+
SPI_FLASH?=0
14+
ALLOW_DOWNGRADE?=0
15+
NVM_FLASH_WRITEONCE?=1
16+
WOLFBOOT_VERSION?=1
17+
V?=0
18+
SPMATH?=1
19+
RAM_CODE?=1
20+
DUALBANK_SWAP?=1
21+
# Dual-bank swap layout (2x512KB, 8KB pages, 1MB total). BOOT and
22+
# UPDATE sit at the same offset within their respective banks so that
23+
# WOLFBOOT_PARTITION_BOOT_ADDRESS = 0x08010000 stays valid before and
24+
# after FLASH_OPTCR.SWAP_BANK toggles which physical bank is mapped
25+
# to 0x08000000. fork_bootloader() in hal_init keeps a copy of
26+
# wolfBoot at the start of bank 2 so the chip boots from either bank.
27+
# Bank 1 (active by default):
28+
# 0x08000000 wolfBoot (64 KB)
29+
# 0x08010000 BOOT partition (448 KB)
30+
# Bank 2 (active after SWAP_BANK toggle):
31+
# 0x08080000 wolfBoot copy (64 KB)
32+
# 0x08090000 UPDATE partition (448 KB)
33+
WOLFBOOT_SECTOR_SIZE?=0x2000
34+
WOLFBOOT_PARTITION_SIZE?=0x70000
35+
WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x08010000
36+
WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x08090000
37+
WOLFBOOT_PARTITION_SWAP_ADDRESS?=0xFFFFFFFF
38+
FLAGS_HOME=0
39+
DISABLE_BACKUP=0
40+
DEBUG_UART=1

config/examples/stm32c5.config

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
ARCH?=ARM
2+
TZEN?=0
3+
TARGET?=stm32c5
4+
SIGN?=ECC256
5+
HASH?=SHA256
6+
DEBUG?=0
7+
VTOR?=1
8+
CORTEX_M0?=0
9+
CORTEX_M33?=1
10+
NO_ASM?=0
11+
NO_MPU=1
12+
EXT_FLASH?=0
13+
SPI_FLASH?=0
14+
ALLOW_DOWNGRADE?=0
15+
NVM_FLASH_WRITEONCE?=1
16+
WOLFBOOT_VERSION?=1
17+
V?=0
18+
SPMATH?=1
19+
RAM_CODE?=1
20+
DUALBANK_SWAP?=0
21+
# Flash layout for dual-bank (2x512KB, 8KB pages, 1MB total):
22+
# Bank 1 (0x08000000): wolfBoot (64KB) + BOOT partition (448KB)
23+
# Bank 2 (0x08080000): UPDATE partition (448KB) + SWAP (8KB)
24+
WOLFBOOT_SECTOR_SIZE?=0x2000
25+
WOLFBOOT_PARTITION_SIZE?=0x70000
26+
WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x08010000
27+
WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x08080000
28+
WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x080F0000
29+
FLAGS_HOME=0
30+
DISABLE_BACKUP=0
31+
DEBUG_UART=1

docs/Targets.md

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ This README describes configuration of supported targets.
3939
* [Renesas RZN2L](#renesas-rzn2l)
4040
* [SiFive HiFive1 RISC-V](#sifive-hifive1-risc-v)
4141
* [STM32C0](#stm32c0)
42+
* [STM32C5](#stm32c5)
4243
* [STM32F1](#stm32f1)
4344
* [STM32F4](#stm32f4)
4445
* [STM32F7](#stm32f7)
@@ -1820,6 +1821,174 @@ the new image. LD2 transitions from the slow (v1) blink to the fast
18201821
transition.
18211822
18221823
1824+
## STM32C5
1825+
1826+
The STM32C5 family (for example the STM32C5A3ZGT6 on NUCLEO-C5A3ZG) is
1827+
a mainstream Cortex-M33 part **without TrustZone**, so the port is
1828+
single-image only (no `-tz` or `-ns` variants). On the -ZG variant:
1829+
1 MB internal flash, 256 KB SRAM, 8 KB pages, **128-bit (quad-word)
1830+
flash write quantum** with per-quad-word ECC.
1831+
1832+
The HAL writes flash in 16-byte aligned quad-words. When wolfBoot
1833+
asks for a smaller or unaligned write, the HAL reads the surrounding
1834+
flash and merges so each programmed quad-word is a complete ECC
1835+
block - sub-quad-word writes leave ECC undefined and reads come back
1836+
with bit-flipped "corrected" data.
1837+
1838+
### Flash layout (stm32c5.config)
1839+
1840+
Dual-bank flash (2 x 512 KB, 8 KB pages). Bank 1 holds wolfBoot +
1841+
BOOT, bank 2 holds UPDATE + SWAP:
1842+
1843+
```
1844+
Bank 1:
1845+
0x08000000 - 0x0800FFFF wolfBoot bootloader (64 KB)
1846+
0x08010000 - 0x0807FFFF BOOT partition (0x70000, 448 KB)
1847+
Bank 2:
1848+
0x08080000 - 0x080EFFFF UPDATE partition (0x70000, 448 KB)
1849+
0x080F0000 - 0x080F1FFF SWAP sector (8 KB)
1850+
```
1851+
1852+
### Clock and UART
1853+
1854+
The reset SYSCLK is **HSIDIV3 = HSIS / 3 = 16 MHz** (RCC_CFGR1.SW=0
1855+
selects HSIDIV3, RCC_CR1 reset value 0x22 = HSIDIV3ON | HSIDIV3RDY).
1856+
wolfBoot brings SYSCLK to **144 MHz HCLK** in `clock_psi_on()` (called
1857+
from `hal_init()`) via the
1858+
PSIS clock chain (HSE 48 MHz reference -> PSI ref=48 MHz / out=144 MHz
1859+
-> PSIS), with all bus prescalers /1 (PCLK1 = PCLK2 = PCLK3 = 144 MHz),
1860+
flash 4 wait states and WRHIGHFREQ programming delay = 2.
1861+
1862+
By default (`WOLFBOOT_RESTORE_CLOCK` set in `options.mk`),
1863+
`hal_prepare_boot()` switches SYSCLK back to HSIDIV3 before handoff
1864+
but **leaves PSIS, PSI and HSE running**. The loaded firmware's own
1865+
`clock_psi_on()` then just pushes SYSCLK back from HSIDIV3 to PSIS -
1866+
the HSE/PSI configuration it would have written is already in place,
1867+
so it skips the HSE startup wait and the PSI reconfiguration entirely.
1868+
This mirrors ST's `HAL_RCC_ResetSystemClock()` (the lightweight
1869+
restore) rather than the full `HAL_RCC_Reset()`. Disabling HSE in
1870+
`hal_prepare_boot()` and forcing the loaded firmware to re-enable it
1871+
on a back-to-back cycle is not reliable on this part; the lightweight
1872+
restore avoids that path entirely. Pass `WOLFBOOT_RESTORE_CLOCK=0`
1873+
to skip the SYSCLK switch entirely and inherit PSIS @ 144 MHz directly.
1874+
1875+
UART is always available in the test-app and enabled in wolfBoot via
1876+
`DEBUG_UART=1` (on by default in the example config). USART2_BRR is
1877+
computed for PCLK1 = 144 MHz. The NUCLEO-C5A3ZG ST-LINK virtual COM
1878+
port is wired to MCU pins 36/37 (PA2/PA3) - **USART2** on AF7, 115200
1879+
8N1, **not USART1 on PA9/PA10** (PA9/PA10 only reach the Arduino
1880+
headers).
1881+
1882+
### Building
1883+
1884+
```sh
1885+
cp config/examples/stm32c5.config .config
1886+
make clean
1887+
make
1888+
```
1889+
1890+
Default signing scheme is ECC256 + SHA256. Produces `wolfboot.bin`
1891+
(~25 KB), `test-app/image_v1_signed.bin`, and `factory.bin` (BL +
1892+
signed v1).
1893+
1894+
### Flashing
1895+
1896+
Use `STM32_Programmer_CLI` (from STM32CubeIDE or STM32CubeProgrammer
1897+
v2.22+). pyocd has no STM32C5 target as of this writing. The C5
1898+
debug access port is AP2; `mode=UR` (under-reset) is the most
1899+
reliable connect mode while a previous image is running.
1900+
1901+
```sh
1902+
STM32_Programmer_CLI -c port=swd mode=UR -e all \
1903+
-d factory.bin 0x08000000 -v -rst
1904+
```
1905+
1906+
The test app blinks LD2 (PG1, **active low**): five slow blinks on
1907+
v1 then it triggers an update and resets; v2 blinks fast forever
1908+
once `wolfBoot_success()` is acknowledged.
1909+
1910+
### Testing an Update
1911+
1912+
Sign the test application as version 2 and flash it directly to the
1913+
update partition:
1914+
1915+
```sh
1916+
./tools/keytools/sign --ecc256 --sha256 \
1917+
test-app/image.bin wolfboot_signing_private_key.der 2
1918+
STM32_Programmer_CLI -c port=swd mode=UR \
1919+
-d test-app/image_v2_signed.bin 0x08080000 -v -rst
1920+
```
1921+
1922+
On reset wolfBoot detects the staged v2, the v1 test-app calls
1923+
`wolfBoot_update_trigger()` after its blink sequence and resets,
1924+
wolfBoot performs the bank-to-bank swap, and v2 boots. With
1925+
`DEBUG_UART=1` the UART log shows:
1926+
1927+
```
1928+
Booting version: 0x1
1929+
TEST APP / App version: 1 / triggering update -> reset
1930+
... swap output ...
1931+
Booting version: 0x2
1932+
TEST APP / App version: 2 / update OK -- success confirmed
1933+
```
1934+
1935+
### DUALBANK_SWAP variant
1936+
1937+
`config/examples/stm32c5-dualbank.config` builds wolfBoot with
1938+
`DUALBANK_SWAP=1`, using the STM32C5's `FLASH_OPTCR.SWAP_BANK`
1939+
option byte (bit 31) to flip which physical bank is mapped at
1940+
`0x08000000`. This replaces the copy-based swap with a single
1941+
option-byte toggle and a system reset - much faster, no swap
1942+
sector required.
1943+
1944+
Layout:
1945+
1946+
```
1947+
Bank 1 (active by default):
1948+
0x08000000 wolfBoot (64 KB)
1949+
0x08010000 BOOT partition (448 KB)
1950+
Bank 2 (active after SWAP_BANK toggle):
1951+
0x08080000 wolfBoot copy (64 KB)
1952+
0x08090000 UPDATE partition (448 KB)
1953+
```
1954+
1955+
`hal_init()` runs `fork_bootloader()` on the first boot, comparing
1956+
the contents of bank 1 and bank 2 and copying wolfBoot from bank 1
1957+
into bank 2 if they differ. This guarantees the chip can boot from
1958+
`0x08000000` regardless of which physical bank `SWAP_BANK` currently
1959+
maps there. Subsequent boots are no-ops because the two copies match.
1960+
1961+
Build, flash, and stage v2 are the same as the default config except
1962+
the UPDATE partition lives at `0x08090000`:
1963+
1964+
```sh
1965+
cp config/examples/stm32c5-dualbank.config .config
1966+
make distclean && make
1967+
STM32_Programmer_CLI -c port=swd mode=UR -e all \
1968+
-d factory.bin 0x08000000 -v -rst
1969+
./tools/keytools/sign --ecc256 --sha256 \
1970+
test-app/image.bin wolfboot_signing_private_key.der 2
1971+
STM32_Programmer_CLI -c port=swd mode=UR \
1972+
-d test-app/image_v2_signed.bin 0x08090000 -v -rst
1973+
```
1974+
1975+
After the swap completes the partition addresses stay the same from
1976+
software's perspective (`WOLFBOOT_PARTITION_BOOT_ADDRESS = 0x08010000`
1977+
keeps pointing at "current BOOT") - only the underlying bank is
1978+
different. Subsequent updates stage at `0x08090000` again.
1979+
1980+
### TrustZone (TZEN) is not supported
1981+
1982+
The STM32C5 silicon does not implement the ARMv8-M Security Extensions
1983+
(`__SAUREGION_PRESENT 0U` in the CMSIS device header) and has no
1984+
GTZC, no `FLASH_NS_*` / `FLASH_SECCR*` aliases, and no secure
1985+
peripheral address space. wolfBoot's TrustZone ports (L5, U5, H5)
1986+
cannot be ported to the C5 - the hardware needed to partition memory
1987+
and peripherals into secure / non-secure worlds is absent. For
1988+
application security on the C5 use the MPU (`__MPU_PRESENT 1U`) and
1989+
flash RDP (Read Out Protection); both are wolfBoot-orthogonal.
1990+
1991+
18231992
## STM32H5
18241993
18251994
Like [STM32L5](#stm32l5) and [STM32U5](#stm32u5), STM32H5 support is also demonstrated

0 commit comments

Comments
 (0)