1. Article purpose[edit source]
This article explains how to protect the software loaded by the main processor into the coprocessor and ensure the authentication of the loaded firmware.
![]() |
The protection of the coprocessor firmware by authentication is enabled by default on the STM32MP25x lines |
2. Introduction[edit source]
The Linux® OS, (through the remoteproc framework) or the U-Boot enables the loading of firmware into control remote processors. Thanks to a specific OP-TEE trusted application (TA) and the resource isolation framework running on the Arm® TrustZone, it is possible to authenticate a Cortex®-M firmware image and install them on isolated RAMs to ensure its integrity during the execution.
From the user's point of view the management of a nonauthenticated or authenticated firmware does not differ, whether the firmware is managed by the Linux or by the U-Boot. The difference is that the management of the authenticated firmware is delegated to the OP-TEE firmware running in the Arm® TrustZone.
3. Principles[edit source]
The protection of the coprocessor firmware relies on hash computation and on the encryption key to authenticate a firmware, as does the isolation of the execution memories to ensure execution integrity.
The firmware is signed using an OP-TEE signature script. This script concatenates one or several ELF binaries, then adds a header and TLV (type length value) metadata that provide elements for the authentication among other firmware information:
- A hash table, which contains the hash of the segment to load.
- A signature computed with the private key.
- During the execution phase:
- The signed firmware binary is read from the file system by the remoteproc framework and the image is provided to the OP-TEE trusted application.
- The firmware image is authenticated and installed by the OP-TEE trusted application in the protected Cortex®-M memories.
3.1. Firmware signature[edit source]
A common strategy to sign a firmware consists in adding a header that contains the signature required for authentication. The signature is generated by computing a hash on the whole binary including the header, and encrypting this hash using a private key.
On the STM32MP microprocessors, the signature procedure is similar except that it is optimized to minimize the use of memory and speed up the authentication process. The principle is to compute the hash of the segments to load, instead of computing the hash of the full image.
![Signature procedure](/stm32mpu/nsfr_img_auth.php/thumb/4/4d/Rproc_fw_signature.png/900px-Rproc_fw_signature.png)
- The signature consists in:
- Parsing the program table in the ELF file to identify the segments to load.
- Computing the hash of each segment and saving the result in the hash table chunk of the header.
- Computing the hash of the header and signing the hash with the private key.
- The public key is embedded in the OP-TEE OS firmware that is in charge of the Cortex®-M firmware authentication.
3.2. Memory management[edit source]
3.2.1. STM32MP15x lines
[edit source]
The MCU SRAM and the RETRAM memories can be dedicated to Cortex®-M usage (isolated), shared with the Cortex®-A nonsecure context, or protected for Cortex®-A secure access only.
The ETZPC mechanism is used:
- During the load and authentication phase: to lock the memories for Cortex®-A secure access only.
- During the execution phase: to isolate the memories for coprocessor usage (such as code execution and data) or share them with the Cortex®-A nonsecure context (such as RPMsg shared buffers).
- On coprocessor stop: to lock the memories for Cortex®-A secure access only to clean up the memories.
![]() |
When creating a Cortex®-M firmware build, developers must be careful to pack in a same memory bank all the data shared with the Cortex®-A (for example, resource table and trace buffer). This enables the isolation of the rest of the memory banks for the Cortex®-M context. |
The information provided in this article respects the memory mapping described in memory mapping section:
- RETRAM, MCU SRAM1 and MCU SRAM2 are used for code execution and data. They are only accessible by the Cortex®-A secure context when no firmware is running, and assigned to Cortex®-M context during authenticated firmware execution.
- MCU SRAM3 (IPC buffers) is shared between the Cortex®-A nonsecure context and the Cortex®-M. No protection is applied to this memory bank.
- MCU SRAM4 is reserved for the Cortex®-A nonsecure context. It is thus free for other usage.
This memory mapping can be customized depending on project requirements. In this case, the code has to be updated in a synchronized manner in STM32CubeMP15 firmware, Linux kernel device tree and the OP-TEE rproc PTA code.
3.2.2. STM32MP25x lines
[edit source]
The DDR, SRAM and/or RETRAM memory regions can be dedicated to Cortex®-M usage (isolated), shared with the Cortex®-A nonsecure context, or protected for Cortex®-A secure access only.
The RIF is used:
- During the load and authentication phase: To lock the memories for Cortex®-A secure access only.
- During the execution phase: To apply the access right defined in the OP-TEE device-tree to isolate the memories for the coprocessor usage.
- On coprocessor stop: to lock the memories for Cortex®-A secure access only to clean up the memories.
![]() |
When creating a Cortex®-M firmware build, developers must be careful to pack in a same memory bank all the data shared with the Cortex®-A (e.g resource table and trace buffer). This enables the isolation of the rest of the memory banks for the Cortex®-M context. |
The information provided in this article respects the memory mapping described in memory mapping section:
- Some DDR memory regions and the MCU SRAM2 are prereserved for code execution and data. They are only accessible by the Cortex®-M secure or nonsecure contexts thanks to the RIF CID filtering.
- A dedicated DDR memory region is shared between the Cortex®-A nonsecure context and the Cortex®-M. No protection is applied to this memory bank.
This memory mapping can be customized depending on project requirements. In this case, the code has to be updated in a synchronized manner in the STM32CubeMP2, the TF-M firmware, the Linux kernel device tree and the OP-TEE device tree code.
3.3. Authentication[edit source]
The authentication is executed in the secure context by an OP-TEE trusted application.
1. The firmware is copied by the Linux kernel (or U-Boot) to nonsecure DDR memory.
2. The firmware header is copied to a secure memory to ensure its integrity during the authentication steps.
3. The header hash is computed.
4. The signature is decrypted using the public key. The result is the hash of the header computed by the signing tools. Both hashes are compared to authenticate the firmware header.
5. At this step the firmware header is valid. The next step, the loading of the firmware in coprocessor memories, can start:
For each segment to load:
6. The segment is copied to the RAMs that can be accessed only by the secure context to ensure their integrity.
7. The hash of the copied segment is computed and compared to the hash stored in the firmware header.
8. At this step all segments have been copied and authenticated. The firmware is loaded and ready for execution.
3.4. Authenticated firmware live-cycle[edit source]
From the user's point of view, the management of a firmware live-cycle does not change, whether the firmware is managed by OP-TEE or not. The management of firmware is described in the remoteproc article.
The main difference compared to a nonauthenticated firmware is that the remote processor framework delegates the coprocessor management to the OP-TEE.
![]() |
Since the authentication is activated, it is no longer possible to load and start a nonauthenticated firmware. |
![]() |
The authenticated firmware can also be preloaded and started by the U-Boot. |
3.4.1. Firmware loading and authentication[edit source]
- As for a nonauthenticated firmware, the firmware is read from the file system by the remoteproc framework. The image is then sent to the OP-TEE trusted application.
- The OP-TEE remoteproc trusted application manages authentication and memory isolation in the secure context.
3.4.2. Firmware start and stop[edit source]
- As for a nonauthenticated firmware, the firmware start and stop requests are handled by the remoteproc framework, but they are delegated to the Arm® TrustZone.
- The firmware start and stop are executed by the OP-TEE. The reset and RAM access rights are only accessible by the secure context.
- During the firmware loading step, the Cortex®-M memories access rights are updated to provide access to the Cortex®-A secure context. Once the firmware is loaded, the default memory access right defined in the OP-TEE device tree are reapplied.
- After stopping the firmware, the Cortex®-M memories are cleaned by OP-TEE.
3.4.3. RPMsg management[edit source]
As the resource tables and RPMsg vrings and buffers are in a shared memory and accessible by both Cortex, it is possible to communicate with the authenticated firmware.
3.4.4. Firmware core dump[edit source]
Since the memories are protected, it is not possible to dump the firmware when a crash occurs.
4. Implementation[edit source]
The authentication mechanisms require STM32MPU Embedded Software distribution with OP-TEE running in the Arm® TrustZone context.
4.1. Components configuration[edit source]
4.1.1. OP-TEE configuration[edit source]
The OP-TEE OS firmware has to embed:
- The remote processor trusted application (TA).
- The remote processor platform pseudo trusted application (PTA).
- The stm32mp remoteproc driver.
- The public key to authenticate the firmware.
The CFG_DRIVERS_REMOTEPROC and CFG_STM32MP_REMOTEPROC configurations enable the OP-TEE remoteproc driver. The CFG_REMOTEPROC_PTA configuration enables the OP-TEE remoteproc framework.
The RPROC_SIGN_KEY= <key file path name> directive can be added to specify the public key file to use to authenticate the remote processor firmware.This directive is optional. If it is not defined, the keys/default.pem default key is used.
Example of build command specifying the public key (where <other directives> has to be replaced by usual directives to build OP-TEE OS):
make <other directives> RPROC_SIGN_KEY=my_public_key.pem
See here for how to information.
![]() |
The public key is then integrated in the OP-TEE OS binary for the authentication. |
![]() |
For STM32MP15x lines
Refer to OP-TEE OS configuration |
4.1.2. Linux kernel configuration[edit source]
Activate the TEE_REMOTEPROC in the kernel configuration, using the Linux menuconfig tool: Menuconfig or how to configure kernel.
Device drivers --->
Remoteproc drivers --->
<*> Support for Remote Processor subsystem
<*> STM32 remoteproc support
<*> Trusted firmware support by a trusted application
4.1.3. U-Boot Configuration[edit source]
Activate the REMOTEPROC_OPTEE in the U-Boot configuration, using the menuconfig tool.
Remote Processor drivers --->
[*] Support for the remoteproc in OPTEE
[*] Support for STM32 coprocessor
4.2. Device tree configuration[edit source]
4.2.1. stm32-rproc compatible property[edit source]
As described in Documentation/devicetree/bindings/remoteproc/stm32-rproc.yaml a specific compatible is used to select the management of the remoteproc firmware enabling the signature:
- Use "st,stm32mp1-m4" for the STM32MP15x lines
Cortex®-M4 coprocessor management by Linux (support of ELF format)
- Use "st,stm32mp1-m4-tee" for the STM32MP15x lines
Cortex®-M4 coprocessor management by OPTEE(support of signed format)
- Use "st,stm32mp2-m33" for the STM32MP25x lines
Cortex®-M33 coprocessor management by Linux (support of ELF format)
- Use "st,stm32mp2-m33-tee" for the STM32MP25x lines
Cortex®-M33 coprocessor management by OPTEE (support of signed format)
4.2.2. OP-TEE device tree[edit source]
The stm32mp_remoteproc driver is in charge of configuring the memories based on the memory region properties declared in the OP-TEE device tree.
- Memory regions declarations:
- The memory regions are defined in the Device tree. As example on STM32MP25x lines
, the STM32CubeMP2 firmware and TF-M firmware memory regions in the DDR are declared in the stm32mp257f-ev1-ca35tdcid-resmem.dtsi file.
reserved-memory { ... tfm_code: tfm-code@80000000 { compatible = "shared-dma-pool"; reg = <0x0 0x80000000 0x0 0x100000>; no-map; }; cm33_cube_fw: cm33-cube-fw@80100000 { compatible = "shared-dma-pool"; reg = <0x0 0x80100000 0x0 0x800000>; no-map; }; tfm_data: tfm-data@80900000 { compatible = "shared-dma-pool"; reg = <0x0 0x80900000 0x0 0x100000>; no-map; }; cm33_cube_data: cm33-cube-data@80a00000 { compatible = "shared-dma-pool"; reg = <0x0 0x80a00000 0x0 0x800000>; no-map; }; };
- Memory regions access right
- A firewall configuration must be associated to these memory regions to:
- Isolate the memory regions that embedded the Cortex®-M firmware code and data.
- Share the memory regions used for the interprocessor communication.
- The firewall configuration is applied thanks to:
- Memory region references:
- During the firmware loading process, the OP-TEE remoteproc verifies whether the address and size of the segments to be loaded match with the memory regions declared in the OP-TEE device tree. The memory regions that contain the Cortex®-M firmware must be referenced in the device tree remoteproc node.
&m33_rproc {
memory-region = <&cm33_cube_fw>, <&cm33_cube_data>,
<&ipc_shmem>, <&tfm_code>, <&tfm_data>;
};
Refer to the OP-TEE remoteproc device tree configuration for more details.
4.2.3. Linux kernel device tree[edit source]
The firmware images are loaded by the OP-TEE remoteproc framework. The Linux device only needs to reference memory regions shared with the Cortex®-M for the interprocessor communication, including:
- The resource table.
- The log buffer shared through the resource table.
- The shared buffers for RPMsg communication.
- Other shared buffers defined by the developer for its project.
In the following example, the "ipc_shmem_1" memory region contains the resource table and the remoteproc trace buffer. The "vdev0<xxx>" memory regions reference the shared memories for the RPMsg protocol.
- Memory regions declarations:
reserved-memory { ... ipc_shmem_1: ipc-shmem-1@81200000 { compatible = "shared-dma-pool"; reg = <0x0 0x81200000 0x0 0xf8000>; no-map; }; vdev0vring0: vdev0vring0@812f8000 { compatible = "shared-dma-pool"; reg = <0x0 0x812f8000 0x0 0x1000>; no-map; }; vdev0vring1: vdev0vring1@812f9000 { compatible = "shared-dma-pool"; reg = <0x0 0x812f9000 0x0 0x1000>; no-map; }; vdev0buffer: vdev0buffer@812fa000 { compatible = "shared-dma-pool"; reg = <0x0 0x812fa000 0x0 0x6000>; no-map; }; };
Refer to the Linux kernel remoteproc device tree configuration for more details.
4.2.4. U-boot device tree[edit source]
The U-Boot device tree can be aligned with the Linux kernel device tree, but the reserved memory declarations are optional (No interprocessor communication in U-Boot)). Only the 'compatible' field is important to specify that OP-TEE is responsible for authenticating and loading the firmware.
- Memory region references:
&m33_rproc {
memory-region = <&ipc_shmem_1>, <&vdev0vring0>,
<&vdev0vring1>, <&vdev0buffer>;
};
![]() |
on (STM32MP25x lines ![]() |
4.3. STM32Cube firmware[edit source]
The memory mappings for the STM32Cube firmware are defined in the STM32Cube firmware linker script, which must be aligned with the values defined in the OP-TEE and Linux device trees.
Most of the STM32Cube firmware code and data can be isolated but some memories have to be shared with the Cortex®-A, including:
- The resource table.
- The log buffer shared through the resource table.
- The shared buffers for RPMsg communication.
- Other shared buffers defined by the developer for its project.
The following example shows a linker script for a stm32mp2Cube firmware on STM32MP25x lines , aligned with the device tree example above:
- The memory regions definition:
MEMORY { NS_VECTOR_TBL (xrw) : ORIGIN = 0x80100000, LENGTH = 0x00000600 FLASH (rx) : ORIGIN = 0x80100600, LENGTH = 8M - LENGTH(NS_VECTOR_TBL) RAM (rwx) : ORIGIN = 0x80A00000, LENGTH = 8M VIRTIO_SHMEM (rw) : ORIGIN = 0x812F8000, LENGTH = 32K IPC_SHMEM (rw) : ORIGIN = 0x81200000, LENGTH = 8M - LENGTH(VIRTIO_SHMEM) }
- The resource table and remoteproc trace buffer:
SECTIONS { ... /* resource table */ .resource_table : { . = ALIGN(4); KEEP (*(.resource_table*)) . = ALIGN(4); } >IPC_SHMEM /* remoteproc trace buffer */ .sys_logs : { . = ALIGN(4);}} KEEP (*(.sys_logs*))}} . = ALIGN(4);}} } > IPC_SHMEM}} ... }
- The RPMsg shared buffers:
The VIRTIO_SHMEM memory region is already reserved for the RPMsg virtio rings and buffers defining following symbol in the linker script:
/* Symbols needed for OpenAMP to enable rpmsg */
__OPENAMP_region_start__ = ORIGIN(VIRTIO_SHMEM);
__OPENAMP_region_end__ = ORIGIN(VIRTIO_SHMEM)+LENGTH(VIRTIO_SHMEM);
4.4. Cortex®-M firmware signature[edit source]
4.4.1. Generate private and public keys[edit source]
An RSA key has to be generated to sign and authenticate the Cortex®-M firmware. For instance, the ssh-keygen command can be used.
ssh-keygen
Two files are created:
- id_rsa: the private key to sign the firmware.
- id_rsa.pub: the public key to authenticate the firmware.
4.4.2. Firmware signature[edit source]
The OP-TEE STMicroelectronics distribution integrates the sign_rproc_fw.py, which can concatenate several firmware ELF binary files and sign them.
This script:
- Adds a header on top of the ELF binaries.
- Adds a TLV (Type Length Value) chunk that contains information to authenticate the firmware images.
- Computes the hash of the program segments of each ELF binary that is loaded in the Cortex®-M memories and fill the hash table included in the TLV.
- Computes the hash of the header and the TLV and signs it using the private key.
4.4.2.1. Prerequisites[edit source]
Install Python libraries needed by OP-TEE signature Python script: pip install pyelftools pycryptodomex
4.4.2.2. Sign the firmware[edit source]
- Use the default RSA key available in the OP-TEE repository (that can be updated): keys/default_rproc.pem
./scripts/sign_rproc_fw.py sign --in <firmware 1> --in<firmware 2> --out <signed firmware name>
- Or specify the key to use as a parameter:
./scripts/sign_rproc_fw.py sign --in <firmware 1> --in<firmware 2> --out <signed firmware name> --key <id_rsa private key>
./scripts/sign_rproc_fw.py sign --in <firmware 1> --in<firmware 2> --out <signed firmware name> --plat-tlv BOOTADDR 0x80100000
- Secure boot address is the Cortex®-M TrustZone® is enabled:
./scripts/sign_rproc_fw.py sign --in <firmware 1> --in<firmware 2> --out <signed firmware name> --plat-tlv BOOTADDR 0x80000000 --plat-tlv BOOTSEC 0x1
- For more script options:
./scripts/sign_rproc_fw.py sign --help
5. Code source[edit source]
5.1. OP-TEE[edit source]
ta/remoteproc/remoteproc_core.c core/pta/stm32mp/remoteproc_pta.c core/drivers/remoteproc/stm32_remoteproc.c
5.2. Linux Kernel[edit source]
drivers/remoteproc/tee_remoteproc.c drivers/remoteproc/stm32_rproc.c
5.3. U-Boot[edit source]
drivers/remoteproc/rproc-optee.c
5.4. STM32Cube example[edit source]
Projects/STM32MP157C-DK2/Applications/OpenAMP/OpenAMP_for_signed_fw
Projects/STM32MP257F-EV1/Applications/TFM/TFM_Protected_Storage Projects/STM32MP257F-EV1/Applications/OpenAMP/OpenAMP_TTY_echo