[PATCH 03/12] efi_loader: add FF-A runtime support in EFI variable TEE driver

Ilias Apalodimas ilias.apalodimas at linaro.org
Mon Apr 27 18:21:14 CEST 2026


Hi Harsimran,

On Fri, 24 Apr 2026 at 20:32, Harsimran Singh Tungal
<harsimransingh.tungal at arm.com> wrote:
>
> Enable MM variable services over FF-A after ExitBootServices
>
> This patch extends lib/efi_loader/efi_variable_tee.c to support FF-A
> communication with the secure world during EFI runtime. It enables EFI
> runtime variable access and MM communication using FF-A transport when
> ExitBootServices() has already been called.
>
> Key changes:
>  ------------
>   - Introduce runtime-safe implementations for MM communication,
>     notification, and variable access using FF-A driver.
>   - Introduce communication-buffer helper (get_comm_buf()) that switches
>     between dynamic allocation (boot phase) and the fixed FF-A shared
>     buffer (runtime phase).
>   - Mark persistent data and code with __efi_runtime and
>     __efi_runtime_data attributes.
>   - Use direct physical address mapping for shared buffers since
>     U-Boot operates with 1:1 physical-to-virtual mapping.
>   - Only per-buffer cache maintenance is performed at runtime,
>     as whole D-cache invalidation would violate the OS coherency model
>     after ExitBootServices().
>   - Add runtime-phase tracking (efi_runtime_enabled).

Why is this needed? For the memory allocations?

[...]

>   *
>   *  Authors:
>   *    Abdellatif El Khlifi <abdellatif.elkhlifi at arm.com>
> @@ -14,6 +14,7 @@
>
>  #if CONFIG_IS_ENABLED(ARM_FFA_TRANSPORT)
>  #include <arm_ffa.h>
> +#include <arm_ffa_runtime.h>
>  #endif
>  #include <cpu_func.h>
>  #include <dm.h>
> @@ -34,20 +35,47 @@
>  #define MM_DENIED (-3)
>  #define MM_NO_MEMORY (-5)
>
> +static const int __efi_runtime_rodata mm_sp_errmap[] = {
> +       [-MM_NOT_SUPPORTED]      = -EINVAL,
> +       [-MM_INVALID_PARAMETER]  = -EPERM,
> +       [-MM_DENIED]             = -EACCES,
> +       [-MM_NO_MEMORY]          = -EBUSY,
> +};
> +

These are already defined above and used in ffa_notify_mm_sp(). If you
plan to convert them, do it for the entire file.

[...]

> +/**
> + * efi_is_runtime_enabled() - Indicate whether the system is in the UEFI runtime phase
> + *
> + * This helper returns whether the firmware has transitioned into the
> + * UEFI runtime phase, meaning that ExitBootServices() has been invoked.
> + *
> + * Return:
> + *   true  - The system is operating in UEFI runtime mode.
> + *   false - The system is still in the boot services phase.
> + */
> +static bool __efi_runtime efi_is_runtime_enabled(void)
> +{
> +       return efi_runtime_enabled;
> +}

Enabled is a bit confusing. efi_at_runtime() should be enough. The
efi_tcg.c code calls this 'ebs_called'

> +
>  /**
>   * get_connection() - Retrieve OP-TEE session for a specific UUID.
>   *
> @@ -169,6 +197,28 @@ static efi_status_t optee_mm_communicate(void *comm_buf, ulong dsize)
>  }
>
>  #if CONFIG_IS_ENABLED(ARM_FFA_TRANSPORT)
> +/**
> + * ffa_map_sp_event_runtime() - Map MM SP response to errno (runtime-safe)
> + * @sp_event_ret: MM SP return code from ffa_notify_mm_sp_runtime()
> + *
> + * Convert the MM SP return code into a standard U-Boot errno. This helper
> + * is marked __efi_runtime to ensure it is safe to call after
> + * ExitBootServices().
> + *
> + * Return: 0 on success, negative errno on failure
> + */
> +static __efi_runtime int ffa_map_sp_event_runtime(int sp_event_ret)
> +{
> +       int idx = -sp_event_ret;
> +
> +       if (sp_event_ret == MM_SUCCESS)
> +               return 0;
> +       if (idx > 0 && idx < (int)ARRAY_SIZE(mm_sp_errmap) &&
> +           mm_sp_errmap[idx])
> +               return mm_sp_errmap[idx];
> +       return -EACCES;
> +}
> +
>  /**
>   * ffa_notify_mm_sp() - Announce there is data in the shared buffer
>   *
> @@ -225,6 +275,35 @@ static int ffa_notify_mm_sp(void)
>         return ret;
>  }
>
> +/**
> + * ffa_notify_mm_sp_runtime() - Runtime implementation of
> + *                              ffa_notify_mm_sp()
> + *
> + * Notify the MM partition in the trusted world that
> + * data is available in the shared buffer.
> + * This is a blocking call during which trusted world has exclusive access
> + * to the MM shared buffer.
> + *
> + * Return:
> + *
> + * 0 on success
> + */
> +static int __efi_runtime ffa_notify_mm_sp_runtime(void)
> +{
> +       struct ffa_send_direct_data msg = {0};
> +       int ret;
> +       int sp_event_ret;
> +
> +       msg.data0 = CONFIG_FFA_SHARED_MM_BUF_OFFSET;
> +
> +       ret = ffa_sync_send_receive_runtime(mm_sp_id, &msg, 1);
> +       if (ret)
> +               return ret;
> +
> +       ret = ffa_map_sp_event_runtime(sp_event_ret);
> +       return ret;
> +}
> +
>  /**
>   * ffa_discover_mm_sp_id() - Query the MM partition ID
>   *
> @@ -360,6 +439,116 @@ static efi_status_t ffa_mm_communicate(void *comm_buf, ulong comm_buf_size)
>         return efi_ret;
>  }
>
> +/**
> + * ffa_mm_communicate_runtime() - Runtime implementation of ffa_mm_communicate()
> + * @comm_buf:          locally allocated communication buffer used for rx/tx
> + * @comm_buf_size:     communication buffer size
> + *
> + * Issue a door bell event to notify the MM partition (SP) running in OP-TEE
> + * that there is data to read from the shared buffer.
> + * Communication with the MM SP is performed using FF-A transport.
> + * On the event, MM SP can read the data from the buffer and
> + * update the MM shared buffer with response data.
> + * The response data is copied back to the communication buffer.
> + *
> + * Return:
> + *
> + * EFI status code
> + */
> +static efi_status_t __efi_runtime ffa_mm_communicate_runtime(void *comm_buf,
> +                                                            ulong comm_buf_size)
> +{

There's a lot of code duplication between the boottime and runtime
variants, but I don;t see why we need it. Can't we have a single
function that works both boottime and runtime?

[...]
> +        * must clean the corresponding cache lines to the Point of Coherency (PoC)
> +        * before entering secure world.
> +        *
> +        * S -> NS (response path):
> +        *
> +        * The secure world may update the same shared buffer in DDR. After returning
> +        * to non-secure, any cached copies of that region in NS may be stale. We
> +        * therefore invalidate the shared buffer range after the FF-A call to drop
> +        * those lines and force subsequent reads to fetch the latest data from DDR.
> +        *
> +        * Note: Whole-cache invalidation must not be used in EFI runtime context.
> +        * After ExitBootServices(), the OS owns the cache hierarchy; global invalidation
> +        * could drop OS dirty lines and violate the OS coherency model. Always operate
> +        * on the shared buffer range only.
> +        */
> +       if (IS_ENABLED(CONFIG_ARM64))
> +               flush_dcache_range((unsigned long)comm_buf,

This doesn't seem to be marked as efi_runtime. How was this patchset tested?

> +                                  (unsigned long)((u8 *)comm_buf +
> +                                                  CONFIG_FFA_SHARED_MM_BUF_SIZE));
> +
> +       /* Announce there is data in the shared buffer */
> +
> +       ffa_ret = ffa_notify_mm_sp_runtime();
> +
> +       if (IS_ENABLED(CONFIG_ARM64))
> +               invalidate_dcache_range((unsigned long)comm_buf,
> +                                       (unsigned long)((u8 *)comm_buf +
> +                                                       CONFIG_FFA_SHARED_MM_BUF_SIZE));
> +
> +       switch (ffa_ret) {
> +       case 0: {

[...]


>
>  /**
> @@ -433,9 +643,86 @@ static efi_status_t mm_communicate(u8 *comm_buf, efi_uintn_t dsize)
>         return var_hdr->ret_status;
>  }
>
> +/**
> + * mm_communicate_runtime() - Runtime implementation of mm_communicate()
> + *
> + * @comm_buf:  locally allocated communication buffer
> + * @dsize:             buffer size
> + *
> + * The SP (also called partition) can be any MM SP such as  StandAlonneMM or smm-gateway.
> + * The comm_buf format is the same for both partitions.
> + * When using the u-boot OP-TEE driver, StandAlonneMM is supported.
> + * When using the u-boot FF-A  driver, any MM SP is supported.
> + *
> + * Return:             status code
> + */
> +static efi_status_t __efi_runtime mm_communicate_runtime(u8 *comm_buf, efi_uintn_t dsize)
> +{
> +       efi_status_t ret = EFI_UNSUPPORTED;
> +       struct efi_mm_communicate_header *mm_hdr;
> +       struct smm_variable_communicate_header *var_hdr;
> +       enum mm_comms_select mm_comms;
> +
> +       dsize += MM_COMMUNICATE_HEADER_SIZE + MM_VARIABLE_COMMUNICATE_SIZE;
> +       mm_hdr = (struct efi_mm_communicate_header *)comm_buf;
> +       var_hdr = (struct smm_variable_communicate_header *)mm_hdr->data;
> +
> +       if (IS_ENABLED(CONFIG_ARM_FFA_TRANSPORT)) {
> +               mm_comms = get_mm_comms_runtime();
> +               if (mm_comms == MM_COMMS_FFA)
> +                       ret = ffa_mm_communicate_runtime(comm_buf, dsize);

Same remarks here. I think having a single variant instead of an
_runtime one is going to be easier to maintain. Especially for the
functions that are very similar.

> +       }
> +
> +       if (ret != EFI_SUCCESS)
> +               return ret;
> +
> +       return var_hdr->ret_status;
> +}
> +
> +/**

[...]

> -                       efi_uintn_t func, efi_status_t *ret)
> +static __efi_runtime u8 *setup_mm_hdr(void **dptr, efi_uintn_t payload_size,
> +                                     efi_uintn_t func, efi_status_t *ret)
>  {
> -       const efi_guid_t mm_var_guid = EFI_MM_VARIABLE_GUID;
>         struct efi_mm_communicate_header *mm_hdr;
>         struct smm_variable_communicate_header *var_hdr;
>         u8 *comm_buf;
> @@ -465,16 +751,15 @@ static u8 *setup_mm_hdr(void **dptr, efi_uintn_t payload_size,
>                 return NULL;
>         }
>
> -       comm_buf = calloc(1, MM_COMMUNICATE_HEADER_SIZE +
> -                         MM_VARIABLE_COMMUNICATE_SIZE +
> -                         payload_size);
> +       comm_buf = get_comm_buf(payload_size);
>         if (!comm_buf) {
>                 *ret = EFI_OUT_OF_RESOURCES;
>                 return NULL;
>         }
>
>         mm_hdr = (struct efi_mm_communicate_header *)comm_buf;
> -       guidcpy(&mm_hdr->header_guid, &mm_var_guid);
> +       efi_memcpy_runtime(&mm_hdr->header_guid, &mm_var_guid_runtime,
> +                          sizeof(mm_hdr->header_guid));
>         mm_hdr->message_len = MM_VARIABLE_COMMUNICATE_SIZE + payload_size;
>
>         var_hdr = (struct smm_variable_communicate_header *)mm_hdr->data;
> @@ -982,6 +1267,9 @@ void efi_variables_boot_exit_notify(void)
>                         efi_get_next_variable_name_runtime;
>         efi_runtime_services.set_variable = efi_set_variable_runtime;
>         efi_update_table_header_crc32(&efi_runtime_services.hdr);
> +
> +       /* Set efi_runtime_enabled as true after ExitBootServices */
> +       efi_runtime_enabled = true;
>  }
>
>  /**
> @@ -993,6 +1281,25 @@ efi_status_t efi_init_variables(void)
>  {
>         efi_status_t ret;
>
> +       if (IS_ENABLED(CONFIG_ARM_FFA_RT_MODE)) {
> +               /*
> +                * The FF-A shared buffer is accessed by EFI runtime services, so it must
> +                * be marked as runtime memory in the EFI memory map.
> +                */
> +               ffa_shared_buf = (void *)CONFIG_FFA_SHARED_MM_BUF_ADDR;
> +               ret = efi_add_memory_map(CONFIG_FFA_SHARED_MM_BUF_ADDR,
> +                                        CONFIG_FFA_SHARED_MM_BUF_SIZE,
> +                                        EFI_RUNTIME_SERVICES_DATA);
> +               if (ret != EFI_SUCCESS) {
> +                       log_err("EFI: failed to add FF-A shared buffer to runtime map (%lu)\n",
> +                               ret);
> +                       return ret;
> +               }
> +               log_info("EFI: FF-A shared buffer runtime map: addr=0x%lx size=0x%lx\n",
> +                        (ulong)CONFIG_FFA_SHARED_MM_BUF_ADDR,
> +                        (ulong)CONFIG_FFA_SHARED_MM_BUF_SIZE);
> +       }
> +

You should remove the memory map reservation if any of the code below fails.

>         /* Create a cached copy of the variables that will be enabled on ExitBootServices() */
>         ret = efi_var_mem_init();
>         if (ret != EFI_SUCCESS)
> --
> 2.34.1
>

Thanks
/Ilias


More information about the U-Boot mailing list