From 8e41e5c6e976b4a8ede38a3932a6d89dc0259842 Mon Sep 17 00:00:00 2001 From: John Powell Date: Mon, 22 Jun 2026 16:54:01 -0500 Subject: feat: place errata into their own section This patch moves CPU reset functions and reset errata workarounds into their own linker subsections .text.errata and .rodata.errata, as well as modifies CPU macros to allow this. When BL31 LFA is integrated later, this allows these subsections to be grouped together and padded, allowing them to be updated during live activation without affectinn the rest of the BL31 image. Signed-off-by: John Powell Change-Id: Id6f248fa796a998152e4e0b946fa3717cb0cfbf6 --- include/lib/cpus/aarch64/cpu_macros.S | 73 +++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 21 deletions(-) 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 /* -- cgit v1.2.3 From 1a95ccecd33ed1ac13c748461cdd5df5c9a41657 Mon Sep 17 00:00:00 2001 From: John Powell Date: Wed, 20 May 2026 16:10:03 -0500 Subject: feat(lfa): add build flag for BL31 LFA support This is the first patch in the series to enable live firmware activation in BL31, and it adds the build flag to enable this to the makefiles. Signed-off-by: John Powell Change-Id: I0b6f511e61f9cdcc8ee786507ee1c2e721147da7 --- Makefile | 6 ++++++ docs/getting_started/build-options.rst | 5 +++++ make_helpers/defaults.mk | 1 + 3 files changed, 12 insertions(+) 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/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/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 -- cgit v1.2.3 From 769380b33b0a20f6c05b22e6897680a9e28f573d Mon Sep 17 00:00:00 2001 From: John Powell Date: Wed, 20 May 2026 16:42:56 -0500 Subject: feat(lfa): bl31 linker file updates for lfa This patch modifies the BL31 linker script to carve out regions needed by BL31 live activation framework. Change-Id: I8b5f38a62e159bf1cad8dc4ab74cf6a9bb137074 Signed-off-by: John Powell --- bl31/bl31.ld.S | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 84 insertions(+), 2 deletions(-) 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 #include #include +#include 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; -- cgit v1.2.3 From 779393ae1b092ec6f4e02c3a522b8e9ab8acd345 Mon Sep 17 00:00:00 2001 From: John Powell Date: Mon, 18 May 2026 12:29:33 -0500 Subject: feat(lfa): xlat changes for BL31 LFA This patch adds xlat library changes needed to support live activation of BL31 images. Signed-off-by: John Powell Change-Id: I3956542b7978133dddcb0d1ace5512b6cfbfedc0 --- include/lib/xlat_tables/xlat_tables_v2.h | 5 +++++ lib/xlat_tables_v2/xlat_tables_context.c | 7 +++++++ 2 files changed, 12 insertions(+) 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/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 -- cgit v1.2.3 From 567e4357fb29be24ab5917102d2a5ed6f6feba4d Mon Sep 17 00:00:00 2001 From: John Powell Date: Mon, 18 May 2026 12:52:12 -0500 Subject: feat(lfa): add relocatable code for BL31 live activation This patch adds the relocatable code functions that are copied out of the main BL31 image during a live activation and updates the holding pen code to have a relocatable variant. Change-Id: I30ba6e118a6b888e0adf4dbd605d15ac0b40bea8 Signed-off-by: John Powell --- include/common/bl_common.h | 9 +++ include/services/lfa_holding_pen.h | 11 ++++ services/std_svc/lfa/lfa.mk | 4 ++ services/std_svc/lfa/lfa_holding_pen.c | 64 ++++++++++++++++--- services/std_svc/lfa/lfa_relocatable.S | 108 +++++++++++++++++++++++++++++++++ 5 files changed, 186 insertions(+), 10 deletions(-) create mode 100644 services/std_svc/lfa/lfa_relocatable.S diff --git a/include/common/bl_common.h b/include/common/bl_common.h index 3bde8fd09..b8b4dc85b 100644 --- a/include/common/bl_common.h +++ b/include/common/bl_common.h @@ -164,6 +164,15 @@ 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); +#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/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/services/std_svc/lfa/lfa.mk b/services/std_svc/lfa/lfa.mk index 53af69fd7..e9e78b71d 100644 --- a/services/std_svc/lfa/lfa.mk +++ b/services/std_svc/lfa/lfa.mk @@ -12,3 +12,7 @@ LFA_SOURCES += $(addprefix services/std_svc/lfa/, \ ifeq (${ENABLE_RMM}, 1) LFA_SOURCES += services/std_svc/rmmd/rmmd_rmm_lfa.c endif + +ifeq (${ENABLE_LFA_BL31}, 1) +LFA_SOURCES += services/std_svc/lfa/lfa_relocatable.S +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 +#include #include #include #include #include +#include #include #include @@ -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_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 +#include + +/* 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 -- cgit v1.2.3 From e6bccf43c4f93a25a0e1c7877107022537a17cad Mon Sep 17 00:00:00 2001 From: John Powell Date: Fri, 26 Jun 2026 13:52:52 -0500 Subject: feat(lfa): lfa service updates This patch updates the LFA service components in preparation for BL31 specific changes. Change-Id: I6d4dd90b11fa1d8729c4f9026d2ddce0272aea76 Signed-off-by: John Powell --- include/services/lfa_component_desc.h | 37 ++++++++++++- include/services/lfa_svc.h | 7 +++ services/std_svc/lfa/lfa_main.c | 99 ++++++++++++++++------------------- services/std_svc/rmmd/rmmd_rmm_lfa.c | 1 + 4 files changed, 88 insertions(+), 56 deletions(-) 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_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/services/std_svc/lfa/lfa_main.c b/services/std_svc/lfa/lfa_main.c index 819b169a7..0022e0331 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 @@ -32,7 +32,6 @@ void lfa_reset_activation(void) { current_activation.component_id = LFA_INVALID_COMPONENT; current_activation.prime_status = PRIME_NONE; - current_activation.cpu_rendezvous_required = false; } static int convert_to_lfa_error(int ret) @@ -54,8 +53,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 +62,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 +83,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,6 +95,14 @@ static int lfa_cancel(uint32_t component_id) return LFA_INVALID_PARAMETERS; } + activator = lfa_components[component_id].activator; + if (activator->cancel != NULL) { + ret = activator->cancel(¤t_activation); + if (ret != LFA_SUCCESS) { + return LFA_BUSY; + } + } + ret = plat_lfa_cancel(component_id); if (ret != LFA_SUCCESS) { return LFA_BUSY; @@ -120,13 +126,13 @@ static int lfa_activate(uint32_t component_id, uint64_t flags, 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) { + activator = lfa_components[component_id].activator; + if (activator == NULL) { return LFA_NOT_SUPPORTED; } @@ -135,35 +141,20 @@ static int lfa_activate(uint32_t component_id, uint64_t flags, return LFA_ACTIVATION_FAILED; } - activator = lfa_components[component_id].activator; - if (activator->activate != NULL) { - /* - * 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) { - 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; - } + /* If caller requests it, see if we can skip CPU rendezvous. */ + if (flags & LFA_SKIP_CPU_RENDEZVOUS_BIT) { + if (!activator->cpu_rendezvous_required) { + INFO("Caller requested skip CPU rendezvous.\n"); + current_activation.cpu_rendezvous = false; + } else { + ERROR("CPU rendezvous cannot be skipped!\n"); + return LFA_INVALID_PARAMETERS; } - - ret = activator->activate(¤t_activation, ep_address, - context_id); } - /* - * Update the activation pending flag only if the activation was - * successful. - */ + ret = activator->activate(¤t_activation, ep_address, context_id); + + /* Clear pending flag on successful activation. */ if (ret == LFA_SUCCESS) { lfa_components[component_id].activation_pending = false; } @@ -225,7 +216,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; } /* @@ -268,8 +265,9 @@ 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 +290,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 +307,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 +336,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: @@ -372,7 +365,6 @@ uint64_t lfa_smc_handler(uint32_t smc_fid, u_register_t x1, u_register_t x2, ret = lfa_activate(fw_seq_id, x2, x3, x4); /* TODO: implement activate again */ SMC_RET2(handle, ret, 0ULL); - break; case LFA_CANCEL: @@ -383,11 +375,8 @@ uint64_t lfa_smc_handler(uint32_t smc_fid, u_register_t x1, u_register_t x2, 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/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, }; -- cgit v1.2.3 From c6ca1acd3238b3d3aed1ea3bef4b2ae64a2a6a53 Mon Sep 17 00:00:00 2001 From: John Powell Date: Fri, 26 Jun 2026 13:53:38 -0500 Subject: feat(lfa): update LFA service to support BL31 live activation This patch adds the bulk of the BL31 live activation service framework including the warm reset handler. Signed-off-by: John Powell Change-Id: Id9cbeeddbc023ee2540b88008e6c2502d816ee24 --- bl31/aarch64/bl31_entrypoint.S | 65 +++--- include/arch/aarch64/el3_common_macros.S | 32 +++ include/common/bl_common.h | 6 +- include/plat/common/plat_lfa.h | 12 + make_helpers/constraints.mk | 6 +- services/std_svc/lfa/bl31_lfa.c | 373 ++++++++++++++++++++++++++++++- services/std_svc/lfa/lfa.mk | 6 +- services/std_svc/lfa/lfa_reset.c | 173 ++++++++++++++ 8 files changed, 631 insertions(+), 42 deletions(-) create mode 100644 services/std_svc/lfa/lfa_reset.c 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/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 #include #include +#include /* * 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 b8b4dc85b..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 */ @@ -171,6 +171,10 @@ 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 /******************************************************************************* 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 #include +/* + * 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/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/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 +#include + +#include +#include +#include +#include #include +#include #include -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 e9e78b71d..d3d5cd864 100644 --- a/services/std_svc/lfa/lfa.mk +++ b/services/std_svc/lfa/lfa.mk @@ -6,7 +6,6 @@ LFA_SOURCES += $(addprefix services/std_svc/lfa/, \ lfa_main.c \ - bl31_lfa.c \ lfa_holding_pen.c) ifeq (${ENABLE_RMM}, 1) @@ -14,5 +13,8 @@ LFA_SOURCES += services/std_svc/rmmd/rmmd_rmm_lfa.c endif ifeq (${ENABLE_LFA_BL31}, 1) -LFA_SOURCES += services/std_svc/lfa/lfa_relocatable.S +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_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 +#include +#include +#include +#include +#include +#include + +/* + * 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(); +} -- cgit v1.2.3 From c457b88f839c2bc88da003e6cc17baeeaf544a7e Mon Sep 17 00:00:00 2001 From: John Powell Date: Mon, 18 May 2026 13:16:40 -0500 Subject: feat(fvp): add FVP platform support for BL31 live activation This patch adds the platform components of BL31 live activation. Signed-off-by: John Powell Change-Id: I5c08eecdc0420f05462740b72a59ee2e837cf701 --- include/plat/arm/common/arm_def.h | 14 +++++++ include/plat/arm/common/plat_arm_lfa_components.h | 4 +- plat/arm/board/fvp/fvp_common.c | 11 ++++++ plat/arm/board/fvp/fvp_lfa.c | 45 ++++++++++++++++++++++- plat/arm/board/fvp/include/platform_def.h | 21 ++++++++++- 5 files changed, 92 insertions(+), 3 deletions(-) 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/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 + +#include #include #include #include +#include #include #include @@ -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 */ -- cgit v1.2.3 From 8936385d33a3ee77642f1bf3dda41c538d935fca Mon Sep 17 00:00:00 2001 From: John Powell Date: Thu, 29 Jan 2026 13:07:51 -0600 Subject: feat(docs): add documentation page for BL31 LFA This patch adds a documentation page about the BL31 LFA implementation and outlines the limitations and requirements to use it. Change-Id: I55008a5a45561918bbf386bc1da96f6d281638a3 Signed-off-by: John Powell --- docs/components/bl31-lfa.rst | 91 ++++++++++++++++++++++++++++++++++++++++++++ docs/components/index.rst | 1 + 2 files changed, 92 insertions(+) create mode 100644 docs/components/bl31-lfa.rst 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 -- cgit v1.2.3 From 7a5b0839b5b95b21c82cd0a5d10dc13901e53185 Mon Sep 17 00:00:00 2001 From: John Powell Date: Wed, 20 May 2026 19:18:05 -0500 Subject: feat(lfa): serialize activate across CPUs and manage rendezvous policy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Track activation state across CPUs so skip-rendezvous activations don’t block or interleave. Split activate into prepare/finish to latch the rendezvous policy, notify the platform once per activation, and only clear activation_pending after all callers finish. Return LFA_BUSY for conflicting skip requests and LFA_CALL_AGAIN for multi‑stage activations. Change-Id: Id5a71fb6db12fe240e2bc1c9000606df935374b2 Signed-off-by: Manish V Badarkhe Signed-off-by: John Powell --- services/std_svc/lfa/lfa_main.c | 262 ++++++++++++++++++++++++++++++++-------- 1 file changed, 209 insertions(+), 53 deletions(-) diff --git a/services/std_svc/lfa/lfa_main.c b/services/std_svc/lfa/lfa_main.c index 0022e0331..2f76756bc 100644 --- a/services/std_svc/lfa/lfa_main.c +++ b/services/std_svc/lfa/lfa_main.c @@ -15,10 +15,22 @@ #include #include -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; /* * Spinlock to serialize LFA operations (PRIME, ACTIVATE). @@ -32,6 +44,11 @@ void lfa_reset_activation(void) { current_activation.component_id = LFA_INVALID_COMPONENT; current_activation.prime_status = PRIME_NONE; + current_activation.cpu_rendezvous = false; + activation_in_progress = false; + activation_skip_cpu_rendezvous = false; + activation_users = 0U; + activation_failed = false; } static int convert_to_lfa_error(int ret) @@ -114,54 +131,6 @@ static int lfa_cancel(uint32_t component_id) 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; - } - - if ((component_id >= lfa_component_count) || - (current_activation.component_id != component_id)) { - return LFA_INVALID_PARAMETERS; - } - - activator = lfa_components[component_id].activator; - if (activator == NULL) { - return LFA_NOT_SUPPORTED; - } - - ret = plat_lfa_notify_activate(component_id); - if (ret != 0) { - return LFA_ACTIVATION_FAILED; - } - - /* If caller requests it, see if we can skip CPU rendezvous. */ - if (flags & LFA_SKIP_CPU_RENDEZVOUS_BIT) { - if (!activator->cpu_rendezvous_required) { - INFO("Caller requested skip CPU rendezvous.\n"); - current_activation.cpu_rendezvous = false; - } else { - ERROR("CPU rendezvous cannot be skipped!\n"); - return LFA_INVALID_PARAMETERS; - } - } - - ret = activator->activate(¤t_activation, ep_address, context_id); - - /* Clear pending flag on successful activation. */ - if (ret == LFA_SUCCESS) { - lfa_components[component_id].activation_pending = false; - } - - return ret; -} - static int lfa_prime(uint32_t component_id, uint64_t *flags) { int ret = LFA_SUCCESS; @@ -261,6 +230,110 @@ 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; + } + /* + * 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; + + 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; + 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) @@ -362,9 +435,92 @@ 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(¤t_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: -- cgit v1.2.3 From 7196825ce71266d6656165657d4936d185bd439b Mon Sep 17 00:00:00 2001 From: Manish V Badarkhe Date: Thu, 5 Mar 2026 22:45:36 +0000 Subject: feat(lfa): harden cancel handling and lock around LFA_CANCEL - block cancel once all active CPUs have entered ACTIVATE - take lfa_lock during cancel SMC handling Change-Id: I1443644845d19e0322f5d188461a1c4083c57707 Signed-off-by: Manish V Badarkhe Signed-off-by: John Powell --- services/std_svc/lfa/lfa_main.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/services/std_svc/lfa/lfa_main.c b/services/std_svc/lfa/lfa_main.c index 2f76756bc..31c05bf58 100644 --- a/services/std_svc/lfa/lfa_main.c +++ b/services/std_svc/lfa/lfa_main.c @@ -31,6 +31,7 @@ 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). @@ -49,6 +50,7 @@ void lfa_reset_activation(void) activation_skip_cpu_rendezvous = false; activation_users = 0U; activation_failed = false; + activation_cancel_locked = false; } static int convert_to_lfa_error(int ret) @@ -112,6 +114,14 @@ static int lfa_cancel(uint32_t component_id) return LFA_INVALID_PARAMETERS; } + /* + * Deny cancellation once the activation round is no longer cancellable. + */ + if (activation_in_progress && + (activation_cancel_locked || activation_skip_cpu_rendezvous)) { + return LFA_BUSY; + } + activator = lfa_components[component_id].activator; if (activator->cancel != NULL) { ret = activator->cancel(¤t_activation); @@ -122,6 +132,10 @@ static int lfa_cancel(uint32_t component_id) ret = plat_lfa_cancel(component_id); if (ret != LFA_SUCCESS) { + /* + * Cancellation can fail when activation cannot be stopped + * (e.g. activation requested with skip_cpu_rendezvous=1). + */ return LFA_BUSY; } @@ -271,6 +285,7 @@ static int lfa_activate_prepare(uint32_t component_id, uint64_t flags, (*activator)->cpu_rendezvous_required; } activation_skip_cpu_rendezvous = false; + activation_cancel_locked = false; } /* * Pass skip_cpu_rendezvous (flag[0]) only if flag[0]==1 @@ -310,6 +325,9 @@ static int lfa_activate_prepare(uint32_t component_id, uint64_t flags, /* 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; } @@ -328,6 +346,7 @@ static void lfa_activate_finish(uint32_t component_id, bool activation_complete) 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; } @@ -524,7 +543,14 @@ uint64_t lfa_smc_handler(uint32_t smc_fid, u_register_t x1, u_register_t x2, 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; -- cgit v1.2.3