This document describes the SPMC (S-EL1) implementation for OP-TEE. More information on the SPMC can be found in the FF-A specification can be found in the FF-A spec.
The SPMC is a critical component in the FF-A flow. Some of its major responsibilities are:
- Initialisation and run-time management of the SPs:
The SPMC component is responsible for initialisation of the Secure Partitions (loading the image, setting up the stack, heap, …).
- Routing messages between endpoints:
The SPMC is responsible for passing FF-A messages from normal world to SPs and back. It also responsible for passing FF-A messages between SPs.
- Memory management:
The SPMC is responsible for the memory management of the SPs. Memory can be shared between SPs and between a SP to the normal world.
This document describes OP-TEE as a S-EL1 SPMC.
Secure Partitions (SPs) are the endpoints used in the FF-A protocol. When OP-TEE is used as a SPMC SPs run primarily inside S-EL0.
OP-TEE will use FF-A for it transport layer when the OP-TEE
configuration flag is enabled.
The SPMC will expose the OP-TEE core, privileged mode, as an secure endpoint
itself. This is used to handle all GlobalPlaform programming mode operations.
All GlobalPlatform messages are encapsulated inside FF-A messages.
The OP-TEE endpoint will unpack the messages and afterwards handle them as
standard OP-TEE calls. This is needed as TF-A (S-EL3) does only allow
FF-A messages to be passed to the secure world when the SPMD is enabled.
SPs run from the initial boot of the system until power down and don’t have any built-in session management compared to GPD TEE TAs. The only means of communicating with the outside world is through messages defined in the FF-A specification. The context of a SP is saved between executions.
The Trusted Service repository includes the libsp libary which export all needed functions to build a S-EL0 SP. It also includes many examples of how to create and implement a SP.
SPMC Program Flow¶
SP images are either embedded into the OP-TEE image or loaded from the FIP by
BL2. This makes it possible to start SPs during boot, before the rich OS is
available in the normal world.
ldelf is used to load and initialise the SPs.
SPs are loaded and started as the last step in OP-TEE’s initialisation process.
This is done by adding
sp_init_all() to the
boot_final initcall level.
Initialise all SPs which have been added by the
SP_PATHScompiler option and run them
All SPs are loaded and started. A
FFA_MSG_WAITmessage is sent to the Normal World.
Each SP is loaded into the system using
ldelf and started. This is based
around the same process as loading the early TAs.
All SPs are run after they are loaded and run until a
FFA_MSG_WAIT is sent
by the SP.
Load the SP
struct ffa_init_info. The
struct ffa_init_infois passed to the SP during it first run.
Initialise the registers of the SP
Handle the SPs FF-A message
Once all SPs are loaded and started we return to the SPMD and the Normal World is booted.
SP message handling¶
The SPMC is split into 2 main message handlers:
Used to handle message coming from the Normal World.
Used to handle message where the source or the destination is a SP.
FFA_MSG_SEND_DIRECT_REQ message is received by the SPMC from the
Normal World, a new thread is started.
The FF-A message is passed to the thread and it will call the
Whenever the SPMC (
sp_msg_handler()) receives a message not intended for
one of the SPs, it will exit the thread and return to the Normal World
passing the FF-A message.
Currently only a
FFA_MSG_SEND_DIRECT_REQ can be passed from the Normal
World to a SP.
Every message received by the SPMC from the Normal World is handled in the
When entering a SP we need to be running in a OP-TEE thread. This is needed to
be able to push the TS session (We push the TS session to get access to the SP
Currently the only possibility to enter a SP from the Normal world is via a
FFA_MSG_SEND_DIRECT_REQ. Whenever we receive a
message which doesn’t have OP-TEE as the endpoint-id, we start a thread and
forward the FF-A message to the
sp_msg_handler() is responsible for all messages coming or going
to/from a SP. It runs in a while loop and will handle every message until it
comes across a messages which is not intended for the secure world.
After a message is handled by the SPMC or when it needs to be forwarded to a SP,
sp_enter() is called.
sp_enter() will copy the FF-A arguments and resume the SP.
When the SPMC needs to have access to the SPs memory, it will call
ts_push_current_session() to gain access and
to release the access.
Running and exiting SPs¶
The SPMC resumes/starts the SP by calling the
sp_enter(). This will set up
the SP context and jump into S-EL0.
Whenever the SP performs a system call it will end up in
sp_handle_svc() stores the current context of the SP and makes sure that we
don’t return to S-EL0 but instead returns to S-EL1 back to
sp_enter() will pass the FF-A registers (x0-x7) to
spmc_sp_msg_handler(). This will process the FF-A message.
RxTx buffer managment¶
RxTx buffers are used by the SPMC to exchange information between an endpoint
and the SPMC. The rxtx_buf struct is used by the SPMC for abstracting buffer
Every SP has a
struct rxtx_buf wich will be passed to every function that
needs access to the rxtx buffer.
struct rxtx_buf is defined for the Normal World, which gives
access to the Normal World buffers.
SPMC config options¶
To configure OP-TEE as a S-EL1 SPMC with Secure Partition support, the following flags should be set for optee_os:
Furthermore TF-A should be configured as the SPMD, expecting a S-EL1 SPMC:
ARM_SPMC_MANIFEST_DTS=<path to SPMC manifest dts>
SP loading mechanism¶
OP-TEE SPMC supports two methods for finding and loading the SP executable images. Currently only ELF executables are supported. In the build repo the loading method can be selected with the SP_PACKAGING_METHOD option.
In this case the early TA mechanism of optee_os is reused: the SP ELF files are
embedded into the main OP-TEE binary. Each ELF should start with a specific
section (.sp_head) containing a struct which describes the SP (UUID, stack size,
etc.). The images can be added to optee_os using the
SP_PATHS config option,
the build repo will set this up automatically when
SP_PACKAGING_METHOD=embedded is selected. The images passed in
are processed by
ts_bin_to_c.py in optee_os and linked into the main binary.
At runtime the
for_each_secure_partition() macro can iterate through these
images, so a particular SP can be found by UUID and then loaded by ldelf.
The SP manifest file  used by the SPMC to setup SPs is also handled by
ts_bin_to_c.py, it will be concatenated to the end of the SP ELF.
In this case the SP ELF files and the corresponding SP manifest DTs are
encapsulated into SP packages and packed into the FIP. The goal of providing
this alternative flow is to make updating SPs easier (independent of the main
OP-TEE binary) and to get aligned with Hafnium (S-EL2 SPMC). For more
information about the FIP, please refer to the TF-A documentation . The SP
packaging process and the package format is provided by TF-A, detailed
description is available at . In the build repo this method can be
SP_PACKAGING_METHOD=fip, it covers all the necessary setup
automatically. In case of using another buildsystem, the following steps should
SP_LAYOUT_FILE: provide a JSON file which describes the SPs (path to SP executable and corresponding DT, example ). The TF-A buildsystem will create the SP packages (using sptool) based on this and pack them into the FIP.
ARM_BL2_SP_LIST_DTS: provide a DT snippet which describes the SPs’ UUIDs and load addresses (example: ). This will be injected into the SP list in
TB_FW_CONFIGDT of TF-A, and BL2 will load the SP packages based on this. Note that BL2 doesn’t automatically load all images from the FIP: it’s necessary to explicitly define them in
TB_FW_CONFIG(using this injected snippet or manually editing the DT).
ARM_SPMC_MANIFEST_DTS: provide the SPMC manifest (example: ). This DT is passed to the SPMC as a boot argument (in the TF-A naming convention this is the
TOS_FW_CONFIG). It should contain the list of SP packages and their load addresses in the
compatible = "arm,sp_pkg"node.
At boot optee_os will parse the SP package load addresses from the SPMC manifest
and find the SP packages already loaded by BL2. Iterating through the SP
packages, based on the SP package header in each package it will map the SP
executable image and the corresponding manifest DT and collect these to the
fip_sp_list list. Later when initialising the SPs, the
macro is used to iterate this list and load the executables using ldelf, just
like for the embedded SP case.