summaryrefslogtreecommitdiff
path: root/services/std_svc/lfa/bl31_lfa.c
diff options
context:
space:
mode:
Diffstat (limited to 'services/std_svc/lfa/bl31_lfa.c')
-rw-r--r--services/std_svc/lfa/bl31_lfa.c373
1 files changed, 364 insertions, 9 deletions
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;
+}