TF-M FW update testing on QEMU

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.


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:

  1. Boot loader stage 1 (BL1) application processor trusted ROM
  2. Boot loader stage 2 (BL2) trusted boot firmware
  3. Boot loader stage 3 (MCUboot) secure boot
  4. Boot loader stage 3-1 - trusted secure firmware
  5. Boot loader stage 3-2 - non-secure firmware

TF-M loading steps:

  1. BL1 ROM code loads TF-M binary and calls BL2
  2. BL2 starts MCUboot
  3. MCUboot loads TFM-S FW
  4. 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 script and toflash binary from here:

evg@evg:~/projects/tfm-fwu$ ./
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
// Add Non-secure image primary slot
// 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/ 

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 ...


Instructions how to build TF-M

Security for Arm Cortex-M devices with FreeRTOS

SSE-200 Subsystem for MPS2+ specification