summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorManish V Badarkhe <manish.badarkhe@arm.com>2026-06-30 20:34:41 +0000
committerTrustedFirmware Code Review <review@review.trustedfirmware.org>2026-06-30 20:34:41 +0000
commitc64fe42d75709fc3da7d0820caf15bce2ca8a2cd (patch)
tree518f75362d7407e2d247d434c1c853c033897fa3
parent599baaa539b3996067b9ce8f0f225c8baa6e0757 (diff)
parent7196825ce71266d6656165657d4936d185bd439b (diff)
downloadarm-trusted-firmware-c64fe42d75709fc3da7d0820caf15bce2ca8a2cd.tar.gz
arm-trusted-firmware-c64fe42d75709fc3da7d0820caf15bce2ca8a2cd.zip
Merge changes from topic "bl31_lfa_integration" into integrationHEADmaster
* changes: feat(lfa): harden cancel handling and lock around LFA_CANCEL feat(lfa): serialize activate across CPUs and manage rendezvous policy feat(docs): add documentation page for BL31 LFA feat(fvp): add FVP platform support for BL31 live activation feat(lfa): update LFA service to support BL31 live activation feat(lfa): lfa service updates feat(lfa): add relocatable code for BL31 live activation feat(lfa): xlat changes for BL31 LFA feat(lfa): bl31 linker file updates for lfa feat(lfa): add build flag for BL31 LFA support feat: place errata into their own section
-rw-r--r--Makefile6
-rw-r--r--bl31/aarch64/bl31_entrypoint.S65
-rw-r--r--bl31/bl31.ld.S86
-rw-r--r--docs/components/bl31-lfa.rst91
-rw-r--r--docs/components/index.rst1
-rw-r--r--docs/getting_started/build-options.rst5
-rw-r--r--include/arch/aarch64/el3_common_macros.S32
-rw-r--r--include/common/bl_common.h15
-rw-r--r--include/lib/cpus/aarch64/cpu_macros.S73
-rw-r--r--include/lib/xlat_tables/xlat_tables_v2.h5
-rw-r--r--include/plat/arm/common/arm_def.h14
-rw-r--r--include/plat/arm/common/plat_arm_lfa_components.h4
-rw-r--r--include/plat/common/plat_lfa.h12
-rw-r--r--include/services/lfa_component_desc.h37
-rw-r--r--include/services/lfa_holding_pen.h11
-rw-r--r--include/services/lfa_svc.h7
-rw-r--r--lib/xlat_tables_v2/xlat_tables_context.c7
-rw-r--r--make_helpers/constraints.mk6
-rw-r--r--make_helpers/defaults.mk1
-rw-r--r--plat/arm/board/fvp/fvp_common.c11
-rw-r--r--plat/arm/board/fvp/fvp_lfa.c45
-rw-r--r--plat/arm/board/fvp/include/platform_def.h21
-rw-r--r--services/std_svc/lfa/bl31_lfa.c373
-rw-r--r--services/std_svc/lfa/lfa.mk8
-rw-r--r--services/std_svc/lfa/lfa_holding_pen.c64
-rw-r--r--services/std_svc/lfa/lfa_main.c355
-rw-r--r--services/std_svc/lfa/lfa_relocatable.S108
-rw-r--r--services/std_svc/lfa/lfa_reset.c173
-rw-r--r--services/std_svc/rmmd/rmmd_rmm_lfa.c1
29 files changed, 1467 insertions, 170 deletions
diff --git a/Makefile b/Makefile
index 0d092368c..a0f3ae288 100644
--- a/Makefile
+++ b/Makefile
@@ -521,6 +521,10 @@ ifdef FDT_SOURCES
NEED_FDT := yes
endif #(FDT_SOURCES)
+ifneq (${ENABLE_LFA_BL31},0)
+ CTX_INCLUDE_EL2_REGS := 1
+endif #(ENABLE_LFA_BL31)
+
################################################################################
# Include libraries' Makefile that are used in all BL
################################################################################
@@ -651,6 +655,7 @@ $(eval $(call assert_booleans,\
PRESERVE_DSU_PMU_REGS \
HOB_LIST \
LFA_SUPPORT \
+ ENABLE_LFA_BL31 \
SUPPORT_SP_LIVE_ACTIVATION \
TEST_IO_SHORT_READ_FI \
SDEI_SUPPORT \
@@ -936,6 +941,7 @@ $(eval $(call add_defines,\
HOB_LIST \
HW_CONFIG_BASE \
LFA_SUPPORT \
+ ENABLE_LFA_BL31 \
SUPPORT_SP_LIVE_ACTIVATION \
TEST_IO_SHORT_READ_FI \
TEST_IO_SHORT_READ_FI_IMAGE_ID \
diff --git a/bl31/aarch64/bl31_entrypoint.S b/bl31/aarch64/bl31_entrypoint.S
index 387d2066e..52255f263 100644
--- a/bl31/aarch64/bl31_entrypoint.S
+++ b/bl31/aarch64/bl31_entrypoint.S
@@ -16,6 +16,10 @@
.globl bl31_entrypoint
.globl bl31_warm_entrypoint
+#if ENABLE_LFA_BL31
+ .globl bl31_lfa_entrypoint
+#endif
+
/* -----------------------------------------------------
* bl31_entrypoint() is the cold boot entrypoint,
* executed only by the primary cpu.
@@ -169,34 +173,7 @@ func bl31_warm_entrypoint
_exception_vectors=runtime_exceptions \
_pie_fixup_size=0
-/*
- * We're about to enable MMU and participate in PSCI state coordination.
- *
- * The PSCI implementation invokes platform routines that enable CPUs to
- * participate in coherency. On a system where CPUs are not
- * cache-coherent without appropriate platform specific programming,
- * having caches enabled until such time might lead to coherency issues
- * (resulting from stale data getting speculatively fetched, among
- * others). Therefore we keep data caches disabled even after enabling
- * the MMU for such platforms.
- *
- * On systems with hardware-assisted coherency, or on single cluster
- * platforms, such platform specific programming is not required to
- * enter coherency (as CPUs already are); and there's no reason to have
- * caches disabled either.
- *
- * IMPORTANT: after invoking bl31_plat_enable_mmu(), the stack may end up
- * corrupted. Thus, when using this function, we must operate under the
- * assumption that we've no stack to use. Therefore, DO NOT place this
- * in another C function call or, generally, any place that would break
- * the aforementioned assumption.
- */
-#if HW_ASSISTED_COHERENCY || WARMBOOT_ENABLE_DCACHE_EARLY
- mov x0, xzr
-#else
- mov x0, #DISABLE_DCACHE
-#endif
- bl bl31_plat_enable_mmu
+ el3_enable_mmu
bl bl31_warmboot
@@ -220,3 +197,35 @@ func bl31_warm_entrypoint
#endif
b el3_exit
endfunc bl31_warm_entrypoint
+
+#if ENABLE_LFA_BL31
+ /*
+ *---------------------------------------------------------------------
+ * This CPU has been warm reset during a BL31 live firmware activation
+ * --------------------------------------------------------------------
+ */
+func bl31_lfa_entrypoint
+ /*
+ * When a BL31 LFA update requests a warm reboot this is the entry
+ * point used. We are trying to do as little as possible and just get
+ * the new BL31 up and running as quickly as possible while restoring
+ * the state of the system.
+ */
+ el3_entrypoint_common \
+ _init_sctlr=PROGRAMMABLE_RESET_ADDRESS \
+ _warm_boot_mailbox=0 \
+ _secondary_cold_boot=0 \
+ _init_memory=0 \
+ _init_c_runtime=0 \
+ _exception_vectors=runtime_exceptions \
+ _pie_fixup_size=0
+
+ el3_enable_mmu
+
+ /* Jump to C LFA warm reset entrypoint. */
+ bl lfa_warm_reset_entrypoint
+
+ b el3_exit
+endfunc bl31_lfa_entrypoint
+
+#endif /* ENABLE_LFA_BL31 */
diff --git a/bl31/bl31.ld.S b/bl31/bl31.ld.S
index bb337f403..d156680b8 100644
--- a/bl31/bl31.ld.S
+++ b/bl31/bl31.ld.S
@@ -7,13 +7,20 @@
#include <common/bl_common.ld.h>
#include <lib/per_cpu/per_cpu_defs.h>
#include <lib/xlat_tables/xlat_tables_defs.h>
+#include <platform_def.h>
OUTPUT_FORMAT(PLATFORM_LINKER_FORMAT)
OUTPUT_ARCH(PLATFORM_LINKER_ARCH)
ENTRY(bl31_entrypoint)
MEMORY {
- RAM (rwx): ORIGIN = BL31_BASE, LENGTH = BL31_LIMIT - BL31_BASE
+
+ RAM (rwx): ORIGIN = BL31_BASE, \
+ LENGTH = BL31_LIMIT - BL31_BASE - (ENABLE_LFA_BL31 * (2 * PAGE_SIZE))
+
+#if ENABLE_LFA_BL31
+ LFA (rwx): ORIGIN = BL31_LIMIT - (2 * PAGE_SIZE), LENGTH = (2 * PAGE_SIZE)
+#endif
#if SEPARATE_NOBITS_REGION
NOBITS (rw!a): ORIGIN = BL31_NOBITS_BASE, LENGTH = BL31_NOBITS_LIMIT - BL31_NOBITS_BASE
@@ -50,6 +57,33 @@ SECTIONS {
__TEXT_START__ = .;
*bl31_entrypoint.o(.text*)
+
+#if ENABLE_LFA_BL31
+ /*
+ * When LFA for BL31 is enabled we must group these two subsections
+ * together so they live in a fixed area of the binary that can be
+ * padded to a constant size. This means this section can be replaced
+ * on it's own and expand for new workarounds to be added. Otherwise
+ * the linker is free to do whatever it likes with them in text and
+ * rodata as usual.
+ */
+ . = ALIGN(PAGE_SIZE);
+ __ERRATA_START__ = .;
+ *(.text.errata*)
+ *(.rodata.errata*)
+
+ ASSERT(PLAT_LFA_ERRATA_SECTION_SIZE >= (. - __ERRATA_START__),
+ "Section .errata is too big for PLAT_LFA_ERRATA_SECTION_SIZE!");
+
+ FILL(0x00)
+ . += (( PLAT_LFA_ERRATA_SECTION_SIZE > (. - __ERRATA_START__) )
+ ? ( PLAT_LFA_ERRATA_SECTION_SIZE - (. - __ERRATA_START__) )
+ : 0);
+
+ . = ALIGN(PAGE_SIZE);
+ __ERRATA_END__ = .;
+#endif
+
*(SORT_BY_ALIGNMENT(SORT(.text*)))
*(.vectors)
__TEXT_END_UNALIGNED__ = .;
@@ -91,6 +125,33 @@ SECTIONS {
__RO_START__ = .;
*bl31_entrypoint.o(.text*)
+
+#if ENABLE_LFA_BL31
+ /*
+ * When LFA for BL31 is enabled we must group these two subsections
+ * together so they live in a fixed area of the binary that can be
+ * padded to a constant size. This means this section can be replaced
+ * on it's own and expand for new workarounds to be added. Otherwise
+ * the linker is free to do whatever it likes with them in text and
+ * rodata as usual.
+ */
+ . = ALIGN(PAGE_SIZE);
+ __ERRATA_START__ = .;
+ *(.text.errata*)
+ *(.rodata.errata*)
+
+ ASSERT(PLAT_LFA_ERRATA_SECTION_SIZE >= (. - __ERRATA_START__),
+ "Section .errata is too big for PLAT_LFA_ERRATA_SECTION_SIZE!");
+
+ FILL(0x00)
+ . += (( PLAT_LFA_ERRATA_SECTION_SIZE > (. - __ERRATA_START__) )
+ ? ( PLAT_LFA_ERRATA_SECTION_SIZE - (. - __ERRATA_START__) )
+ : 0);
+
+ . = ALIGN(PAGE_SIZE);
+ __ERRATA_END__ = .;
+#endif
+
*(SORT_BY_ALIGNMENT(.text*))
*(SORT_BY_ALIGNMENT(.rodata*))
@@ -147,6 +208,26 @@ SECTIONS {
. = LOADADDR(.spm_shim_exceptions) + SIZEOF(.spm_shim_exceptions);
#endif /* SPM_MM || (SPMC_AT_EL3 && SPMC_AT_EL3_SEL0_SP) */
+#if ENABLE_LFA_BL31
+ .lfa_relocatable : ALIGN(PAGE_SIZE) {
+ __LFA_RELOCATABLE_CODE_START__ = .;
+ KEEP(*(.lfa_relocatable_code*))
+ __LFA_RELOCATABLE_CODE_END__ = .;
+ . = ALIGN(PAGE_SIZE);
+ __LFA_RELOCATABLE_DATA_START__ = .;
+ KEEP(*(.lfa_relocatable_data*))
+ . = ALIGN(PAGE_SIZE);
+ __LFA_RELOCATABLE_DATA_END__ = .;
+ __LFA_RELOCATABLE_LIMIT__ = .;
+ } >LFA AT> RAM
+
+ PROVIDE(__LFA_RELOCATABLE_LMA__ = LOADADDR(.lfa_relocatable));
+
+ . = LOADADDR(.lfa_relocatable) + SIZEOF(.lfa_relocatable);
+ ASSERT(__LFA_RELOCATABLE_LIMIT__ <= BL31_LIMIT, \
+ "__LFA_RELOCATABLE_END__ has exceeded its limit.")
+#endif /* ENABLE_LFA_BL31 */
+
#if SEPARATE_RWDATA_REGION
. = BL31_RWDATA_BASE;
ASSERT(BL31_RWDATA_BASE == ALIGN(PAGE_SIZE),
@@ -193,7 +274,8 @@ SECTIONS {
#endif /* SEPARATE_RWDATA_REGION */
__BL31_END__ = .;
- ASSERT(. <= BL31_LIMIT, "BL31 image has exceeded its limit.")
+ ASSERT(. <= BL31_LIMIT - (ENABLE_LFA_BL31 * (2 * PAGE_SIZE)), \
+ "BL31 image has exceeded its limit.")
. = BL31_NOBITS_BASE;
diff --git a/docs/components/bl31-lfa.rst b/docs/components/bl31-lfa.rst
new file mode 100644
index 000000000..f9e7672f9
--- /dev/null
+++ b/docs/components/bl31-lfa.rst
@@ -0,0 +1,91 @@
+BL31 Live Firmware Activation
+*****************************
+
+This document outlines the current BL31 support for live firmware activation.
+Live firmware activation is essentially updating underlying firmware without
+rebooting the whole system, which can avoid the downtime of a reboot.
+
+This is done by defining a special code section for errata and padding it to a
+fixed size, so the code in this section can change while reducing the chances
+that the rest of the compiled firmware is altered.
+
+The current BL31 live activation framework does not replace the entire BL31
+image, and instead simply replaces the CPU ops and errata code sections which
+is less risky and still allows for CPU library errata fixes to be deployed
+via LFA.
+
+Limitations & Assumptions
+=========================
+
+At the time of this writing, LFA for BL31 is a proof of concept feature and has
+some limitations and assumptions that must be respected for it to work properly.
+The current implementation of BL31 LFA is not intended to be used in production
+environments and is for evaluation and testing purposes only.
+
+- Live Activation of BL31 images is limited to simple errata workarounds in
+ assembly code only. A linker script change has been made to place errata
+ code/data into a contiguous memory range which is padded up to
+ PLAT_LFA_ERRATA_SECTION_SIZE bytes. This allows extra space to be left for
+ errata workarounds to be added without moving anything else around or
+ increasing the size of the image into a new page. This definition should be
+ placed in the platform_def.h file.
+
+- Builds should be done using the same system, with the same GCC version, using
+ the same build configuration and flags (except for new errata flags). Even
+ still, it is not 100% guaranteed that the compiler will not change anything,
+ but it is likely that live activation will work.
+
+- Warm reboots should be used with live activations to ensure there are no
+ issues with unwinding the stack.
+
+Challenges
+==========
+
+The main challenge is ensuring that TF-A builds are capable of live-activation
+without making an onerous amount of assumptions. Unfortunately, the ways of
+going about this are not trivial. Longer term, persistent state data in TF-A
+needs to be identified and placed into a defined data structure at a specific
+memory address to ensure that it stays in the same place across live
+activations. This would allow the compiler and linker to move things around
+while still allowing everything to work properly.
+
+Additionally, we need a way to reliably determine whether an image can be
+successfully live-activated before attempting it, as an incompatible image
+will likely result in a crash, wiping out everything and requiring a full
+system reboot.
+
+Running the Code
+================
+
+Basic LFA testing can be done with TFTF on an FVP model, so to start off build
+TFTF with the following parameters.
+
+.. code:: bash
+
+ make CROSS_COMPILE=aarch64-none-elf- PLAT=fvp TESTS=lfa all
+
+Then in TF-A, build with these parameters:
+
+.. code:: bash
+
+ make CROSS_COMPILE=aarch64-none-elf- PLAT=fvp LFA_SUPPORT=1 ENABLE_LFA_BL31=1 BL33=path/to/tftf.bin all fip
+
+Once the build is complete, stash the bl1.bin and fip.bin files, make any
+changes you want to try to live activate in the CPU library, then do the build
+again. The resulting bl31.bin file is what we will load into FVP memory to load
+and live activate.
+
+The model used for testing is FVP_Base_RevC-2xAEMvA, other models could
+be used as well with appropriate build and run parameters.
+
+.. code:: bash
+
+ ./FVP_Base_RevC-2xAEMvA \
+ --data cluster0.cpu0=path/to/new/bl31.bin@0xFB000000 \
+ -C bp.flashloader0.fname=path/to/fip.bin \
+ -C bp.secure_memory=1 \
+ -C bp.secureflashloader.fname=path/to/bl1.bin \
+ -C cache_state_modelled=0 \
+ -C cluster0.restriction_on_speculative_execution=2 \
+ -C cluster1.restriction_on_speculative_execution=2 \
+ -C pctl.startup=0.0.0.0
diff --git a/docs/components/index.rst b/docs/components/index.rst
index 29a889b1f..f94992fb7 100644
--- a/docs/components/index.rst
+++ b/docs/components/index.rst
@@ -32,3 +32,4 @@ Components
context-management-library
numa-per-cpu
cpu-service
+ bl31-lfa
diff --git a/docs/getting_started/build-options.rst b/docs/getting_started/build-options.rst
index 7a571495f..9b70e6ed4 100644
--- a/docs/getting_started/build-options.rst
+++ b/docs/getting_started/build-options.rst
@@ -1611,6 +1611,11 @@ Common build options
- ``LFA_SUPPORT``: Boolean flag to enable support for Live Firmware
activation as per the specification. This option defaults to 0.
+- ``ENABLE_LFA_BL31``: Boolean flag to enable live activation of BL31 using
+ the EL3 LFA framework. This option depends on ``LFA_SUPPORT`` and defaults
+ to 0. See :doc:`../components/bl31-lfa` for the design, assumptions, and
+ limitations of BL31 self-update.
+
- ``TRANSFER_LIST``: Setting this to ``1`` enables support for Firmware
Handoff using Transfer List defined in `Firmware Handoff specification`_.
This defaults to ``0``. Current implementation follows the Firmware Handoff
diff --git a/include/arch/aarch64/el3_common_macros.S b/include/arch/aarch64/el3_common_macros.S
index 2181b731a..119cf5246 100644
--- a/include/arch/aarch64/el3_common_macros.S
+++ b/include/arch/aarch64/el3_common_macros.S
@@ -13,6 +13,7 @@
#include <context.h>
#include <lib/el3_runtime/cpu_data.h>
#include <lib/per_cpu/per_cpu_macros.S>
+#include <lib/xlat_tables/xlat_mmu_helpers.h>
/*
* Helper macro to initialise EL3 registers we care about.
@@ -576,4 +577,35 @@ feat_sctlr2_not_supported\@:
isb
.endm
+ .macro el3_enable_mmu
+ /*
+ * We're about to enable MMU and participate in PSCI state coordination.
+ *
+ * The PSCI implementation invokes platform routines that enable CPUs to
+ * participate in coherency. On a system where CPUs are not
+ * cache-coherent without appropriate platform specific programming,
+ * having caches enabled until such time might lead to coherency issues
+ * (resulting from stale data getting speculatively fetched, among
+ * others). Therefore we keep data caches disabled even after enabling
+ * the MMU for such platforms.
+ *
+ * On systems with hardware-assisted coherency, or on single cluster
+ * platforms, such platform specific programming is not required to
+ * enter coherency (as CPUs already are); and there's no reason to have
+ * caches disabled either.
+ *
+ * IMPORTANT: after invoking bl31_plat_enable_mmu(), the stack may end up
+ * corrupted. Thus, when using this function, we must operate under the
+ * assumption that we've no stack to use. Therefore, DO NOT place this
+ * in another C function call or, generally, any place that would break
+ * the aforementioned assumption.
+ */
+#if HW_ASSISTED_COHERENCY || WARMBOOT_ENABLE_DCACHE_EARLY
+ mov x0, xzr
+#else
+ mov x0, #DISABLE_DCACHE
+#endif
+ bl bl31_plat_enable_mmu
+ .endm
+
#endif /* EL3_COMMON_MACROS_S */
diff --git a/include/common/bl_common.h b/include/common/bl_common.h
index 3bde8fd09..681a5885e 100644
--- a/include/common/bl_common.h
+++ b/include/common/bl_common.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013-2025, Arm Limited and Contributors. All rights reserved.
+ * Copyright (c) 2013-2026, Arm Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
@@ -164,6 +164,19 @@ IMPORT_SYM(uintptr_t, __COHERENT_RAM_START__, BL_COHERENT_RAM_BASE);
IMPORT_SYM(uintptr_t, __COHERENT_RAM_END__, BL_COHERENT_RAM_END);
#endif
+#if ENABLE_LFA_BL31
+/* These symbols are needed so the relocatable code can be loaded. */
+IMPORT_SYM(uintptr_t, __LFA_RELOCATABLE_CODE_START__, LFA_RELOCATABLE_CODE_START);
+IMPORT_SYM(uintptr_t, __LFA_RELOCATABLE_CODE_END__, LFA_RELOCATABLE_CODE_END);
+IMPORT_SYM(uintptr_t, __LFA_RELOCATABLE_LMA__, LFA_RELOCATABLE_LMA);
+IMPORT_SYM(uintptr_t, __LFA_RELOCATABLE_DATA_START__, LFA_RELOCATABLE_DATA_START);
+IMPORT_SYM(uintptr_t, __LFA_RELOCATABLE_DATA_END__, LFA_RELOCATABLE_DATA_END);
+IMPORT_SYM(uintptr_t, __ERRATA_START__, ERRATA_START);
+IMPORT_SYM(uintptr_t, __ERRATA_END__, ERRATA_END);
+IMPORT_SYM(uintptr_t, __CPU_OPS_START__, CPU_OPS_START);
+IMPORT_SYM(uintptr_t, __CPU_OPS_END__, CPU_OPS_END);
+#endif
+
/*******************************************************************************
* Structure used for telling the next BL how much of a particular type of
* memory is available for its use and how much is already used.
diff --git a/include/lib/cpus/aarch64/cpu_macros.S b/include/lib/cpus/aarch64/cpu_macros.S
index 334ab7cc4..3996fdf0c 100644
--- a/include/lib/cpus/aarch64/cpu_macros.S
+++ b/include/lib/cpus/aarch64/cpu_macros.S
@@ -84,7 +84,7 @@
* that we are at the end of the list. Some cpus call declare_cpu_ops
* twice, so only do this once.
*/
- .pushsection .rodata.errata_entries
+ .pushsection .rodata.errata.errata_entries
.ifndef \_name\()_errata_list_start
\_name\()_errata_list_start:
.endif
@@ -108,7 +108,7 @@
.popsection
/* Place CPU string in rodata */
- .pushsection .rodata
+ .pushsection .rodata.errata
\_name\()_cpu_str:
.asciz "\_name"
.popsection
@@ -202,7 +202,7 @@
*/
.macro add_erratum_entry _cpu:req, _cve:req, _id:req, _chosen:req, _split_wa=0
#if INCLUDE_ERRATA_LIST
- .pushsection .rodata.errata_entries
+ .pushsection .rodata.errata.errata_entries
.align 3
.ifndef \_cpu\()_errata_list_start
\_cpu\()_errata_list_start:
@@ -256,11 +256,11 @@
.if \_chosen
/* put errata directly into the reset function */
- .pushsection .text.asm.\_cpu\()_reset_func, "ax"
+ .pushsection .text.errata.\_cpu\()_reset_func, "ax"
.else
/* or something else that will get garbage collected by the
* linker */
- .pushsection .text.asm.erratum_\_cpu\()_\_id\()_wa, "ax"
+ .pushsection .text.errata.erratum_\_cpu\()_\_id\()_wa, "ax"
.endif
/* revision is stored in x14, get it */
mov x0, x14
@@ -280,7 +280,7 @@
.macro workaround_runtime_start _cpu:req, _cve:req, _id:req, _chosen:req, _midr
add_erratum_entry \_cpu, \_cve, \_id, \_chosen
- func erratum_\_cpu\()_\_id\()_wa
+ errata_func_compat erratum_\_cpu\()_\_id\()_wa
mov x8, x30
/*
* Let errata specify if they need MIDR checking. Sadly, storing the
@@ -328,7 +328,7 @@
.endif
erratum_\_cpu\()_\_id\()_skip_runtime:
ret x8
- endfunc erratum_\_cpu\()_\_id\()_wa
+ errata_endfunc_compat erratum_\_cpu\()_\_id\()_wa
.endm
/*******************************************************************************
@@ -566,6 +566,16 @@
#endif
.endm
+.macro errata_func_compat _name, _align=2
+ .section .text.errata.\_name, "ax"
+ .type \_name, %function
+ .align \_align
+ \_name:
+#if ENABLE_BTI
+ BTI jc
+#endif
+.endm
+
/*
* This macro is used to mark the end of a function.
*/
@@ -575,11 +585,32 @@
#else
-#define func_compat func
-#define endfunc_compat endfunc
+.macro func_compat _name
+ func \_name
+.endm
+
+.macro errata_func_compat _name, _align=2
+ .cfi_sections .debug_frame
+ .section .text.errata.\_name, "ax"
+ .type \_name, %function
+ .cfi_startproc
+ .align \_align
+ \_name:
+#if ENABLE_BTI
+ BTI jc
+#endif
+.endm
+
+.macro endfunc_compat _name
+ endfunc \_name
+.endm
#endif /* __clang_version__ < 17 */
+.macro errata_endfunc_compat _name
+ endfunc_compat \_name
+.endm
+
/*
* Helpers to select which revisions errata apply to.
*
@@ -601,35 +632,35 @@
* argument: x0 - cpu_rev_var
*/
.macro check_erratum_ls _cpu:req, _cve:req, _id:req, _rev_num:req
- func_compat check_erratum_\_cpu\()_\_id
+ errata_func_compat check_erratum_\_cpu\()_\_id
cpu_rev_var_ls \_rev_num
ret
- endfunc_compat check_erratum_\_cpu\()_\_id
+ errata_endfunc_compat check_erratum_\_cpu\()_\_id
.endm
.macro check_erratum_hs _cpu:req, _cve:req, _id:req, _rev_num:req
- func_compat check_erratum_\_cpu\()_\_id
+ errata_func_compat check_erratum_\_cpu\()_\_id
cpu_rev_var_hs \_rev_num
ret
- endfunc_compat check_erratum_\_cpu\()_\_id
+ errata_endfunc_compat check_erratum_\_cpu\()_\_id
.endm
.macro check_erratum_range _cpu:req, _cve:req, _id:req, _rev_num_lo:req, _rev_num_hi:req
- func_compat check_erratum_\_cpu\()_\_id
+ errata_func_compat check_erratum_\_cpu\()_\_id
cpu_rev_var_range \_rev_num_lo, \_rev_num_hi
ret
- endfunc_compat check_erratum_\_cpu\()_\_id
+ errata_endfunc_compat check_erratum_\_cpu\()_\_id
.endm
.macro check_erratum_chosen _cpu:req, _cve:req, _id:req, _chosen:req
- func_compat check_erratum_\_cpu\()_\_id
+ errata_func_compat check_erratum_\_cpu\()_\_id
.if \_chosen
mov x0, #ERRATA_APPLIES
.else
mov x0, #ERRATA_MISSING
.endif
ret
- endfunc_compat check_erratum_\_cpu\()_\_id
+ errata_endfunc_compat check_erratum_\_cpu\()_\_id
.endm
/*
@@ -637,7 +668,7 @@
* body: clobber x0 to x4
*/
.macro check_erratum_custom_start _cpu:req, _cve:req, _id:req
- func_compat check_erratum_\_cpu\()_\_id
+ errata_func_compat check_erratum_\_cpu\()_\_id
.endm
.macro check_erratum_custom_end _cpu:req, _cve:req, _id:req
@@ -656,7 +687,7 @@
* Name of cpu as given to declare_cpu_ops
*/
.macro cpu_reset_prologue _cpu:req
- func_compat \_cpu\()_reset_func
+ errata_func_compat \_cpu\()_reset_func
mov x15, x30
get_rev_var x14, x0
.endm
@@ -675,13 +706,13 @@
.macro cpu_reset_func_start _cpu:req
/* the func/endfunc macros will change sections. So change the section
* back to the reset function's */
- .section .text.asm.\_cpu\()_reset_func, "ax"
+ .section .text.errata.\_cpu\()_reset_func, "ax"
.endm
.macro cpu_reset_func_end _cpu:req
isb
ret x15
- endfunc_compat \_cpu\()_reset_func
+ errata_endfunc_compat \_cpu\()_reset_func
.endm
/*
diff --git a/include/lib/xlat_tables/xlat_tables_v2.h b/include/lib/xlat_tables/xlat_tables_v2.h
index bfc4e5ce0..88c23e15b 100644
--- a/include/lib/xlat_tables/xlat_tables_v2.h
+++ b/include/lib/xlat_tables/xlat_tables_v2.h
@@ -390,6 +390,11 @@ int xlat_change_mem_attributes_ctx(const xlat_ctx_t *ctx, uintptr_t base_va,
size_t size, uint32_t attr);
int xlat_change_mem_attributes(uintptr_t base_va, size_t size, uint32_t attr);
+#if ENABLE_LFA_BL31
+/* Returns a pointer to the current xlat table, only used during BL31 LFA */
+const xlat_ctx_t *get_xlat_tables(void);
+#endif
+
#ifdef PLAT_RO_XLAT_TABLES
/*
* Change the memory attributes of the memory region encompassing the higher
diff --git a/include/plat/arm/common/arm_def.h b/include/plat/arm/common/arm_def.h
index ce44352b0..f1828f094 100644
--- a/include/plat/arm/common/arm_def.h
+++ b/include/plat/arm/common/arm_def.h
@@ -362,6 +362,20 @@ MEASURED_BOOT
MT_MEMORY | MT_RW | EL3_PAS)
#endif
+#if ENABLE_LFA_BL31
+#define ARM_MAP_LFA_RELOCATABLE_CODE \
+ MAP_REGION_FLAT( \
+ BL31_LIMIT - (2 * PAGE_SIZE), \
+ PAGE_SIZE, \
+ MT_CODE | EL3_PAS)
+
+#define ARM_MAP_LFA_RELOCATABLE_DATA \
+ MAP_REGION_FLAT( \
+ BL31_LIMIT - PAGE_SIZE, \
+ PAGE_SIZE, \
+ MT_MEMORY | MT_RW | EL3_PAS)
+#endif /* ENABLE_LFA_BL31 */
+
#if ENABLE_RMM
/*
* We add the EL3_RMM_SHARED size to RMM mapping to map the region as a block.
diff --git a/include/plat/arm/common/plat_arm_lfa_components.h b/include/plat/arm/common/plat_arm_lfa_components.h
index c7e825773..e97150edd 100644
--- a/include/plat/arm/common/plat_arm_lfa_components.h
+++ b/include/plat/arm/common/plat_arm_lfa_components.h
@@ -29,7 +29,9 @@
* Define platform-specific numeric IDs for LFA FVP components.
*/
typedef enum {
- LFA_BL31_COMPONENT = 0,
+#if ENABLE_LFA_BL31
+ LFA_BL31_COMPONENT,
+#endif
#if BL32_BASE
LFA_BL32_COMPONENT,
#endif /* BL32_BASE */
diff --git a/include/plat/common/plat_lfa.h b/include/plat/common/plat_lfa.h
index ad733b75d..3cb23a4dc 100644
--- a/include/plat/common/plat_lfa.h
+++ b/include/plat/common/plat_lfa.h
@@ -10,6 +10,15 @@
#include <services/lfa_component_desc.h>
#include <tools_share/uuid.h>
+/*
+ * This data structure contains component info retrieved from the platform-
+ * implemented API plat_lfa_get_components.
+ *
+ * lfa_component_id Component described by the data structure.
+ * uuid UUID of this component.
+ * activator Activator data structure used by this component.
+ * activation_pending Flag indicating whether this component can be activated.
+ */
typedef struct plat_lfa_component_info {
const uint32_t lfa_component_id;
const uuid_t uuid;
@@ -21,6 +30,9 @@ uint32_t plat_lfa_get_components(plat_lfa_component_info_t **components);
bool is_plat_lfa_activation_pending(uint32_t lfa_component_id);
int plat_lfa_cancel(uint32_t lfa_component_id);
int plat_lfa_load_auth_image(uint32_t lfa_component_id);
+int plat_lfa_get_image_info(uint32_t lfa_component_id, uintptr_t *image_address,
+ size_t *image_size);
int plat_lfa_notify_activate(uint32_t lfa_component_id);
+uint64_t plat_lfa_mailbox_base(void);
#endif /* PLAT_LFA_H */
diff --git a/include/services/lfa_component_desc.h b/include/services/lfa_component_desc.h
index aefef953a..d15041e17 100644
--- a/include/services/lfa_component_desc.h
+++ b/include/services/lfa_component_desc.h
@@ -16,20 +16,55 @@ typedef enum {
PRIME_COMPLETE,
} lfa_prime_status_t;
+/*
+ * This structure contains the status of a component that is currently in the
+ * process of being activated.
+ *
+ * component_id ID of the activation component currently in progress.
+ * Can be either RMM or BL31.
+ * prime_status Current prime status of this component.
+ * image_size Size of the image file to be activated.
+ * image_address Address of the image file to be activated.
+ * reset Flag indicating that the core will warm reset after the
+ * activation.
+ * cpu_rendezvous Flag indicating that active CPUs will rendezvous prior
+ * to activation. This is set to true by default and can
+ * only be cleared if the caller requests it and the flag
+ * cpu_rendezvous_required in the component ops structure
+ * is clear.
+ */
struct lfa_component_status {
uint32_t component_id;
lfa_prime_status_t prime_status;
- bool cpu_rendezvous_required;
+ size_t image_size;
+ uintptr_t image_address;
+ bool reset;
+ bool cpu_rendezvous;
};
typedef int (*component_prime_fn)(struct lfa_component_status *activation);
typedef int (*component_activate_fn)(struct lfa_component_status *activation,
uint64_t ep_address,
uint64_t context_id);
+typedef int (*component_cancel_fn)(struct lfa_component_status *activation);
+/*
+ * This structure is defined per-component and contains pointers to the prime
+ * and activate functions as well as some basic requirements for activation of
+ * that component.
+ *
+ * prime Pointer to prime function for a component.
+ * activate Pointer to activate function for a component.
+ * may_reset_cpu Flag letting the caller know to expect a CPU warm
+ * reset after activation.
+ * cpu_rendezvous_required Flag indicating that a CPU reset is not optional. If
+ * this flag is set, the caller cannot request skipping
+ * the CPU rendezvous.
+ */
struct lfa_component_ops {
component_prime_fn prime;
component_activate_fn activate;
+ component_cancel_fn cancel;
bool may_reset_cpu;
bool cpu_rendezvous_required;
};
diff --git a/include/services/lfa_holding_pen.h b/include/services/lfa_holding_pen.h
index 9420747ee..00473e898 100644
--- a/include/services/lfa_holding_pen.h
+++ b/include/services/lfa_holding_pen.h
@@ -16,4 +16,15 @@ bool lfa_holding_start(void);
enum lfa_retc lfa_holding_wait(void);
void lfa_holding_release(enum lfa_retc status);
+typedef uint64_t holding_lock_t;
+extern holding_lock_t lfa_r_holding_lock_var;
+extern uint64_t lfa_r_ep_addresses[PLATFORM_CORE_COUNT];
+extern uint64_t lfa_r_context_ids[PLATFORM_CORE_COUNT];
+
+void lfa_load_relocatable(void);
+void lfa_r_holding_lock(holding_lock_t *lock);
+void lfa_r_holding_unlock(holding_lock_t *lock);
+void lfa_r_holding_wait(holding_lock_t *lock);
+void lfa_r_holding_wait_warm_reset(holding_lock_t *lock);
+
#endif
diff --git a/include/services/lfa_svc.h b/include/services/lfa_svc.h
index f585cf3e5..3485a7234 100644
--- a/include/services/lfa_svc.h
+++ b/include/services/lfa_svc.h
@@ -77,6 +77,12 @@ enum lfa_retc {
LFA_ACTIVATION_FAILED = -11,
};
+typedef struct {
+ entry_point_info_t ep;
+ uint64_t spinlock_p;
+ uintptr_t plat_wr_mailbox;
+} lfa_mailbox_message_t;
+
/* Initialization routine for the LFA service */
int lfa_setup(void);
@@ -85,5 +91,6 @@ uint64_t lfa_smc_handler(uint32_t smc_fid, u_register_t x1, u_register_t x2,
void *handle, u_register_t flags);
void lfa_reset_activation(void);
bool lfa_is_prime_complete(uint32_t lfa_component_id);
+uint64_t prepare_warm_reset(uintptr_t lfa_ns_ep, uint64_t context_id, uint64_t cpu_spinlock);
#endif /* LFA_SVC_H */
diff --git a/lib/xlat_tables_v2/xlat_tables_context.c b/lib/xlat_tables_v2/xlat_tables_context.c
index d6f809006..a648dd97a 100644
--- a/lib/xlat_tables_v2/xlat_tables_context.c
+++ b/lib/xlat_tables_v2/xlat_tables_context.c
@@ -120,6 +120,13 @@ int xlat_change_mem_attributes(uintptr_t base_va, size_t size, uint32_t attr)
return xlat_change_mem_attributes_ctx(&tf_xlat_ctx, base_va, size, attr);
}
+#if ENABLE_LFA_BL31
+const xlat_ctx_t *get_xlat_tables(void)
+{
+ return &tf_xlat_ctx;
+}
+#endif
+
#ifdef PLAT_RO_XLAT_TABLES
/* Change the memory attributes of the descriptors which resolve the address
* range that belongs to the translation tables themselves, which are by default
diff --git a/make_helpers/constraints.mk b/make_helpers/constraints.mk
index a11603f33..5a828c0cd 100644
--- a/make_helpers/constraints.mk
+++ b/make_helpers/constraints.mk
@@ -62,8 +62,10 @@ endif
ifeq (${CTX_INCLUDE_EL2_REGS}, 1)
ifeq (${SPD},none)
ifeq (${ENABLE_RMM},0)
- $(error CTX_INCLUDE_EL2_REGS is available only when SPD \
- or RMM is enabled)
+ ifeq (${ENABLE_LFA_BL31},0)
+ $(error CTX_INCLUDE_EL2_REGS is available only when SPD \
+ RMM, or BL31 LFA is enabled)
+ endif
endif
endif
endif
diff --git a/make_helpers/defaults.mk b/make_helpers/defaults.mk
index 6e4d4f38d..4ef31efcd 100644
--- a/make_helpers/defaults.mk
+++ b/make_helpers/defaults.mk
@@ -457,6 +457,7 @@ RMM_V1_COMPAT := 0
# Live firmware activation support
LFA_SUPPORT := 0
+ENABLE_LFA_BL31 := 0
# Enable support for arm DSU driver.
USE_DSU_DRIVER := 0
diff --git a/plat/arm/board/fvp/fvp_common.c b/plat/arm/board/fvp/fvp_common.c
index 0e8f0ac14..d834ee2ec 100644
--- a/plat/arm/board/fvp/fvp_common.c
+++ b/plat/arm/board/fvp/fvp_common.c
@@ -109,6 +109,12 @@ arm_config_t arm_config;
#endif
#endif
+#if ENABLE_LFA_BL31
+#define MAP_LFA_STORE \
+ MAP_REGION_FLAT(PLAT_LFA_STORE_BASE, PLAT_LFA_STORE_SIZE, \
+ MT_MEMORY | MT_RO | MT_NS)
+#endif /* ENABLE_LFA_BL31 */
+
/*
* Table of memory regions for various BL stages to map using the MMU.
* This doesn't include Trusted SRAM as setup_page_tables() already takes care
@@ -237,6 +243,11 @@ const mmap_region_t plat_arm_mmap[] = {
#if defined(MAP_EL3_FW_HANDOFF) && !RESET_TO_BL31
MAP_EL3_FW_HANDOFF,
#endif
+#if ENABLE_LFA_BL31
+ ARM_MAP_LFA_RELOCATABLE_DATA,
+ ARM_MAP_LFA_RELOCATABLE_CODE,
+ MAP_LFA_STORE,
+#endif /* ENABLE_LFA_BL31 */
{ 0 }
};
diff --git a/plat/arm/board/fvp/fvp_lfa.c b/plat/arm/board/fvp/fvp_lfa.c
index bb26ccc18..efab8f53f 100644
--- a/plat/arm/board/fvp/fvp_lfa.c
+++ b/plat/arm/board/fvp/fvp_lfa.c
@@ -5,9 +5,12 @@
*/
#include <errno.h>
+
+#include <common/debug.h>
#include <plat/common/platform.h>
#include <services/bl31_lfa.h>
#include <services/el3_spmd_logical_sp.h>
+#include <services/lfa_svc.h>
#include <services/rmmd_rmm_lfa.h>
#include <tools_share/firmware_image_package.h>
@@ -15,17 +18,24 @@
/* Keep this array consistent with enum fvp_lfa_component_id_t */
static plat_lfa_component_info_t fvp_lfa_components[LFA_MAX_DEFINED_COMPONENTS] = {
+
+#if ENABLE_LFA_BL31
[LFA_BL31_COMPONENT] = {LFA_BL31_COMPONENT, UUID_EL3_RUNTIME_FIRMWARE_BL31,
NULL, false},
+#endif /* ENABLE_LFA_BL31 */
+
#if BL32_BASE
[LFA_BL32_COMPONENT] = {LFA_BL32_COMPONENT, UUID_SECURE_PAYLOAD_BL32,
NULL, false},
#endif /* BL32_BASE */
+
[LFA_BL33_COMPONENT] = {LFA_BL33_COMPONENT, UUID_NON_TRUSTED_FIRMWARE_BL33,
NULL, false},
+
#if ENABLE_RMM
[LFA_RMM_COMPONENT] = {LFA_RMM_COMPONENT, UUID_REALM_MONITOR_MGMT_FIRMWARE,
NULL, false},
+
#endif /* ENABLE_RMM */
#if SUPPORT_SP_LIVE_ACTIVATION
@@ -42,7 +52,10 @@ uint32_t plat_lfa_get_components(plat_lfa_component_info_t **components)
return -EINVAL;
}
+#if ENABLE_LFA_BL31
fvp_lfa_components[LFA_BL31_COMPONENT].activator = get_bl31_activator();
+#endif /* ENABLE_LFA_BL31 */
+
#if ENABLE_RMM
fvp_lfa_components[LFA_RMM_COMPONENT].activator = get_rmm_activator();
#endif /* ENABLE_RMM */
@@ -73,6 +86,12 @@ bool is_plat_lfa_activation_pending(uint32_t lfa_component_id)
}
#endif /* SUPPORT_SP_LIVE_ACTIVATION */
+#if ENABLE_LFA_BL31
+ if (lfa_component_id == LFA_BL31_COMPONENT) {
+ return true;
+ }
+#endif /* ENABLE_LFA_BL31 */
+
return false;
}
@@ -84,15 +103,39 @@ int plat_lfa_cancel(uint32_t lfa_component_id)
int plat_lfa_load_auth_image(uint32_t lfa_component_id)
{
+ return 0;
+}
+
+int plat_lfa_get_image_info(uint32_t lfa_component_id, uintptr_t *image_address,
+ size_t *image_size)
+{
+#if ENABLE_LFA_BL31
/*
* In AEM FVP, we don't want to bloat the code by adding
* loading and authentication mechanism, so here we assumed
* that the components are pre-loaded and authenticated already.
+ *
+ * The image is pre-loaded at 0xFB000000, size is not used for
+ * sectional copy.
*/
- return 0;
+ if (lfa_component_id == LFA_BL31_COMPONENT) {
+ *image_size = 0;
+ *image_address = (uintptr_t)(PLAT_LFA_STORE_BASE);
+ VERBOSE("FVP BL31 LFA image at 0x%lX with size %ld bytes\n",
+ (uint64_t)*image_address, *image_size);
+ return 0;
+ }
+#endif
+
+ return -EPERM;
}
int plat_lfa_notify_activate(uint32_t lfa_component_id)
{
return 0;
}
+
+uint64_t plat_lfa_mailbox_base(void)
+{
+ return PLAT_ARM_TRUSTED_MAILBOX_BASE;
+}
diff --git a/plat/arm/board/fvp/include/platform_def.h b/plat/arm/board/fvp/include/platform_def.h
index 34ff23b47..523fb749b 100644
--- a/plat/arm/board/fvp/include/platform_def.h
+++ b/plat/arm/board/fvp/include/platform_def.h
@@ -237,7 +237,11 @@
# define PLAT_SP_IMAGE_MMAP_REGIONS 31
# define PLAT_SP_IMAGE_MAX_XLAT_TABLES 13
# else
-# define PLAT_ARM_MMAP_ENTRIES 9
+# if ENABLE_LFA_BL31
+# define PLAT_ARM_MMAP_ENTRIES 12
+# else
+# define PLAT_ARM_MMAP_ENTRIES 9
+# endif
# if USE_DEBUGFS
# if ENABLE_RMM
# define MAX_XLAT_TABLES 9
@@ -648,4 +652,19 @@ FVP_TRUSTED_SRAM_SIZE == 512
*/
#define PLAT_DRTM_MMAP_ENTRIES PLAT_ARM_MMAP_ENTRIES
+#if ENABLE_LFA_BL31
+/*
+ * A 16MB carved out shared region with Non-Secure to preload firmware image to
+ * live activate for FVP test platforms.
+ */
+#define PLAT_LFA_STORE_BASE UL(0xFB000000)
+#define PLAT_LFA_STORE_SIZE SZ_16M
+
+/*
+ * The errata section is padded to allow for growth without affecting the rest
+ * of the binary.
+ */
+#define PLAT_LFA_ERRATA_SECTION_SIZE U(0x1000)
+#endif /* ENABLE_LFA_BL31 */
+
#endif /* PLATFORM_DEF_H */
diff --git a/services/std_svc/lfa/bl31_lfa.c b/services/std_svc/lfa/bl31_lfa.c
index 2a75065e1..cb6f42e0b 100644
--- a/services/std_svc/lfa/bl31_lfa.c
+++ b/services/std_svc/lfa/bl31_lfa.c
@@ -1,28 +1,258 @@
/*
- * Copyright (c) 2025, Arm Limited. All rights reserved.
+ * Copyright (c) 2025-2026, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
+#include <assert.h>
+#include <errno.h>
+
+#include <common/build_message.h>
+#include <common/debug.h>
+#include <lib/xlat_tables/xlat_tables_v2.h>
+#include <plat/common/platform.h>
#include <services/bl31_lfa.h>
+#include <services/lfa_holding_pen.h>
#include <services/lfa_svc.h>
-static int lfa_bl31_prime(struct lfa_component_status *activation)
+static const uintptr_t TEXT_START = (uintptr_t)__TEXT_START__;
+
+/* Static function prototypes. */
+static int lfa_bl31_check(uintptr_t base_va, size_t size, uint64_t **entry);
+static int lfa_r_bl31_copy(uintptr_t va, uint64_t *entry, void *src, size_t size, bool reset);
+
+static inline size_t lfa_bl31_errata_size(void)
+{
+ return ERRATA_END - ERRATA_START;
+}
+
+static inline size_t lfa_bl31_cpu_ops_size(void)
+{
+ return CPU_OPS_END - CPU_OPS_START;
+}
+
+static int lfa_bl31_patch_region(struct lfa_component_status *activation,
+ uintptr_t dst_va, size_t size, bool reset)
+{
+ uint64_t *entry = NULL;
+ size_t src_offset;
+ uintptr_t src_va;
+ uintptr_t patch_start;
+ uintptr_t patch_end;
+ size_t patch_size;
+
+ patch_start = dst_va & ~(PAGE_SIZE - 1U);
+ patch_end = (dst_va + size + PAGE_SIZE - 1U) & ~(PAGE_SIZE - 1U);
+ patch_size = patch_end - patch_start;
+
+ if ((patch_start < TEXT_START) ||
+ ((patch_size > activation->image_size) &&
+ (activation->image_size != 0))) {
+ ERROR("BL31 LFA: invalid patch region dst=0x%lx size=0x%zx image_size=0x%lx\n",
+ dst_va, size, activation->image_size);
+ return LFA_CRITICAL_ERROR;
+ }
+
+ src_offset = patch_start - TEXT_START;
+ if ((activation->image_size != 0) &&
+ ((src_offset > activation->image_size) ||
+ (patch_size > (activation->image_size - src_offset)))) {
+ ERROR("BL31 LFA: staged image too small for patch region 0x%lx size 0x%zx "
+ "aligned_start 0x%lx aligned_size 0x%zx\n",
+ dst_va, size, patch_start, patch_size);
+ return LFA_CRITICAL_ERROR;
+ }
+
+ src_va = activation->image_address + src_offset;
+
+ if (lfa_bl31_check(patch_start, patch_size, &entry) != 0) {
+ ERROR("BL31 LFA: xlat check failed for patch region dst=0x%lx size=0x%zx "
+ "aligned_start 0x%lx aligned_size 0x%zx\n",
+ dst_va, size, patch_start, patch_size);
+ return LFA_CRITICAL_ERROR;
+ }
+
+ lfa_r_bl31_copy(patch_start, entry, (void *)src_va, patch_size, reset);
+ return LFA_SUCCESS;
+}
+
+int32_t lfa_bl31_prime(struct lfa_component_status *activation)
+{
+ // TODO: Check that activation->image_size is not too large.
+ return LFA_SUCCESS;
+}
+
+/* Function that actually performs the activation of the firmware image */
+static int lfa_bl31_image_activation(struct lfa_component_status *activation,
+ uint64_t ep_address, uint64_t context_id)
+{
+ VERBOSE("BL31 LFA: Starting BL31 Image Activation (core %d)\n", plat_my_core_pos());
+ VERBOSE(" Address: 0x%lX\n", activation->image_address);
+ VERBOSE(" Size: %ld bytes\n", activation->image_size);
+ VERBOSE(" Reset: %s\n", activation->reset ? "yes" : "no");
+
+#if !ENABLE_RUNTIME_INSTRUMENTATION
+ /*
+ * Used to avoid console corruption. Not used if measuring performance
+ * since printouts are removed.
+ */
+ console_flush();
+#endif
+
+ /* Prepare for warm reset and setup NS entrypoint for primary cores */
+ if (activation->reset) {
+ if (prepare_warm_reset(ep_address, context_id,
+ (uint64_t)&lfa_r_holding_lock_var) != LFA_SUCCESS) {
+ lfa_r_holding_unlock(&lfa_r_holding_lock_var);
+ return LFA_CRITICAL_ERROR;
+ }
+ }
+
+ if (lfa_bl31_patch_region(activation, CPU_OPS_START,
+ lfa_bl31_cpu_ops_size(), false) != LFA_SUCCESS) {
+ return LFA_CRITICAL_ERROR;
+ }
+
+ /*
+ * Patch the errata section last so reset, if requested, is triggered
+ * only after cpu_ops has been updated to point at the new errata code.
+ */
+ return lfa_bl31_patch_region(activation, ERRATA_START,
+ lfa_bl31_errata_size(),
+ activation->reset);
+}
+
+int32_t lfa_bl31_activate(struct lfa_component_status *activation,
+ uint64_t ep_address, uint64_t context_id)
{
- return LFA_WRONG_STATE;
+ uint32_t core_pos = plat_my_core_pos();
+ int ret = LFA_SUCCESS;
+
+ lfa_r_ep_addresses[core_pos] = ep_address;
+ lfa_r_context_ids[core_pos] = context_id;
+
+ /* Case when Firmware requires CPU Rendezvous for LFA Activation */
+ if (activation->cpu_rendezvous) {
+ /*
+ * Only one core will return true from this function and it
+ * controls the activation process.
+ */
+ if (lfa_holding_start()) {
+ VERBOSE("BL31 LFA: CPU %d is leader core\n", core_pos);
+
+ /*
+ * Before we release other cores from the holding pen
+ * try to get the image info. That way if this fails the
+ * system will still be in a recoverable state.
+ */
+ ret = plat_lfa_get_image_info(activation->component_id,
+ &activation->image_address,
+ &activation->image_size);
+ if (ret) {
+ ERROR("BL31 LFA: Could not get image info!\n");
+ lfa_holding_release(LFA_CRITICAL_ERROR);
+ return LFA_CRITICAL_ERROR;
+ }
+
+ /*
+ * Only one core will load the relocatable code module and
+ * acquire the relocatable lock, then release the rest of the
+ * cores which will then wait for us to release the relocatable
+ * lock once the activation is complete.
+ */
+ lfa_load_relocatable();
+ lfa_r_holding_lock(&lfa_r_holding_lock_var);
+ lfa_holding_release(LFA_SUCCESS);
+
+ /*
+ * Perform image activation. Will not return if reset
+ * requested.
+ */
+ ret = lfa_bl31_image_activation(activation, ep_address, context_id);
+
+ /* Unlock holding pen for all other cores */
+ lfa_r_holding_unlock(&lfa_r_holding_lock_var);
+
+ if (ret) {
+ ERROR("BL31 LFA: failed image activation with error: %d\n", ret);
+ return ret;
+ }
+
+ INFO("BL31 LFA: Successful image activation!\n");
+ } else {
+ /* Wait until release from rendezvous holding pen. */
+ ret = lfa_holding_wait();
+ if (ret != LFA_SUCCESS) {
+ return ret;
+ }
+
+ if (activation->reset) {
+ /*
+ * Prepare for warm reset and setup NS
+ * entrypoint for secondary cores
+ */
+ if (prepare_warm_reset(ep_address, context_id,
+ (uint64_t)&lfa_r_holding_lock_var) != LFA_SUCCESS) {
+ return LFA_CRITICAL_ERROR;
+ }
+
+ /*
+ * Wait until activation of the image is
+ * complete and warm reset occurs on main core
+ * then reset.
+ */
+ lfa_r_holding_wait_warm_reset(&lfa_r_holding_lock_var);
+ } else {
+ /*
+ * Wait until main core is completed with
+ * activation
+ */
+ lfa_r_holding_wait(&lfa_r_holding_lock_var);
+ }
+ }
+ }
+
+ /*
+ * No CPU rendezvous required. Perform LFA Activate with single core
+ * NOTE: Assumption that only one core will call activate with
+ * CPU_RENDEZVOUS=0
+ */
+ else {
+ /*
+ * Loading relocatable module normally happens in the holding
+ * pen. Since there is no holding pen without CPU rendezvous we
+ * are doing it here instead.
+ */
+ lfa_load_relocatable();
+
+ /* Perform actual image activation */
+ ret = lfa_bl31_image_activation(activation, ep_address, context_id);
+ if (ret) {
+ ERROR("Failed image activation with error: %d\n", ret);
+ return ret;
+ }
+ }
+
+ NOTICE("BL31 Live Activation Without Reset %u\n", plat_my_core_pos());
+ NOTICE(" Version : %s\n", build_version_string);
+ NOTICE(" %s\n", build_message);
+
+ return ret;
}
-static int lfa_bl31_activate(struct lfa_component_status *activation,
- uint64_t ep_address,
- uint64_t context_id)
+int32_t lfa_bl31_cancel(struct lfa_component_status *activation)
{
- return LFA_WRONG_STATE;
+ /* Let CPUs out of the holding pen if needed. */
+ lfa_holding_release(LFA_ACTIVATION_FAILED);
+
+ return LFA_SUCCESS;
}
-static struct lfa_component_ops bl31_activator = {
+struct lfa_component_ops bl31_activator = {
.prime = lfa_bl31_prime,
.activate = lfa_bl31_activate,
- .may_reset_cpu = false,
+ .cancel = lfa_bl31_cancel,
+ .may_reset_cpu = true,
.cpu_rendezvous_required = true,
};
@@ -30,3 +260,128 @@ struct lfa_component_ops *get_bl31_activator(void)
{
return &bl31_activator;
}
+
+uint64_t *lfa_get_xlat_table_entry(uintptr_t virtual_addr, const xlat_ctx_t *ctx)
+{
+ uint64_t *table = ctx->base_table;
+ unsigned int entries = ctx->base_table_entries;
+ unsigned int start_level = GET_XLAT_TABLE_LEVEL_BASE((unsigned long long)ctx->va_max_address + 1ULL);
+
+ for (unsigned int level = start_level; level <= XLAT_TABLE_LEVEL_MAX; level++) {
+ uint64_t idx, desc, desc_type;
+
+ idx = XLAT_TABLE_IDX(virtual_addr, level);
+ if (idx >= entries) {
+ return NULL;
+ }
+
+ desc = table[idx];
+ desc_type = desc & DESC_MASK;
+
+ if (desc_type == INVALID_DESC) {
+ return NULL;
+ }
+
+ if (level == XLAT_TABLE_LEVEL_MAX) {
+ /*
+ * Only page descriptors allowed at the final lookup
+ * level.
+ */
+ assert(desc_type == PAGE_DESC);
+ return &table[idx];
+ }
+
+ if (desc_type == BLOCK_DESC) {
+ return &table[idx];
+ }
+
+ assert(desc_type == TABLE_DESC);
+ table = (uint64_t *)(uintptr_t)(desc & TABLE_ADDR_MASK);
+ entries = XLAT_TABLE_ENTRIES;
+ }
+
+ return NULL;
+}
+
+int lfa_bl31_check(uintptr_t base_va, size_t size, uint64_t **entry)
+{
+ const xlat_ctx_t *ctx = get_xlat_tables();
+
+ assert(ctx != NULL);
+ assert(ctx->initialized);
+
+ if (!IS_PAGE_ALIGNED(base_va)) {
+ WARN("%s: Address 0x%lx is not aligned on a page boundary.\n", __func__, base_va);
+ return -EINVAL;
+ }
+
+ if (size == 0U) {
+ WARN("%s: Size is 0.\n", __func__);
+ return -EINVAL;
+ }
+
+ size_t pages_count = size / PAGE_SIZE;
+
+ VERBOSE("BL31 update will impact %zu pages starting from address 0x%lx...\n",
+ pages_count, base_va);
+
+ *entry = lfa_get_xlat_table_entry(base_va, ctx);
+
+ uint64_t *last = lfa_get_xlat_table_entry(base_va + (pages_count - 1) * PAGE_SIZE, ctx);
+
+ /* Check that all descriptors are in the same page. */
+ if ((((uint64_t)*entry) >> 12) != ((((uint64_t)last) >> 12))) {
+ ERROR("All page descriptors are not part of the same page.\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+__attribute__((noinline, section(".lfa_relocatable_code")))
+int lfa_r_bl31_copy(uintptr_t base_va, uint64_t *entry, void *src, size_t size, bool reset)
+{
+ do {
+ uint64_t desc = *entry;
+ size_t count = MIN(PAGE_SIZE, size) / sizeof(uint64_t);
+
+ *entry = INVALID_DESC;
+ dsbishst();
+ tlbivae3is(TLBI_ADDR(base_va));
+ dsbish();
+ isb();
+ *entry = (desc & ~LOWER_ATTRS(AP_RO));
+ dsbish();
+
+ for (uint64_t i = 0; i < count; i++) {
+ ((uint64_t *) base_va)[i] = ((uint64_t *) src)[i];
+ }
+ if (size <= PAGE_SIZE) {
+ for (uint64_t i = count * sizeof(uint64_t); i < size; i++) {
+ ((uint8_t *) base_va)[i] = ((uint8_t *) src)[i];
+ }
+ size = 0;
+ } else {
+ size -= PAGE_SIZE;
+ }
+
+ *entry = INVALID_DESC;
+ dsbishst();
+ tlbivae3is(TLBI_ADDR(base_va));
+ dsbish();
+ isb();
+ *entry = desc;
+
+ base_va += PAGE_SIZE;
+ src += PAGE_SIZE;
+ entry++;
+ } while (size > 0);
+
+ dsbish();
+
+ if (reset) {
+ wfi();
+ }
+
+ return 0;
+}
diff --git a/services/std_svc/lfa/lfa.mk b/services/std_svc/lfa/lfa.mk
index 53af69fd7..d3d5cd864 100644
--- a/services/std_svc/lfa/lfa.mk
+++ b/services/std_svc/lfa/lfa.mk
@@ -6,9 +6,15 @@
LFA_SOURCES += $(addprefix services/std_svc/lfa/, \
lfa_main.c \
- bl31_lfa.c \
lfa_holding_pen.c)
ifeq (${ENABLE_RMM}, 1)
LFA_SOURCES += services/std_svc/rmmd/rmmd_rmm_lfa.c
endif
+
+ifeq (${ENABLE_LFA_BL31}, 1)
+LFA_SOURCES += $(addprefix services/std_svc/lfa/, \
+ bl31_lfa.c \
+ lfa_relocatable.S \
+ lfa_reset.c)
+endif
diff --git a/services/std_svc/lfa/lfa_holding_pen.c b/services/std_svc/lfa/lfa_holding_pen.c
index 9aa1e8c30..39eac41a8 100644
--- a/services/std_svc/lfa/lfa_holding_pen.c
+++ b/services/std_svc/lfa/lfa_holding_pen.c
@@ -7,10 +7,12 @@
#include <string.h>
+#include <common/bl_common.h>
#include <common/debug.h>
#include <lib/psci/psci_lib.h>
#include <lib/spinlock.h>
#include <lib/utils_def.h>
+#include <lib/xlat_tables/xlat_tables_v2.h>
#include <plat/common/platform.h>
#include <services/lfa_holding_pen.h>
@@ -18,7 +20,7 @@
static spinlock_t holding_lock;
static spinlock_t activation_lock;
-static uint32_t activation_count;
+static volatile uint32_t activation_count;
static enum lfa_retc activation_status;
/**
@@ -44,28 +46,31 @@ static enum lfa_retc activation_status;
*/
bool lfa_holding_start(void)
{
- bool status;
- unsigned int no_of_cpus;
+ unsigned int no_of_cpus = psci_num_cpus_running_on_safe(plat_my_core_pos());
+ bool last_cpu;
+ /*
+ * This lock ensures only one CPU uses the activation_count
+ * variable at a time.
+ */
spin_lock(&activation_lock);
+ /* First CPU to arrive locks the holding pen. */
if (activation_count == 0U) {
- /* First CPU locks holding lock */
spin_lock(&holding_lock);
}
activation_count += 1U;
+ last_cpu = (activation_count == no_of_cpus);
- no_of_cpus = psci_num_cpus_running_on_safe(plat_my_core_pos());
- status = (activation_count == no_of_cpus);
- if (!status) {
- VERBOSE("Hold, %d CPU left\n",
- no_of_cpus - activation_count);
+ if (!last_cpu) {
+ VERBOSE("Hold, %d CPU left\n", no_of_cpus - activation_count);
}
+ /* Release the lock once count is updated. */
spin_unlock(&activation_lock);
- return status;
+ return last_cpu;
}
/**
@@ -103,3 +108,42 @@ void lfa_holding_release(enum lfa_retc status)
activation_status = status;
spin_unlock(&holding_lock);
}
+
+#if ENABLE_LFA_BL31
+void lfa_load_relocatable(void)
+{
+ int ret;
+
+ /*
+ * The relocatable code area is outside of the normal BL31 image, so
+ * before we can use it we have to make it writeable then copy the
+ * relocatable code into it. Then once the copy is complete we must
+ * mark it read only and executable again.
+ */
+ ret = xlat_change_mem_attributes(LFA_RELOCATABLE_CODE_START,
+ (LFA_RELOCATABLE_DATA_START - LFA_RELOCATABLE_CODE_START),
+ MT_MEMORY | MT_RW | MT_EXECUTE_NEVER | EL3_PAS);
+ if (ret != 0) {
+ ERROR("LFA: Could not update memory attributes: %d\n", ret);
+ panic();
+ }
+
+ memcpy((void *)LFA_RELOCATABLE_CODE_START, (void *)LFA_RELOCATABLE_LMA,
+ (uintptr_t)(LFA_RELOCATABLE_DATA_START - LFA_RELOCATABLE_CODE_START));
+
+ ret = xlat_change_mem_attributes(LFA_RELOCATABLE_CODE_START,
+ (LFA_RELOCATABLE_DATA_START - LFA_RELOCATABLE_CODE_START),
+ MT_MEMORY | MT_RO | MT_EXECUTE | EL3_PAS);
+ if (ret != 0) {
+ ERROR("LFA: Could not update memory attributes: %d\n", ret);
+ panic();
+ }
+
+ /*
+ * The relocatable data section contains unknown information at this
+ * point so zero it out before we use it.
+ */
+ memset((void *)LFA_RELOCATABLE_DATA_START, 0,
+ (LFA_RELOCATABLE_DATA_END - LFA_RELOCATABLE_DATA_START));
+}
+#endif /* ENABLE_LFA_BL31 */
diff --git a/services/std_svc/lfa/lfa_main.c b/services/std_svc/lfa/lfa_main.c
index 819b169a7..31c05bf58 100644
--- a/services/std_svc/lfa/lfa_main.c
+++ b/services/std_svc/lfa/lfa_main.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025, Arm Limited. All rights reserved.
+ * Copyright (c) 2025-2026, Arm Limited. All rights reserved.
* Copyright (c) 2025, NVIDIA Corporation. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
@@ -15,10 +15,23 @@
#include <services/rmmd_rmm_lfa.h>
#include <smccc_helpers.h>
-static uint32_t lfa_component_count;
static plat_lfa_component_info_t *lfa_components;
static struct lfa_component_status current_activation;
-static bool is_lfa_initialized;
+
+/*
+ * Multiple cores call into LFA service which shares these variables, so ensure
+ * that any accesses to these variables go back to memory. These are sort of
+ * "lazy" state variables where timing isn't really critical so we aren't
+ * explicitly using mutex for access control but we just want to make sure
+ * nothing is optimized away.
+ */
+static volatile uint32_t lfa_component_count;
+static volatile bool is_lfa_initialized;
+static volatile bool activation_in_progress;
+static volatile bool activation_skip_cpu_rendezvous;
+static volatile uint32_t activation_users;
+static volatile bool activation_failed;
+static volatile bool activation_cancel_locked;
/*
* Spinlock to serialize LFA operations (PRIME, ACTIVATE).
@@ -32,7 +45,12 @@ void lfa_reset_activation(void)
{
current_activation.component_id = LFA_INVALID_COMPONENT;
current_activation.prime_status = PRIME_NONE;
- current_activation.cpu_rendezvous_required = false;
+ current_activation.cpu_rendezvous = false;
+ activation_in_progress = false;
+ activation_skip_cpu_rendezvous = false;
+ activation_users = 0U;
+ activation_failed = false;
+ activation_cancel_locked = false;
}
static int convert_to_lfa_error(int ret)
@@ -54,8 +72,7 @@ static bool lfa_initialize_components(void)
lfa_component_count = plat_lfa_get_components(&lfa_components);
if (lfa_component_count == 0U || lfa_components == NULL) {
- /* unlikely to reach here */
- ERROR("Invalid LFA component setup: count = 0 or components are NULL");
+ ERROR("Could not retrieve LFA components.\n");
return false;
}
@@ -64,8 +81,7 @@ static bool lfa_initialize_components(void)
static uint64_t get_fw_activation_flags(uint32_t fw_seq_id)
{
- const plat_lfa_component_info_t *comp =
- &lfa_components[fw_seq_id];
+ const plat_lfa_component_info_t *comp = &lfa_components[fw_seq_id];
uint64_t flags = 0ULL;
flags |= ((comp->activator == NULL ? 0ULL : 1ULL)
@@ -86,6 +102,7 @@ static uint64_t get_fw_activation_flags(uint32_t fw_seq_id)
static int lfa_cancel(uint32_t component_id)
{
int ret = LFA_SUCCESS;
+ struct lfa_component_ops *activator;
if (lfa_component_count == 0U) {
return LFA_WRONG_STATE;
@@ -97,76 +114,33 @@ static int lfa_cancel(uint32_t component_id)
return LFA_INVALID_PARAMETERS;
}
- ret = plat_lfa_cancel(component_id);
- if (ret != LFA_SUCCESS) {
+ /*
+ * Deny cancellation once the activation round is no longer cancellable.
+ */
+ if (activation_in_progress &&
+ (activation_cancel_locked || activation_skip_cpu_rendezvous)) {
return LFA_BUSY;
}
- /* TODO: add proper termination prime and activate phases */
- lfa_reset_activation();
-
- return ret;
-}
-
-static int lfa_activate(uint32_t component_id, uint64_t flags,
- uint64_t ep_address, uint64_t context_id)
-{
- int ret = LFA_ACTIVATION_FAILED;
- struct lfa_component_ops *activator;
-
- if ((lfa_component_count == 0U) ||
- (!lfa_components[component_id].activation_pending) ||
- (current_activation.prime_status != PRIME_COMPLETE)) {
- return LFA_COMPONENT_WRONG_STATE;
- }
-
- /* Check if fw_seq_id is in range. */
- if ((component_id >= lfa_component_count) ||
- (current_activation.component_id != component_id)) {
- return LFA_INVALID_PARAMETERS;
- }
-
- if (lfa_components[component_id].activator == NULL) {
- return LFA_NOT_SUPPORTED;
- }
-
- ret = plat_lfa_notify_activate(component_id);
- if (ret != 0) {
- return LFA_ACTIVATION_FAILED;
+ activator = lfa_components[component_id].activator;
+ if (activator->cancel != NULL) {
+ ret = activator->cancel(&current_activation);
+ if (ret != LFA_SUCCESS) {
+ return LFA_BUSY;
+ }
}
- activator = lfa_components[component_id].activator;
- if (activator->activate != NULL) {
+ ret = plat_lfa_cancel(component_id);
+ if (ret != LFA_SUCCESS) {
/*
- * Pass skip_cpu_rendezvous (flag[0]) only if flag[0]==1
- * & CPU_RENDEZVOUS is not required.
+ * Cancellation can fail when activation cannot be stopped
+ * (e.g. activation requested with skip_cpu_rendezvous=1).
*/
- if (flags & LFA_SKIP_CPU_RENDEZVOUS_BIT) {
- if (!activator->cpu_rendezvous_required) {
- INFO("Skipping rendezvous requested by caller.\n");
- current_activation.cpu_rendezvous_required = false;
- }
- /*
- * Return error if caller tries to skip rendezvous when
- * it is required.
- */
- else {
- ERROR("CPU Rendezvous is required, can't skip.\n");
- return LFA_INVALID_PARAMETERS;
- }
- }
-
- ret = activator->activate(&current_activation, ep_address,
- context_id);
+ return LFA_BUSY;
}
- /*
- * Update the activation pending flag only if the activation was
- * successful.
- */
- if (ret == LFA_SUCCESS) {
- lfa_components[component_id].activation_pending = false;
- }
+ /* TODO: add proper termination prime and activate phases */
+ lfa_reset_activation();
return ret;
}
@@ -225,7 +199,13 @@ static int lfa_prime(uint32_t component_id, uint64_t *flags)
}
}
+ /*
+ * Update current activation status fields. CPU rendezvous is enabled
+ * by default but can be disabled if requested and allowed.
+ */
+ current_activation.cpu_rendezvous = true;
current_activation.prime_status = PRIME_COMPLETE;
+ current_activation.reset = lfa_components[component_id].activator->may_reset_cpu;
}
/*
@@ -264,12 +244,122 @@ int lfa_setup(void)
return 0;
}
+static int lfa_activate_prepare(uint32_t component_id, uint64_t flags,
+ struct lfa_component_ops **activator)
+{
+ int ret = LFA_ACTIVATION_FAILED;
+ /* First caller decides activation policy and performs platform notify. */
+ bool first_cpu = !activation_in_progress;
+
+ /* Check if fw_seq_id is in range. */
+ if ((component_id >= lfa_component_count) ||
+ (current_activation.component_id != component_id)) {
+ return LFA_INVALID_PARAMETERS;
+ }
+
+ if ((lfa_component_count == 0U) ||
+ (!lfa_components[component_id].activation_pending) ||
+ (current_activation.prime_status != PRIME_COMPLETE)) {
+ return LFA_COMPONENT_WRONG_STATE;
+ }
+
+ if (lfa_components[component_id].activator == NULL) {
+ return LFA_NOT_SUPPORTED;
+ }
+
+ *activator = lfa_components[component_id].activator;
+
+ if (first_cpu) {
+ activation_failed = false;
+
+ /*
+ * If rendezvous is optional for this component, default to
+ * rendezvous unless caller explicitly asks to skip it.
+ * This choice is latched for the whole activation.
+ */
+ if (!(*activator)->cpu_rendezvous_required &&
+ ((flags & LFA_SKIP_CPU_RENDEZVOUS_BIT) == 0U)) {
+ current_activation.cpu_rendezvous = true;
+ } else {
+ current_activation.cpu_rendezvous =
+ (*activator)->cpu_rendezvous_required;
+ }
+ activation_skip_cpu_rendezvous = false;
+ activation_cancel_locked = false;
+ }
+ /*
+ * Pass skip_cpu_rendezvous (flag[0]) only if flag[0]==1
+ * & CPU_RENDEZVOUS is not required.
+ */
+ if (flags & LFA_SKIP_CPU_RENDEZVOUS_BIT) {
+ if (!(*activator)->cpu_rendezvous_required) {
+ /*
+ * Late skip request is rejected if activation already
+ * started without skip.
+ */
+ if (!first_cpu && !activation_skip_cpu_rendezvous) {
+ return LFA_BUSY;
+ }
+ INFO("Skipping rendezvous requested by caller.\n");
+ current_activation.cpu_rendezvous = false;
+ activation_skip_cpu_rendezvous = true;
+ }
+ /*
+ * Return error if caller tries to skip rendezvous when
+ * it is required.
+ */
+ else {
+ ERROR("CPU Rendezvous is required, can't skip.\n");
+ return LFA_INVALID_PARAMETERS;
+ }
+ }
+
+ if (first_cpu) {
+ /* Notify platform once per activation round. */
+ ret = plat_lfa_notify_activate(component_id);
+ if (ret != 0) {
+ return LFA_ACTIVATION_FAILED;
+ }
+ activation_in_progress = true;
+ }
+
+ /* Track how many CPUs have entered LFA_ACTIVATE for this round. */
+ activation_users += 1U;
+ if (activation_users >= psci_num_cpus_running_on_safe(plat_my_core_pos())) {
+ activation_cancel_locked = true;
+ }
+
+ return LFA_SUCCESS;
+}
+
+
+static void lfa_activate_finish(uint32_t component_id, bool activation_complete)
+{
+ if (activation_users > 0U) {
+ activation_users -= 1U;
+ }
+
+ if (!activation_complete) {
+ return;
+ }
+
+ if (activation_users == 0U) {
+ activation_in_progress = false;
+ activation_skip_cpu_rendezvous = false;
+ activation_cancel_locked = false;
+ if (!activation_failed) {
+ lfa_components[component_id].activation_pending = false;
+ }
+ }
+}
+
uint64_t lfa_smc_handler(uint32_t smc_fid, u_register_t x1, u_register_t x2,
u_register_t x3, u_register_t x4, void *cookie,
void *handle, u_register_t flags)
{
- uint64_t retx1, retx2;
- uint64_t lfa_flags;
+ uint64_t retx1;
+ uint64_t retx2;
+ uint64_t lfa_flags = 0;
uint8_t *uuid_p;
uint32_t fw_seq_id = (uint32_t)x1;
int ret;
@@ -292,9 +382,9 @@ uint64_t lfa_smc_handler(uint32_t smc_fid, u_register_t x1, u_register_t x2,
break;
case LFA_GET_INFO:
- /**
+ /*
* The current specification limits this input parameter to be zero for
- * version 1.0 of LFA
+ * version 1.0 of LFA.
*/
if (x1 == 0ULL) {
SMC_RET3(handle, LFA_SUCCESS, lfa_component_count, 0);
@@ -309,29 +399,27 @@ uint64_t lfa_smc_handler(uint32_t smc_fid, u_register_t x1, u_register_t x2,
}
/*
- * Check if fw_seq_id is in range. LFA_GET_INFO must be called first to scan
- * platform firmware and create a valid number of firmware components.
+ * Check if fw_seq_id is in range. LFA_GET_INFO must be called
+ * first to scan platform firmware and create a valid number of
+ * firmware components.
*/
if (fw_seq_id >= lfa_component_count) {
SMC_RET1(handle, LFA_INVALID_PARAMETERS);
}
- /*
- * grab the UUID of asked fw_seq_id and set the return UUID
- * variables
- */
+ /* Get the UUID of requested fw_seq_id. */
uuid_p = (uint8_t *)&lfa_components[fw_seq_id].uuid;
memcpy(&retx1, uuid_p, sizeof(uint64_t));
memcpy(&retx2, uuid_p + sizeof(uint64_t), sizeof(uint64_t));
/*
- * check the given fw_seq_id's update available
- * and accordingly set the active_pending flag
+ * Check the given fw_seq_id update available and accordingly
+ * set the active_pending flag.
*/
lfa_components[fw_seq_id].activation_pending =
is_plat_lfa_activation_pending(fw_seq_id);
- INFO("Component %lu %s live activation:\n", x1,
+ INFO("Component %lu %s live activation.\n", x1,
lfa_components[fw_seq_id].activator ? "supports" :
"does not support");
@@ -340,10 +428,7 @@ uint64_t lfa_smc_handler(uint32_t smc_fid, u_register_t x1, u_register_t x2,
lfa_components[fw_seq_id].activation_pending ? "true" : "false");
}
- INFO("x1 = 0x%016lx, x2 = 0x%016lx\n", retx1, retx2);
-
SMC_RET4(handle, LFA_SUCCESS, retx1, retx2, get_fw_activation_flags(fw_seq_id));
-
break;
case LFA_PRIME:
@@ -369,25 +454,111 @@ uint64_t lfa_smc_handler(uint32_t smc_fid, u_register_t x1, u_register_t x2,
break;
case LFA_ACTIVATE:
- ret = lfa_activate(fw_seq_id, x2, x3, x4);
- /* TODO: implement activate again */
- SMC_RET2(handle, ret, 0ULL);
+ /* LFA_ACTIVATE flow:
+ * - LFA_SKIP_CPU_RENDEZVOUS_BIT controls skip request on entry.
+ * - Serialize with lfa_lock, but never block behind a
+ * skip-rendezvous activation.
+ * - lfa_activate_prepare() validates state, selects activator,
+ * and latches policy.
+ * - The first CPU decides rendezvous policy for the whole
+ * activation.
+ * - Skip rendezvous keeps the lock for the full activation;
+ * normal path releases it.
+ * - activator->activate() may return -EAGAIN, mapped to
+ * LFA_CALL_AGAIN in lfa_flags.
+ * - lfa_activate_finish() updates bookkeeping and clears
+ * in-progress state when done.
+ */
+ bool hold_lock = false;
+ bool activation_complete = true;
+ struct lfa_component_ops *activator;
+ /* Caller request: skip CPU rendezvous for this activation. */
+ bool skip_cpu_rendezvous =
+ ((x2 & LFA_SKIP_CPU_RENDEZVOUS_BIT) != 0U);
+
+ /*
+ * When skip_cpu_rendezvous=1 (or when a skip rendezvous
+ * activation is already in progress), concurrent LFA_ACTIVATE
+ * calls must not be accepted; return LFA_BUSY instead of
+ * blocking.
+ */
+ if (skip_cpu_rendezvous) {
+ /* Non-blocking serialization for skip rendezvous requests. */
+ if (!spin_trylock(&lfa_lock)) {
+ SMC_RET1(handle, LFA_BUSY);
+ }
+ } else {
+ /*
+ * Normal path: take the lock, but reject if a skip
+ * activation is in progress.
+ */
+ spin_lock(&lfa_lock);
+ if (activation_skip_cpu_rendezvous) {
+ spin_unlock(&lfa_lock);
+ SMC_RET1(handle, LFA_BUSY);
+ }
+ }
+
+ ret = lfa_activate_prepare(fw_seq_id, x2, &activator);
+ hold_lock = (ret == LFA_SUCCESS) && activation_skip_cpu_rendezvous;
+
+ /*
+ * Keep the lock held for the full activation only when skip
+ * rendezvous is in effect.
+ */
+ if (!hold_lock) {
+ spin_unlock(&lfa_lock);
+ }
+
+ if (ret != LFA_SUCCESS) {
+ SMC_RET2(handle, ret, 0ULL);
+ }
+
+ if (activator->activate != NULL) {
+ ret = activator->activate(&current_activation, x3, x4);
+ }
+
+ /* Reacquire lock before updating shared activation state. */
+ if (!hold_lock) {
+ spin_lock(&lfa_lock);
+ }
+
+ lfa_flags = 0ULL;
+ if (ret == -EAGAIN) {
+ /* Multi-stage activation: caller must reissue LFA_ACTIVATE. */
+ ret = LFA_SUCCESS;
+ lfa_flags = LFA_CALL_AGAIN;
+ activation_complete = false;
+ } else if (ret != LFA_SUCCESS) {
+ activation_failed = true;
+ }
+
+ /* Update activation bookkeeping and clear in-progress state if complete. */
+ lfa_activate_finish(fw_seq_id, activation_complete);
+
+ spin_unlock(&lfa_lock);
+
+ SMC_RET2(handle, ret, lfa_flags);
break;
case LFA_CANCEL:
+ if (!spin_trylock(&lfa_lock)) {
+ SMC_RET1(handle, LFA_BUSY);
+ }
+
ret = lfa_cancel(x1);
+
+ spin_unlock(&lfa_lock);
+
SMC_RET1(handle, ret);
break;
default:
WARN("Unimplemented LFA Service Call: 0x%x\n", smc_fid);
SMC_RET1(handle, SMC_UNK);
- break; /* unreachable */
-
+ break;
}
- SMC_RET1(handle, SMC_UNK);
-
return 0;
}
diff --git a/services/std_svc/lfa/lfa_relocatable.S b/services/std_svc/lfa/lfa_relocatable.S
new file mode 100644
index 000000000..7436584bb
--- /dev/null
+++ b/services/std_svc/lfa/lfa_relocatable.S
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2026, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <asm_macros.S>
+#include <platform_def.h>
+
+/* Relocatable code section */
+
+ .globl lfa_r_holding_lock
+ .globl lfa_r_holding_wait
+ .globl lfa_r_holding_unlock
+ .globl lfa_r_holding_wait_warm_reset
+
+/*
+ * The 'rfunc' macro is a modified version of the 'func' macro in
+ * "include/common/asm_macros_common.S". It differs in that it does not change
+ * the section to ".text.asm". See the original version for comments.
+ */
+ .macro rfunc _name, _align=2
+ .cfi_sections .debug_frame
+ .section .lfa_relocatable_code.\_name, "ax"
+ .type \_name, %function
+ .cfi_startproc
+ .align \_align
+ \_name:
+#if ENABLE_BTI
+ bti jc
+#endif
+ .endm
+
+/*
+ * Enable holding lock.
+ *
+ * void lfa_r_holding_lock(spinlock_t *lock);
+ */
+rfunc lfa_r_holding_lock
+ mov w1, #1
+ stlr w1, [x0]
+ ret
+endfunc lfa_r_holding_lock
+
+/*
+ * Release hold.
+ *
+ * Use store-release to unconditionally clear the spinlock variable.
+ * Store operation generates an event to all cores waiting in WFE
+ * when address is monitored by the global monitor.
+ *
+ * void lfa_r_holding_unlock(spinlock_t *lock);
+ */
+rfunc lfa_r_holding_unlock
+ mov w1, #0
+ stlr w1, [x0]
+ ret
+endfunc lfa_r_holding_unlock
+
+/*
+ * Check lock using load-exclusive instruction.
+ *
+ * void lfa_r_holding_wait(spinlock_t *lock);
+ */
+rfunc lfa_r_holding_wait
+ sevl
+l1: wfe
+ ldaxr w1, [x0]
+ cbnz w1, l1
+ ret
+endfunc lfa_r_holding_wait
+
+/*
+ * Check lock using load-exclusive instruction.
+ * Issue RMR_EL3 for warm reset if lock is unlocked
+ *
+ * void lfa_r_holding_wait(spinlock_t *lock);
+ */
+rfunc lfa_r_holding_wait_warm_reset
+ sevl
+l2:
+ wfe
+ ldaxr w1, [x0]
+ cbnz w1, l2
+l3:
+ wfi
+ b l3
+endfunc lfa_r_holding_wait_warm_reset
+
+/* Relocatable data section */
+
+ .globl lfa_r_holding_lock_var
+ .globl lfa_r_ep_addresses
+ .globl lfa_r_context_ids
+
+.align 8
+.section .lfa_relocatable_data.lfa_r_holding_lock_var, "aw"
+lfa_r_holding_lock_var: .quad 0x0
+
+.align 8
+.section .lfa_relocatable_data.lfa_r_ep_addresses, "aw"
+lfa_r_ep_addresses:
+ .fill PLATFORM_CORE_COUNT, 8, 0x0
+
+.align 8
+.section .lfa_relocatable_data.lfa_r_context_ids, "aw"
+lfa_r_context_ids:
+ .fill PLATFORM_CORE_COUNT, 8, 0x0
diff --git a/services/std_svc/lfa/lfa_reset.c b/services/std_svc/lfa/lfa_reset.c
new file mode 100644
index 000000000..07db8cc97
--- /dev/null
+++ b/services/std_svc/lfa/lfa_reset.c
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2026, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <common/build_message.h>
+#include <lib/el3_runtime/context_mgmt.h>
+#include <lib/psci/psci.h>
+#include <lib/spinlock.h>
+#include <plat/arm/common/plat_arm.h>
+#include <plat/common/platform.h>
+#include <services/lfa_svc.h>
+
+/*
+ * These variables are used pre and post live activation so place them in the
+ * relocatable data section to ensure they don't get moved around and persist
+ * across live activation.
+ */
+static lfa_mailbox_message_t lfa_mailbox[PLATFORM_CORE_COUNT] __attribute__((section(".lfa_relocatable_data")));
+static const plat_psci_ops_t *lfa_psci_ops __attribute__((section(".lfa_relocatable_data")));
+static uintptr_t plat_wr_ep __attribute__((section(".lfa_relocatable_data")));
+static uint64_t reset_cpu_count __attribute__((section(".lfa_relocatable_data")));
+static spinlock_t cpucountlock __attribute__((section(".lfa_relocatable_data")));
+
+extern plat_psci_ops_t *psci_plat_pm_ops;
+extern int psci_validate_entry_point(entry_point_info_t *ep,
+ uintptr_t entrypoint,
+ u_register_t context_id);
+extern void bl31_lfa_entrypoint(void);
+extern void bl31_warm_entrypoint(void);
+
+static void add_reset_cpu(void)
+{
+ spin_lock(&cpucountlock);
+ reset_cpu_count++;
+ spin_unlock(&cpucountlock);
+}
+
+static uint64_t remove_reset_cpu(void)
+{
+ uint64_t cpu_count;
+
+ spin_lock(&cpucountlock);
+ if (reset_cpu_count > 0) {
+ reset_cpu_count--;
+ }
+ cpu_count = reset_cpu_count;
+ spin_unlock(&cpucountlock);
+
+ return cpu_count;
+}
+
+/*
+ * Prepare CPU warm reset. This functioned should be called before entering
+ * relocatable code for each CPU.
+ */
+uint64_t prepare_warm_reset(uintptr_t lfa_ns_ep, uint64_t context_id, uint64_t cpu_spinlock)
+{
+ const psci_power_state_t state_info = { {ARM_LOCAL_STATE_OFF} };
+ unsigned int core_pos = plat_my_core_pos();
+ lfa_mailbox_message_t mmesg;
+ int rc;
+ static spinlock_t setuplock;
+
+ spin_lock(&setuplock);
+ if (plat_wr_ep == 0 && plat_lfa_mailbox_base()) {
+ plat_wr_ep = *((uintptr_t *)plat_lfa_mailbox_base());
+ }
+
+ if (lfa_psci_ops == NULL) {
+ plat_setup_psci_ops((uintptr_t)bl31_lfa_entrypoint, &lfa_psci_ops);
+ }
+ spin_unlock(&setuplock);
+
+ rc = psci_validate_entry_point(&mmesg.ep, lfa_ns_ep, (u_register_t)context_id);
+ if (rc != PSCI_E_SUCCESS) {
+ ERROR("BL31 LFA: Invalid NS entrypoint! (error %d)\n", rc);
+ return LFA_CRITICAL_ERROR;
+ }
+
+ /* Stash the holding pen lock so we can release it after reboot. */
+ mmesg.spinlock_p = (uint64_t)cpu_spinlock;
+
+ /* Stash the platform warmboot entry point so we can restore it. */
+ mmesg.plat_wr_mailbox = plat_wr_ep;
+ memcpy((void *)&lfa_mailbox[core_pos], &mmesg, sizeof(lfa_mailbox_message_t));
+
+ /*
+ * TODO: refactor PSCI suspend API to be usable here. The main issue is that
+ * PSCI suspend will put the core down immediately, but the use case here is
+ * slightly different since we have to do the firmware copy operation at the
+ * very end right before putting the core down for reset.
+ */
+ if (lfa_psci_ops->pwr_domain_suspend != NULL) {
+ /*
+ * We can't use the proper PSCI API since it will put the core
+ * down immediately, we still need to copy BL31.
+ */
+#if USE_GIC_DRIVER
+ gic_cpuif_disable(core_pos);
+#endif
+ lfa_psci_ops->pwr_domain_suspend(&state_info);
+ } else {
+ ERROR("BL31: PSCI power domain suspend not supported!\n");
+ return LFA_CRITICAL_ERROR;
+ }
+
+ add_reset_cpu();
+
+ cm_el2_sysregs_context_save(NON_SECURE);
+
+ return LFA_SUCCESS;
+}
+
+/*
+ * Entrypoint function for CPU warm reset case.
+ */
+void __no_pauth lfa_warm_reset_entrypoint(void)
+{
+ static spinlock_t resetlock;
+ uint32_t counter_freq;
+ const psci_power_state_t state_info = { {ARM_LOCAL_STATE_OFF} };
+ lfa_mailbox_message_t mmesg;
+ unsigned int core_pos = plat_my_core_pos();
+
+#if ENABLE_FEAT_RME
+ /*
+ * At warm boot GPT data structures have already been initialized in RAM
+ * but the sysregs for this CPU need to be initialized. Note that the GPT
+ * accesses are controlled attributes in GPCCR and do not depend on the
+ * SCR_EL3.C bit.
+ */
+ if (gpt_enable() != 0) {
+ panic();
+ }
+#endif
+
+ /* TODO see if we can use psci_warmboot_entrypoint here. */
+ psci_plat_pm_ops->pwr_domain_suspend_finish(&state_info);
+#if USE_GIC_DRIVER
+ gic_cpuif_enable(core_pos);
+#endif
+
+ /* Init registers that never change for the lifetime of TF-A */
+ cm_manage_extensions_el3(core_pos);
+
+ /* Re-init the cntfrq_el0 register */
+ counter_freq = plat_get_syscnt_freq2();
+ write_cntfrq_el0(counter_freq);
+
+ memcpy(&mmesg, (void *)&lfa_mailbox[core_pos], sizeof(lfa_mailbox_message_t));
+
+ spin_lock(&resetlock);
+ NOTICE("BL31 LFA Warm Reset on CPU %u\n", core_pos);
+ NOTICE(" Version : %s\n", build_version_string);
+ NOTICE(" %s\n", build_message);
+ console_flush();
+ spin_unlock(&resetlock);
+
+ /* The last CPU to reach here restores the warm entrypoint. */
+ if (remove_reset_cpu() == 0) {
+ plat_arm_program_trusted_mailbox(mmesg.plat_wr_mailbox);
+ plat_wr_ep = 0;
+ lfa_psci_ops = NULL;
+ }
+
+ spin_unlock((spinlock_t *)mmesg.spinlock_p);
+
+ cm_init_my_context(&mmesg.ep);
+
+ cm_prepare_el3_exit_ns();
+}
diff --git a/services/std_svc/rmmd/rmmd_rmm_lfa.c b/services/std_svc/rmmd/rmmd_rmm_lfa.c
index 74c8cdddc..23c14e23c 100644
--- a/services/std_svc/rmmd/rmmd_rmm_lfa.c
+++ b/services/std_svc/rmmd/rmmd_rmm_lfa.c
@@ -78,6 +78,7 @@ static int lfa_rmm_activate(struct lfa_component_status *activation,
static struct lfa_component_ops rmm_activator = {
.prime = lfa_rmm_prime,
.activate = lfa_rmm_activate,
+ .cancel = NULL,
.may_reset_cpu = false,
.cpu_rendezvous_required = true,
};