From 1d47fa7b622b654a8e6a53cf53e7811c42618505 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 23 Oct 2026 18:02:29 +0100 Subject: [PATCH] Add an MCXN947 secure application handoff config with m33mu tests --- .github/workflows/mcxn-emulator-tests.yml | 73 ++++++++++++++ .github/workflows/test-configs.yml | 7 ++ Makefile | 8 ++ config/examples/mcxn-tz-wolftrust.config | 48 ++++++++++ docs/Targets.md | 7 +- docs/secure_app_handoff.md | 8 ++ hal/mcxn.c | 23 ++++- test-app/emu-test-apps/Makefile | 23 ++++- test-app/emu-test-apps/common/emu_hal.c | 101 ++++++++++++++++++++ test-app/emu-test-apps/common/emu_update.c | 38 ++++++++ test-app/emu-test-apps/mcxn947/Makefile | 75 +++++++++++++++ test-app/emu-test-apps/mcxn947/target.ld.in | 89 +++++++++++++++++ test-app/emu-test-apps/mcxn947/uart.c | 61 ++++++++++++ test-app/emu-test-apps/test.sh | 34 +++++-- 14 files changed, 582 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/mcxn-emulator-tests.yml create mode 100644 config/examples/mcxn-tz-wolftrust.config create mode 100644 test-app/emu-test-apps/mcxn947/Makefile create mode 100644 test-app/emu-test-apps/mcxn947/target.ld.in create mode 100644 test-app/emu-test-apps/mcxn947/uart.c diff --git a/.github/workflows/mcxn-emulator-tests.yml b/.github/workflows/mcxn-emulator-tests.yml new file mode 100644 index 0000000000..2a614c5d3f --- /dev/null +++ b/.github/workflows/mcxn-emulator-tests.yml @@ -0,0 +1,73 @@ +name: mcxn-emulator-tests + +on: + push: + pull_request: + +jobs: + # Boot, update and rollback on the m33mu MCXN947 model with the + # TrustZone secure-application (wolfTrust) handoff config. + mcxn-emulator-tests: + runs-on: ubuntu-latest + container: + image: ghcr.io/wolfssl/wolfboot-ci-m33mu:latest + timeout-minutes: 45 + strategy: + fail-fast: false + matrix: + config: [mcxn-tz-wolftrust] + steps: + - uses: actions/checkout@v4 + + - name: Init submodules + run: | + git config --global --add safe.directory "$GITHUB_WORKSPACE" + git submodule update --init --single-branch + + - uses: actions/checkout@v4 + with: + repository: nxp-mcuxpresso/CMSIS_5 + path: CMSIS_5 + + - name: Cache MCUXpresso SDK + id: cache-mcuxpresso + uses: actions/cache@v4 + with: + path: | + mcuxpresso-sdk + venv + key: mcuxpresso-sdk-frdmmcxn947-emu-${{ hashFiles('.github/workflows/mcxn-emulator-tests.yml') }} + + - name: Setup MCUXpresso SDK + if: steps.cache-mcuxpresso.outputs.cache-hit != 'true' + run: | + python3 -m venv venv + . venv/bin/activate + pip install west + west init -m https://github.com/nxp-mcuxpresso/mcuxsdk-manifests.git mcuxpresso-sdk + cd mcuxpresso-sdk + sed -i '/se_hostlib\|emwin/d' manifests/boards/frdmmcxn947.yml + west update_board --set board frdmmcxn947 + + - name: Use the MCUXpresso SDK for the wolfBoot build + run: | + echo "MCUXSDK=1" >> "$GITHUB_ENV" + echo "MCUXPRESSO=$GITHUB_WORKSPACE/mcuxpresso-sdk/mcuxsdk" >> "$GITHUB_ENV" + echo "MCUXPRESSO_CMSIS=$GITHUB_WORKSPACE/CMSIS_5/CMSIS" >> "$GITHUB_ENV" + + - name: Configure ${{ matrix.config }} and build wolfboot + run: | + cp config/examples/${{ matrix.config }}.config .config + make wolfboot.bin + + - name: Run emu test (${{ matrix.config }}) + working-directory: test-app/emu-test-apps + run: | + BOOT_TIMEOUT=30 UPDATE_TIMEOUT=60 REBOOT_TIMEOUT=30 ./test.sh + + - name: Upload emulator logs on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: mcxn-emu-logs-${{ matrix.config }} + path: test-app/emu-test-apps/mcxn947/*.log diff --git a/.github/workflows/test-configs.yml b/.github/workflows/test-configs.yml index 29f668613f..9ebae2045c 100644 --- a/.github/workflows/test-configs.yml +++ b/.github/workflows/test-configs.yml @@ -435,6 +435,13 @@ jobs: config-file: ./config/examples/mcxn-tz-psa-hw.config board-name: frdmmcxn947 + nxp_mcxn_tz_wolftrust_test: + uses: ./.github/workflows/test-build-mcux-sdk-manifests.yml + with: + arch: arm + config-file: ./config/examples/mcxn-tz-wolftrust.config + board-name: frdmmcxn947 + nxp_rw612_test: uses: ./.github/workflows/test-build-mcux-sdk-manifests.yml with: diff --git a/Makefile b/Makefile index 5ebacc53f0..0c6887fa16 100644 --- a/Makefile +++ b/Makefile @@ -397,6 +397,14 @@ ifeq ($(TARGET),imx_rt7xx) MAIN_TARGET:=wolfboot.bin test-app/image_v1_signed.bin endif endif + +ifeq ($(TARGET),mcxn) + # The secure-app partitions sit at the Secure flash alias, 256MB above the + # flash offsets, and the payload is a Secure runtime rather than a test app. + ifeq ($(WOLFBOOT_SECURE_APP),1) + MAIN_TARGET:=wolfboot.bin + endif +endif endif # TZEN=1 ifeq ($(TARGET),pic32cz) diff --git a/config/examples/mcxn-tz-wolftrust.config b/config/examples/mcxn-tz-wolftrust.config new file mode 100644 index 0000000000..69c8f2a8c3 --- /dev/null +++ b/config/examples/mcxn-tz-wolftrust.config @@ -0,0 +1,48 @@ +ARCH?=ARM +TZEN?=1 +TARGET?=mcxn +SIGN?=ECC256 +HASH?=SHA256 +MCUXSDK?=1 +MCUXPRESSO?=$(PWD)/../NXP/mcuxpresso-sdk/mcuxsdk +MCUXPRESSO_CMSIS?=$(PWD)/../NXP/CMSIS_5/CMSIS +MCUXPRESSO_CPU?=MCXN947VDF_cm33_core0 +MCUXPRESSO_DRIVERS?=$(MCUXPRESSO)/devices/MCX/MCXN/MCXN947 +MCUXPRESSO_PROJECT_TEMPLATE?=$(MCUXPRESSO)/examples/_boards/frdmmcxn947/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=1 +WOLFBOOT_VERSION?=0 +V?=0 +SPMATH?=1 +RAM_CODE?=1 +DUALBANK_SWAP?=0 +IMAGE_HEADER_SIZE?=1024 +WOLFCRYPT_TZ?=0 + +WOLFBOOT_SECURE_APP?=1 +WOLFBOOT_SECURE_HANDOFF_ADDRESS?=0x30030000 + +# 8KB sectors +WOLFBOOT_SECTOR_SIZE?=0x2000 + +# 256KB wolfBoot region (bootloader + empty NSC), partitions at the Secure +# flash alias: 256KB boot, 256KB update in bank 1, 8KB swap +BOOTLOADER_PARTITION_SIZE?=0x40000 +WOLFBOOT_KEYVAULT_ADDRESS?=0x20000 +WOLFBOOT_KEYVAULT_SIZE?=0 +WOLFBOOT_NSC_ADDRESS?=0x20000 +WOLFBOOT_NSC_SIZE?=0x2000 +WOLFBOOT_PARTITION_SIZE?=0x40000 +WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x10040000 +WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x10180000 +WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x101C0000 diff --git a/docs/Targets.md b/docs/Targets.md index 13275e6bb0..7b312b4814 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -7567,7 +7567,7 @@ cp config/examples/mcxn.config .config` make ``` -We provide three configuration files: +We provide these configuration files: - `mcxn.config`: basic configuration file; both wolfBoot and your application run in secure world. - `mcxn-tz.config`: wolfBoot runs in secure world, your application runs in @@ -7575,6 +7575,11 @@ We provide three configuration files: - `mcxn-wolfcrypt-tz.config`: same as above, but also includes a non-secure callable (NSC) wolfPKCS11 API to perform crypto operations via wolfCrypt and access a secure keyvault provided by wolfBoot. +- `mcxn-tz-wolftrust.config`: wolfBoot hands off to a signed secure application + (for example wolfTrust) that stays in the secure world, with the measured-boot + record at `0x30030000` (see [Secure Application Handoff](secure_app_handoff.md)). + The partitions are at the secure flash alias (`0x10040000` boot, `0x10180000` + update). ### MCX N: Loading the firmware diff --git a/docs/secure_app_handoff.md b/docs/secure_app_handoff.md index e51a65fb2b..80df0e9fc7 100644 --- a/docs/secure_app_handoff.md +++ b/docs/secure_app_handoff.md @@ -88,6 +88,14 @@ derives the PSA lifecycle from the flash product state and the debug authentication status (`hal/stm32h5_lifecycle.h`), and `hal/stm32_tz.c` keeps the whole SRAM1 window and the secure app flash Secure across the handoff. +## MCXN947 + +`config/examples/mcxn-tz-wolftrust.config` puts the record at `0x30030000` and +the partitions at the Secure flash alias. `hal/mcxn.c` uses the shared +`hal_sau_init()` (no Non-secure regions), leaves every peripheral Secure, and +reports the lifecycle through the weak default. `mcxn-emulator-tests.yml` runs +boot, update and rollback on the m33mu MCXN947 model. + ## Tests - `tools/unit-tests/unit-secure-handoff.c` — record layout, the builder, and the diff --git a/hal/mcxn.c b/hal/mcxn.c index 6b30362926..318eeed5ad 100644 --- a/hal/mcxn.c +++ b/hal/mcxn.c @@ -24,6 +24,7 @@ #include #include "fsl_common.h" #include "image.h" +#include "hal.h" #include "clock_config.h" #include "fsl_clock.h" @@ -172,7 +173,23 @@ static flash_config_t pflash; static uint32_t pflash_sector_size = WOLFBOOT_SECTOR_SIZE; uint32_t SystemCoreClock; -#ifdef TZEN +#define MCXN_FLASH_S_ALIAS 0x10000000U +#define MCXN_FLASH_SIZE 0x00200000U + +/* Partitions may be linked at the Secure flash alias; the ROM flash API + * takes the flash offset. */ +static uint32_t RAMFUNCTION mcxn_flash_offset(uint32_t address) +{ + if ((address >= MCXN_FLASH_S_ALIAS) && + (address < (MCXN_FLASH_S_ALIAS + MCXN_FLASH_SIZE))) { + address -= MCXN_FLASH_S_ALIAS; + } + return address; +} + +/* A secure application stays Secure across the jump: it uses the shared + * hal_sau_init() (no Non-secure regions) and keeps every peripheral Secure. */ +#if defined(TZEN) && !defined(WOLFBOOT_SECURE_APP) static void hal_sau_init(void) { /* Non-secure callable area */ @@ -249,7 +266,7 @@ void __assert_func(const char *a, int b, const char *c, const char *d) void hal_prepare_boot(void) { -#ifdef TZEN +#if defined(TZEN) && !defined(WOLFBOOT_SECURE_APP) periph_unsecure(); #endif } @@ -260,6 +277,7 @@ int RAMFUNCTION hal_flash_write(uint32_t address, const uint8_t *data, int len) const uint32_t word_size = 4U; int written = 0; + address = mcxn_flash_offset(address); while (len > 0) { if ((address & (word_size - 1U)) || (len < (int)word_size)) { uint32_t aligned = address & ~(word_size - 1U); @@ -316,6 +334,7 @@ int RAMFUNCTION hal_flash_erase(uint32_t address, int len) sector_size = WOLFBOOT_SECTOR_SIZE; } + address = mcxn_flash_offset(address); /* Drive the loop from the end of the requested range so the tail is * erased when the start address is rounded back to a sector. */ end = address + (uint32_t)len; diff --git a/test-app/emu-test-apps/Makefile b/test-app/emu-test-apps/Makefile index f70d868502..035fbb6644 100644 --- a/test-app/emu-test-apps/Makefile +++ b/test-app/emu-test-apps/Makefile @@ -85,27 +85,42 @@ else ifeq ($(TARGET),m2354) else ifeq ($(TARGET),imx_rt7xx) EMU_DIR=imxrt700 EMU_CPU=imxrt700 +else ifeq ($(TARGET),mcxn) + EMU_DIR=mcxn947 + EMU_CPU=mcxn947 else EMU_DIR= EMU_CPU= endif ifeq ($(strip $(EMU_DIR)),) - $(error Unsupported or unset TARGET=$(TARGET). Use TARGET=stm32h563|stm32h5|stm32u585|stm32u5|stm32l552|stm32l5|nrf5340|mcxw|m2354|imx_rt7xx) + $(error Unsupported or unset TARGET=$(TARGET). Use TARGET=stm32h563|stm32h5|stm32u585|stm32u5|stm32l552|stm32l5|nrf5340|mcxw|m2354|imx_rt7xx|mcxn) endif EMU_PATH=$(WOLFBOOT_ROOT)/test-app/emu-test-apps/$(EMU_DIR) -.PHONY: all clean test-emu sign-emu run-emu +.PHONY: all clean test-emu sign-emu run-emu check-standalone + +ifeq ($(WOLFBOOT_SECURE_APP),1) + EMU_SUB_ARGS=SECURE_HANDOFF_ADDRESS=$(WOLFBOOT_SECURE_HANDOFF_ADDRESS) +endif all: - $(MAKE) -C $(EMU_PATH) clean app.bin + $(MAKE) -C $(EMU_PATH) clean app.bin $(EMU_SUB_ARGS) sign-emu: all $(WOLFBOOT_ROOT)/wolfboot_signing_private_key.der @cp $(EMU_PATH)/app.bin $(EMU_PATH)/image.bin $(SIGN_ENV) $(SIGN_TOOL) $(SIGN_OPTIONS) $(EMU_PATH)/image.bin $(PRIVATE_KEY) $(EMU_VERSION) -run-emu: sign-emu +# run-emu boots the signed app without wolfBoot; a secure-app payload needs +# wolfBoot's UART setup and handoff record, so it only runs under test.sh. +check-standalone: +ifeq ($(WOLFBOOT_SECURE_APP),1) + @echo "run-emu: secure-app configs boot through wolfBoot; use test.sh" >&2 + @exit 1 +endif + +run-emu: check-standalone sign-emu @echo "[EMU] CPU=$(EMU_CPU) VERSION=$(EMU_VERSION)" @$(M33MU) --cpu $(EMU_CPU) --uart-stdout --no-tz --boot-offset=$(IMAGE_HEADER_SIZE) \ $(EMU_PATH)/image_v$(EMU_VERSION)_signed.bin --timeout 2 \ diff --git a/test-app/emu-test-apps/common/emu_hal.c b/test-app/emu-test-apps/common/emu_hal.c index 9db01a921b..4a81093a03 100644 --- a/test-app/emu-test-apps/common/emu_hal.c +++ b/test-app/emu-test-apps/common/emu_hal.c @@ -196,6 +196,100 @@ static int imxrt700_flash_erase(uint32_t address, int len) } #endif +#if defined(EMU_MCXN947) +#include "fsl_flash.h" + +/* The flash is programmed through the ROM flash API, which takes the flash + * offset; the partitions sit at the Secure alias. */ +#define MCXN_FLASH_S_ALIAS 0x10000000u +#define MCXN_FLASH_SIZE 0x00200000u +#define MCXN_FLASH_PHRASE FSL_FEATURE_FLASH_PFLASH_PHRASE_SIZE + +static flash_config_t mcxn_flash; +static int mcxn_flash_ready; + +static uint32_t mcxn_flash_offset(uint32_t address) +{ + if ((address >= MCXN_FLASH_S_ALIAS) && + (address < (MCXN_FLASH_S_ALIAS + MCXN_FLASH_SIZE))) { + address -= MCXN_FLASH_S_ALIAS; + } + return address; +} + +static int mcxn_flash_init(void) +{ + if (mcxn_flash_ready == 0) { + memset(&mcxn_flash, 0, sizeof(mcxn_flash)); + if (FLASH_Init(&mcxn_flash) != kStatus_FLASH_Success) { + return -1; + } + mcxn_flash_ready = 1; + } + return 0; +} + +static int mcxn_flash_write(uint32_t address, const uint8_t *data, int len) +{ + uint8_t phrase[MCXN_FLASH_PHRASE]; + uint32_t aligned; + uint32_t off; + uint32_t copy; + int done = 0; + + if (mcxn_flash_init() != 0) { + return -1; + } + address = mcxn_flash_offset(address); + while (done < len) { + off = address & (MCXN_FLASH_PHRASE - 1u); + copy = (uint32_t)(len - done); + if ((off == 0u) && (copy >= MCXN_FLASH_PHRASE)) { + copy &= ~(MCXN_FLASH_PHRASE - 1u); + if (FLASH_Program(&mcxn_flash, address, (uint8_t *)data + done, + copy) != kStatus_FLASH_Success) { + return -1; + } + } + else { + aligned = address - off; + if (copy > (MCXN_FLASH_PHRASE - off)) { + copy = MCXN_FLASH_PHRASE - off; + } + memcpy(phrase, (const void *)aligned, sizeof(phrase)); + memcpy(phrase + off, data + done, copy); + if (FLASH_Program(&mcxn_flash, aligned, phrase, + sizeof(phrase)) != kStatus_FLASH_Success) { + return -1; + } + } + address += copy; + done += (int)copy; + } + return 0; +} + +static int mcxn_flash_erase(uint32_t address, int len) +{ + uint32_t p; + uint32_t end; + + if (mcxn_flash_init() != 0) { + return -1; + } + address = mcxn_flash_offset(address); + end = address + (uint32_t)len; + for (p = address & ~(EMU_FLASH_SECTOR_SIZE - 1u); p < end; + p += EMU_FLASH_SECTOR_SIZE) { + if (FLASH_Erase(&mcxn_flash, p, EMU_FLASH_SECTOR_SIZE, + kFLASH_ApiEraseKey) != kStatus_FLASH_Success) { + return -1; + } + } + return 0; +} +#endif + int hal_flash_write(uint32_t address, const uint8_t *data, int len) { if (data == 0 || len <= 0) { @@ -222,6 +316,8 @@ int hal_flash_write(uint32_t address, const uint8_t *data, int len) } #elif defined(EMU_IMXRT700) return imxrt700_flash_write(address, data, len); +#elif defined(EMU_MCXN947) + return mcxn_flash_write(address, data, len); #else memcpy((void *)address, data, (size_t)len); #endif @@ -253,6 +349,11 @@ int hal_flash_erase(uint32_t address, int len) return 0; } return imxrt700_flash_erase(address, len); +#elif defined(EMU_MCXN947) + if (len <= 0) { + return 0; + } + return mcxn_flash_erase(address, len); #else uint32_t end; #if defined(EMU_STM32) diff --git a/test-app/emu-test-apps/common/emu_update.c b/test-app/emu-test-apps/common/emu_update.c index 72b5cb25bf..261bc269d4 100644 --- a/test-app/emu-test-apps/common/emu_update.c +++ b/test-app/emu-test-apps/common/emu_update.c @@ -28,6 +28,9 @@ #include "target.h" #include "hal.h" #include "wolfboot/wolfboot.h" +#ifdef EMU_SECURE_HANDOFF_ADDRESS +#include "wolfboot/secure_handoff.h" +#endif #define MSGSIZE 16 #define PAGESIZE 256 @@ -283,6 +286,38 @@ static void wait_for_update(uint32_t version) } } +#ifdef EMU_SECURE_HANDOFF_ADDRESS +/* The record wolfBoot published must describe the image that is running. */ +static int emu_check_handoff(uint32_t version) +{ + const volatile wolfBoot_secure_handoff_t *rec = + (const volatile wolfBoot_secure_handoff_t *)EMU_SECURE_HANDOFF_ADDRESS; + uint8_t *hash = NULL; + uint32_t i; + + if ((rec->magic != WOLFBOOT_SECURE_HANDOFF_MAGIC) || + (rec->magic_inverse != ~WOLFBOOT_SECURE_HANDOFF_MAGIC) || + (rec->version != WOLFBOOT_SECURE_HANDOFF_VERSION) || + (rec->size != sizeof(wolfBoot_secure_handoff_t)) || + (rec->image_version != version) || + (rec->hash_algorithm != WOLFBOOT_SECURE_HANDOFF_HASH_SHA256) || + (rec->measurement_size != WOLFBOOT_SECURE_HANDOFF_DIGEST_SIZE)) { + return -1; + } + if (wolfBoot_find_header((uint8_t *)WOLFBOOT_PARTITION_BOOT_ADDRESS + + IMAGE_HEADER_OFFSET, HDR_SHA256, &hash) != + WOLFBOOT_SECURE_HANDOFF_DIGEST_SIZE) { + return -1; + } + for (i = 0u; i < WOLFBOOT_SECURE_HANDOFF_DIGEST_SIZE; i++) { + if (rec->measurement[i] != hash[i]) { + return -1; + } + } + return 0; +} +#endif + int main(void) { uint32_t version; @@ -291,6 +326,9 @@ int main(void) version = emu_current_version(); printf("get_version=%lu\n", (unsigned long)version); +#ifdef EMU_SECURE_HANDOFF_ADDRESS + printf("handoff=%s\n", (emu_check_handoff(version) == 0) ? "ok" : "bad"); +#endif if (version == 4u) { #ifdef TZEN diff --git a/test-app/emu-test-apps/mcxn947/Makefile b/test-app/emu-test-apps/mcxn947/Makefile new file mode 100644 index 0000000000..78152e31d1 --- /dev/null +++ b/test-app/emu-test-apps/mcxn947/Makefile @@ -0,0 +1,75 @@ +# Makefile +# +# 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 + +CC=arm-none-eabi-gcc +OBJCOPY ?= arm-none-eabi-objcopy + +MCUXPRESSO ?= $(abspath ../../../../NXP/mcuxpresso-sdk/mcuxsdk) +MCUXPRESSO_CMSIS ?= $(abspath ../../../../NXP/CMSIS_5/CMSIS) +MCUXPRESSO_DRIVERS ?= $(MCUXPRESSO)/devices/MCX/MCXN/MCXN947 +MCUXPRESSO_CPU ?= MCXN947VDF_cm33_core0 +ROMAPI_FLASH := $(MCUXPRESSO_DRIVERS)/drivers/romapi/flash + +CFLAGS := -mcpu=cortex-m33 -mthumb -mcmse -Os -ffreestanding -fdata-sections -ffunction-sections -g -ggdb +CFLAGS += -I. -I../common -I../../../include -DEMU_MCXN947 +CFLAGS += -DIMAGE_HEADER_SIZE=$(IMAGE_HEADER_SIZE) +CFLAGS += -DCPU_$(MCUXPRESSO_CPU) -DNDEBUG -U__ARM_FEATURE_DSP +# ECC flash: a programmed phrase cannot be reprogrammed, as in the wolfBoot build. +CFLAGS += -DNVM_FLASH_WRITEONCE +ifneq ($(SECURE_HANDOFF_ADDRESS),) + CFLAGS += -DEMU_SECURE_HANDOFF_ADDRESS=$(SECURE_HANDOFF_ADDRESS) +endif +CFLAGS += -I$(MCUXPRESSO_DRIVERS) -I$(MCUXPRESSO_DRIVERS)/drivers -I$(ROMAPI_FLASH) +CFLAGS += -I$(MCUXPRESSO_DRIVERS)/../periph -I$(MCUXPRESSO)/drivers/common +CFLAGS += -I$(MCUXPRESSO_CMSIS)/Include -I$(MCUXPRESSO_CMSIS)/Core/Include +LDFLAGS := -nostdlib -T target.ld -Wl,-gc-sections +LDLIBS := -Wl,--start-group -lc -lm -lgcc -lnosys -Wl,--end-group +# The MCXN TrustZone config hands off to a Secure payload +# (WOLFBOOT_SECURE_APP), so the test app runs in the secure world. +NSC_OBJ := + +VPATH := ../common + +COMMON_SRCS := emu_ivt.c emu_startup.c emu_syscalls.c emu_update.c emu_hal.c +LIBWOLFBOOT_SRC := ../../../src/libwolfboot.c +SRCS := $(COMMON_SRCS) uart.c +OBJS := $(SRCS:.c=.o) libwolfboot.o fsl_flash.o $(NSC_OBJ) + +all: app.bin + +app.elf: $(OBJS) target.ld + $(CC) $(CFLAGS) $(OBJS) $(LDFLAGS) $(LDLIBS) -o $@ + +app.bin: app.elf + $(OBJCOPY) -O binary $< $@ + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +libwolfboot.o: $(LIBWOLFBOOT_SRC) + $(CC) $(CFLAGS) -c $< -o $@ + +fsl_flash.o: $(ROMAPI_FLASH)/src/fsl_flash.c + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + rm -f $(SRCS:.c=.o) libwolfboot.o fsl_flash.o app.elf app.bin + +.PHONY: all clean diff --git a/test-app/emu-test-apps/mcxn947/target.ld.in b/test-app/emu-test-apps/mcxn947/target.ld.in new file mode 100644 index 0000000000..4dca44980c --- /dev/null +++ b/test-app/emu-test-apps/mcxn947/target.ld.in @@ -0,0 +1,89 @@ +/* target.ld.in + * + * 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 + */ + +/* Minimal linker script for the MCXN947 emulator test app. The app runs in the + * Secure world from the boot partition base plus the image header. Secure SRAM + * above the secure-app handoff record is used for data and stack. + * @FLASH_ORIGIN@ is WOLFBOOT_PARTITION_BOOT_ADDRESS + IMAGE_HEADER_SIZE, + * @FLASH_SIZE@ is WOLFBOOT_PARTITION_SIZE - IMAGE_HEADER_SIZE. */ +MEMORY +{ + FLASH (rx) : ORIGIN = @FLASH_ORIGIN@, LENGTH = @FLASH_SIZE@ + RAM (rwx) : ORIGIN = 0x30038000, LENGTH = 0x00030000 +} + +_estack = ORIGIN(RAM) + LENGTH(RAM); +_sidata = LOADADDR(.data); + +SECTIONS +{ + .isr_vector : + { + KEEP(*(.isr_vector)) + } > FLASH + + .text : + { + *(.text*) + *(.rodata*) + *(.ARM.extab* .gnu.linkonce.armextab.*) + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + *(.glue_7) + *(.glue_7t) + *(.eh_frame) + } > FLASH + + .preinit_array : + { + __preinit_array_start = .; + KEEP(*(.preinit_array*)) + __preinit_array_end = .; + } > FLASH + + .init_array : + { + __init_array_start = .; + KEEP(*(.init_array*)) + __init_array_end = .; + } > FLASH + + .fini_array : + { + __fini_array_start = .; + KEEP(*(.fini_array*)) + __fini_array_end = .; + } > FLASH + + .data : + { + _sdata = .; + *(.data*) + _edata = .; + } > RAM AT > FLASH + + .bss (NOLOAD) : + { + _sbss = .; + *(.bss*) + *(COMMON) + _ebss = .; + } > RAM +} diff --git a/test-app/emu-test-apps/mcxn947/uart.c b/test-app/emu-test-apps/mcxn947/uart.c new file mode 100644 index 0000000000..d1bee0b803 --- /dev/null +++ b/test-app/emu-test-apps/mcxn947/uart.c @@ -0,0 +1,61 @@ +/* uart.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 +#include "emu_app.h" + +/* LP_FLEXCOMM4 / LPUART4, Secure alias. wolfBoot (DEBUG_UART) set up the + * clocks, pins and baud rate before the jump, so only TX/RX are enabled. */ +#define LPUART4_BASE 0x500B4000u +#define LPUART4_STAT (*(volatile uint32_t *)(LPUART4_BASE + 0x14u)) +#define LPUART4_CTRL (*(volatile uint32_t *)(LPUART4_BASE + 0x18u)) +#define LPUART4_DATA (*(volatile uint32_t *)(LPUART4_BASE + 0x1Cu)) + +#define UART_STAT_TDRE 0x00800000u +#define UART_STAT_RDRF 0x00200000u +#define UART_CTRL_TE_RE 0x000C0000u +#define UART_TX_POLL_LIMIT 100000u + +void emu_uart_init(void) +{ + LPUART4_CTRL |= UART_CTRL_TE_RE; +} + +void emu_uart_write(uint8_t c) +{ + uint32_t t = UART_TX_POLL_LIMIT; + + while (((LPUART4_STAT & UART_STAT_TDRE) == 0u) && (t > 0u)) { + t--; + } + if (t != 0u) { + LPUART4_DATA = (uint32_t)c; + } +} + +int emu_uart_read(uint8_t *c) +{ + if ((LPUART4_STAT & UART_STAT_RDRF) == 0u) { + return 0; + } + *c = (uint8_t)(LPUART4_DATA & 0xFFu); + return 1; +} diff --git a/test-app/emu-test-apps/test.sh b/test-app/emu-test-apps/test.sh index 6322768299..b6642cb2ee 100755 --- a/test-app/emu-test-apps/test.sh +++ b/test-app/emu-test-apps/test.sh @@ -57,6 +57,7 @@ TARGET="${TARGET:-}" EMU_DIR="" UART_BASE="" +HANDOFF_CHECK=0 get_m33mu_target() { case "$1" in @@ -67,6 +68,7 @@ get_m33mu_target() { mcxw|mcxw71) echo "mcxw71c" ;; m2354) echo "m2354" ;; imx_rt7xx) echo "imxrt700" ;; + mcxn) echo "mcxn947" ;; *) echo "" ;; esac } @@ -79,6 +81,7 @@ case "$TARGET" in mcxw|mcxw71) EMU_DIR=mcxw71; UART_BASE=40038000 ;; m2354) EMU_DIR=m2354; UART_BASE=40070000 ;; imx_rt7xx) EMU_DIR=imxrt700; UART_BASE=40110000 ;; + mcxn) EMU_DIR=mcxn947; UART_BASE=40096000; HANDOFF_CHECK=1 ;; *) die "unsupported TARGET=$TARGET" ;; esac @@ -110,10 +113,11 @@ TZEN="$(cfg_get TZEN)" # If ARCH_OFFSET is unset/0 but partitions are in 0x0800_0000 (STM32), use that # as the base so bin-assemble offsets stay within the flash image. -# M2354 is excluded: its flash starts at 0 and the high partition addresses are -# the non-secure alias, not a different flash base. +# M2354 and MCXN are excluded: their flash starts at 0 and the high partition +# addresses are an alias, not a different flash base. if [[ "$ARCH_OFFSET" == "0" || "$ARCH_OFFSET" == "0x0" ]]; then - if [[ "$TARGET" != "m2354" && "$TARGET" != "imx_rt7xx" ]] && (( BOOT_ADDR >= 0x08000000 )); then + if [[ "$TARGET" != "m2354" && "$TARGET" != "imx_rt7xx" && "$TARGET" != "mcxn" ]] && \ + (( BOOT_ADDR >= 0x08000000 )); then ARCH_OFFSET=0x08000000 fi fi @@ -131,9 +135,9 @@ fi normalize_flash_addr() { local addr="$1" - if [[ "$TARGET" == "m2354" ]]; then - # Base is Secure and base + 0x10000000 is the non-secure alias; the flash - # image is physical APROM, so strip the alias bit to get a file offset. + if [[ "$TARGET" == "m2354" || "$TARGET" == "mcxn" ]]; then + # Flash and its alias differ by 0x10000000 (M2354: NS alias, MCXN: Secure + # alias); the flash image is physical, so strip the alias bit. if (( addr >= 0x10000000 && addr < 0x20000000 )); then addr=$((addr - 0x10000000)) fi @@ -228,6 +232,7 @@ write_target_ld() { mcxw|mcxw71) base="ARM-mcxw" ;; m2354) base="ARM-m2354" ;; imx_rt7xx) base="ARM-imx_rt7xx" ;; + mcxn) base="ARM-mcxn" ;; *) die "unsupported TARGET for linker template: $TARGET" ;; esac @@ -485,6 +490,19 @@ run_update_scenario() { fi } +SECURE_APP="$(cfg_get WOLFBOOT_SECURE_APP)" + +# A secure-app payload reports whether the handoff record matches its image. +expect_handoff() { + local label="$1" + local log_file="$2" + [[ "$HANDOFF_CHECK" == "1" && "$SECURE_APP" == "1" ]] || return 0 + if ! grep -q "handoff=ok" "$log_file"; then + tail -n 40 "$log_file" | sed 's/^/ | /' + die "$label: handoff record does not describe the booted image" + fi +} + WB_MAKE_ARGS=() # m33mu has no RT700 boot ROM or FCB: it takes the reset vector from the # start of the NOR, so wolfBoot links there instead of 0x28004000, and the @@ -522,6 +540,7 @@ if want_scenario "A"; then if [[ $factory_rc -ne 0 && $factory_rc -ne 127 ]]; then die "factory run: m33mu exited with $factory_rc" fi + expect_handoff "factory run" "$EMU_PATH/factory.log" log "factory run: ok (version=1)" log "Scenario A: receive v7 update without trigger (expect BKPT 0x4D, stay on v1)" @@ -543,6 +562,7 @@ if want_scenario "B"; then --timeout "$REBOOT_TIMEOUT" --expect-bkpt=0x4a --quit-on-faults \ "$FACTORY_FLASH" >"$run_log" 2>&1 || die "reboot v4 run $i: m33mu failed" grep -q "\\[EXPECT BKPT\\] Success" "$run_log" || die "reboot v4 run $i: expected BKPT 0x4A" + expect_handoff "reboot v4 run $i" "$run_log" log "Scenario B: reboot run $i: BKPT 0x4A hit" done fi @@ -560,6 +580,7 @@ if want_scenario "C"; then --timeout "$REBOOT_TIMEOUT" --expect-bkpt=0x4b --persist --quit-on-faults \ "$FACTORY_FLASH" >"$run_log_v3" 2>&1 || die "reboot v3 run: m33mu failed" grep -q "\\[EXPECT BKPT\\] Success" "$run_log_v3" || die "reboot v3 run: expected BKPT 0x4B" + expect_handoff "reboot v3 run" "$run_log_v3" log "Scenario C: reboot v3: BKPT 0x4B hit" print_partition_flags "Scenario C: flags after v3 reboot" @@ -580,6 +601,7 @@ if want_scenario "C"; then if [[ $fallback_rc -ne 0 && $fallback_rc -ne 127 ]]; then die "fallback run: m33mu exited with $fallback_rc" fi + expect_handoff "fallback run" "$run_log_fallback" log "Scenario C: fallback ok (version=1)" fi