One of the samples available in the nRF Connect SDK is the Secure Partition Manager, which is required to run applications in the nonsecure domain of the CPU. The sample uses the SPM library available in NCS.
This post shows you how you can add your own custom SPM to your application, instead of modifying the one already in NCS. It will also show how to to write your own secure functions that can be called from the nonsecure domain.
The template project, written by Sebastian Bøe, is attached at the bottom of this tutorial. You will need to be on the 1.2 tag or the master branch of NCS.
First off, what are the secure and nonsecure domains and why do we have them?
The Arm Cortex-M33 inside the nRF9160 comes with Arm’s TrustZone technology, which reduces the risk of an attack by isolating the critical security firmware from the rest of the application. In practice, this means it’s possible to create two environments inside the CPU that can run isolated from each other: a secure and a nonsecure environment. An application running as secure will only have access to the resources configured as secure, and same with nonsecure, which is what makes it resilient to attacks.
To run a nonsecure application, one first needs a secure application to configure permissions and resources as nonsecure before boot-up. This is what the Secure Partition Manager sample does. For how to actually do this, we need to get into something called multi-image builds.
Multi-image builds are when the firmware that is programmed consists of not only one application, but several separate images, where one of the images (the parent image) requires one or more other images (the child images) to be present. In NCS, all nonsecure samples require the secure partition manager as a child image to be able to access the CPU resources that are required by the application.
The SPM library provides default secure services to the nonsecure application, which are functions implemented in the secure firmware, but that can be called from the nonsecure firmware. These functions can be found in ncs/nrf/include/secure_services.h.
Since the nonsecure image is the parent image, you will have to build the application with the nonsecure board (nrf9160_pca10090ns).
This is what the project should look like. Note that this project folder doesn't necessarily have to be in the NCS path.
| |---non_secure_app |---secure_module |---zephyr |---CMakeLists.txt |---module.yml |---spm
Copy the SPM sample from NCS and place it in project/secure_module/zephyr/ as you can see in the diagram above. The reason for having the path spm_module/zephyr/spm is a convention for zephyr modules.
In the same location as the spm folder, there also needs to be a CMakeLists.txt file and a module.yml file. The CMakeLists.txt file will make sure that if the correct configuration is enabled (CONFIG_SPM), the spm sample is added as a child image like this:
The module.yml file needs to be present in all zephyr modules and looks like this:
Now that you’ve got those two files in place, you can define your secure function in the main.c file of the SPM sample like this:
You can now call this function in your nonsecure application like this. Make sure you declare the function first:
printk("Hello World! %s\n", CONFIG_BOARD);
In the prj.conf file in the nonsecure application you need to enable the following configs:
CONFIG_SPM to ensure that the secure application is included as a child image, see the CMakeLists.txt file in the secure application. CONFIG_SPM_BUILD_STRATEGY_SKIP_BUILD to make sure that the application does not build the SPM in NCS but rather the SPM you have in your application.
The CMakeLists.txt file in the non_secure application needs to have the following code, to be placed over the line including boilerplate.cmake:
This will add the spm_module in this project as a module in Zephyr, see here.