RSE communication design

The RSE communication protocol is designed to be a lightweight serialization of the psa_call() API through a combination of in-band MHU (Message Handling Unit) transport and parameter-passing through shared memory.

Message format

To call an RSE service, the client must send a message in-band over the MHU sender link to RSE and wait for a reply message on the MHU receiver (either by polling the MHU or waiting for interrupt). The messages are defined as packed C structs, which are serialized in byte-order over the MHU links.

Messages encoding a psa_call() to RSE take the form:

__PACKED_STRUCT serialized_psa_msg_t {
    struct serialized_rse_comms_header_t header;
        struct rse_embed_msg_t embed;
        struct rse_pointer_access_msg_t pointer_access;
    } msg;

Replies from RSE take the form:

__PACKED_STRUCT serialized_psa_reply_t {
    struct serialized_rse_comms_header_t header;
        struct rse_embed_reply_t embed;
        struct rse_pointer_access_reply_t pointer_access;
    } reply;

All messages (calls and replies), in all protocols carry the following header:

__PACKED_STRUCT serialized_rse_comms_header_t {
    uint8_t protocol_ver;
    uint8_t seq_num;
    uint16_t client_id;

The client_id can be used by the caller to identify different clients at the endpoint for access control purposes. It is combined with an RSE-internal identifier for the endpoint to create the PSA Client ID for the call.

The sequence number, seq_num, is returned in the reply message to allow the client to identify which message is being responded to, since replies may be out-of-order. The sender is free to assign sequence numbers using a scheme of its choice, and it is the sender’s responsibility to ensure that two messages with the same sequence number are not in simultaneous flight.

The protocol_ver identifies which message protocol is being used. There are two protocols currently supported, “embed” (protocol_ver=0) and “pointer access” (protocol_ver=1).

Embed protocol

The embed protocol embeds the psa_call iovecs into the message sent over the MHU. It has the following format:

__PACKED_STRUCT rse_embed_msg_t {
    psa_handle_t handle;
    uint32_t ctrl_param;
    uint16_t io_size[PSA_MAX_IOVEC];
    uint8_t payload[RSE_COMMS_PAYLOAD_MAX_SIZE];

The handle is the psa_call handle parameter and the ctrl_param packs the type, in_len and out_len parameters of psa_call into one parameter.

The io_size array contains the sizes of the up to PSA_MAX_IOVEC (4) iovecs, in order, with the invec sizes before the outvec sizes.

The payload array then contains the invec data packed contiguously in order. The length of this parameter is variable, equal to the sum of the invec lengths in io_size. The caller does not need to pad the payload to the maximum size. The maximum payload size for this protocol, RSE_COMMS_PAYLOAD_MAX_SIZE, is a build-time option.

Replies in the embed protocol take the form:

__PACKED_STRUCT rse_embed_reply_t {
    int32_t return_val;
    uint16_t out_size[PSA_MAX_IOVEC];
    uint8_t payload[RSE_COMMS_PAYLOAD_MAX_SIZE];

The return_val is the return value of the psa_call() invocation, the out_size is the (potentially updated) sizes of the outvecs and the payload buffer contains the outvec data serialized contiguously in outvec order.

Pointer access protocol

The pointer access protocol passes the psa_call iovecs as pointers to shared memory using the following MHU message format:

__PACKED_STRUCT rse_pointer_access_msg_t {
    psa_handle_t handle;
    uint32_t ctrl_param;
    uint32_t io_sizes[PSA_MAX_IOVEC];
    uint64_t host_ptrs[PSA_MAX_IOVEC];

The handle, ctrl_param and io_sizes have the same definition as in the embed protocol.

The host_ptrs point to each of the up to PSA_MAX_IOVEC iovec buffers in host system memory. It is the caller’s responsibility to write the invec data to the invec pointers before sending the message, and ensure that the memory pointed-to remains valid for the duration of the call.

The reply message has the form:

__PACKED_STRUCT rse_pointer_access_reply_t {
    int32_t return_val;
    uint32_t out_size[PSA_MAX_IOVEC];

The return_val and out_size have the same meaning as in the embed protocol.

RSE writes the outvec data to the pointers supplied in the call message prior to sending the MHU reply message, so no further payload is sent in the reply message.

Implementation structure

The RSE side of the communication implementation is located in platform/ext/target/arm/rse/common/rse_comms. The implementation is structured as follows:

  • rse_comms.c: Implements the TF-M RPC layer using RSE comms implementation.

  • rse_comms_hal.c: Abstracts MHU message sending and receiving.

  • rse_comms_protocol.c: The common part of the RSE comms protocol.

  • rse_comms_protocol_embed.c: The embed RSE comms protocol.

  • rse_comms_protocol_protocol_access.c: The pointer access RSE comms protocol.

  • rse_comms_atu.c: Allocates and frees ATU regions for host pointer access.

  • rse_comms_permissions_hal.c: Checks service access permissions and pointer validity.

A reference implementation of the client side of the RSE comms is available in the Trusted Firmware-A repository.

Copyright (c) 2022-2023, Arm Limited. All rights reserved.