[PATCH 03/12] efi_loader: add FF-A runtime support in EFI variable TEE driver
Harsimran Singh Tungal
harsimransingh.tungal at arm.com
Fri Apr 24 19:31:42 CEST 2026
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).
The change reuses the statically reserved shared buffer, replaces
allocations with __efi_runtime copies, and updates the runtime service
table so EFI variable runtime calls reach the secure partition via FF-A.
Signed-off-by: Harsimran Singh Tungal <harsimransingh.tungal at arm.com>
---
lib/efi_loader/efi_variable_tee.c | 331 ++++++++++++++++++++++++++++--
1 file changed, 319 insertions(+), 12 deletions(-)
diff --git a/lib/efi_loader/efi_variable_tee.c b/lib/efi_loader/efi_variable_tee.c
index 6a1fa39bb6f..e4d97dc55ab 100644
--- a/lib/efi_loader/efi_variable_tee.c
+++ b/lib/efi_loader/efi_variable_tee.c
@@ -4,7 +4,7 @@
*
* Copyright (C) 2019 Linaro Ltd. <sughosh.ganu at linaro.org>
* Copyright (C) 2019 Linaro Ltd. <ilias.apalodimas at linaro.org>
- * Copyright 2022-2023 Arm Limited and/or its affiliates <open-source-office at arm.com>
+ * Copyright 2022-2023, 2026 Arm Limited and/or its affiliates <open-source-office at arm.com>
*
* 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,
+};
+
static const char *mm_sp_svc_uuid = MM_SP_UUID;
-static u16 mm_sp_id;
+static u16 __efi_runtime_data mm_sp_id;
#endif
+static void *__efi_runtime_data ffa_shared_buf;
+static const efi_guid_t __efi_runtime_rodata mm_var_guid_runtime =
+ EFI_MM_VARIABLE_GUID;
+
extern struct efi_var_file __efi_runtime_data *efi_var_buf;
-static efi_uintn_t max_buffer_size; /* comm + var + func + data */
-static efi_uintn_t max_payload_size; /* func + data */
+static efi_uintn_t __efi_runtime_data max_buffer_size; /* comm + var + func + data */
+static efi_uintn_t __efi_runtime_data max_payload_size; /* func + data */
static const u16 __efi_runtime_rodata pk[] = u"PK";
+static bool __efi_runtime_data efi_runtime_enabled;
struct mm_connection {
struct udevice *tee;
u32 session;
};
+/**
+ * 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;
+}
+
/**
* 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)
+{
+ ulong tx_data_size;
+ int ffa_ret;
+ efi_status_t efi_ret;
+ struct efi_mm_communicate_header *mm_hdr;
+
+ if (!comm_buf)
+ return EFI_INVALID_PARAMETER;
+
+ /* Discover MM partition ID at boot time */
+ if (!mm_sp_id)
+ return EFI_UNSUPPORTED;
+
+ mm_hdr = (struct efi_mm_communicate_header *)comm_buf;
+ tx_data_size = mm_hdr->message_len + sizeof(efi_guid_t) + sizeof(size_t);
+
+ if (comm_buf_size != tx_data_size || tx_data_size > CONFIG_FFA_SHARED_MM_BUF_SIZE)
+ return EFI_INVALID_PARAMETER;
+
+ /*
+ * Shared buffer cache maintenance for FF-A / OP-TEE communication:
+ *
+ * NS -> S (request path):
+ *
+ * The non-secure side populates the shared buffer. If the buffer is cached
+ * in NS, the updated bytes may reside in dirty D-cache lines and not yet be
+ * visible in DDR. Since the secure world typically reads the shared buffer
+ * directly from DDR (e.g. with caches disabled / non-coherent mapping), we
+ * 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,
+ (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: {
+ ulong rx_data_size;
+
+ rx_data_size = ((struct efi_mm_communicate_header *)comm_buf)->message_len +
+ sizeof(efi_guid_t) +
+ sizeof(size_t);
+
+ if (rx_data_size > comm_buf_size) {
+ efi_ret = EFI_OUT_OF_RESOURCES;
+ break;
+ }
+
+ efi_ret = EFI_SUCCESS;
+ break;
+ }
+ case -EINVAL:
+ efi_ret = EFI_DEVICE_ERROR;
+ break;
+ case -EPERM:
+ efi_ret = EFI_INVALID_PARAMETER;
+ break;
+ case -EACCES:
+ efi_ret = EFI_ACCESS_DENIED;
+ break;
+ case -EBUSY:
+ efi_ret = EFI_OUT_OF_RESOURCES;
+ break;
+ default:
+ efi_ret = EFI_ACCESS_DENIED;
+ }
+
+ return efi_ret;
+}
+
/**
* get_mm_comms() - detect the available MM transport
*
@@ -386,6 +575,27 @@ static enum mm_comms_select get_mm_comms(void)
return MM_COMMS_FFA;
}
+
+/**
+ * get_mm_comms_runtime() - detect the available MM transport at runtime
+ *
+ * Make sure the FF-A bus is available at runtime and ready
+ * for use.
+ *
+ * Return:
+ *
+ * MM_COMMS_FFA or MM_COMMS_UNDEFINED
+ */
+static enum mm_comms_select __efi_runtime get_mm_comms_runtime(void)
+{
+ bool ret;
+
+ ret = efi_is_runtime_enabled();
+ if (!ret)
+ return MM_COMMS_UNDEFINED;
+
+ return MM_COMMS_FFA;
+}
#endif
/**
@@ -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);
+ }
+
+ if (ret != EFI_SUCCESS)
+ return ret;
+
+ return var_hdr->ret_status;
+}
+
+/**
+ * get_comm_buf() - Obtain a communication buffer for MM/FF-A exchange
+ * @payload_size: size of the payload that will be appended to the
+ * MM communication header
+ * This helper returns a buffer suitable for constructing an
+ * EFI_MM_COMMUNICATE message. During the boot phase a new buffer is
+ * dynamically allocated. After ExitBootServices(), dynamic
+ * allocation is no longer permitted, and all runtime communication must
+ * use the statically reserved FF-A shared buffer.
+ *
+ * Return:
+ * Pointer to a valid communication buffer on success,
+ * NULL if allocation fails during the boot phase.
+ */
+static __efi_runtime u8 *get_comm_buf(efi_uintn_t payload_size)
+{
+ u8 *comm_buf;
+
+ /* After ExitBootServices(), dynamic allocation is no longer permitted.
+ * Use the predefined FF-A shared buffer at runtime; otherwise allocate
+ * a fresh buffer during the boot phase.
+ */
+ if (efi_is_runtime_enabled()) {
+ if (IS_ENABLED(CONFIG_ARM_FFA_RT_MODE)) {
+ comm_buf = ffa_shared_buf;
+ if (!comm_buf)
+ return NULL;
+ efi_memset_runtime(comm_buf, 0, CONFIG_FFA_SHARED_MM_BUF_SIZE);
+ } else {
+ return NULL;
+ }
+ } else {
+ comm_buf = calloc(1, MM_COMMUNICATE_HEADER_SIZE +
+ MM_VARIABLE_COMMUNICATE_SIZE +
+ payload_size);
+ if (!comm_buf)
+ return NULL;
+ }
+ return comm_buf;
+}
+
/**
* setup_mm_hdr() - Allocate a buffer for StandAloneMM and initialize the
- * header data.
+ * header data. It is runtime safe.
*
* @dptr: pointer address of the corresponding StandAloneMM
* function
@@ -444,10 +731,9 @@ static efi_status_t mm_communicate(u8 *comm_buf, efi_uintn_t dsize)
* @ret: EFI return code
* Return: buffer or NULL
*/
-static u8 *setup_mm_hdr(void **dptr, efi_uintn_t payload_size,
- 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);
+ }
+
/* 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
More information about the U-Boot
mailing list