This post describes the steps to create a basic TrustZone example for the Armv8-M processor with Security Extension by using Arm DS and Arm GNU toolchain. It is assumed that you have a basic knowledge of the Armv8-M Security Extension and embedded-M software programming.
The Secure and Non-secure images make function calls across security boundary based on Armv8-M Security Extension. The steps to create a basic TrustZone example for the Armv8-M processor using Arm DS and Arm GNU toolchain are as follows:
This blog assumes that you have the following tools available on your machine:
In the early stage of a project, you must define the overall system configuration, which includes memory, peripherals, and related interrupts in the security domain. The partitioning of the memory space into Secure and Non-secure is handled by the Security Attribution Unit (SAU) and Implementation Defined Attribution Units (IDAU).
Consider the Arm Microcontroller Prototyping System 2 (MPS2) Fixed Virtual Platform (FVP) model as an example. Part of memory configuration in the security domain is shown in the following table. The corresponding memory regions are used in the Secure and Non-secure software projects.
1. Getting started with Cortex-M using Arm DS, Arm GNU toolchain and CMSIS
Arm DS is the most comprehensive embedded C/C++ dedicated software development solution, supporting multicore debug for Cortex-A, Cortex-R, Cortex–M, and Neoverse Arm CPUs. See Getting started with Cortex-M using Arm DS and CMSIS for how to set up a Cortex-M project using Arm DS and CMSIS.
Arm DS supports to use a different compiler toolchain other than the one installed Arm DS, see the document register a compiler toolchain in Arm DS
For a Secure project, you must select Secure in Security Mode as shown in the following figure. Then, follow the Arm DS wizard to create the Secure project.
The Secure project is built with Cortex-M Security Extensions (CMSE) support. The project includes the <arm_cmse.h> header file and is compiled with the command-line build option -mcmse.
In Arm DS, you can add -mcmse by checking Secure Code from Project-> Properties->C/C++ Build-> Settings as shown in the following figure:
The following figure shows a Secure project structure:
2. Add Arm GNU toolchain linker script file
The linker script file is used in the linker stage, and this file:
In Arm GNU toolchain, the linker allocates the veneer code of Secure APIs in the .gnu.sgstubs section. You must place this section in the Secure Non-secure Callable (NSC) region in the GCC linker script .ld file. The following snippet shows you how to place veneer section in the NSC region. You should set __STACKSEAL_SIZE to 8 in the linker script to reserve the memory space for secure stack sealing.
__ROM_BASE_NSC = 0x101FFC00; __ROM_SIZE_NSC = 0x400; MEMORY { FLASH_NSC (rx) : ORIGIN = __ROM_BASE_NSC, LENGTH = __ROM_SIZE_NSC } .gnu.sgstubs : { . = ALIGN(32); *(.gnu.sgstubs*) } > FLASH_NSC
3. Configure memory security attributes
In a CMSIS-based project, SAU configurations are done in the TZ_SAU_Setup() function. The TZ_SAU_Setup() function is called from the SystemInit() function in the reset handler. The partition_ARMv81MML.h file contains the TZ_SAU_Setup() function and related setting parameters.
The partition_ARMv81MML.h file in the Secure project defines Non-secure regions and Secure, NSC regions as follows:
// Initialize SAU Region 0 Setup SAU Region 0 memory attributes #define SAU_INIT_REGION0 1 #define SAU_INIT_START0 0x101FFC00 // Start address #define SAU_INIT_END0 0x101FFFFF // End address #define SAU_INIT_NSC0 1 // Initialize SAU Region 1 Setup SAU Region 1 memory attributes #define SAU_INIT_REGION1 1 #define SAU_INIT_START1 0x00200000 #define SAU_INIT_END1 0x003FFFFF #define SAU_INIT_NSC1 0 // Initialize SAU Region 2 Setup SAU Region 2 memory attributes #define SAU_INIT_REGION2 1 #define SAU_INIT_START2 0x20200000 #define SAU_INIT_END2 0x203FFFFF #define SAU_INIT_NSC2 0
The TZ_SAU_Setup() function uses these settings to configure SAU regions one by one. When SAU is enabled, the memory that is not covered by any enabled SAU region is Secure. The following code snippet shows you how to configure SAU regions.
#define SAU_INIT_REGION(n) \ SAU->RNR = (n & SAU_RNR_REGION_Msk); \ SAU->RBAR = (SAU_INIT_START##n & SAU_RBAR_BADDR_Msk); \ SAU->RLAR = (SAU_INIT_END##n & SAU_RLAR_LADDR_Msk) | \ ((SAU_INIT_NSC##n << SAU_RLAR_NSC_Pos) & SAU_RLAR_NSC_Msk) | 1U __STATIC_INLINE void TZ_SAU_Setup (void) { #if defined (SAU_INIT_REGION0) && (SAU_INIT_REGION0 == 1U) SAU_INIT_REGION(0); #endif #if defined (SAU_INIT_REGION1) && (SAU_INIT_REGION1 == 1U) SAU_INIT_REGION(1); #endif #if defined (SAU_INIT_REGION2) && (SAU_INIT_REGION2 == 1U) SAU_INIT_REGION(2); #endif SAU->CTRL = ((SAU_INIT_CTRL_ENABLE << SAU_CTRL_ENABLE_Pos) & SAU_CTRL_ENABLE_Msk) | ((SAU_INIT_CTRL_ALLNS << SAU_CTRL_ALLNS_Pos) & SAU_CTRL_ALLNS_Msk) ; ... }
4. Implement and export Secure APIs
The Interface.c file implements Secure APIs as follows:
The ns_callable_fn1 is a simple Secure API that is exported to the Non-secure world. For example:
int32_t __attribute__((cmse_nonsecure_entry)) ns_callable_fn1(int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4) { int32_t ret = 0; ret = arg1 + arg2 + arg3 + arg4; return ret; }
5. Add code to launch a Non-secure image
After finishing system initialization, Secure software sets up the MSP_NS from the first item in the Non-secure vector table and branches to the Non-secure reset handler.
The following code shows you how to launch a Non-secure image:
/* TZ_START_NS: Start address of non-secure application */ #define TZ_START_NS 0x00200000 /* typedef for non-secure callback functions */ typedef void (*funcptr_void) (void) __attribute__((cmse_nonsecure_call)); /* Secure main() */ int main(void) { funcptr_void NonSecure_ResetHandler; printf("S:hello from Secure world\n"); /* Set non-secure main stack (MSP_NS) */ __TZ_set_MSP_NS(*((uint32_t *)(TZ_START_NS))); /* Get non-secure reset handler */ NonSecure_ResetHandler = (funcptr_void)(*((uint32_t *)((TZ_START_NS) + 4U))); /* Start non-secure state software application */ NonSecure_ResetHandler(); return 0; }
6. Build and generate the Secure image and import library
Add the Arm GNU toolchain linker option to generate the import library cmse_out_lib.o and Secure image. The following figure shows you the linker options:
1. Create a Non-secure project in Arm DS
You can follow the same procedure in Getting started with Cortex-M using Arm DS and CMSIS to create a Non-secure project. It is shown in the Project Explorer window as follows:
2. Import library and call the Secure APIs
Add the interface header file Interface.h and the import library cmse_out_lib.o to the Non-secure project, as shown in the following figure:
Call these Secure APIs as a normal function call. For example:
#include "Interface.h" int main(void) { Params test; int32_t result = 0; test.arg1 = 1; test.arg2 = 2; test.arg3 = 3; test.arg4 = 4; test.arg5 = 5; printf("NS: hello from Non-secure world \n\r"); printf("NS: call Secure function\n\r"); result = ns_callable_fn1(test.arg1, test.arg2, test.arg3, test.arg4); printf("NS: get add result from Secure side: %d + %d + %d + %d = %d\n\r",test.arg1,test.arg2,test.arg3,test.arg4,result); return 0; }
After the Non-secure project is built successfully, perform the following steps:
1. Set up the debug session in Arm DS to load and run Secure and Non-secure images
In Arm DS, select Run → Debug Configurations → New Launch configuration, select the appropriate target, such as MPS2_Cortex_M55 FVP, set the models parameters as below-C NSC_CFG_0=1 -C fvp_mps2.DISABLE_GATING=1 -a SecurityExample_ns.elf -a SecurityExample_s.elf
2. In the Files tab, load the symbol from the Secure and non-secure images for debug purpose, as follows:
3. Select Connect only for Run control in the Debugger tab as follows:
4. Run the images in Arm DS Click Debug to start. The executable image is downloaded into the target. You can run the executable by pressing the F8 key.
The text output in the Target Console view is as follows:
S: hello from Secure world NS: hello from Non-secure world NS: call Secure function NS: get add result from Secure side: 1 + 2 + 3 + 4 = 10
This section lists relevant Arm publications for your reference:
Thanks for this real complete job !