Chapter 10 Examples: STM32 Nucleo-144

The previous chapters laid the foundation by providing an overview on basic topics from implementation using C language to TrustZone-M. Then chapter 9 provided an overview on the most important features of a STM32L5 microcontroller. All these basic chapters are used in the following examples and should make understanding the examples much easier. The goal of this chapter is to develop both secure and non-secure world example firmwares.

Currently the following examples are implemented:

The following examples are implemented on the hardware described in chapter 9.1.

Please note: Neither the code nor the option byte configuration is suitable for production use. Don’t copy your code from the internet in real world projects!

10.1 Tools and Hardware Configuration

There are some prerequisites to successfully reproduce the example:

The following links and softwares are used through this chapter:

STM32CubeIDE.

STM32CubeIDE is an Integrated Development Environment. Based on open-source solutions like Eclipse or the GNU C/C++ toolchain, this IDE includes compilation reporting features and advanced debug features. It also integrate additional features present in other tools from the ecosystem, such as the HW and SW initilialization and code generation from STM32CubeMX.[9]

STM32CubeIDE is also TrustZone aware.

STM32CubeProgrammer * ST-Link drivers.

STM32CubeProgrammer provides an easy-to-use and efficient environment for reading, writing and verifying device memory through both the debug interface (JTAG and SWD) and the bootloader interface (UART and USB).[9]

STM32CubeMX. STM32Cube initialization code generator.

STM32CubeMX is a graphical tool used to configure any STM32 device. This easy-to-use graphical user interface generates the initialization C-code for Cortex-M cores and generates the Linux Device Tree for Cortex-A cores.[9]

Personally I am running all these tools in a Windows 10 VM and I pass the STM32Link device from the Host through to the VM. That setup until know seems to work pretty stable.

I recommend the following steps:

  1. Install all Tools listed above.
  2. Do the initial Hardware setup for the respective project.
  3. Check out the example code and import it to STM32CubeIDE.
  4. Compile and run the example without modifications

10.1.1 STM32CubeIDE Setup

STM provides a pretty good guide on how to work with TrustZone enabled projects for STM32L5 cores with their STM32CubeIDE in AN5394: Getting started with projects based on the STM32L5 Series in STM32CubeIDE. This Application Note includes a guide on how to import an existing projects, so I won’t replicate that content here.

You can use the “Import” function of STM32CubeIDE to work on the examples, after you cloned them from the repository.

10.2 Secure Random Number Generator (Example 1)

You can find the source code for this example on GitHub.

Example 1 implements secure and non-secure firmware for the Nucleo-144 with the following features:

  • Secure world: There is a function callable from NS, which generates a random number using the hardware random number generator. The random number is returned to NS world.

  • Secure world: There is a function callable from NS, which takes a random number and writes it to UART.

  • Non-secure world: main() generates a random number and writes it to UART. The user can decide upon pressing a physical button whether the random number is generated using the secure world function (not pressed) or by trying to access the hardware random generator from non-secure world directly. (pressed). Writing to UART is always done using the secure world.

  • Illegal accesse to the RNG is visualized by blinking the red LED. The system also prints a security voilation message on UART and then resets.

The system security settings are:

  • The random number generator security state is secure.
  • The UART (LPUART) security state is is secure.
  • The Button (PC13) security state is non-secure.

The idea is, that we as an user can trigger a security violation by pressing the button: When the user presses the button, main() in non-secure world will try to generate the random number directly and thus will trigger a security violation, because the RNG is mapped to secure world only.

After running the firmware on the Nucleo and connecting to the board over UART, you are going to see random numbers appearing on the screen. Then, if you press the blue button, security violation will occour and the system will reset:

Example 1 running. The last few messages show a security violation, when the button was pressed and the system resetted itself.

Figure 10.1: Example 1 running. The last few messages show a security violation, when the button was pressed and the system resetted itself.


STM32 uses a ported version of CMSIS for the projects created in STM32CubeProgrammer. To get an overview on CMSIS in general, follow the links in the NextLevel box below.

More on CMSIS:

  • Chapter 6.3: CMSIS: ROM segment and Boot Process
  • Chapter 8.2: SAU Initialization

10.2.1 Option Bytes Configuration

As describes in 9.3 in detail, option bytes are used to do reset-enduring system configuration. These option bytes are automatically read back by the system on PoR.

The following option bytes settings are used in the example implemented in this chapter. For other projects you might want to adjust the settings.

To enlarge the following picures, open them by right-click and then “open in New Tab” *


Read-out protection. You can find technical details on the Read-out protections (RDP) in chapter 9.2.

For this example, RDP level is set to AA, which is no protection: Level 0.

More on Lifecycle Configurations:

  • Chapter 9.2.1: STM32 Product Life Cycle

STM32CubeProg: RDP Settings

TZEN, SECBOOTADD0, NSBOOTADD0. TrustZone and the Boot addresses are set up in the configuration bytes. Also the watermark protected areas are set up here. Details on watermarked areas are described in chapter 9.5.4.

  • TrustZone is enabled via TZEN=1
  • Hide protected areas are not enabled.

More on Flash Protections:

  • Chapter 5.7: System Design: Security Gates and System Security Controllers
  • Chapter 9.5.4: STM32L5: Flash

STM32CubeProg: User Configuration

STM32CubeProg: User Configuration

STM32CubeProg: User Configuration

STM32CubeProg: User Configuration

10.2.2 GTZC Configuration

In this example project was set up by the STM32CubeMX. Thus GTZC is also set up by the initialization code generated by STM32CubeMX. Follow the links in the NextLevel box for details on how GTZC and its subcomponents are configured using the STM32 HAL.

See 10.3.1.1 in the appendix of this chapter for the STM32CubeMX setup of this project.

More on STM32L5: GTZC and subcomponents:

  • Chapter 9.5.1: TrustZone Security Controller (TZSC)
  • Chapter 9.5.1.1: STM32CubeL5 HAL for TZSC
  • Chapter 9.5.2: STM32L5: TrustZone Illegal Access Controller (TZIC)
  • Chapter 9.5.2.1: STM32CubeL5 HAL for TZIC
  • Chapter 9.5.3: STM32L5: SRAM

In this example STM32CubeMX generated the following GTZC initialization code MX_GTZC_Init() according to chapter 10.3.1.1 in the appendix. As we can see

  • the peripherals RNG and LPUART1 are mapped to secure world using HAL_GTZC_TZSC_ConfigPeriphAttributes()
  • the block-based memory protection controller is set up (mapping SRAM to secure / non-secure worlds) using the HAL function HAL_GTZC_MPCBB_ConfigMem().
  • TZIC interrupts for RNG and Flash security events are enabled using the HAL function HAL_GTZC_TZIC_EnableIT().

You can find a explanation on the HAL API in the respective chapters. See the links in the box above.

MX_GTZC_Init(void) on GitHub.

static void MX_GTZC_Init(void)
{
  MPCBB_ConfigTypeDef MPCBB1_NonSecureArea_Desc = {0};
  MPCBB_ConfigTypeDef MPCBB2_NonSecureArea_Desc = {0};

  if (HAL_GTZC_TZSC_ConfigPeriphAttributes(GTZC_PERIPH_LPUART1, GTZC_TZSC_PERIPH_SEC|GTZC_TZSC_PERIPH_NPRIV) != HAL_OK)
  {
    Error_Handler();
  }
 
  if (HAL_GTZC_TZSC_ConfigPeriphAttributes(GTZC_PERIPH_VREFBUF, GTZC_TZSC_PERIPH_SEC|GTZC_TZSC_PERIPH_NPRIV) != HAL_OK)
  {
    Error_Handler();
  }
 
  if (HAL_GTZC_TZSC_ConfigPeriphAttributes(GTZC_PERIPH_RNG, GTZC_TZSC_PERIPH_SEC|GTZC_TZSC_PERIPH_NPRIV) != HAL_OK)
  {
    Error_Handler();
  }
 
  MPCBB1_NonSecureArea_Desc.SecureRWIllegalMode = GTZC_MPCBB_SRWILADIS_ENABLE;
  MPCBB1_NonSecureArea_Desc.InvertSecureState = GTZC_MPCBB_INVSECSTATE_NOT_INVERTED;
  [...]
  MPCBB1_NonSecureArea_Desc.AttributeConfig.MPCBB_LockConfig_array[0] =   0x00000000;
 
  if (HAL_GTZC_MPCBB_ConfigMem(SRAM1_BASE, &MPCBB1_NonSecureArea_Desc) != HAL_OK)
  {
    Error_Handler();
  }
 
  MPCBB2_NonSecureArea_Desc.SecureRWIllegalMode = GTZC_MPCBB_SRWILADIS_ENABLE;
  [...]
  MPCBB2_NonSecureArea_Desc.AttributeConfig.MPCBB_LockConfig_array[0] =   0x00000000;
  if (HAL_GTZC_MPCBB_ConfigMem(SRAM2_BASE, &MPCBB2_NonSecureArea_Desc) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_GTZC_TZIC_EnableIT(GTZC_PERIPH_RNG) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_GTZC_TZIC_EnableIT(GTZC_PERIPH_FLASH) != HAL_OK)
  {
    Error_Handler();
  }

}

10.2.3 Peripheral Configuration

STM32CubeMX also does automatically add code for settings up LPUART, RNG and the GPIOs für the LEDs. Overall the following functions will be generated automatically:

main.c, main.h

static void MX_GPIO_Init(void);
static void MX_GTZC_Init(void);
static void MX_RTC_Init(void);
static void MX_LPUART1_UART_Init(void);
static void MX_RNG_Init(void);

Additionally file scope handles to the peripherals will be added to your main.c, which you can use to access the peripherals:

UART_HandleTypeDef hlpuart1;
RNG_HandleTypeDef hrng;

10.2.4 Secure World Implementation

Check chapter 10.2 for a list of features we want to implement.

Please note: This code is not production ready and only for demonstration purposes. Don’t copy and paste code from the internet :)

Two secure functions were implemented and declared as entry functions. The first one is to generate and return a random number in secure world (CMSE_NS_ENTRY uint32_t SECURE_GenerateRandom()) and return the number to the non-secure world. The handle (hrng) was configured and set up by the STM32CubeMX code generator.

The second one secure world entry function takes a random number and transmits it over the LPUART interface. (CMSE_NS_ENTRY void SECURE_TransmitRandomUart(uint32_t random32bit))

In the previous chapters on the basic topics we already discussed how secure functions are implemented and how they return values. Please check the Next Level box above for details.

More on Secure function calls:

  • Chapter 6.4: CMSIS: Non-Secure Callable segment
  • Chapter 7.2: AAPCS: Subroutine Call
  • Chapter 5.4.1: Banked Registers
  • Chapter 8.3: Details: Secure function call
  • Chapter 5.6.1: Overview: Secure function call

main.c:

CMSE_NS_ENTRY uint32_t SECURE_GenerateRandom(){
    uint32_t random32bit;
    HAL_StatusTypeDef status = HAL_ERROR;
    status = HAL_RNG_GenerateRandomNumber(&hrng, &random32bit);
    while (status == HAL_ERROR){}
    return random32bit;
}

CMSE_NS_ENTRY void SECURE_TransmitRandomUart(uint32_t random32bit){
    char buff[50];
    sprintf(buff, "Random: %lu\r\n", random32bit);
     HAL_StatusTypeDef status = HAL_UART_Transmit(&hlpuart1, (uint8_t*) buff, strlen(buff), 1000);
     if (status != HAL_OK){
         Error_Handler();
     }
}

To notify the user about illegal accesses to the RNG by blinking the LED and resetting the system, we need the STM32 GTZC HAL callback function named HAL_GTZC_TZIC_Callback. The GTZC HAL implements an interrupt handler for the GTZC_IRQ, which in turn calls the callback when the interrupt is triggered. The peripheral which caused the illegal access is passed to the callback as parameter periph.

main.c:

void HAL_GTZC_TZIC_Callback(uint32_t periph){
        if(periph == GTZC_PERIPH_RNG){
            HAL_GPIO_WritePin(LED_RED_GPIO_Port, LED_RED_Pin, 1);
            TransmitCharPUart("Security Violation in RNG\r\nReseting System now.\r\n");
            HAL_NVIC_SystemReset();
        }
}

void TransmitCharPUart(char* text){
     HAL_StatusTypeDef status = HAL_UART_Transmit(&hlpuart1, (uint8_t*) text, strlen(text), 1000);
     if (status != HAL_OK){
         Error_Handler();
     }
}

10.2.5 Non-Secure World Implementation

Check chapter 10.2 for a list of features we want to implement.

The function NONSECURE_GenerateRandom() accesses the RNG directly and not by calling the secure world function implemented above. (see chapter 10.2.5.1)

In main() we generate the random number depended on the state (pressed / not pressed) of the button on the Nucleo-144. When the button is pressed non-secure world firmware tries to generate a random number directly (by calling NONSECURE_GenerateRandom). In non-pressed state SECURE_GenerateRandom() is used. In the first case an illegal access event is raised and the GTZC_IRQn is issued from TZIC. See chapter 10.2.2.

main.c:

uint32_t NONSECURE_GenerateRandom(){
    MX_RNG_Init();
    uint32_t random32bit;
    HAL_StatusTypeDef status = HAL_ERROR;
    status = HAL_RNG_GenerateRandomNumber(&hrng, &random32bit);
    while (status == HAL_ERROR){}
    return random32bit;
 }

int main(void)
{
  HAL_Init();

  MX_GPIO_Init();
  MX_ADC1_Init();
  MX_RTC_Init();

  while (1)
  {
      uint32_t random32bit;
      GPIO_PinState button = HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13);
      if(button)
          random32bit = NONSECURE_GenerateRandom();
      else
          random32bit = SECURE_GenerateRandom();
      HAL_Delay(1000);
      SECURE_TransmitRandomUart(random32bit);
  }
}

10.2.5.1 RNG in Non-Secure world

To raise a illegal access from non-secure world by accessing the RNG without having it mapped to non-secure world, we copy the RNG initialization code from secure world (especially MX_RNG_Init) into the non-secure world project, but don’t update the security configuration of the RNG.

To have all needed definitions available in main.c, we first need to uncomment

#define HAL_RNG_MODULE_ENABLED

in stm32l5xx_hal_conf.h (on Github).

Then we copy the following code into main.c:

RNG_HandleTypeDef hrng;

static void MX_RNG_Init(void)
{

  hrng.Instance = RNG;
  hrng.Init.ClockErrorDetection = RNG_CED_ENABLE;
  if (HAL_RNG_Init(&hrng) != HAL_OK)
  {
    Error_Handler();
  }
 
}

10.3 Appendix

10.3.1 Example 1

10.3.1.1 Initial Project Configuration

This chapter is for reference. The example on GitHub contains a ioc file, which has the following configuration already applied. If you want to create your own ioc for the project, follow through the step of this chapter.

Example Project Configuration: Project Setup

Figure 10.2: Example Project Configuration: Project Setup

Example Project Configuration: Project Setup

Figure 10.3: Example Project Configuration: Project Setup

Example Project Configuration: Project Setup

Figure 10.4: Example Project Configuration: Project Setup

  • Example Project Configuration: Project Setup

    Figure 10.5: Example Project Configuration: Project Setup

10.3.1.1.1 GTZC Configuration
Example Project Configuration: GTZC Configuration

Figure 10.6: Example Project Configuration: GTZC Configuration

Example Project Configuration: GTZC Configuration

Figure 10.7: Example Project Configuration: GTZC Configuration

Example Project Configuration: GTZC Configuration

Figure 10.8: Example Project Configuration: GTZC Configuration

10.3.1.1.2 GPIO and Peripheral Configuration
Example Project Configuration: GPIO and Peripheral Configuration

Figure 10.9: Example Project Configuration: GPIO and Peripheral Configuration

Example Project Configuration: GPIO and Peripheral Configuration

Figure 10.10: Example Project Configuration: GPIO and Peripheral Configuration

Example Project Configuration: GPIO and Peripheral Configuration

Figure 10.11: Example Project Configuration: GPIO and Peripheral Configuration

Example Project Configuration: GPIO and Peripheral Configuration

Figure 10.12: Example Project Configuration: GPIO and Peripheral Configuration

Example Project Configuration: GPIO and Peripheral Configuration

Figure 10.13: Example Project Configuration: GPIO and Peripheral Configuration

Example Project Configuration: GPIO and Peripheral Configuration

Figure 10.14: Example Project Configuration: GPIO and Peripheral Configuration

10.3.1.1.3 Final Project Configuration
Example Project Configuration: Final Project Configuration

Figure 10.15: Example Project Configuration: Final Project Configuration

Example Project Configuration: Final Project Configuration

Figure 10.16: Example Project Configuration: Final Project Configuration

References

[9] ST Microelectronics, “STM32 Software Development Tools.” 2020, [Online]. Available: https://www.st.com/en/development-tools/stm32-software-development-tools.html.