Add NXP RW612 target with TrustZone support

pull/926/head
Aidan Garske 2026-10-06 22:46:00 +01:00
parent a16e2e8f11
commit d147db6dfd
27 changed files with 2394 additions and 65 deletions

View File

@ -359,6 +359,37 @@ jobs:
config-file: ./config/examples/mcxn-tz-psa-hw.config
board-name: frdmmcxn947
nxp_rw612_test:
uses: ./.github/workflows/test-build-mcux-sdk-manifests.yml
with:
arch: arm
config-file: ./config/examples/rw612.config
board-name: frdmrw612
nxp_rw612_tz_test:
uses: ./.github/workflows/test-build-mcux-sdk-manifests.yml
with:
arch: arm
config-file: ./config/examples/rw612-tz.config
board-name: frdmrw612
nxp_rw612_wolfcrypt_tz_test:
uses: ./.github/workflows/test-build-mcux-sdk-manifests.yml
with:
arch: arm
config-file: ./config/examples/rw612-tz.config
board-name: frdmrw612
make-args: "WOLFCRYPT_TZ=1 WOLFCRYPT_TZ_PKCS11=1"
# Without the console the TRNG still needs the reset driver linked in
nxp_rw612_wolfcrypt_tz_no_uart_test:
uses: ./.github/workflows/test-build-mcux-sdk-manifests.yml
with:
arch: arm
config-file: ./config/examples/rw612-tz.config
board-name: frdmrw612
make-args: "WOLFCRYPT_TZ=1 WOLFCRYPT_TZ_PKCS11=1 DEBUG_UART=0"
nxp_s32k142_test:
uses: ./.github/workflows/test-build.yml
with:

View File

@ -25,10 +25,14 @@ jobs:
run: |
make keysclean && make -C tools/keytools clean && rm -f include/target.h
- name: Fetch NXP MCUXpresso SDK headers
run: |
tools/ci/fetch-mcuxsdk-headers.sh "$GITHUB_WORKSPACE/mcuxsdk"
- name: Build unit tests
run: |
make -C tools/unit-tests
make -C tools/unit-tests MCUXSDK="$GITHUB_WORKSPACE/mcuxsdk"
- name: Run unit tests
run: |
make -C tools/unit-tests run
make -C tools/unit-tests run MCUXSDK="$GITHUB_WORKSPACE/mcuxsdk"

3
.gitignore vendored
View File

@ -244,6 +244,9 @@ tools/unit-tests/unit-ahci-unlock-panic
tools/unit-tests/unit-ata-security-passphrase-zeroize
tools/unit-tests/unit-arm-tee-psa-ipc
tools/unit-tests/unit-flash-write-mcxa
tools/unit-tests/unit-rw612-flash
tools/unit-tests/unit-rw612-tz
tools/unit-tests/unit-rw612-uart
tools/unit-tests/unit-flash-write-nrf52
tools/unit-tests/unit-flash-write-same51
tools/unit-tests/unit-flash-write-samr21

43
arch.mk
View File

@ -1511,6 +1511,49 @@ ifeq ($(TARGET),mcxn)
endif
endif
ifeq ($(TARGET),rw612)
CORTEX_M33=1
ARCH_FLASH_OFFSET=0x08000000
ifneq ($(TZEN),1)
LSCRIPT_IN=hal/$(TARGET)-ns.ld
endif
MCUXPRESSO_FLASH_CONFIG?=$(MCUXPRESSO_PROJECT_TEMPLATE)/../flash_config
CFLAGS+=\
-I$(MCUXPRESSO_DRIVERS) \
-I$(MCUXPRESSO_DRIVERS)/drivers \
-I$(MCUXPRESSO_DRIVERS)/drivers/romapi/flexspi \
-I$(MCUXPRESSO_DRIVERS)/../periph \
-I$(MCUXPRESSO) \
-I$(MCUXPRESSO)/drivers \
-I$(MCUXPRESSO)/drivers/common \
-I$(MCUXPRESSO)/drivers/flexcomm \
-I$(MCUXPRESSO)/drivers/flexcomm/usart \
-I$(MCUXPRESSO)/drivers/trng \
-I$(MCUXPRESSO_FLASH_CONFIG) \
-I$(MCUXPRESSO_PROJECT_TEMPLATE) \
-I$(MCUXPRESSO_CMSIS)/Include \
-I$(MCUXPRESSO_CMSIS)/Core/Include
CFLAGS+=-DCPU_$(MCUXPRESSO_CPU) -DBOOT_HEADER_ENABLE=1 -DWOLFBOOT_ROM_IMAGE_HEADER
CFLAGS+=-DDEBUG_CONSOLE_ASSERT_DISABLE=1
# The RW612 Cortex-M33 has no DSP extension
CFLAGS+=-mcpu=cortex-m33+nodsp -DCORTEX_M33
LDFLAGS+=-mcpu=cortex-m33+nodsp
OBJS+=\
$(MCUXPRESSO_DRIVERS)/drivers/fsl_clock.o \
$(MCUXPRESSO_DRIVERS)/drivers/fsl_reset.o \
$(MCUXPRESSO_FLASH_CONFIG)/flash_config.o
ifeq ($(DEBUG_UART),1)
OBJS+=\
$(MCUXPRESSO)/drivers/flexcomm/fsl_flexcomm.o \
$(MCUXPRESSO)/drivers/flexcomm/usart/fsl_usart.o
endif
ifeq ($(WOLFCRYPT_TZ),1)
OBJS+=$(MCUXPRESSO)/drivers/trng/fsl_trng.o
endif
endif
ifeq ($(TARGET),nrf5340)
ifneq ($(TZEN), 1)
LSCRIPT_IN=hal/$(TARGET)-ns.ld

View File

@ -0,0 +1,42 @@
ARCH?=ARM
TZEN?=1
TARGET?=rw612
SIGN?=ECC256
HASH?=SHA256
MCUXSDK?=1
MCUXPRESSO?=$(PWD)/../NXP/mcuxpresso-sdk/mcuxsdk
MCUXPRESSO_CMSIS?=$(MCUXPRESSO)/arch/arm/CMSIS
MCUXPRESSO_CPU?=RW612ETA2I
MCUXPRESSO_DRIVERS?=$(MCUXPRESSO)/devices/Wireless/RW/RW612
MCUXPRESSO_PROJECT_TEMPLATE?=$(MCUXPRESSO)/examples/_boards/frdmrw612/project_template
DEBUG?=0
DEBUG_UART?=1
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
NO_ARM_ASM?=0
WOLFBOOT_VERSION?=0
V?=0
SPMATH?=1
RAM_CODE?=1
DUALBANK_SWAP?=0
# 4KB sectors on the external FlexSPI NOR (W25Q512JV)
WOLFBOOT_SECTOR_SIZE?=0x1000
# 384KB wolfBoot, 8KB NSC, 1MB partitions, 4KB swap, 128KB keyvault
# The keyvault sits outside the range erased by a bootloader self-update
WOLFBOOT_KEYVAULT_ADDRESS?=0x08400000
WOLFBOOT_KEYVAULT_SIZE?=0x20000
WOLFBOOT_NSC_ADDRESS?=0x08060000
WOLFBOOT_NSC_SIZE?=0x2000
WOLFBOOT_PARTITION_SIZE?=0x100000
WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x08100000
WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x08200000
WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x08300000

View File

@ -0,0 +1,37 @@
ARCH?=ARM
TZEN?=0
TARGET?=rw612
SIGN?=ECC256
HASH?=SHA256
MCUXSDK?=1
MCUXPRESSO?=$(PWD)/../NXP/mcuxpresso-sdk/mcuxsdk
MCUXPRESSO_CMSIS?=$(MCUXPRESSO)/arch/arm/CMSIS
MCUXPRESSO_CPU?=RW612ETA2I
MCUXPRESSO_DRIVERS?=$(MCUXPRESSO)/devices/Wireless/RW/RW612
MCUXPRESSO_PROJECT_TEMPLATE?=$(MCUXPRESSO)/examples/_boards/frdmrw612/project_template
DEBUG?=0
DEBUG_UART?=1
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
NO_ARM_ASM?=0
WOLFBOOT_VERSION?=0
V?=0
SPMATH?=1
RAM_CODE?=1
DUALBANK_SWAP?=0
# 4KB sectors on the external FlexSPI NOR (W25Q512JV)
WOLFBOOT_SECTOR_SIZE?=0x1000
# 1MB boot, 1MB partitions, 4KB swap
WOLFBOOT_PARTITION_SIZE?=0x100000
WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x08100000
WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x08200000
WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x08300000

View File

@ -35,6 +35,7 @@ This README describes configuration of supported targets.
* [NXP MCXA153](#nxp-mcxa153)
* [NXP MCXW716](#nxp-mcxw716)
* [NXP MCXN947](#nxp-mcxn947)
* [NXP RW612](#nxp-rw612)
* [NXP S32K1XX](#nxp-s32k1xx)
* [NXP P1021 PPC](#nxp-qoriq-p1021-ppc)
* [NXP T10xx PPC (T1024 / T1040)](#nxp-qoriq-t10xx-ppc-t1024--t1040)
@ -7261,6 +7262,183 @@ c
Sample application for DICE attestation is available on MCXN947.
Please find the details in `docs/MCXN947-DICE.md`.
## NXP RW612
The NXP RW612 is a wireless MCU (Wi-Fi 6, Bluetooth LE, 802.15.4) with a
260MHz Cortex-M33 and TrustZone-M. Code executes in place from an external
FlexSPI NOR flash mapped at `0x08000000` (secure alias `0x18000000`). The
support has been tested on the FRDM-RW612 board (64MB Winbond W25Q512JV NOR,
4KB sectors) with its on-board MCU-Link.
`wolfboot.bin` contains the FlexSPI configuration block (FCB) at offset `0x400`
and the boot ROM image header in its vector table at `0x1000`, so it boots from
the start of the flash without any `nxpimage` wrapping. Flash erase and program
use the boot ROM FlexSPI API from RAM. The RW612 Cortex-M33 has no DSP
extension, so wolfBoot is built with `-mcpu=cortex-m33+nodsp`.
This requires the NXP MCUXpresso SDK, placed under `../NXP` by default:
```sh
mkdir -p ../NXP && cd ../NXP
python3 -m venv west-venv
. west-venv/bin/activate
pip install west
west init -m https://github.com/nxp-mcuxpresso/mcuxsdk-manifests.git mcuxpresso-sdk
cd mcuxpresso-sdk
west update_board --set board frdmrw612
deactivate
```
`update_board` fetches every project the board's SDK examples use. The wolfBoot
build only needs four of them (drivers, CMSIS, the board's project template and
flash configuration, and the RW612 device files), so this is enough instead:
```sh
west update core CMSIS mcu-sdk-examples mcux-devices-wireless
```
### RW612: Configuring and compiling
```sh
cp config/examples/rw612.config .config
make
```
We provide two configuration files:
- `rw612.config`: both wolfBoot and the application run in the secure world.
- `rw612-tz.config`: wolfBoot runs in the secure world, the application runs
in the non-secure world.
To add a non-secure callable wolfPKCS11 API to wolfCrypt and a secure keyvault
provided by wolfBoot, build `rw612-tz.config` with
`make WOLFCRYPT_TZ=1 WOLFCRYPT_TZ_PKCS11=1`.
Both sign with ECC256/SHA256; any other algorithm supported by wolfBoot can be
selected with `SIGN=` and `HASH=`. Signatures that need a larger header must
also raise `WOLFBOOT_SECTOR_SIZE` to at least `IMAGE_HEADER_SIZE`, for example
ML-DSA-44:
```sh
make SIGN=ML_DSA ML_DSA_LEVEL=2 IMAGE_SIGNATURE_SIZE=2420 \
IMAGE_HEADER_SIZE=8192 WOLFBOOT_SECTOR_SIZE=0x2000
```
| Region | Address | Size |
|---------------------------|--------------|-------|
| FCB | `0x08000400` | 512B |
| wolfBoot | `0x08001000` | - |
| NSC veneers (TZ) | `0x08060000` | 8KB |
| Boot partition | `0x08100000` | 1MB |
| Update partition | `0x08200000` | 1MB |
| Swap sector | `0x08300000` | 4KB |
| Keyvault (TZ) | `0x08400000` | 128KB |
With TrustZone, wolfBoot keeps its data in secure SRAM at `0x30000000` and
the non-secure application uses SRAM from `0x20040000`. Only the boot
partition, that SRAM, the group 1 clock and reset controls (`CLKCTL1`,
`RSTCTL1`), GPIO and FLEXCOMM0-3 are non-secure; extend `hal_sau_init()` in
`hal/rw612.c` to give the application more peripherals.
### RW612: Loading the firmware
Program the flash with [pyOCD](https://pyocd.io) through the MCU-Link
(CMSIS-DAP firmware, as shipped). `factory.bin` holds wolfBoot and the signed
v1 application in one image laid out from `0x08000000`:
```sh
pip install pyocd
pyocd pack install rw612eta2i
pyocd flash -t rw612eta2i -a 0x08000000 -e sector factory.bin
```
or write them separately:
```sh
pyocd flash -t rw612eta2i -a 0x08000000 -e sector wolfboot.bin
pyocd flash -t rw612eta2i -a 0x08100000 -e sector test-app/image_v1_signed.bin
```
If the MCU-Link has been updated to J-Link firmware, pyOCD drives it through
the SEGGER J-Link software: install the J-Link Software and Documentation Pack,
`pip install pylink-square`, and add `-O jlink.device=RW612` to the commands
above (on Linux, put the J-Link directory on `LD_LIBRARY_PATH` if pyOCD cannot
find `libjlinkarm`). J-Link Commander works as well (add `-USB <serial>` when
more than one J-Link is attached):
```sh
JLinkExe -device RW612 -if SWD -speed 4000 -NoGui 1 -CommanderScript flash.jlink
```
```
exec EnableEraseAllFlashBanks
connect
h
erase 0x08100000 0x08300FFF
loadbin factory.bin 0x08000000
r
g
qc
```
The FlexSPI NOR is not an internal flash bank, so an explicit `erase` range
needs `exec EnableEraseAllFlashBanks`; without it J-Link reports `No Flash bank
within given address range` and erases nothing. The `erase` clears the boot,
update and swap areas so no stale partition state is left (add
`erase 0x08400000 0x0841FFFF` to also clear the TrustZone keyvault), and
`loadbin` erases the sectors it writes. Keep `connect` ahead of every command
that talks to the target: some J-Link Commander versions stop processing the
script after an implicit connect, so `loadbin` never runs.
The debug console is FLEXCOMM3 on the MCU-Link virtual COM port at 115200
baud. After reset:
```
Hello from RW612 firmware version 1
```
### RW612: Testing firmware update
1) Sign the test-app with version 2:
```sh
./tools/keytools/sign --ecc256 test-app/image.bin wolfboot_signing_private_key.der 2
```
2) Create the update trigger and assemble the update partition image
(`0xFFFFB` is `WOLFBOOT_PARTITION_SIZE` minus 5):
```sh
echo -n "pBOOT" > trigger_magic.bin
./tools/bin-assemble/bin-assemble \
update.bin \
0x0 test-app/image_v2_signed.bin \
0xFFFFB trigger_magic.bin
```
3) Flash `update.bin` to the update partition:
```sh
pyocd flash -t rw612eta2i -a 0x08200000 -e sector update.bin
```
With J-Link Commander, `loadbin update.bin 0x08200000` followed by `r` and `g`
does the same.
wolfBoot verifies and installs version 2, which confirms the update with
`wolfBoot_success()`:
```
Hello from RW612 firmware version 2
Update successful, firmware version 2 confirmed
```
### RW612: Debugging
```sh
pyocd gdbserver -t rw612eta2i
arm-none-eabi-gdb wolfboot.elf -ex "target remote :3333"
```
## NXP S32K1XX
The NXP S32K1xx family (S32K142, S32K144, S32K146, S32K148) are automotive-grade

60
hal/rw612-ns.ld 100644
View File

@ -0,0 +1,60 @@
MEMORY
{
FLASH (rx) : ORIGIN = @ARCH_FLASH_OFFSET@, LENGTH = @BOOTLOADER_PARTITION_SIZE@
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 256K
}
SECTIONS
{
.text :
{
_start_text = .;
. = 0x400;
KEEP(*(.flash_conf))
. = 0x1000;
_rom_image_start = .;
KEEP(*(.isr_vector))
*(.keystore*)
*(.text*)
*(.rodata*)
*(.init*)
*(.fini*)
. = ALIGN(4);
_end_text = .;
} > FLASH
.edidx :
{
. = ALIGN(4);
*(.ARM.exidx*)
} > FLASH
_stored_data = .;
.data : AT (_stored_data)
{
_start_data = .;
KEEP(*(.ramcode*))
. = ALIGN(4);
KEEP(*(.data*))
. = 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);
_rom_image_size = _stored_data + SIZEOF(.data) - _rom_image_start;

363
hal/rw612.c 100644
View File

@ -0,0 +1,363 @@
/* rw612.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
*/
#include <stdint.h>
#include <string.h>
#include <target.h>
#include "fsl_common.h"
#include "image.h"
#include "loader.h"
#include "fsl_clock.h"
#include "fsl_romapi_flexspi.h"
#ifdef DEBUG_UART
#include "fsl_io_mux.h"
#include "fsl_usart.h"
#endif
#ifdef TZEN
#include "hal/armv8m_tz.h"
#endif
#if defined(WOLFCRYPT_SECURE_MODE) && !defined(NONSECURE_APP)
#include "fsl_trng.h"
#endif
#include "rw612.h"
#if (WOLFBOOT_SECTOR_SIZE % RW612_FLASH_SECTOR_SIZE) != 0
#error WOLFBOOT_SECTOR_SIZE must be a multiple of the 4KB NOR erase sector
#endif
uint32_t SystemCoreClock;
static flexspi_nor_config_t flash_cfg;
static const flexspi_nor_flash_driver_t *rom_flexspi;
#ifdef WOLFCRYPT_SECURE_MODE
void hal_trng_init(void);
int hal_trng_get_entropy(unsigned char *out, unsigned int len);
#endif
#ifdef TZEN
static void hal_sau_init(void)
{
/* Non-secure callable area */
sau_init_region(0, WOLFBOOT_NSC_ADDRESS,
WOLFBOOT_NSC_ADDRESS + WOLFBOOT_NSC_SIZE - 1, 1);
/* Non-secure: application flash area (boot partition) */
sau_init_region(1, WOLFBOOT_PARTITION_BOOT_ADDRESS,
WOLFBOOT_PARTITION_BOOT_ADDRESS + WOLFBOOT_PARTITION_SIZE - 1, 0);
/* Non-secure RAM, above the physical SRAM used by wolfBoot */
sau_init_region(2, 0x20040000, 0x2012FFFF, 0);
/* Group 0 clock/reset, FlexSPI, IO_MUX, DMA and crypto stay secure */
sau_init_region(3, 0x40020000, 0x40021FFF, 0); /* RSTCTL1, CLKCTL1 */
sau_init_region(4, 0x40100000, 0x40103FFF, 0); /* GPIO */
sau_init_region(5, 0x40106000, 0x40109FFF, 0); /* FLEXCOMM0..3 */
/* Their interrupts target the non-secure world */
NVIC->ITNS[0] |= (1UL << GPIO_INTA_IRQn) | (1UL << GPIO_INTB_IRQn) |
(0xFUL << FLEXCOMM0_IRQn);
SAU_CTRL = SAU_INIT_CTRL_ENABLE;
SCB_SHCSR |= SCB_SHCSR_SECUREFAULT_EN;
}
#endif
#ifdef DEBUG_UART
void uart_init(void)
{
usart_config_t config;
CLOCK_AttachClk(kSFRO_to_FLEXCOMM3);
IO_MUX_SetPinMux(IO_MUX_FC3_USART_DATA);
USART_GetDefaultConfig(&config);
config.baudRate_Bps = RW612_UART_BAUD;
config.enableTx = true;
config.enableRx = true;
(void)USART_Init(USART3, &config, RW612_UART_CLK_HZ);
}
void uart_write(const char *buf, unsigned int sz)
{
unsigned int i;
for (i = 0; i < sz; i++) {
if (buf[i] == '\n') {
(void)USART_WriteBlocking(USART3, (const uint8_t *)"\r", 1U);
}
(void)USART_WriteBlocking(USART3, (const uint8_t *)&buf[i], 1U);
}
}
#endif
#ifdef __WOLFBOOT
/* The boot ROM leaves the FlexSPI XIP cache off; enable it like SystemInit */
static void rw612_xip_cache_enable(void)
{
if ((CACHE64_CTRL0->CCR & CACHE64_CTRL_CCR_ENCACHE_MASK) != 0U) {
return;
}
CACHE64_CTRL0->CCR = CACHE64_CTRL_CCR_INVW1_MASK |
CACHE64_CTRL_CCR_INVW0_MASK;
CACHE64_CTRL0->CCR |= CACHE64_CTRL_CCR_GO_MASK;
while ((CACHE64_CTRL0->CCR & CACHE64_CTRL_CCR_GO_MASK) != 0U) {
}
CACHE64_CTRL0->CCR = CACHE64_CTRL_CCR_ENWRBUF_MASK |
CACHE64_CTRL_CCR_ENCACHE_MASK;
CACHE64_POLSEL0->REG0_TOP = 0x07FFFC00U;
CACHE64_POLSEL0->REG1_TOP = 0x0U;
CACHE64_POLSEL0->POLSEL = 0x1U;
__ISB();
__DSB();
}
#endif
#if defined(BOOT_BENCHMARK) && defined(__WOLFBOOT)
/* DWT CYCCNT stops without a debugger; SysTick on the 1MHz LPOSC ticks in us */
uint64_t hal_get_timer_us(void)
{
static uint64_t elapsed_us;
static uint32_t last;
static int started;
uint32_t now;
if (!started) {
CLOCK_AttachClk(kLPOSC_to_SYSTICK_CLK);
SysTick->LOAD = SysTick_LOAD_RELOAD_Msk;
SysTick->VAL = 0U;
SysTick->CTRL = SysTick_CTRL_ENABLE_Msk;
last = SysTick->VAL;
started = 1;
}
now = SysTick->VAL;
elapsed_us += (last - now) & SysTick_LOAD_RELOAD_Msk;
last = now;
return elapsed_us;
}
#endif
void hal_init(void)
{
#ifdef __WOLFBOOT
rw612_xip_cache_enable();
#endif
#if defined(__WOLFBOOT) && defined(DEBUG_UART)
uart_init();
#endif
#if defined(TZEN) && !defined(NONSECURE_APP)
hal_sau_init();
#endif
}
#ifdef __WOLFBOOT
void __assert_func(const char *a, int b, const char *c, const char *d)
{
(void)a;
(void)b;
(void)c;
(void)d;
while (1) {
}
}
void hal_prepare_boot(void)
{
}
#endif
static uint32_t RAMFUNCTION rw612_flash_offset(uint32_t address)
{
return (address & RW612_FLASH_ALIAS_MASK) - RW612_FLASH_BASE;
}
static void RAMFUNCTION rw612_flash_cache_flush(void)
{
FLEXSPI->AHBCR |= FLEXSPI_AHBCR_CLRAHBRXBUF_MASK;
FLEXSPI->AHBCR &= ~FLEXSPI_AHBCR_CLRAHBRXBUF_MASK;
CACHE64_CTRL0->CCR |= CACHE64_CTRL_CCR_INVW0_MASK |
CACHE64_CTRL_CCR_INVW1_MASK |
CACHE64_CTRL_CCR_GO_MASK;
while ((CACHE64_CTRL0->CCR & CACHE64_CTRL_CCR_GO_MASK) != 0U) {
}
CACHE64_CTRL0->CCR &= ~(CACHE64_CTRL_CCR_INVW0_MASK |
CACHE64_CTRL_CCR_INVW1_MASK);
__DSB();
__ISB();
}
static int RAMFUNCTION rw612_flash_init(void)
{
uint32_t tree;
uint32_t primask;
status_t st = kStatus_Success;
if (rom_flexspi != NULL) {
return 0;
}
tree = ((SOCCTRL->CHIP_INFO & 0x0FU) == 0U) ?
RW612_ROM_API_TREE_A0 : RW612_ROM_API_TREE_A1;
memcpy(&flash_cfg, (const void *)RW612_FCB_ADDRESS, sizeof(flash_cfg));
primask = __get_PRIMASK();
__disable_irq();
rom_flexspi = (const flexspi_nor_flash_driver_t *)(uintptr_t)
((const uint32_t *)(uintptr_t)tree)[RW612_ROM_API_FLEXSPI_IDX];
st = rom_flexspi->init(RW612_FLEXSPI_INSTANCE, &flash_cfg);
rw612_flash_cache_flush();
__set_PRIMASK(primask);
if (st != kStatus_Success) {
rom_flexspi = NULL;
return -1;
}
return 0;
}
int RAMFUNCTION hal_flash_write(uint32_t address, const uint8_t *data, int len)
{
uint32_t page[RW612_FLASH_PAGE_SIZE / sizeof(uint32_t)];
uint32_t offset;
uint32_t page_off;
uint32_t chunk;
uint32_t primask;
uint32_t i;
status_t st = kStatus_Success;
int ret = 0;
if (len < 0) {
return -1;
}
if (rw612_flash_init() != 0) {
return -1;
}
offset = rw612_flash_offset(address);
while ((len > 0) && (ret == 0)) {
page_off = offset & (RW612_FLASH_PAGE_SIZE - 1U);
chunk = RW612_FLASH_PAGE_SIZE - page_off;
if (chunk > (uint32_t)len) {
chunk = (uint32_t)len;
}
/* Pad with 0xFF in a loop: memset is not in RAM during self-update */
for (i = 0; i < (RW612_FLASH_PAGE_SIZE / sizeof(uint32_t)); i++) {
page[i] = 0xFFFFFFFFU;
}
memcpy((uint8_t *)page + page_off, data, chunk);
primask = __get_PRIMASK();
__disable_irq();
st = rom_flexspi->page_program(RW612_FLEXSPI_INSTANCE, &flash_cfg,
offset - page_off, page, false);
rw612_flash_cache_flush();
__set_PRIMASK(primask);
if (st != kStatus_Success) {
ret = -1;
}
offset += chunk;
data += chunk;
len -= (int)chunk;
}
return ret;
}
void RAMFUNCTION hal_flash_unlock(void)
{
}
void RAMFUNCTION hal_flash_lock(void)
{
}
int RAMFUNCTION hal_flash_erase(uint32_t address, int len)
{
uint32_t offset;
uint32_t end;
uint32_t primask;
status_t st = kStatus_Success;
int ret = 0;
if (len <= 0) {
return -1;
}
if (rw612_flash_init() != 0) {
return -1;
}
offset = rw612_flash_offset(address);
end = offset + (uint32_t)len;
offset -= offset % RW612_FLASH_SECTOR_SIZE;
while ((offset < end) && (ret == 0)) {
primask = __get_PRIMASK();
__disable_irq();
st = rom_flexspi->erase_sector(RW612_FLEXSPI_INSTANCE, &flash_cfg,
offset);
rw612_flash_cache_flush();
__set_PRIMASK(primask);
if (st != kStatus_Success) {
ret = -1;
}
offset += RW612_FLASH_SECTOR_SIZE;
}
return ret;
}
#if defined(WOLFCRYPT_SECURE_MODE) && !defined(NONSECURE_APP)
static int trng_ready;
void hal_trng_init(void)
{
trng_config_t config;
trng_ready = 0;
if ((TRNG_GetDefaultConfig(&config) == kStatus_Success) &&
(TRNG_Init(TRNG, &config) == kStatus_Success)) {
trng_ready = 1;
}
}
void hal_trng_fini(void)
{
trng_ready = 0;
}
int hal_trng_get_entropy(unsigned char *out, unsigned int len)
{
if (!trng_ready) {
return -1;
}
return (TRNG_GetRandomData(TRNG, out, len) == kStatus_Success) ? 0 : -1;
}
#endif

46
hal/rw612.h 100644
View File

@ -0,0 +1,46 @@
/* rw612.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
*/
#ifndef RW612_H
#define RW612_H
#define RW612_FLASH_BASE 0x08000000U
#define RW612_FLASH_ALIAS_MASK 0x0FFFFFFFU
#ifndef RW612_FCB_ADDRESS
#define RW612_FCB_ADDRESS (RW612_FLASH_BASE + 0x400U)
#endif
#define RW612_FLASH_PAGE_SIZE 256U
/* NOR erase unit; WOLFBOOT_SECTOR_SIZE may be any multiple of it */
#define RW612_FLASH_SECTOR_SIZE 0x1000U
#define RW612_FLEXSPI_INSTANCE 0U
#ifndef RW612_ROM_API_TREE_A0
#define RW612_ROM_API_TREE_A0 0x13024100U
#endif
#ifndef RW612_ROM_API_TREE_A1
#define RW612_ROM_API_TREE_A1 0x13030000U
#endif
#define RW612_ROM_API_FLEXSPI_IDX 5U
#define RW612_UART_CLK_HZ 16000000U
#define RW612_UART_BAUD 115200U
#endif /* RW612_H */

77
hal/rw612.ld 100644
View File

@ -0,0 +1,77 @@
MEMORY
{
FLASH (rx) : ORIGIN = @ARCH_FLASH_OFFSET@, LENGTH = @WOLFBOOT_NSC_ADDRESS@ - @ARCH_FLASH_OFFSET@
RAM (rwx) : ORIGIN = 0x30000000, LENGTH = 0x20000
RAM_HEAP (rwx) : ORIGIN = 0x30020000, LENGTH = 0x20000
FLASH_KEYVAULT (rw) : ORIGIN = @WOLFBOOT_KEYVAULT_ADDRESS@, LENGTH = @WOLFBOOT_KEYVAULT_SIZE@
FLASH_NSC (rx) : ORIGIN = @WOLFBOOT_NSC_ADDRESS@, LENGTH = @WOLFBOOT_NSC_SIZE@
}
SECTIONS
{
.text :
{
_start_text = .;
. = 0x400;
KEEP(*(.flash_conf))
. = 0x1000;
_rom_image_start = .;
KEEP(*(.isr_vector))
*(.keystore*)
*(.text*)
*(.rodata*)
*(.init*)
*(.fini*)
. = ALIGN(4);
_end_text = .;
} > FLASH
.edidx :
{
. = ALIGN(4);
*(.ARM.exidx*)
} > FLASH
_stored_data = .;
.data : AT (_stored_data)
{
_start_data = .;
KEEP(*(.ramcode*))
. = ALIGN(4);
KEEP(*(.data*))
. = ALIGN(4);
_end_data = .;
} > RAM
.bss (NOLOAD) :
{
_start_bss = .;
__bss_start__ = .;
*(.bss*)
*(COMMON)
. = ALIGN(4);
_end_bss = .;
__bss_end__ = .;
_end = .;
} > RAM
.gnu.sgstubs ORIGIN(FLASH_NSC) :
{
. += 0x400;
. = ALIGN(4);
*(.gnu.sgstubs*) /* Secure Gateway Stubs */
. = ALIGN(4);
} > FLASH_NSC
. = ALIGN(4);
}
END_STACK = ORIGIN(RAM) + LENGTH(RAM);
_flash_keyvault = ORIGIN(FLASH_KEYVAULT);
_flash_keyvault_size = LENGTH(FLASH_KEYVAULT);
_start_heap = ORIGIN(RAM_HEAP);
_heap_size = LENGTH(RAM_HEAP);
_rom_image_size = _stored_data + SIZEOF(.data) - _rom_image_start;

View File

@ -620,6 +620,18 @@ typedef void(*NMIHANDLER)(void);
# define isr_NMI isr_empty
#endif
#ifdef WOLFBOOT_ROM_IMAGE_HEADER
/* Boot ROMs such as the RW612's read the image length at 0x20 and the load
* address at 0x34 of the vector table */
extern uint32_t _rom_image_size;
extern uint32_t _rom_image_start;
# define IV_IMAGE_LENGTH ((void (*)(void))(&_rom_image_size))
# define IV_IMAGE_ADDRESS ((void (*)(void))(&_rom_image_start))
#else
# define IV_IMAGE_LENGTH 0
# define IV_IMAGE_ADDRESS 0
#endif
#ifdef CORTEX_R5
asm volatile (
" .sect \".isr_vector\"\n"
@ -646,12 +658,12 @@ void (* const IV[])(void) =
isr_fault, // BusFault
isr_fault, // UsageFault
isr_securefault, // SecureFault on M23/33, reserved otherwise (0)
0, // reserved
IV_IMAGE_LENGTH, // reserved (ROM image header: length)
0, // reserved
0, // reserved
isr_empty, // SVC
isr_empty, // DebugMonitor
0, // reserved
IV_IMAGE_ADDRESS, // reserved (ROM image header: load address)
isr_empty, // PendSV
#ifdef TARGET_va416x0

View File

@ -0,0 +1,58 @@
MEMORY
{
FLASH (rx) : ORIGIN = @WOLFBOOT_TEST_APP_ADDRESS@, LENGTH = @WOLFBOOT_TEST_APP_SIZE@
RAM (rwx) : ORIGIN = 0x20040000, LENGTH = 256K
}
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 = _end);
PROVIDE(_end_stack = ORIGIN(RAM) + LENGTH(RAM));

View File

@ -0,0 +1,58 @@
MEMORY
{
FLASH (rx) : ORIGIN = @WOLFBOOT_TEST_APP_ADDRESS@, LENGTH = @WOLFBOOT_TEST_APP_SIZE@
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 256K
}
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 = _end);
PROVIDE(_end_stack = ORIGIN(RAM) + LENGTH(RAM));

View File

@ -33,7 +33,7 @@ ifeq ($(ARCH),PPC)
endif
endif
MCUXPRESSO_CMSIS?=$(MCUXPRESSO)/CMSIS
ifneq (,$(filter $(TARGET),mcxa mcxw mcxn))
ifneq (,$(filter $(TARGET),mcxa mcxw mcxn rw612))
ifneq ($(WOLFCRYPT_TZ_PSA),1)
TEST_APP_NO_RNG?=1
endif
@ -1036,6 +1036,30 @@ ifeq ($(TARGET),mcxn)
endif
endif
ifeq ($(TARGET),rw612)
RW612_SDK_SRCS:=\
$(MCUXPRESSO_DRIVERS)/drivers/fsl_clock.c \
$(MCUXPRESSO_DRIVERS)/drivers/fsl_reset.c \
$(MCUXPRESSO)/drivers/flexcomm/fsl_flexcomm.c \
$(MCUXPRESSO)/drivers/flexcomm/usart/fsl_usart.c
ifeq ($(TZEN),1)
LSCRIPT_TEMPLATE=ARM-rw612-ns.ld
CFLAGS:=$(filter-out -mcmse, $(CFLAGS))
# wolfBoot's copies are built with -mcmse, which selects secure bases
APP_OBJS+=$(addprefix rw612_ns/,$(notdir $(RW612_SDK_SRCS:.c=.o)))
vpath %.c $(sort $(dir $(RW612_SDK_SRCS)))
else
LSCRIPT_TEMPLATE=ARM-rw612.ld
# Flash writes run from the same NOR the app executes from
../hal/$(TARGET).o: CFLAGS+=-DRAM_CODE -D__WOLFBOOT
../test-app/libwolfboot.o: CFLAGS+=-DRAM_CODE -D__WOLFBOOT
APP_OBJS+=$(RW612_SDK_SRCS:.c=.o)
endif
ifeq (,$(findstring nosys.specs,$(LDFLAGS)))
LDFLAGS+=--specs=nosys.specs
endif
endif
ifeq ($(TARGET),lpc55s69)
CFLAGS+=-ffunction-sections -fdata-sections
CFLAGS+=-DWOLFSSL_NXP_RNG_1
@ -1581,6 +1605,11 @@ delta-extra-data: image.bin
../hal/uart/uart_drv_$(UART_TARGET)_ns.o: ../hal/uart/uart_drv_$(UART_TARGET).c FORCE
$(Q)$(CC) $(CFLAGS) -c -o $(@) ../hal/uart/uart_drv_$(UART_TARGET).c -DNONSECURE_APP
rw612_ns/%.o: %.c
@echo "\t[CC-$(ARCH)] $@"
$(Q)mkdir -p $(dir $@)
$(Q)$(CC) $(CFLAGS) -c $(OUTPUT_FLAG) $@ $<
board_$(BOARD).o: ../hal/boards/$(BOARD)/wolfHAL_board.c
@echo "\t[CC-$(ARCH)] $@"
$(Q)$(CC) $(CFLAGS) -DDEBUG_UART -c $(OUTPUT_FLAG) $@ $<

View File

@ -0,0 +1,80 @@
/* app_rw612.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
*/
#include <stdint.h>
#include "target.h"
#include "wolfboot/wolfboot.h"
#include "printf.h"
#ifdef WOLFCRYPT_SECURE_MODE
#include "wolfssl/wolfcrypt/types.h"
#include "wolfssl/wolfcrypt/random.h"
#endif
extern void hal_init(void);
#ifdef WOLFCRYPT_SECURE_MODE
static void print_random_number(void)
{
uint8_t rnd;
int ret;
ret = wcs_get_random(&rnd, sizeof(rnd));
if (ret != 0)
wolfBoot_printf("Random number: generate failed (%d)\n", ret);
else
wolfBoot_printf("Today's lucky number: 0x%02x\n", rnd);
}
#endif
void main(void)
{
uint32_t boot_ver;
hal_init();
#ifdef TZEN
boot_ver = wolfBoot_nsc_current_firmware_version();
#else
boot_ver = wolfBoot_current_firmware_version();
#endif
wolfBoot_printf("Hello from RW612 firmware version %d\n", boot_ver);
#ifdef WOLFCRYPT_SECURE_MODE
print_random_number();
#endif
if (boot_ver != 1) {
#ifdef TZEN
wolfBoot_nsc_success();
#else
wolfBoot_success();
#endif
wolfBoot_printf("Update successful, firmware version %d confirmed\n",
boot_ver);
}
while (1) {
__asm__ volatile ("wfi");
}
}

View File

@ -0,0 +1,34 @@
#!/bin/sh
# fetch-mcuxsdk-headers.sh -- fetch the NXP MCUXpresso SDK sources the NXP HAL
# unit tests build against, laid out like a west checkout, at pinned commits.
#
# usage: tools/ci/fetch-mcuxsdk-headers.sh <new-dir>
# make -C tools/unit-tests MCUXSDK=<new-dir>
set -e
OUT="${1:?usage: fetch-mcuxsdk-headers.sh <new-dir>}"
NXP=https://github.com/nxp-mcuxpresso
# fetch <dest> <repo> <commit> <paths...>: sparse, depth 1, blobs on demand
fetch() {
dest="$OUT${1:+/$1}"; url="$NXP/$2"; rev="$3"
shift 3
git init -q "$dest"
git -C "$dest" remote add origin "$url"
git -C "$dest" sparse-checkout set --no-cone "$@"
git -C "$dest" fetch -q --depth 1 --filter=blob:none origin "$rev"
git -C "$dest" checkout -q FETCH_HEAD
}
if [ -e "$OUT" ]; then
echo "$OUT already exists" >&2
exit 1
fi
fetch "" mcuxsdk-core c6f4223f45fdab59509f25ee3ea63a71bd29d8aa \
/drivers/common/ /drivers/trng/ /drivers/mcx_spc/ /drivers/flexcomm/
fetch devices/Wireless mcux-devices-wireless \
0d2c97770475558c72f5b6947f468bf97282df79 /RW/periph/ /RW/RW612/
fetch devices/MCX mcux-devices-mcx \
7c0e68e9094e3943edbe1c9bf68305556adaa2c8 /MCXA/periph/ /MCXA/MCXA153/

View File

@ -201,7 +201,23 @@ else
$(info Skipping x86-64 EFI unit test: gnu-efi headers not found (set ENABLE_EFI_TESTS=1 to force))
endif
# NXP HAL tests build against the real MCUXpresso SDK headers, fetched into
# MCUXSDK by tools/ci/fetch-mcuxsdk-headers.sh, with only CMSIS mocked.
MCUXSDK?=
MCXA_SDK=$(MCUXSDK)/devices/MCX/MCXA/MCXA153
RW612_SDK=$(MCUXSDK)/devices/Wireless/RW/RW612
ifneq ($(wildcard $(MCXA_SDK)/MCXA153.h),)
TESTS+=unit-flash-write-mcxa
else
$(info Skipping MCXA unit test: set MCUXSDK (see tools/ci/fetch-mcuxsdk-headers.sh))
endif
ifneq ($(wildcard $(RW612_SDK)/RW612.h),)
TESTS+=unit-rw612-flash
TESTS+=unit-rw612-tz
TESTS+=unit-rw612-uart
else
$(info Skipping RW612 unit tests: set MCUXSDK (see tools/ci/fetch-mcuxsdk-headers.sh))
endif
TESTS+=unit-flash-write-nrf52
TESTS+=unit-nrf5340-uart-crlf
TESTS+=unit-flash-write-samr21
@ -1917,10 +1933,29 @@ unit-t10xx-dts-memac: unit-t10xx-dts-memac.c nxp_t10xx_fixup_extract.h ../../src
unit-efi-x86-open-image: unit-efi-x86-open-image.c ../../hal/x86_64_efi.c
gcc -o $@ unit-efi-x86-open-image.c -I/usr/include/efi -I/usr/include/efi/x86_64 -DTARGET_X86_64_EFI -fshort-wchar $(CFLAGS) $(LDFLAGS)
# unit-flash-write-mcxa includes hal/mcxa.c directly, with mcxa_fsl_stub/
# standing in for the (not vendored) NXP MCUXpresso SDK headers it includes.
# NXP HAL tests include hal/<chip>.c directly. SDK headers go in as -isystem
# so their own 64-bit-host warnings stay out of the test build.
MCUXSDK_TEST_INC=-Icmsis_stub -isystem $(MCUXSDK)/drivers/common
MCXA_TEST_INC=-DCPU_MCXA153VLH $(MCUXSDK_TEST_INC) -isystem $(MCXA_SDK) \
-isystem $(MCXA_SDK)/../periph -isystem $(MCXA_SDK)/drivers \
-isystem $(MCXA_SDK)/drivers/romapi -isystem $(MCUXSDK)/drivers/mcx_spc
RW612_TEST_INC=-DCPU_RW612ETA2I $(MCUXSDK_TEST_INC) -isystem $(RW612_SDK) \
-isystem $(RW612_SDK)/../periph -isystem $(RW612_SDK)/drivers \
-isystem $(RW612_SDK)/drivers/romapi/flexspi -isystem $(MCUXSDK)/drivers/trng
unit-flash-write-mcxa: unit-flash-write-mcxa.c ../../hal/mcxa.c
gcc -o $@ unit-flash-write-mcxa.c -Imcxa_fsl_stub $(CFLAGS) $(LDFLAGS)
gcc -o $@ unit-flash-write-mcxa.c $(MCXA_TEST_INC) $(CFLAGS) $(LDFLAGS)
unit-rw612-flash: unit-rw612-flash.c ../../hal/rw612.c
gcc -o $@ unit-rw612-flash.c $(RW612_TEST_INC) $(CFLAGS) $(LDFLAGS)
unit-rw612-tz: unit-rw612-tz.c ../../hal/rw612.c
gcc -o $@ unit-rw612-tz.c $(RW612_TEST_INC) -I../.. $(CFLAGS) $(LDFLAGS)
unit-rw612-uart: unit-rw612-uart.c ../../hal/rw612.c
gcc -o $@ unit-rw612-uart.c -DDEBUG_UART $(RW612_TEST_INC) \
-isystem $(MCUXSDK)/drivers/flexcomm \
-isystem $(MCUXSDK)/drivers/flexcomm/usart $(CFLAGS) $(LDFLAGS)
unit-flash-write-nrf52: unit-flash-write-nrf52.c ../../hal/nrf52.c
gcc -o $@ unit-flash-write-nrf52.c -DTARGET_nrf52 -I../../hal $(CFLAGS) $(LDFLAGS)
@ -1998,9 +2033,10 @@ GENERATED_SRC:=aurix_erased_extract.h \
# Tests that are only in $(TESTS) when their toolchain/headers are
# present; clean must remove them regardless, or a host that has since
# lost gnu-efi/multilib keeps a stale binary forever.
# lost gnu-efi/multilib/the MCUXpresso SDK keeps a stale binary forever.
CONDITIONAL_TESTS:=unit-efi-x86-open-image unit-linux-loader-e820 \
unit-linux-loader-syssize
unit-linux-loader-syssize unit-flash-write-mcxa unit-rw612-flash \
unit-rw612-tz unit-rw612-uart
clean: covclean
rm -f $(TESTS) $(CONDITIONAL_TESTS) $(GENERATED_SRC) *.o *.gcno *.gcda \

View File

@ -0,0 +1,107 @@
/* core_cm33.h
*
* Host stand-in for the Arm CMSIS core header, so the HAL unit tests build
* against a vendor's real device headers: core registers and intrinsics are
* mock objects the tests define and inspect.
*
* 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
*/
#ifndef CORE_CM33_STUB_H
#define CORE_CM33_STUB_H
#include <stdint.h>
#define __I volatile const
#define __O volatile
#define __IO volatile
#define __IM volatile const
#define __OM volatile
#define __IOM volatile
#define __STATIC_INLINE static inline
#define __STATIC_FORCEINLINE static inline
#define __WEAK __attribute__((weak))
#define __ASM __asm__
#define __CORTEX_M 33U
typedef struct {
__IOM uint32_t ISER[16];
uint32_t RESERVED0[16];
__IOM uint32_t ICER[16];
uint32_t RESERVED1[16];
__IOM uint32_t ISPR[16];
uint32_t RESERVED2[16];
__IOM uint32_t ICPR[16];
uint32_t RESERVED3[16];
__IOM uint32_t IABR[16];
uint32_t RESERVED4[16];
__IOM uint32_t ITNS[16];
uint32_t RESERVED5[16];
__IOM uint8_t IPR[496];
} NVIC_Type;
typedef struct {
__IOM uint32_t CTRL;
__IOM uint32_t LOAD;
__IOM uint32_t VAL;
__IM uint32_t CALIB;
} SysTick_Type;
#define SysTick_CTRL_ENABLE_Msk (1UL << 0)
#define SysTick_CTRL_TICKINT_Msk (1UL << 1)
#define SysTick_LOAD_RELOAD_Msk 0xFFFFFFUL
extern NVIC_Type mock_nvic;
extern SysTick_Type mock_systick;
#define NVIC (&mock_nvic)
#define SysTick (&mock_systick)
extern uint32_t mock_primask;
extern int mock_barriers;
static inline uint32_t __get_PRIMASK(void) { return mock_primask; }
static inline void __set_PRIMASK(uint32_t v) { mock_primask = v; }
static inline void __disable_irq(void) { mock_primask = 1; }
static inline void __enable_irq(void) { mock_primask = 0; }
static inline void __DSB(void) { mock_barriers++; }
static inline void __ISB(void) { }
static inline void __DMB(void) { }
static inline void __NOP(void) { }
static inline uint32_t __get_IPSR(void) { return 0; }
static inline void NVIC_EnableIRQ(IRQn_Type irq)
{
NVIC->ISER[(uint32_t)irq >> 5] = 1UL << ((uint32_t)irq & 0x1FUL);
}
static inline void NVIC_DisableIRQ(IRQn_Type irq)
{
NVIC->ICER[(uint32_t)irq >> 5] = 1UL << ((uint32_t)irq & 0x1FUL);
}
static inline void NVIC_ClearPendingIRQ(IRQn_Type irq)
{
NVIC->ICPR[(uint32_t)irq >> 5] = 1UL << ((uint32_t)irq & 0x1FUL);
}
static inline void NVIC_SetPriority(IRQn_Type irq, uint32_t priority)
{
NVIC->IPR[(uint32_t)irq] = (uint8_t)priority;
}
static inline void NVIC_SetTargetState(IRQn_Type irq)
{
NVIC->ITNS[(uint32_t)irq >> 5] |= 1UL << ((uint32_t)irq & 0x1FUL);
}
#endif /* CORE_CM33_STUB_H */

View File

@ -1,5 +0,0 @@
/* Stub: hal/mcxa.c only uses this under #ifdef __WOLFBOOT, which the unit
* test does not define. */
#ifndef FSL_CLOCK_STUB_H
#define FSL_CLOCK_STUB_H
#endif /* FSL_CLOCK_STUB_H */

View File

@ -1,18 +0,0 @@
/* Minimal stand-in for the NXP MCUXpresso SDK header of the same name.
* hal/mcxa.c only needs the flash config type and a status code from it;
* the real SDK is not vendored in this source tree. */
#ifndef FSL_COMMON_STUB_H
#define FSL_COMMON_STUB_H
#include <string.h>
#include <stdint.h>
typedef int status_t;
#define kStatus_Success 0
/* Opaque driver state in the real SDK; the stub needs no fields. */
typedef struct {
uint32_t unused;
} flash_config_t;
#endif /* FSL_COMMON_STUB_H */

View File

@ -1,16 +0,0 @@
/* Minimal stand-in for the NXP MCUXpresso SDK ROM flash API declarations
* used by hal/mcxa.c's hal_flash_write()/hal_flash_erase(). The unit test
* provides the definitions of these functions. Parameter names match the
* real MCXA ROM API (fsl_romapi.h): FLASH_EraseSector takes a length in
* bytes, not a sector count. */
#ifndef FSL_ROMAPI_STUB_H
#define FSL_ROMAPI_STUB_H
status_t FLASH_ProgramPhrase(flash_config_t *config, uint32_t start,
uint8_t *src, uint32_t length_in_bytes);
status_t FLASH_EraseSector(flash_config_t *config, uint32_t start,
uint32_t length_in_bytes, uint32_t key);
#define kFLASH_ApiEraseKey 0x6b65796b
#endif /* FSL_ROMAPI_STUB_H */

View File

@ -1,5 +0,0 @@
/* Stub: hal/mcxa.c only uses this under #ifdef __WOLFBOOT, which the unit
* test does not define. */
#ifndef FSL_SPC_STUB_H
#define FSL_SPC_STUB_H
#endif /* FSL_SPC_STUB_H */

View File

@ -41,10 +41,11 @@
#include <string.h>
#include <sys/mman.h>
/* NXP MCUXpresso SDK stand-ins (mcxa_fsl_stub/) provide flash_config_t,
* status_t and the FLASH_* prototypes hal/mcxa.c expects; this test supplies
* their bodies below, simulating flash programming as a plain memcpy onto
* the mock buffer that "address" points into. */
/* The NXP SDK ROM API calls through a fixed ROM address; point it at a mock
* tree whose flash driver simulates programming as a memcpy onto the buffer
* that "address" points into. */
extern struct _bootloader_tree mock_rom_api;
#define FSL_FEATURE_ROMAPI_BASE ((uintptr_t)&mock_rom_api)
#include "fsl_common.h"
#include "fsl_romapi.h"
#include "image.h"
@ -54,7 +55,7 @@ static int erase_calls;
static uint32_t last_erase_start;
static uint32_t last_erase_len;
status_t FLASH_ProgramPhrase(flash_config_t *config, uint32_t start,
static status_t mock_program_phrase(flash_config_t *config, uint32_t start,
uint8_t *src, uint32_t len)
{
(void)config;
@ -62,7 +63,7 @@ status_t FLASH_ProgramPhrase(flash_config_t *config, uint32_t start,
return kStatus_Success;
}
status_t FLASH_EraseSector(flash_config_t *config, uint32_t start,
static status_t mock_erase_sector(flash_config_t *config, uint32_t start,
uint32_t length_in_bytes, uint32_t key)
{
(void)config; (void)key;
@ -72,22 +73,35 @@ status_t FLASH_EraseSector(flash_config_t *config, uint32_t start,
return kStatus_Success;
}
static flash_driver_interface_t mock_flash_driver;
bootloader_tree_t mock_rom_api;
#include "../../hal/mcxa.c"
/* hal_flash_write() treats "address" as a real pointer into memory-mapped
* flash (it memcpy()s from it directly). struct fields carrying such
* addresses are uint32_t, matching the real 32-bit target. MAP_32BIT keeps
* the mock flash buffer inside that range on a 64-bit test host, exactly as
* unit-ata-security-passphrase-zeroize.c does for 32-bit DMA pointers. */
* addresses are uint32_t, matching the real 32-bit target, so the mock flash
* buffer is mapped below 4GB (MAP_32BIT on x86-64, a low hint elsewhere). */
#ifdef MAP_32BIT
#define MOCK_FLASH_HINT NULL
#define MOCK_FLASH_FLAGS (MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT)
#else
#define MOCK_FLASH_HINT ((void *)0x20000000UL)
#define MOCK_FLASH_FLAGS (MAP_PRIVATE | MAP_ANONYMOUS)
#endif
#define MOCK_FLASH_SIZE 64
static uint8_t *mock_flash;
static void setup(void)
{
mock_flash = mmap(NULL, MOCK_FLASH_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0);
mock_flash = mmap(MOCK_FLASH_HINT, MOCK_FLASH_SIZE, PROT_READ | PROT_WRITE,
MOCK_FLASH_FLAGS, -1, 0);
ck_assert_ptr_ne(mock_flash, MAP_FAILED);
ck_assert_uint_lt((uintptr_t)mock_flash, 0xFFFFFFFFUL - MOCK_FLASH_SIZE);
memset(mock_flash, 0xFF, MOCK_FLASH_SIZE);
mock_flash_driver.flash_program_phrase = mock_program_phrase;
mock_flash_driver.flash_erase_sector = mock_erase_sector;
mock_rom_api.flash_driver = &mock_flash_driver;
erase_calls = 0;
last_erase_start = 0;
last_erase_len = 0;

View File

@ -0,0 +1,442 @@
/* unit-rw612-flash.c
*
* Unit tests for the RW612 FlexSPI NOR flash HAL (hal/rw612.c), run against
* a mock boot ROM FlexSPI driver and a mock NOR array.
*
* 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 <check.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
/* RW612 layout (config/examples/rw612*.config) with the 8KB logical sector
* ML-DSA builds use over the 4KB NOR erase sector, in place of target.h */
#define H_TARGETS_TARGET_
#define WOLFBOOT_FIXED_PARTITIONS
#define WOLFBOOT_SECTOR_SIZE 0x2000
#define WOLFBOOT_PARTITION_SIZE 0x100000
#define WOLFBOOT_PARTITION_BOOT_ADDRESS 0x08100000
#define WOLFBOOT_PARTITION_UPDATE_ADDRESS 0x08200000
#define WOLFBOOT_PARTITION_SWAP_ADDRESS 0x08300000
#define WOLFBOOT_NSC_ADDRESS 0x08060000
#define WOLFBOOT_NSC_SIZE 0x2000
#define WOLFBOOT_KEYVAULT_ADDRESS 0x08400000
#define WOLFBOOT_KEYVAULT_SIZE 0x20000
#include "fsl_common.h"
#include "fsl_romapi_flexspi.h"
static FLEXSPI_Type mock_flexspi;
static CACHE64_CTRL_Type mock_cache64;
CACHE64_POLSEL_Type mock_cache64_polsel;
SOCCIU_Type mock_socctrl;
NVIC_Type mock_nvic;
uint32_t mock_primask;
int mock_barriers;
static int ahb_rx_clears;
static int cache_invalidations;
static uint32_t cache64_seen;
FLEXSPI_Type *mock_flexspi_regs(void)
{
if ((mock_flexspi.AHBCR & FLEXSPI_AHBCR_CLRAHBRXBUF_MASK) != 0U)
ahb_rx_clears++;
return &mock_flexspi;
}
CACHE64_CTRL_Type *mock_cache64_regs(void)
{
cache64_seen |= mock_cache64.CCR;
if ((mock_cache64.CCR & CACHE64_CTRL_CCR_GO_MASK) != 0U) {
cache_invalidations++;
mock_cache64.CCR &= ~CACHE64_CTRL_CCR_GO_MASK;
}
return &mock_cache64;
}
/* The HAL reads 32-bit ROM table pointers, so the mock tables live below 4GB */
static uint8_t *mock_rom;
static uint8_t mock_fcb[sizeof(flexspi_nor_config_t)];
#define RW612_ROM_API_TREE_A0 ((uint32_t)(uintptr_t)mock_rom)
#define RW612_ROM_API_TREE_A1 ((uint32_t)(uintptr_t)(mock_rom + 0x40))
#define RW612_FCB_ADDRESS ((uintptr_t)mock_fcb)
/* Point the SDK peripheral instances at the mocks above */
#undef FLEXSPI
#undef CACHE64_CTRL0
#undef CACHE64_POLSEL0
#undef SOCCTRL
#define FLEXSPI (mock_flexspi_regs())
#define CACHE64_CTRL0 (mock_cache64_regs())
#define CACHE64_POLSEL0 (&mock_cache64_polsel)
#define SOCCTRL (&mock_socctrl)
#include "../../hal/rw612.c"
#ifdef MAP_32BIT
#define MOCK_ROM_HINT NULL
#define MOCK_ROM_FLAGS (MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT)
#else
#define MOCK_ROM_HINT ((void *)0x10000000UL)
#define MOCK_ROM_FLAGS (MAP_PRIVATE | MAP_ANONYMOUS)
#endif
#define MOCK_NOR_SIZE 0x8000
#define MOCK_MAX_CALLS 16
#define TEST_BASE 0x08001000U
static uint8_t mock_nor[MOCK_NOR_SIZE];
static flexspi_nor_flash_driver_t *drv_a0;
static flexspi_nor_flash_driver_t *drv_a1;
static int init_calls;
static int program_calls;
static int erase_calls;
static status_t init_ret;
static int fail_program_at;
static int fail_erase_at;
static int init_cfg_ok;
static int rom_irq_enabled;
static uint32_t program_dst[MOCK_MAX_CALLS];
static uint32_t erase_addr[MOCK_MAX_CALLS];
static status_t mock_init(uint32_t instance, flexspi_nor_config_t *config)
{
(void)instance;
init_calls++;
if (mock_primask == 0U)
rom_irq_enabled = 1;
init_cfg_ok = (memcmp(config, mock_fcb, sizeof(*config)) == 0);
return init_ret;
}
static status_t mock_page_program(uint32_t instance,
flexspi_nor_config_t *config, uint32_t dst, const uint32_t *src,
bool keepState)
{
const uint8_t *p = (const uint8_t *)src;
uint32_t i;
(void)instance; (void)config; (void)keepState;
if (mock_primask == 0U)
rom_irq_enabled = 1;
if (program_calls < MOCK_MAX_CALLS)
program_dst[program_calls] = dst;
program_calls++;
if (program_calls == fail_program_at)
return kStatus_Fail;
ck_assert_uint_eq(dst % RW612_FLASH_PAGE_SIZE, 0);
ck_assert_uint_le(dst + RW612_FLASH_PAGE_SIZE, MOCK_NOR_SIZE);
/* NOR programming can only clear bits */
for (i = 0; i < RW612_FLASH_PAGE_SIZE; i++)
mock_nor[dst + i] &= p[i];
return kStatus_Success;
}
static status_t mock_erase_sector(uint32_t instance,
flexspi_nor_config_t *config, uint32_t address)
{
(void)instance; (void)config;
if (mock_primask == 0U)
rom_irq_enabled = 1;
if (erase_calls < MOCK_MAX_CALLS)
erase_addr[erase_calls] = address;
erase_calls++;
if (erase_calls == fail_erase_at)
return kStatus_Fail;
ck_assert_uint_eq(address % RW612_FLASH_SECTOR_SIZE, 0);
ck_assert_uint_le(address + RW612_FLASH_SECTOR_SIZE, MOCK_NOR_SIZE);
memset(mock_nor + address, 0xFF, RW612_FLASH_SECTOR_SIZE);
return kStatus_Success;
}
static void setup(void)
{
uint32_t i;
mock_rom = mmap(MOCK_ROM_HINT, 4096, PROT_READ | PROT_WRITE,
MOCK_ROM_FLAGS, -1, 0);
ck_assert_ptr_ne(mock_rom, MAP_FAILED);
ck_assert_uint_lt((uintptr_t)mock_rom, 0xFFFFF000UL);
drv_a0 = (flexspi_nor_flash_driver_t *)(mock_rom + 0x100);
drv_a1 = (flexspi_nor_flash_driver_t *)(mock_rom + 0x200);
drv_a0->init = drv_a1->init = mock_init;
drv_a0->page_program = drv_a1->page_program = mock_page_program;
drv_a0->erase_sector = drv_a1->erase_sector = mock_erase_sector;
((uint32_t *)(uintptr_t)RW612_ROM_API_TREE_A0)[RW612_ROM_API_FLEXSPI_IDX] =
(uint32_t)(uintptr_t)drv_a0;
((uint32_t *)(uintptr_t)RW612_ROM_API_TREE_A1)[RW612_ROM_API_FLEXSPI_IDX] =
(uint32_t)(uintptr_t)drv_a1;
for (i = 0; i < sizeof(mock_fcb); i++)
mock_fcb[i] = (uint8_t)i;
memset(mock_nor, 0xFF, sizeof(mock_nor));
*(uint32_t *)&mock_socctrl.CHIP_INFO = 1U;
mock_primask = 0U;
mock_barriers = 0;
memset(&mock_flexspi, 0, sizeof(mock_flexspi));
memset(&mock_cache64, 0, sizeof(mock_cache64));
ahb_rx_clears = cache_invalidations = 0;
cache64_seen = 0;
rom_flexspi = NULL;
init_calls = program_calls = erase_calls = 0;
init_ret = kStatus_Success;
fail_program_at = fail_erase_at = 0;
init_cfg_ok = 0;
rom_irq_enabled = 0;
}
static void teardown(void)
{
munmap(mock_rom, 4096);
}
START_TEST(test_write_unaligned_keeps_page_neighbours)
{
uint8_t data[8];
int i;
memset(mock_nor + 0x1000, 0x5A, 16);
for (i = 0; i < 8; i++)
data[i] = (uint8_t)(0xA0 + i);
ck_assert_int_eq(hal_flash_write(TEST_BASE + 0x20, data, 8), 0);
ck_assert_int_eq(program_calls, 1);
ck_assert_uint_eq(program_dst[0], 0x1000);
for (i = 0; i < 16; i++)
ck_assert_uint_eq(mock_nor[0x1000 + i], 0x5A);
for (i = 0; i < 8; i++)
ck_assert_uint_eq(mock_nor[0x1020 + i], data[i]);
for (i = 0x1028; i < 0x1100; i++)
ck_assert_uint_eq(mock_nor[i], 0xFF);
}
END_TEST
START_TEST(test_write_spanning_pages)
{
uint8_t data[20];
int i;
for (i = 0; i < 20; i++)
data[i] = (uint8_t)(i + 1);
ck_assert_int_eq(hal_flash_write(TEST_BASE + 250, data, 20), 0);
ck_assert_int_eq(program_calls, 2);
ck_assert_uint_eq(program_dst[0], 0x1000);
ck_assert_uint_eq(program_dst[1], 0x1100);
for (i = 0; i < 20; i++)
ck_assert_uint_eq(mock_nor[0x1000 + 250 + i], data[i]);
ck_assert_uint_eq(mock_nor[0x1000 + 249], 0xFF);
ck_assert_uint_eq(mock_nor[0x1000 + 270], 0xFF);
}
END_TEST
START_TEST(test_write_secure_alias)
{
uint8_t data[4] = { 0x11, 0x22, 0x33, 0x44 };
ck_assert_int_eq(hal_flash_write(0x18001010U, data, 4), 0);
ck_assert_int_eq(program_calls, 1);
ck_assert_uint_eq(program_dst[0], 0x1000);
ck_assert_mem_eq(mock_nor + 0x1010, data, 4);
}
END_TEST
START_TEST(test_write_invalid_len)
{
uint8_t data[1] = { 0 };
ck_assert_int_eq(hal_flash_write(TEST_BASE, data, -1), -1);
ck_assert_int_eq(init_calls, 0);
ck_assert_int_eq(program_calls, 0);
}
END_TEST
START_TEST(test_write_stops_on_failure)
{
uint8_t data[600];
memset(data, 0, sizeof(data));
fail_program_at = 1;
ck_assert_int_eq(hal_flash_write(TEST_BASE, data, (int)sizeof(data)), -1);
ck_assert_int_eq(program_calls, 1);
ck_assert_uint_eq(mock_primask, 0);
}
END_TEST
START_TEST(test_init_once_with_fcb_copy)
{
uint8_t data[4] = { 0 };
ck_assert_int_eq(hal_flash_write(TEST_BASE, data, 4), 0);
ck_assert_int_eq(hal_flash_erase(TEST_BASE, WOLFBOOT_SECTOR_SIZE), 0);
ck_assert_int_eq(init_calls, 1);
ck_assert_int_eq(init_cfg_ok, 1);
ck_assert_ptr_eq(rom_flexspi, drv_a1);
}
END_TEST
START_TEST(test_init_selects_a0_tree)
{
*(uint32_t *)&mock_socctrl.CHIP_INFO = 0U;
ck_assert_int_eq(hal_flash_erase(TEST_BASE, WOLFBOOT_SECTOR_SIZE), 0);
ck_assert_ptr_eq(rom_flexspi, drv_a0);
}
END_TEST
START_TEST(test_init_failure_retries)
{
uint8_t data[4] = { 0 };
init_ret = kStatus_Fail;
ck_assert_int_eq(hal_flash_write(TEST_BASE, data, 4), -1);
ck_assert_int_eq(program_calls, 0);
ck_assert_ptr_null(rom_flexspi);
ck_assert_uint_eq(mock_primask, 0);
init_ret = kStatus_Success;
ck_assert_int_eq(hal_flash_write(TEST_BASE, data, 4), 0);
ck_assert_int_eq(init_calls, 2);
ck_assert_int_eq(program_calls, 1);
}
END_TEST
START_TEST(test_erase_rounds_to_sectors)
{
memset(mock_nor + 0x1000, 0x00, 3 * RW612_FLASH_SECTOR_SIZE);
ck_assert_int_eq(hal_flash_erase(TEST_BASE + RW612_FLASH_SECTOR_SIZE / 2,
RW612_FLASH_SECTOR_SIZE), 0);
ck_assert_int_eq(erase_calls, 2);
ck_assert_uint_eq(erase_addr[0], 0x1000);
ck_assert_uint_eq(erase_addr[1], 0x1000 + RW612_FLASH_SECTOR_SIZE);
ck_assert_uint_eq(mock_nor[0x1000 + 2 * RW612_FLASH_SECTOR_SIZE], 0x00);
}
END_TEST
START_TEST(test_erase_covers_whole_logical_sector)
{
int i;
memset(mock_nor + 0x1000, 0x00, WOLFBOOT_SECTOR_SIZE + 1);
ck_assert_int_eq(hal_flash_erase(TEST_BASE, WOLFBOOT_SECTOR_SIZE), 0);
ck_assert_int_eq(erase_calls,
(int)(WOLFBOOT_SECTOR_SIZE / RW612_FLASH_SECTOR_SIZE));
for (i = 0; i < WOLFBOOT_SECTOR_SIZE; i++)
ck_assert_uint_eq(mock_nor[0x1000 + i], 0xFF);
ck_assert_uint_eq(mock_nor[0x1000 + WOLFBOOT_SECTOR_SIZE], 0x00);
}
END_TEST
START_TEST(test_erase_invalid_len)
{
ck_assert_int_eq(hal_flash_erase(TEST_BASE, 0), -1);
ck_assert_int_eq(hal_flash_erase(TEST_BASE, -1), -1);
ck_assert_int_eq(init_calls, 0);
ck_assert_int_eq(erase_calls, 0);
}
END_TEST
START_TEST(test_erase_stops_on_failure)
{
fail_erase_at = 1;
ck_assert_int_eq(hal_flash_erase(TEST_BASE, 2 * WOLFBOOT_SECTOR_SIZE), -1);
ck_assert_int_eq(erase_calls, 1);
ck_assert_uint_eq(mock_primask, 0);
}
END_TEST
START_TEST(test_primask_preserved)
{
uint8_t data[300];
memset(data, 0, sizeof(data));
ck_assert_int_eq(hal_flash_write(TEST_BASE, data, (int)sizeof(data)), 0);
ck_assert_uint_eq(mock_primask, 0);
ck_assert_int_eq(rom_irq_enabled, 0);
mock_primask = 1U;
ck_assert_int_eq(hal_flash_write(TEST_BASE + 0x400, data,
(int)sizeof(data)), 0);
ck_assert_uint_eq(mock_primask, 1);
ck_assert_int_eq(hal_flash_erase(TEST_BASE, WOLFBOOT_SECTOR_SIZE), 0);
ck_assert_uint_eq(mock_primask, 1);
}
END_TEST
START_TEST(test_cache_flushed_after_each_rom_call)
{
uint8_t data[20] = { 0 };
const uint32_t inv = CACHE64_CTRL_CCR_INVW0_MASK |
CACHE64_CTRL_CCR_INVW1_MASK;
int rom_ops;
ck_assert_int_eq(hal_flash_write(TEST_BASE + 250, data, 20), 0);
ck_assert_int_eq(hal_flash_erase(TEST_BASE, WOLFBOOT_SECTOR_SIZE), 0);
rom_ops = init_calls + program_calls + erase_calls;
ck_assert_int_eq(rom_ops,
3 + (int)(WOLFBOOT_SECTOR_SIZE / RW612_FLASH_SECTOR_SIZE));
ck_assert_int_eq(ahb_rx_clears, rom_ops);
ck_assert_int_eq(cache_invalidations, rom_ops);
ck_assert_int_eq(mock_barriers, rom_ops);
ck_assert_uint_eq(cache64_seen & inv, inv);
ck_assert_uint_eq(mock_cache64.CCR & inv, 0);
ck_assert_uint_eq(mock_flexspi.AHBCR & FLEXSPI_AHBCR_CLRAHBRXBUF_MASK, 0);
}
END_TEST
static Suite *rw612_flash_suite(void)
{
Suite *s = suite_create("rw612-flash");
TCase *tc = tcase_create("rw612-flash");
tcase_add_checked_fixture(tc, setup, teardown);
tcase_add_test(tc, test_write_unaligned_keeps_page_neighbours);
tcase_add_test(tc, test_write_spanning_pages);
tcase_add_test(tc, test_write_secure_alias);
tcase_add_test(tc, test_write_invalid_len);
tcase_add_test(tc, test_write_stops_on_failure);
tcase_add_test(tc, test_init_once_with_fcb_copy);
tcase_add_test(tc, test_init_selects_a0_tree);
tcase_add_test(tc, test_init_failure_retries);
tcase_add_test(tc, test_erase_rounds_to_sectors);
tcase_add_test(tc, test_erase_covers_whole_logical_sector);
tcase_add_test(tc, test_erase_invalid_len);
tcase_add_test(tc, test_erase_stops_on_failure);
tcase_add_test(tc, test_primask_preserved);
tcase_add_test(tc, test_cache_flushed_after_each_rom_call);
suite_add_tcase(s, tc);
return s;
}
int main(void)
{
int fails;
Suite *s = rw612_flash_suite();
SRunner *sr = srunner_create(s);
srunner_run_all(sr, CK_NORMAL);
fails = srunner_ntests_failed(sr);
srunner_free(sr);
return fails;
}

View File

@ -0,0 +1,427 @@
/* unit-rw612-tz.c
*
* Unit tests for the RW612 secure-world HAL in hal/rw612.c: the TrustZone
* attribution map and the SA_TRNG entropy source used by wolfCrypt.
*
* 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 <check.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#define TZEN
#define WOLFCRYPT_SECURE_MODE
/* Build the bootloader side of the HAL, including the XIP cache setup */
#define __WOLFBOOT
#define WOLFBOOT_HASH_SHA256
#define WOLFBOOT_SIGN_ECC256
#define BOOT_BENCHMARK
/* Replace hal/armv8m_tz.h with a recording SAU */
#define TZ_INCLUDED
#define RW612_SAU_REGIONS 8
struct sau_region {
uint32_t start;
uint32_t end;
int nsc;
int set;
};
static struct sau_region sau[RW612_SAU_REGIONS];
static int sau_bad_region;
static uint32_t mock_sau_ctrl;
static uint32_t mock_shcsr;
#define SAU_CTRL mock_sau_ctrl
#define SAU_INIT_CTRL_ENABLE (1U << 0)
#define SCB_SHCSR mock_shcsr
#define SCB_SHCSR_SECUREFAULT_EN (1U << 19)
static void sau_init_region(uint32_t region, uint32_t start_addr,
uint32_t end_addr, int secure)
{
if (region >= RW612_SAU_REGIONS) {
sau_bad_region = 1;
return;
}
sau[region].start = start_addr;
sau[region].end = end_addr;
sau[region].nsc = secure;
sau[region].set = 1;
}
/* RW612 layout (config/examples/rw612*.config) with the 8KB logical sector
* ML-DSA builds use over the 4KB NOR erase sector, in place of target.h */
#define H_TARGETS_TARGET_
#define WOLFBOOT_FIXED_PARTITIONS
#define WOLFBOOT_SECTOR_SIZE 0x2000
#define WOLFBOOT_PARTITION_SIZE 0x100000
#define WOLFBOOT_PARTITION_BOOT_ADDRESS 0x08100000
#define WOLFBOOT_PARTITION_UPDATE_ADDRESS 0x08200000
#define WOLFBOOT_PARTITION_SWAP_ADDRESS 0x08300000
#define WOLFBOOT_NSC_ADDRESS 0x08060000
#define WOLFBOOT_NSC_SIZE 0x2000
#define WOLFBOOT_KEYVAULT_ADDRESS 0x08400000
#define WOLFBOOT_KEYVAULT_SIZE 0x20000
#include "fsl_common.h"
#include "fsl_clock.h"
#include "fsl_romapi_flexspi.h"
#include "fsl_trng.h"
TRNG_Type mock_trng;
static int trng_init_calls;
static int trng_read_calls;
static status_t trng_init_ret;
static status_t trng_read_ret;
static uint32_t trng_init_mode;
status_t TRNG_GetDefaultConfig(trng_config_t *userConfig)
{
userConfig->sampleMode = 0x5AU;
return kStatus_Success;
}
status_t TRNG_Init(TRNG_Type *base, const trng_config_t *userConfig)
{
ck_assert_ptr_eq(base, &mock_trng);
trng_init_calls++;
trng_init_mode = userConfig->sampleMode;
return trng_init_ret;
}
status_t TRNG_GetRandomData(TRNG_Type *base, void *data, size_t dataSize)
{
ck_assert_ptr_eq(base, &mock_trng);
trng_read_calls++;
memset(data, 0xA5, dataSize);
return trng_read_ret;
}
static FLEXSPI_Type mock_flexspi;
static CACHE64_CTRL_Type mock_cache64;
SOCCIU_Type mock_socctrl;
NVIC_Type mock_nvic;
SysTick_Type mock_systick;
uint32_t mock_primask;
int mock_barriers;
FLEXSPI_Type *mock_flexspi_regs(void)
{
return &mock_flexspi;
}
CACHE64_POLSEL_Type mock_cache64_polsel;
static int cache_invalidations;
static uint32_t cache64_seen;
CACHE64_CTRL_Type *mock_cache64_regs(void)
{
cache64_seen |= mock_cache64.CCR;
if ((mock_cache64.CCR & CACHE64_CTRL_CCR_GO_MASK) != 0U) {
cache_invalidations++;
mock_cache64.CCR &= ~CACHE64_CTRL_CCR_GO_MASK;
}
return &mock_cache64;
}
static int systick_attach_calls;
static clock_attach_id_t systick_attach_id;
void CLOCK_AttachClk(clock_attach_id_t connection)
{
systick_attach_calls++;
systick_attach_id = connection;
}
/* Point the SDK peripheral instances at the mocks above */
#undef FLEXSPI
#undef CACHE64_CTRL0
#undef CACHE64_POLSEL0
#undef SOCCTRL
#define FLEXSPI (mock_flexspi_regs())
#define CACHE64_CTRL0 (mock_cache64_regs())
#define CACHE64_POLSEL0 (&mock_cache64_polsel)
#define SOCCTRL (&mock_socctrl)
#undef TRNG
#define TRNG (&mock_trng)
#include "../../hal/rw612.c"
static int sau_covers(uint32_t addr, int nsc)
{
int i;
for (i = 0; i < RW612_SAU_REGIONS; i++) {
if (sau[i].set && (sau[i].nsc == nsc) &&
(addr >= sau[i].start) && (addr <= sau[i].end))
return 1;
}
return 0;
}
static void setup(void)
{
memset(sau, 0, sizeof(sau));
memset(&mock_nvic, 0, sizeof(mock_nvic));
sau_bad_region = 0;
mock_sau_ctrl = 0;
mock_shcsr = 0;
memset(&mock_cache64, 0, sizeof(mock_cache64));
memset(&mock_cache64_polsel, 0, sizeof(mock_cache64_polsel));
cache_invalidations = 0;
cache64_seen = 0;
hal_init();
hal_trng_fini();
trng_init_calls = trng_read_calls = 0;
trng_init_ret = trng_read_ret = kStatus_Success;
trng_init_mode = 0;
}
START_TEST(test_sau_enabled_and_well_formed)
{
int i;
ck_assert_int_eq(sau_bad_region, 0);
ck_assert_uint_ne(mock_sau_ctrl & SAU_INIT_CTRL_ENABLE, 0);
ck_assert_uint_ne(mock_shcsr & SCB_SHCSR_SECUREFAULT_EN, 0);
for (i = 0; i < RW612_SAU_REGIONS; i++) {
if (!sau[i].set)
continue;
ck_assert_uint_le(sau[i].start, sau[i].end);
ck_assert_uint_eq(sau[i].start & 0x1FU, 0);
ck_assert_uint_eq(sau[i].end & 0x1FU, 0x1FU);
}
}
END_TEST
START_TEST(test_nsc_region_is_veneers_only)
{
int i;
int nsc_regions = 0;
for (i = 0; i < RW612_SAU_REGIONS; i++) {
if (sau[i].set && sau[i].nsc) {
nsc_regions++;
ck_assert_uint_eq(sau[i].start, WOLFBOOT_NSC_ADDRESS);
ck_assert_uint_eq(sau[i].end,
WOLFBOOT_NSC_ADDRESS + WOLFBOOT_NSC_SIZE - 1);
}
}
ck_assert_int_eq(nsc_regions, 1);
}
END_TEST
START_TEST(test_application_resources_non_secure)
{
static const uint32_t ns_addrs[] = {
WOLFBOOT_PARTITION_BOOT_ADDRESS,
WOLFBOOT_PARTITION_BOOT_ADDRESS + WOLFBOOT_PARTITION_SIZE - 1,
0x20040000, 0x2012FFFF, /* non-secure SRAM */
0x40020000, 0x40021000, /* RSTCTL1, CLKCTL1 */
0x40100000, /* GPIO */
0x40106000, 0x40109000 /* FLEXCOMM0, FLEXCOMM3 (console) */
};
unsigned int i;
for (i = 0; i < sizeof(ns_addrs) / sizeof(ns_addrs[0]); i++)
ck_assert_msg(sau_covers(ns_addrs[i], 0), "0x%08x not non-secure",
ns_addrs[i]);
}
END_TEST
START_TEST(test_secure_resources_not_non_secure)
{
static const uint32_t s_addrs[] = {
0x08000000, 0x08000400, /* wolfBoot, FCB */
WOLFBOOT_NSC_ADDRESS,
WOLFBOOT_KEYVAULT_ADDRESS,
WOLFBOOT_KEYVAULT_ADDRESS + WOLFBOOT_KEYVAULT_SIZE - 1,
WOLFBOOT_PARTITION_UPDATE_ADDRESS,
WOLFBOOT_PARTITION_SWAP_ADDRESS,
0x20000000, 0x2003FFFF, /* wolfBoot SRAM */
0x40000000, 0x40000040, /* RSTCTL0, PRSTCTL0_SET */
0x40001000, /* CLKCTL0 */
0x40004000, /* IO_MUX */
0x40006000, 0x40007000, /* PUF, ELS */
0x40009000, 0x4000A000, /* PKC, OCOTP */
0x40014000, 0x40024000, /* TRNG, ITRC */
0x40033000, /* CACHE64_CTRL0 */
0x40104000, 0x40105000, /* DMA0, DMA1 */
0x4010A000, /* past FLEXCOMM3 */
0x4010F000, /* DBGMAILBOX */
0x40134000, /* FLEXSPI */
0x40148000, /* AHB_SECURE_CTRL */
0x4014E000, /* GDMA */
0x48000000, /* uncached FlexSPI alias */
0x48000000 + (WOLFBOOT_KEYVAULT_ADDRESS - 0x08000000)
};
unsigned int i;
for (i = 0; i < sizeof(s_addrs) / sizeof(s_addrs[0]); i++)
ck_assert_msg(!sau_covers(s_addrs[i], 0), "0x%08x is non-secure",
s_addrs[i]);
}
END_TEST
START_TEST(test_non_secure_peripheral_irqs_targeted)
{
unsigned int i;
ck_assert_uint_eq(mock_nvic.ITNS[0],
(1UL << GPIO_INTA_IRQn) | (1UL << GPIO_INTB_IRQn) |
(0xFUL << FLEXCOMM0_IRQn));
for (i = 1; i < sizeof(mock_nvic.ITNS) / sizeof(mock_nvic.ITNS[0]); i++)
ck_assert_uint_eq(mock_nvic.ITNS[i], 0);
}
END_TEST
START_TEST(test_xip_cache_enabled_at_init)
{
const uint32_t inv = CACHE64_CTRL_CCR_INVW0_MASK |
CACHE64_CTRL_CCR_INVW1_MASK;
ck_assert_int_eq(cache_invalidations, 1);
ck_assert_uint_eq(cache64_seen & inv, inv);
ck_assert_uint_eq(mock_cache64.CCR,
CACHE64_CTRL_CCR_ENWRBUF_MASK | CACHE64_CTRL_CCR_ENCACHE_MASK);
ck_assert_uint_eq(mock_cache64_polsel.REG0_TOP, 0x07FFFC00U);
ck_assert_uint_eq(mock_cache64_polsel.REG1_TOP, 0U);
ck_assert_uint_eq(mock_cache64_polsel.POLSEL, 1U);
}
END_TEST
START_TEST(test_xip_cache_already_on_left_alone)
{
mock_cache64.CCR = CACHE64_CTRL_CCR_ENCACHE_MASK;
mock_cache64_polsel.POLSEL = 0xA5U;
cache_invalidations = 0;
hal_init();
ck_assert_int_eq(cache_invalidations, 0);
ck_assert_uint_eq(mock_cache64.CCR, CACHE64_CTRL_CCR_ENCACHE_MASK);
ck_assert_uint_eq(mock_cache64_polsel.POLSEL, 0xA5U);
}
END_TEST
START_TEST(test_benchmark_timer_counts_lposc_ticks)
{
memset(&mock_systick, 0, sizeof(mock_systick));
ck_assert_uint_eq(hal_get_timer_us(), 0);
ck_assert_int_eq(systick_attach_calls, 1);
ck_assert_int_eq(systick_attach_id, kLPOSC_to_SYSTICK_CLK);
ck_assert_uint_eq(mock_systick.LOAD, SysTick_LOAD_RELOAD_Msk);
ck_assert_uint_eq(mock_systick.CTRL, SysTick_CTRL_ENABLE_Msk);
/* Down-counter wraps from 0 to the 24-bit reload value */
mock_systick.VAL = SysTick_LOAD_RELOAD_Msk - 99U;
ck_assert_uint_eq(hal_get_timer_us(), 100);
mock_systick.VAL -= 400U;
ck_assert_uint_eq(hal_get_timer_us(), 500);
ck_assert_int_eq(systick_attach_calls, 1);
}
END_TEST
START_TEST(test_trng_needs_init)
{
unsigned char buf[8];
ck_assert_int_eq(hal_trng_get_entropy(buf, sizeof(buf)), -1);
ck_assert_int_eq(trng_read_calls, 0);
}
END_TEST
START_TEST(test_trng_init_and_read)
{
unsigned char buf[8];
unsigned int i;
memset(buf, 0, sizeof(buf));
hal_trng_init();
ck_assert_int_eq(trng_init_calls, 1);
ck_assert_uint_eq(trng_init_mode, 0x5AU);
ck_assert_int_eq(hal_trng_get_entropy(buf, sizeof(buf)), 0);
ck_assert_int_eq(trng_read_calls, 1);
for (i = 0; i < sizeof(buf); i++)
ck_assert_uint_eq(buf[i], 0xA5);
}
END_TEST
START_TEST(test_trng_init_failure)
{
unsigned char buf[8];
trng_init_ret = kStatus_Fail;
hal_trng_init();
ck_assert_int_eq(hal_trng_get_entropy(buf, sizeof(buf)), -1);
ck_assert_int_eq(trng_read_calls, 0);
}
END_TEST
START_TEST(test_trng_read_failure)
{
unsigned char buf[8];
hal_trng_init();
trng_read_ret = kStatus_Fail;
ck_assert_int_eq(hal_trng_get_entropy(buf, sizeof(buf)), -1);
hal_trng_fini();
trng_read_ret = kStatus_Success;
ck_assert_int_eq(hal_trng_get_entropy(buf, sizeof(buf)), -1);
ck_assert_int_eq(trng_read_calls, 1);
}
END_TEST
static Suite *rw612_tz_suite(void)
{
Suite *s = suite_create("rw612-tz");
TCase *tc = tcase_create("rw612-tz");
tcase_add_checked_fixture(tc, setup, NULL);
tcase_add_test(tc, test_sau_enabled_and_well_formed);
tcase_add_test(tc, test_nsc_region_is_veneers_only);
tcase_add_test(tc, test_application_resources_non_secure);
tcase_add_test(tc, test_secure_resources_not_non_secure);
tcase_add_test(tc, test_non_secure_peripheral_irqs_targeted);
tcase_add_test(tc, test_xip_cache_enabled_at_init);
tcase_add_test(tc, test_xip_cache_already_on_left_alone);
tcase_add_test(tc, test_benchmark_timer_counts_lposc_ticks);
tcase_add_test(tc, test_trng_needs_init);
tcase_add_test(tc, test_trng_init_and_read);
tcase_add_test(tc, test_trng_init_failure);
tcase_add_test(tc, test_trng_read_failure);
suite_add_tcase(s, tc);
return s;
}
int main(void)
{
int fails;
Suite *s = rw612_tz_suite();
SRunner *sr = srunner_create(s);
srunner_run_all(sr, CK_NORMAL);
fails = srunner_ntests_failed(sr);
srunner_free(sr);
return fails;
}

View File

@ -0,0 +1,192 @@
/* unit-rw612-uart.c
*
* Unit tests for the RW612 debug console in hal/rw612.c (DEBUG_UART): the
* FLEXCOMM3 clock, pin mux and USART setup, and the newline to CRLF output.
*
* 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 <check.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
/* RW612 layout (config/examples/rw612.config), in place of target.h */
#define H_TARGETS_TARGET_
#define WOLFBOOT_FIXED_PARTITIONS
#define WOLFBOOT_SECTOR_SIZE 0x1000
#define WOLFBOOT_PARTITION_SIZE 0x100000
#define WOLFBOOT_PARTITION_BOOT_ADDRESS 0x08100000
#define WOLFBOOT_PARTITION_UPDATE_ADDRESS 0x08200000
#define WOLFBOOT_PARTITION_SWAP_ADDRESS 0x08300000
#include "fsl_common.h"
static USART_Type mock_usart3;
static MCI_IO_MUX_Type mock_iomux;
static SOCCIU_Type mock_socctrl;
static AON_SOC_CIU_Type mock_aon;
/* Before fsl_io_mux.h: its inline IO_MUX_SetPinMux() expands these */
#undef USART3
#undef MCI_IO_MUX
#undef SOCCTRL
#undef AON_SOC_CIU
#define USART3 (&mock_usart3)
#define MCI_IO_MUX (&mock_iomux)
#define SOCCTRL (&mock_socctrl)
#define AON_SOC_CIU (&mock_aon)
#include "fsl_io_mux.h"
#include "fsl_usart.h"
#define MOCK_TX_MAX 64
uint32_t mock_primask;
int mock_barriers;
static clock_attach_id_t attached_clk;
static int attach_calls;
static usart_config_t init_cfg;
static uint32_t init_clk_hz;
static USART_Type *init_base;
static int init_calls;
static uint8_t tx[MOCK_TX_MAX];
static unsigned int tx_len;
static int tx_bad_base;
void CLOCK_AttachClk(clock_attach_id_t connection)
{
attached_clk = connection;
attach_calls++;
}
void USART_GetDefaultConfig(usart_config_t *config)
{
memset(config, 0, sizeof(*config));
config->baudRate_Bps = 9600U;
}
status_t USART_Init(USART_Type *base, const usart_config_t *config,
uint32_t srcClock_Hz)
{
init_base = base;
init_cfg = *config;
init_clk_hz = srcClock_Hz;
init_calls++;
return kStatus_Success;
}
status_t USART_WriteBlocking(USART_Type *base, const uint8_t *data,
size_t length)
{
if (base != &mock_usart3)
tx_bad_base = 1;
while ((length > 0U) && (tx_len < MOCK_TX_MAX)) {
tx[tx_len++] = *data++;
length--;
}
return kStatus_Success;
}
#include "../../hal/rw612.c"
static void setup(void)
{
memset(&mock_iomux, 0xFF, sizeof(mock_iomux));
memset(&mock_socctrl, 0, sizeof(mock_socctrl));
memset(&mock_aon, 0, sizeof(mock_aon));
memset(&init_cfg, 0, sizeof(init_cfg));
memset(tx, 0, sizeof(tx));
tx_len = 0;
tx_bad_base = 0;
attach_calls = init_calls = 0;
init_base = NULL;
init_clk_hz = 0;
}
START_TEST(test_uart_init_flexcomm3_115200)
{
uart_init();
ck_assert_int_eq(attach_calls, 1);
ck_assert_int_eq(attached_clk, kSFRO_to_FLEXCOMM3);
ck_assert_int_eq(init_calls, 1);
ck_assert_ptr_eq(init_base, &mock_usart3);
ck_assert_uint_eq(init_cfg.baudRate_Bps, 115200U);
ck_assert(init_cfg.enableTx);
ck_assert(init_cfg.enableRx);
ck_assert_uint_eq(init_clk_hz, 16000000U);
}
END_TEST
START_TEST(test_uart_init_muxes_fc3_pins)
{
/* FC3 USART data is GPIO24/GPIO26, enabled in the always-on domain */
uart_init();
ck_assert_uint_eq(mock_iomux.GPIO_GRP0 & 0x05000000U, 0U);
ck_assert_uint_ne(mock_iomux.FC3, 0U);
ck_assert_uint_eq(mock_aon.MCI_IOMUX_EN0 & 0x05000000U, 0x05000000U);
}
END_TEST
START_TEST(test_uart_write_crlf)
{
static const char out[] = "\r\nab\r\n\r\nc";
uart_write("\nab\n\nc", 6);
ck_assert_int_eq(tx_bad_base, 0);
ck_assert_uint_eq(tx_len, sizeof(out) - 1U);
ck_assert_mem_eq(tx, out, sizeof(out) - 1U);
}
END_TEST
START_TEST(test_uart_write_plain_and_empty)
{
uart_write("abc", 3);
ck_assert_uint_eq(tx_len, 3);
ck_assert_mem_eq(tx, "abc", 3);
uart_write("x", 0);
ck_assert_uint_eq(tx_len, 3);
}
END_TEST
static Suite *rw612_uart_suite(void)
{
Suite *s = suite_create("rw612-uart");
TCase *tc = tcase_create("uart");
tcase_add_checked_fixture(tc, setup, NULL);
tcase_add_test(tc, test_uart_init_flexcomm3_115200);
tcase_add_test(tc, test_uart_init_muxes_fc3_pins);
tcase_add_test(tc, test_uart_write_crlf);
tcase_add_test(tc, test_uart_write_plain_and_empty);
suite_add_tcase(s, tc);
return s;
}
int main(void)
{
int fails;
SRunner *sr = srunner_create(rw612_uart_suite());
srunner_run_all(sr, CK_NORMAL);
fails = srunner_ntests_failed(sr);
srunner_free(sr);
return fails == 0 ? 0 : 1;
}