In this article I will be describing how you can test Trusted Firmware M Update functionality over PSA API on ARM Cortex M33 using QEMU.
Theory:
Trusted Firmware-M (TF-M) implements a Secure Processing Environment (SPE) for Armv8-M architecture (e.g. Cortex-M33 processor) and dual-core Cortex-M devices.
TF-M provides a set of secure services – Crypto, Attestation and Secure Storage. It also provides secure boot through a 2nd stage bootloader based on mcuboot for authenticating runtime images and updates of the platform. Applications and Libraries in the Non-secure Processing Environment (NSPE) can utilize these secure services with a standardized set of PSA Functional APIs.

TF-M is divided into several binaries, each with a dedicated main role:
- Boot loader stage 1 (BL1) application processor trusted ROM
- Boot loader stage 2 (BL2) trusted boot firmware
- Boot loader stage 3 (MCUboot) secure boot
- Boot loader stage 3-1 - trusted secure firmware
- Boot loader stage 3-2 - non-secure firmware

TF-M loading steps:
- BL1 ROM code loads TF-M binary and calls BL2
- BL2 starts MCUboot
- MCUboot loads TFM-S FW
- MCUboot loads NS FW
TFM FW Update Concept:
The overall concept of TF-M FW update over PSA API on Cortex M33 shall look like the following:

Platform and approach:
For the test MPS2/AN521 board was taken. It was chosen because QEMU fully supports TrustZone for this platform and TFM is available for it.
The memory map of the flash for MPS2/AN521 looks like the following (see platform/ext/target/mps2/an521/partition/flash_layout.h in TFM):
0x0000_0000 BL2 - MCUBoot (0.5 MB) 0x0008_0000 Secure image primary slot (0.5 MB) 0x0010_0000 Non-secure image primary slot (0.5 MB) 0x0018_0000 Secure image secondary slot (0.5 MB) 0x0020_0000 Non-secure image secondary slot (0.5 MB) 0x0028_0000 Scratch area (0.5 MB) 0x0030_0000 Protected Storage Area (20 KB) 0x0030_5000 Internal Trusted Storage Area (16 KB) 0x0030_9000 NV counters area (4 KB) 0x0030_A000 Unused (984 KB)
Since I don't have a real board and cannot extract BL1 image from ROM I will try to instruct QEMU to start with BL2 image using --kernel option. Additionally I will map 2MB of primary and secondary slot into guest address space to test TFM FW update functionality.
TF-M build:
TF-M code with FW Update functionality over PSA API (and some small fixes) is available here.
In order to enable usage of PSA API change TFM_PSA_API in config/config_default.cmake to ON.
To configure and build BL2 and TF-M Firmware for MPS2 AN521 platform run the following:
evg@evg:~/projects/tfm-fwu$ cmake -S . -B cmake_build -DTFM_PLATFORM=mps2/an521 -DTFM_TOOLCHAIN_FILE=toolchain_GNUARM.cmake evg@evg:~/projects/tfm-fwu$ cmake --build cmake_build -- install
FreeRTOS build:
FreeRTOS code for Non-secure mode which trigger TF-M/NS FW update over PSA API is available here.
In order to build FreeRTOS and trigger TF-M FW update test run the following:
evg@evg:~/projects/freertos-tfm-fwu$ cd freertos_kernel/portable/ThirdParty/GCC/ARM_CM33_TFM evg@evg:~/projects/freertos-tfm-fwu/freertos_kernel/portable/ThirdParty/GCC/ARM_CM33_TFM$ make
If build was successful it's now only needed to prepare flash image of 2MB which has 1 MB slot allocated for TFM-S and NS FreeRTOS and 1 MB slot for new TFM-S/NS Firmware images. For this I use sign.sh script and toflash binary from here:
evg@evg:~/projects/tfm-fwu$ ./sign.sh evg@evg:~/projects/tfm-fwu$ gcc -g -o toflash toflash.c evg@evg:~/projects/tfm-fwu$ ./toflash 1+0 records in 1+0 records out 2097152 bytes (2,1 MB, 2,0 MiB) copied, 0,0038503 s, 545 MB/s
If you look what toflash app is doing it simply glues TFM-S and FreeRTOS signed binaries:
// Flash size is 4MB but we need only 2MB for S and NS FW system("dd if=/dev/zero bs=2M count=1 > m33_flash.bin"); // Add Secure image primary slot merge_flash("./cmake_build/install/outputs/MPS2/AN521/tfm_s_signed.bin","m33_flash.bin",0x0); // Add Non-secure image primary slot merge_flash("./RTOSDemo-signed.bin","m33_flash.bin",0x80000); // the rest is empty
QEMU build and run:
Code for Qemu to map 2MB of flash partitions into guest memory for TF-M FW Update test is available here. Please make sure Qemu is rebuilt with all necessary changes before starting it:
evg@evg:~/projects/qemu-tfm$ ./build/qemu-system-arm -machine mps2-an521 -cpu cortex-m33 -kernel ~/projects/tfm-fwu/cmake_build/install/outputs/MPS2/AN521/bl2.elf -m 16 -nographic-serial pipe:/tmp/guest
Use "-gdb tcp::1234 -S" to enable debugging.
Checking results:
After starting QEMU check that guest loaded TF-M and running FreeRTOS:
evg@evg:~/projects/linux-5.7$ cat /tmp/guest.out [INF] Starting bootloader [INF] Swap type: none [INF] Swap type: none [INF] Bootloader chainload address offset: 0x80000 [INF] Jumping to the first image slot [Sec Thread] Secure image initializing! Booting TFM v1.2.0 [Crypto] MBEDTLS_TEST_NULL_ENTROPY is not suitable for production! Starting FreeRTOS in NS Mode... Updating TFM-NS FW... Write of the image from the flash to SLOT 1 was triggered...
Now let's build new FreeRTOS FW, sign it and send the binary over pipe to the guest:
evg@evg:~/projects/tfm-fwu$ cat RTOSDemo-signed.bin > /tmp/guest.in
and check the guest output:
evg@evg:~/projects/linux-5.7$ cat /tmp/guest.out Success on writing TFM-NS image to SLOT 1 tfm_fwu_install status > 0 Reboot Rebooting after install ... [INF] Starting bootloader [INF] Swap type: none [INF] Swap type: test [INF] Image upgrade secondary slot -> primary slot [INF] Erasing the primary slot [INF] Copying the secondary slot to the primary slot: 0xzx bytes [INF] Bootloader chainload address offset: 0x80000 [INF] Jumping to the first image slot [Sec Thread] Secure image initializing! Booting TFM v1.2.0 [Crypto] MBEDTLS_TEST_NULL_ENTROPY is not suitable for production! Starting FreeRTOS in NS Mode... OTA Update was successfull. Loaded new NS image! Rebooting after install ...
Links:
Instructions how to build TF-M