diff options
| author | Manish V Badarkhe <manish.badarkhe@arm.com> | 2026-06-30 20:34:41 +0000 |
|---|---|---|
| committer | TrustedFirmware Code Review <review@review.trustedfirmware.org> | 2026-06-30 20:34:41 +0000 |
| commit | c64fe42d75709fc3da7d0820caf15bce2ca8a2cd (patch) | |
| tree | 518f75362d7407e2d247d434c1c853c033897fa3 | |
| parent | 599baaa539b3996067b9ce8f0f225c8baa6e0757 (diff) | |
| parent | 7196825ce71266d6656165657d4936d185bd439b (diff) | |
| download | arm-trusted-firmware-c64fe42d75709fc3da7d0820caf15bce2ca8a2cd.tar.gz arm-trusted-firmware-c64fe42d75709fc3da7d0820caf15bce2ca8a2cd.zip | |
* 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
29 files changed, 1467 insertions, 170 deletions
@@ -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(¤t_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(¤t_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(¤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: + 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, }; |
