diff options
Diffstat (limited to 'services/std_svc/spm')
19 files changed, 6352 insertions, 0 deletions
diff --git a/services/std_svc/spm/common/aarch64/spm_helpers.S b/services/std_svc/spm/common/aarch64/spm_helpers.S new file mode 100644 index 0000000..95e69fb --- /dev/null +++ b/services/std_svc/spm/common/aarch64/spm_helpers.S @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2017-2022, ARM Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <asm_macros.S> +#include "spm_common.h" + + .global spm_secure_partition_enter + .global spm_secure_partition_exit + + /* --------------------------------------------------------------------- + * This function is called with SP_EL0 as stack. Here we stash our EL3 + * callee-saved registers on to the stack as a part of saving the C + * runtime and enter the secure payload. + * 'x0' contains a pointer to the memory where the address of the C + * runtime context is to be saved. + * --------------------------------------------------------------------- + */ +func spm_secure_partition_enter + /* Make space for the registers that we're going to save */ + mov x3, sp + str x3, [x0, #0] + sub sp, sp, #SP_C_RT_CTX_SIZE + + /* Save callee-saved registers on to the stack */ + stp x19, x20, [sp, #SP_C_RT_CTX_X19] + stp x21, x22, [sp, #SP_C_RT_CTX_X21] + stp x23, x24, [sp, #SP_C_RT_CTX_X23] + stp x25, x26, [sp, #SP_C_RT_CTX_X25] + stp x27, x28, [sp, #SP_C_RT_CTX_X27] + stp x29, x30, [sp, #SP_C_RT_CTX_X29] + + /* --------------------------------------------------------------------- + * Everything is setup now. el3_exit() will use the secure context to + * restore to the general purpose and EL3 system registers to ERET + * into the secure payload. + * --------------------------------------------------------------------- + */ + b el3_exit +endfunc spm_secure_partition_enter + + /* --------------------------------------------------------------------- + * This function is called with 'x0' pointing to a C runtime context + * saved in spm_secure_partition_enter(). + * It restores the saved registers and jumps to that runtime with 'x0' + * as the new SP register. This destroys the C runtime context that had + * been built on the stack below the saved context by the caller. Later + * the second parameter 'x1' is passed as a return value to the caller. + * --------------------------------------------------------------------- + */ +func spm_secure_partition_exit + /* Restore the previous stack */ + mov sp, x0 + + /* Restore callee-saved registers on to the stack */ + ldp x19, x20, [x0, #(SP_C_RT_CTX_X19 - SP_C_RT_CTX_SIZE)] + ldp x21, x22, [x0, #(SP_C_RT_CTX_X21 - SP_C_RT_CTX_SIZE)] + ldp x23, x24, [x0, #(SP_C_RT_CTX_X23 - SP_C_RT_CTX_SIZE)] + ldp x25, x26, [x0, #(SP_C_RT_CTX_X25 - SP_C_RT_CTX_SIZE)] + ldp x27, x28, [x0, #(SP_C_RT_CTX_X27 - SP_C_RT_CTX_SIZE)] + ldp x29, x30, [x0, #(SP_C_RT_CTX_X29 - SP_C_RT_CTX_SIZE)] + + /* --------------------------------------------------------------------- + * This should take us back to the instruction after the call to the + * last spm_secure_partition_enter().* Place the second parameter to x0 + * so that the caller will see it as a return value from the original + * entry call. + * --------------------------------------------------------------------- + */ + mov x0, x1 + ret +endfunc spm_secure_partition_exit diff --git a/services/std_svc/spm/common/aarch64/spm_shim_exceptions.S b/services/std_svc/spm/common/aarch64/spm_shim_exceptions.S new file mode 100644 index 0000000..836f75c --- /dev/null +++ b/services/std_svc/spm/common/aarch64/spm_shim_exceptions.S @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2017-2022, ARM Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <arch.h> +#include <asm_macros.S> +#include <common/bl_common.h> +#include <context.h> + +/* ----------------------------------------------------------------------------- + * Very simple stackless exception handlers used by the spm shim layer. + * ----------------------------------------------------------------------------- + */ + .globl spm_shim_exceptions_ptr + +vector_base spm_shim_exceptions_ptr, .spm_shim_exceptions + + /* ----------------------------------------------------- + * Current EL with SP0 : 0x0 - 0x200 + * ----------------------------------------------------- + */ +vector_entry SynchronousExceptionSP0, .spm_shim_exceptions + b . +end_vector_entry SynchronousExceptionSP0 + +vector_entry IrqSP0, .spm_shim_exceptions + b . +end_vector_entry IrqSP0 + +vector_entry FiqSP0, .spm_shim_exceptions + b . +end_vector_entry FiqSP0 + +vector_entry SErrorSP0, .spm_shim_exceptions + b . +end_vector_entry SErrorSP0 + + /* ----------------------------------------------------- + * Current EL with SPx: 0x200 - 0x400 + * ----------------------------------------------------- + */ +vector_entry SynchronousExceptionSPx, .spm_shim_exceptions + b . +end_vector_entry SynchronousExceptionSPx + +vector_entry IrqSPx, .spm_shim_exceptions + b . +end_vector_entry IrqSPx + +vector_entry FiqSPx, .spm_shim_exceptions + b . +end_vector_entry FiqSPx + +vector_entry SErrorSPx, .spm_shim_exceptions + b . +end_vector_entry SErrorSPx + + /* ----------------------------------------------------- + * Lower EL using AArch64 : 0x400 - 0x600. No exceptions + * are handled since secure_partition does not implement + * a lower EL + * ----------------------------------------------------- + */ +vector_entry SynchronousExceptionA64, .spm_shim_exceptions + msr tpidr_el1, x30 + mrs x30, esr_el1 + ubfx x30, x30, #ESR_EC_SHIFT, #ESR_EC_LENGTH + + cmp x30, #EC_AARCH64_SVC + b.eq do_smc + + cmp x30, #EC_AARCH32_SVC + b.eq do_smc + + cmp x30, #EC_AARCH64_SYS + b.eq handle_sys_trap + + /* Fail in all the other cases */ + b panic + + /* --------------------------------------------- + * Tell SPM that we are done initialising + * --------------------------------------------- + */ +do_smc: + mrs x30, tpidr_el1 + smc #0 + exception_return + + /* AArch64 system instructions trap are handled as a panic for now */ +handle_sys_trap: +panic: + b panic +end_vector_entry SynchronousExceptionA64 + +vector_entry IrqA64, .spm_shim_exceptions + b . +end_vector_entry IrqA64 + +vector_entry FiqA64, .spm_shim_exceptions + b . +end_vector_entry FiqA64 + +vector_entry SErrorA64, .spm_shim_exceptions + b . +end_vector_entry SErrorA64 + + /* ----------------------------------------------------- + * Lower EL using AArch32 : 0x600 - 0x800 + * ----------------------------------------------------- + */ +vector_entry SynchronousExceptionA32, .spm_shim_exceptions + b . +end_vector_entry SynchronousExceptionA32 + +vector_entry IrqA32, .spm_shim_exceptions + b . +end_vector_entry IrqA32 + +vector_entry FiqA32, .spm_shim_exceptions + b . +end_vector_entry FiqA32 + +vector_entry SErrorA32, .spm_shim_exceptions + b . +end_vector_entry SErrorA32 diff --git a/services/std_svc/spm/common/include/spm_common.h b/services/std_svc/spm/common/include/spm_common.h new file mode 100644 index 0000000..c736919 --- /dev/null +++ b/services/std_svc/spm/common/include/spm_common.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2017-2023, ARM Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef SPM_COMMON_H +#define SPM_COMMON_H + +#include <context.h> + +/******************************************************************************* + * Constants that allow assembler code to preserve callee-saved registers of the + * C runtime context while performing a security state switch. + ******************************************************************************/ +#define SP_C_RT_CTX_X19 0x0 +#define SP_C_RT_CTX_X20 0x8 +#define SP_C_RT_CTX_X21 0x10 +#define SP_C_RT_CTX_X22 0x18 +#define SP_C_RT_CTX_X23 0x20 +#define SP_C_RT_CTX_X24 0x28 +#define SP_C_RT_CTX_X25 0x30 +#define SP_C_RT_CTX_X26 0x38 +#define SP_C_RT_CTX_X27 0x40 +#define SP_C_RT_CTX_X28 0x48 +#define SP_C_RT_CTX_X29 0x50 +#define SP_C_RT_CTX_X30 0x58 + +#define SP_C_RT_CTX_SIZE 0x60 +#define SP_C_RT_CTX_ENTRIES (SP_C_RT_CTX_SIZE >> DWORD_SHIFT) + +#ifndef __ASSEMBLER__ + +#include <stdint.h> +#include <lib/xlat_tables/xlat_tables_v2.h> + +/* Assembly helpers */ +uint64_t spm_secure_partition_enter(uint64_t *c_rt_ctx); +void __dead2 spm_secure_partition_exit(uint64_t c_rt_ctx, uint64_t ret); + +/* Helper to obtain a reference to the SP's translation table context */ +xlat_ctx_t *spm_get_sp_xlat_context(void); + +#endif /* __ASSEMBLER__ */ + +#endif /* SPM_COMMON_H */ diff --git a/services/std_svc/spm/common/include/spm_shim_private.h b/services/std_svc/spm/common/include/spm_shim_private.h new file mode 100644 index 0000000..bcb1147 --- /dev/null +++ b/services/std_svc/spm/common/include/spm_shim_private.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2017-2023, ARM Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef SPM_SHIM_PRIVATE_H +#define SPM_SHIM_PRIVATE_H + +#include <stdint.h> + +#include <lib/utils_def.h> + +/* Assembly source */ +IMPORT_SYM(uintptr_t, spm_shim_exceptions_ptr, SPM_SHIM_EXCEPTIONS_PTR); + +/* Linker symbols */ +IMPORT_SYM(uintptr_t, __SPM_SHIM_EXCEPTIONS_START__, SPM_SHIM_EXCEPTIONS_START); +IMPORT_SYM(uintptr_t, __SPM_SHIM_EXCEPTIONS_END__, SPM_SHIM_EXCEPTIONS_END); + +/* Definitions */ + +#define SPM_SHIM_EXCEPTIONS_SIZE \ + (SPM_SHIM_EXCEPTIONS_END - SPM_SHIM_EXCEPTIONS_START) + +#endif /* SPM_SHIM_PRIVATE_H */ diff --git a/services/std_svc/spm/common/spm.mk b/services/std_svc/spm/common/spm.mk new file mode 100644 index 0000000..65fd72a --- /dev/null +++ b/services/std_svc/spm/common/spm.mk @@ -0,0 +1,23 @@ +# +# Copyright (c) 2022-2023, ARM Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +ifneq (${ARCH},aarch64) + $(error "Error: SPM is only supported on aarch64.") +endif + +INCLUDES += -Iservices/std_svc/spm/common/include + +SPM_SOURCES := $(addprefix services/std_svc/spm/common/, \ + ${ARCH}/spm_helpers.S \ + ${ARCH}/spm_shim_exceptions.S) + +ifeq (1, $(filter 1, ${SPM_MM} ${SPMC_AT_EL3_SEL0_SP})) +SPM_SOURCES += $(addprefix services/std_svc/spm/common/, \ + spm_xlat_common.c) +endif + +# Let the top-level Makefile know that we intend to include a BL32 image +NEED_BL32 := yes diff --git a/services/std_svc/spm/common/spm_xlat_common.c b/services/std_svc/spm/common/spm_xlat_common.c new file mode 100644 index 0000000..a463c8b --- /dev/null +++ b/services/std_svc/spm/common/spm_xlat_common.c @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023, ARM Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <lib/xlat_tables/xlat_tables_v2.h> +#include <platform_def.h> + +/* Place translation tables by default along with the ones used by BL31. */ +#ifndef PLAT_SP_IMAGE_XLAT_SECTION_NAME +#define PLAT_SP_IMAGE_XLAT_SECTION_NAME ".xlat_table" +#endif +#ifndef PLAT_SP_IMAGE_BASE_XLAT_SECTION_NAME +#define PLAT_SP_IMAGE_BASE_XLAT_SECTION_NAME ".bss" +#endif + +/* Allocate and initialise the translation context for the secure partitions. */ +REGISTER_XLAT_CONTEXT2(sp, + PLAT_SP_IMAGE_MMAP_REGIONS, + PLAT_SP_IMAGE_MAX_XLAT_TABLES, + PLAT_VIRT_ADDR_SPACE_SIZE, PLAT_PHY_ADDR_SPACE_SIZE, + EL1_EL0_REGIME, PLAT_SP_IMAGE_XLAT_SECTION_NAME, + PLAT_SP_IMAGE_BASE_XLAT_SECTION_NAME); + +/* Get handle of Secure Partition translation context */ +xlat_ctx_t *spm_get_sp_xlat_context(void) +{ + return &sp_xlat_ctx; +}; diff --git a/services/std_svc/spm/el3_spmc/logical_sp.c b/services/std_svc/spm/el3_spmc/logical_sp.c new file mode 100644 index 0000000..e080832 --- /dev/null +++ b/services/std_svc/spm/el3_spmc/logical_sp.c @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2022, ARM Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <assert.h> +#include <errno.h> +#include <string.h> + +#include <common/debug.h> +#include <services/el3_spmc_logical_sp.h> +#include <services/ffa_svc.h> +#include "spmc.h" + +/******************************************************************************* + * Validate any logical partition descriptors before we initialise. + * Initialization of said partitions will be taken care of during SPMC boot. + ******************************************************************************/ +int el3_sp_desc_validate(void) +{ + struct el3_lp_desc *lp_array; + + /* + * Assert the number of descriptors is less than maximum allowed. + * This constant should be define on a per platform basis. + */ + assert(EL3_LP_DESCS_COUNT <= MAX_EL3_LP_DESCS_COUNT); + + /* Check the array bounds are valid. */ + assert(EL3_LP_DESCS_END >= EL3_LP_DESCS_START); + + /* If no logical partitions are implemented then simply bail out. */ + if (EL3_LP_DESCS_COUNT == 0U) { + return 0; + } + + lp_array = get_el3_lp_array(); + + for (unsigned int index = 0; index < EL3_LP_DESCS_COUNT; index++) { + struct el3_lp_desc *lp_desc = &lp_array[index]; + + /* Validate our logical partition descriptors. */ + if (lp_desc == NULL) { + ERROR("Invalid Logical SP Descriptor\n"); + return -EINVAL; + } + + /* + * Ensure the ID follows the convention to indidate it resides + * in the secure world. + */ + if (!ffa_is_secure_world_id(lp_desc->sp_id)) { + ERROR("Invalid Logical SP ID (0x%x)\n", + lp_desc->sp_id); + return -EINVAL; + } + + /* Ensure we don't conflict with the SPMC partition ID. */ + if (lp_desc->sp_id == FFA_SPMC_ID) { + ERROR("Logical SP ID clashes with SPMC ID(0x%x)\n", + lp_desc->sp_id); + return -EINVAL; + } + + /* Ensure the UUID is not the NULL UUID. */ + if (lp_desc->uuid[0] == 0 && lp_desc->uuid[1] == 0 && + lp_desc->uuid[2] == 0 && lp_desc->uuid[3] == 0) { + ERROR("Invalid UUID for Logical SP (0x%x)\n", + lp_desc->sp_id); + return -EINVAL; + } + + /* Ensure init function callback is registered. */ + if (lp_desc->init == NULL) { + ERROR("Missing init function for Logical SP(0x%x)\n", + lp_desc->sp_id); + return -EINVAL; + } + + /* Ensure that LP only supports receiving direct requests. */ + if (lp_desc->properties & + ~(FFA_PARTITION_DIRECT_REQ_RECV)) { + ERROR("Invalid partition properties (0x%x)\n", + lp_desc->properties); + return -EINVAL; + } + + /* Ensure direct request function callback is registered. */ + if (lp_desc->direct_req == NULL) { + ERROR("No Direct Req handler for Logical SP (0x%x)\n", + lp_desc->sp_id); + return -EINVAL; + } + + /* Ensure that all partition IDs are unique. */ + for (unsigned int inner_idx = index + 1; + inner_idx < EL3_LP_DESCS_COUNT; inner_idx++) { + if (lp_desc->sp_id == lp_array[inner_idx].sp_id) { + ERROR("Duplicate SP ID Detected (0x%x)\n", + lp_desc->sp_id); + return -EINVAL; + } + } + } + return 0; +} diff --git a/services/std_svc/spm/el3_spmc/spmc.h b/services/std_svc/spm/el3_spmc/spmc.h new file mode 100644 index 0000000..48644ac --- /dev/null +++ b/services/std_svc/spm/el3_spmc/spmc.h @@ -0,0 +1,281 @@ +/* + * Copyright (c) 2022, ARM Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef SPMC_H +#define SPMC_H + +#include <stdint.h> + +#include <common/bl_common.h> +#include <lib/psci/psci.h> +#include <lib/spinlock.h> +#include <services/el3_spmc_logical_sp.h> +#include "spm_common.h" + +/* + * Ranges of FF-A IDs for Normal world and Secure world components. The + * convention matches that used by other SPMCs i.e. Hafnium and OP-TEE. + */ +#define FFA_NWD_ID_BASE 0x0 +#define FFA_NWD_ID_LIMIT 0x7FFF +#define FFA_SWD_ID_BASE 0x8000 +#define FFA_SWD_ID_LIMIT SPMD_DIRECT_MSG_ENDPOINT_ID - 1 +#define FFA_SWD_ID_MASK 0x8000 + +/* ID 0 is reserved for the normal world entity, (Hypervisor or OS Kernel). */ +#define FFA_NWD_ID U(0) +/* First ID is reserved for the SPMC */ +#define FFA_SPMC_ID U(FFA_SWD_ID_BASE) +/* SP IDs are allocated after the SPMC ID */ +#define FFA_SP_ID_BASE (FFA_SPMC_ID + 1) +/* Align with Hafnium implementation */ +#define INV_SP_ID 0x7FFF + +/* FF-A Related helper macros. */ +#define FFA_ID_MASK U(0xFFFF) +#define FFA_PARTITION_ID_SHIFT U(16) +#define FFA_FEATURES_BIT31_MASK U(0x1u << 31) +#define FFA_FEATURES_RET_REQ_NS_BIT U(0x1 << 1) + +#define FFA_RUN_EP_ID(ep_vcpu_ids) \ + ((ep_vcpu_ids >> FFA_PARTITION_ID_SHIFT) & FFA_ID_MASK) +#define FFA_RUN_VCPU_ID(ep_vcpu_ids) \ + (ep_vcpu_ids & FFA_ID_MASK) + +#define FFA_PAGE_SIZE (4096) +#define FFA_RXTX_PAGE_COUNT_MASK 0x1F + +/* Ensure that the page size used by TF-A is 4k aligned. */ +CASSERT((PAGE_SIZE % FFA_PAGE_SIZE) == 0, assert_aligned_page_size); + +/* + * Defines to allow an SP to subscribe for power management messages + */ +#define FFA_PM_MSG_SUB_CPU_OFF U(1 << 0) +#define FFA_PM_MSG_SUB_CPU_SUSPEND U(1 << 1) +#define FFA_PM_MSG_SUB_CPU_SUSPEND_RESUME U(1 << 2) + +/* + * Runtime states of an execution context as per the FF-A v1.1 specification. + */ +enum sp_runtime_states { + RT_STATE_WAITING, + RT_STATE_RUNNING, + RT_STATE_PREEMPTED, + RT_STATE_BLOCKED +}; + +/* + * Runtime model of an execution context as per the FF-A v1.1 specification. Its + * value is valid only if the execution context is not in the waiting state. + */ +enum sp_runtime_model { + RT_MODEL_DIR_REQ, + RT_MODEL_RUN, + RT_MODEL_INIT, + RT_MODEL_INTR +}; + +enum sp_runtime_el { + EL1 = 0, + S_EL0, + S_EL1 +}; + +enum sp_execution_state { + SP_STATE_AARCH64 = 0, + SP_STATE_AARCH32 +}; + +enum mailbox_state { + /* There is no message in the mailbox. */ + MAILBOX_STATE_EMPTY, + + /* There is a message that has been populated in the mailbox. */ + MAILBOX_STATE_FULL, +}; + +struct mailbox { + enum mailbox_state state; + + /* RX/TX Buffers. */ + void *rx_buffer; + const void *tx_buffer; + + /* Size of RX/TX Buffer. */ + uint32_t rxtx_page_count; + + /* Lock access to mailbox. */ + spinlock_t lock; +}; + +/* + * Execution context members for an SP. This is a bit like struct + * vcpu in a hypervisor. + */ +struct sp_exec_ctx { + /* + * Store the stack address to restore C runtime context from after + * returning from a synchronous entry into the SP. + */ + uint64_t c_rt_ctx; + + /* Space to maintain the architectural state of an SP. */ + cpu_context_t cpu_ctx; + + /* Track the current runtime state of the SP. */ + enum sp_runtime_states rt_state; + + /* Track the current runtime model of the SP. */ + enum sp_runtime_model rt_model; + + /* Track the source partition ID to validate a direct response. */ + uint16_t dir_req_origin_id; +}; + +/* + * Structure to describe the cumulative properties of an SP. + */ +struct secure_partition_desc { + /* + * Execution contexts allocated to this endpoint. Ideally, + * we need as many contexts as there are physical cpus only + * for a S-EL1 SP which is MP-pinned. + */ + struct sp_exec_ctx ec[PLATFORM_CORE_COUNT]; + + /* ID of the Secure Partition. */ + uint16_t sp_id; + + /* Runtime EL. */ + enum sp_runtime_el runtime_el; + + /* Partition UUID. */ + uint32_t uuid[4]; + + /* Partition Properties. */ + uint32_t properties; + + /* Supported FF-A Version. */ + uint32_t ffa_version; + + /* Execution State. */ + enum sp_execution_state execution_state; + + /* Mailbox tracking. */ + struct mailbox mailbox; + + /* Secondary entrypoint. Only valid for a S-EL1 SP. */ + uintptr_t secondary_ep; + + /* + * Store whether the SP has subscribed to any power management messages. + */ + uint16_t pwr_mgmt_msgs; + + /* + * Store whether the SP has requested the use of the NS bit for memory + * management transactions if it is using FF-A v1.0. + */ + bool ns_bit_requested; +}; + +/* + * This define identifies the only SP that will be initialised and participate + * in FF-A communication. The implementation leaves the door open for more SPs + * to be managed in future but for now it is reasonable to assume that either a + * single S-EL0 or a single S-EL1 SP will be supported. This define will be used + * to identify which SP descriptor to initialise and manage during SP runtime. + */ +#define ACTIVE_SP_DESC_INDEX 0 + +/* + * Structure to describe the cumulative properties of the Hypervisor and + * NS-Endpoints. + */ +struct ns_endpoint_desc { + /* + * ID of the NS-Endpoint or Hypervisor. + */ + uint16_t ns_ep_id; + + /* + * Mailbox tracking. + */ + struct mailbox mailbox; + + /* + * Supported FF-A Version + */ + uint32_t ffa_version; +}; + +/* Reference to power management hooks */ +extern const spd_pm_ops_t spmc_pm; + +/* Setup Function for different SP types. */ +void spmc_sp_common_setup(struct secure_partition_desc *sp, + entry_point_info_t *ep_info, + int32_t boot_info_reg); +void spmc_el1_sp_setup(struct secure_partition_desc *sp, + entry_point_info_t *ep_info); +void spmc_sp_common_ep_commit(struct secure_partition_desc *sp, + entry_point_info_t *ep_info); + +/* + * Helper function to perform a synchronous entry into a SP. + */ +uint64_t spmc_sp_synchronous_entry(struct sp_exec_ctx *ec); + +/* + * Helper function to obtain the descriptor of the current SP on a physical cpu. + */ +struct secure_partition_desc *spmc_get_current_sp_ctx(void); + +/* + * Helper function to obtain the execution context of an SP on a + * physical cpu. + */ +struct sp_exec_ctx *spmc_get_sp_ec(struct secure_partition_desc *sp); + +/* + * Helper function to obtain the index of the execution context of an SP on a + * physical cpu. + */ +unsigned int get_ec_index(struct secure_partition_desc *sp); + +uint64_t spmc_ffa_error_return(void *handle, int error_code); + +/* + * Ensure a partition ID does not clash and follows the secure world convention. + */ +bool is_ffa_secure_id_valid(uint16_t partition_id); + +/* + * Helper function to obtain the array storing the EL3 + * Logical Partition descriptors. + */ +struct el3_lp_desc *get_el3_lp_array(void); + +/* + * Helper function to obtain the RX/TX buffer pair descriptor of the Hypervisor + * or OS kernel in the normal world or the last SP that was run. + */ +struct mailbox *spmc_get_mbox_desc(bool secure_origin); + +/* + * Helper function to obtain the context of an SP with a given partition ID. + */ +struct secure_partition_desc *spmc_get_sp_ctx(uint16_t id); + +/* + * Add helper function to obtain the FF-A version of the calling + * partition. + */ +uint32_t get_partition_ffa_version(bool secure_origin); + + +#endif /* SPMC_H */ diff --git a/services/std_svc/spm/el3_spmc/spmc.mk b/services/std_svc/spm/el3_spmc/spmc.mk new file mode 100644 index 0000000..6442af0 --- /dev/null +++ b/services/std_svc/spm/el3_spmc/spmc.mk @@ -0,0 +1,46 @@ +# +# Copyright (c) 2022, ARM Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +ifneq (${ARCH},aarch64) + $(error "Error: SPMC is only supported on aarch64.") +endif + +SPMC_SOURCES := $(addprefix services/std_svc/spm/el3_spmc/, \ + spmc_main.c \ + spmc_setup.c \ + logical_sp.c \ + spmc_pm.c \ + spmc_shared_mem.c) + +# Specify platform specific logical partition implementation. +SPMC_LP_SOURCES := $(addprefix ${PLAT_DIR}/, \ + ${PLAT}_el3_spmc_logical_sp.c) + + +ifneq ($(wildcard $(SPMC_LP_SOURCES)),) +SPMC_SOURCES += $(SPMC_LP_SOURCES) +endif + +# Let the top-level Makefile know that we intend to include a BL32 image +NEED_BL32 := yes + +ifndef BL32 +# The SPMC is paired with a Test Secure Payload source and we intend to +# build the Test Secure Payload if no other image has been provided +# for BL32. +# +# In cases where an associated Secure Payload lies outside this build +# system/source tree, the dispatcher Makefile can either invoke an external +# build command or assume it is pre-built. + +BL32_ROOT := bl32/tsp + +# Conditionally include SP's Makefile. The assumption is that the TSP's build +# system is compatible with that of Trusted Firmware, and it'll add and populate +# necessary build targets and variables. + +include ${BL32_ROOT}/tsp.mk +endif diff --git a/services/std_svc/spm/el3_spmc/spmc_main.c b/services/std_svc/spm/el3_spmc/spmc_main.c new file mode 100644 index 0000000..ada6f45 --- /dev/null +++ b/services/std_svc/spm/el3_spmc/spmc_main.c @@ -0,0 +1,2112 @@ +/* + * Copyright (c) 2022, ARM Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <assert.h> +#include <errno.h> + +#include <arch_helpers.h> +#include <bl31/bl31.h> +#include <bl31/ehf.h> +#include <bl31/interrupt_mgmt.h> +#include <common/debug.h> +#include <common/fdt_wrappers.h> +#include <common/runtime_svc.h> +#include <common/uuid.h> +#include <lib/el3_runtime/context_mgmt.h> +#include <lib/smccc.h> +#include <lib/utils.h> +#include <lib/xlat_tables/xlat_tables_v2.h> +#include <libfdt.h> +#include <plat/common/platform.h> +#include <services/el3_spmc_logical_sp.h> +#include <services/ffa_svc.h> +#include <services/spmc_svc.h> +#include <services/spmd_svc.h> +#include "spmc.h" +#include "spmc_shared_mem.h" + +#include <platform_def.h> + +/* Declare the maximum number of SPs and El3 LPs. */ +#define MAX_SP_LP_PARTITIONS SECURE_PARTITION_COUNT + MAX_EL3_LP_DESCS_COUNT + +/* + * Allocate a secure partition descriptor to describe each SP in the system that + * does not reside at EL3. + */ +static struct secure_partition_desc sp_desc[SECURE_PARTITION_COUNT]; + +/* + * Allocate an NS endpoint descriptor to describe each VM and the Hypervisor in + * the system that interacts with a SP. It is used to track the Hypervisor + * buffer pair, version and ID for now. It could be extended to track VM + * properties when the SPMC supports indirect messaging. + */ +static struct ns_endpoint_desc ns_ep_desc[NS_PARTITION_COUNT]; + +static uint64_t spmc_sp_interrupt_handler(uint32_t id, + uint32_t flags, + void *handle, + void *cookie); + +/* + * Helper function to obtain the array storing the EL3 + * Logical Partition descriptors. + */ +struct el3_lp_desc *get_el3_lp_array(void) +{ + return (struct el3_lp_desc *) EL3_LP_DESCS_START; +} + +/* + * Helper function to obtain the descriptor of the last SP to whom control was + * handed to on this physical cpu. Currently, we assume there is only one SP. + * TODO: Expand to track multiple partitions when required. + */ +struct secure_partition_desc *spmc_get_current_sp_ctx(void) +{ + return &(sp_desc[ACTIVE_SP_DESC_INDEX]); +} + +/* + * Helper function to obtain the execution context of an SP on the + * current physical cpu. + */ +struct sp_exec_ctx *spmc_get_sp_ec(struct secure_partition_desc *sp) +{ + return &(sp->ec[get_ec_index(sp)]); +} + +/* Helper function to get pointer to SP context from its ID. */ +struct secure_partition_desc *spmc_get_sp_ctx(uint16_t id) +{ + /* Check for Secure World Partitions. */ + for (unsigned int i = 0U; i < SECURE_PARTITION_COUNT; i++) { + if (sp_desc[i].sp_id == id) { + return &(sp_desc[i]); + } + } + return NULL; +} + +/* + * Helper function to obtain the descriptor of the Hypervisor or OS kernel. + * We assume that the first descriptor is reserved for this entity. + */ +struct ns_endpoint_desc *spmc_get_hyp_ctx(void) +{ + return &(ns_ep_desc[0]); +} + +/* + * Helper function to obtain the RX/TX buffer pair descriptor of the Hypervisor + * or OS kernel in the normal world or the last SP that was run. + */ +struct mailbox *spmc_get_mbox_desc(bool secure_origin) +{ + /* Obtain the RX/TX buffer pair descriptor. */ + if (secure_origin) { + return &(spmc_get_current_sp_ctx()->mailbox); + } else { + return &(spmc_get_hyp_ctx()->mailbox); + } +} + +/****************************************************************************** + * This function returns to the place where spmc_sp_synchronous_entry() was + * called originally. + ******************************************************************************/ +__dead2 void spmc_sp_synchronous_exit(struct sp_exec_ctx *ec, uint64_t rc) +{ + /* + * The SPM must have initiated the original request through a + * synchronous entry into the secure partition. Jump back to the + * original C runtime context with the value of rc in x0; + */ + spm_secure_partition_exit(ec->c_rt_ctx, rc); + + panic(); +} + +/******************************************************************************* + * Return FFA_ERROR with specified error code. + ******************************************************************************/ +uint64_t spmc_ffa_error_return(void *handle, int error_code) +{ + SMC_RET8(handle, FFA_ERROR, + FFA_TARGET_INFO_MBZ, error_code, + FFA_PARAM_MBZ, FFA_PARAM_MBZ, FFA_PARAM_MBZ, + FFA_PARAM_MBZ, FFA_PARAM_MBZ); +} + +/****************************************************************************** + * Helper function to validate a secure partition ID to ensure it does not + * conflict with any other FF-A component and follows the convention to + * indicate it resides within the secure world. + ******************************************************************************/ +bool is_ffa_secure_id_valid(uint16_t partition_id) +{ + struct el3_lp_desc *el3_lp_descs = get_el3_lp_array(); + + /* Ensure the ID is not the invalid partition ID. */ + if (partition_id == INV_SP_ID) { + return false; + } + + /* Ensure the ID is not the SPMD ID. */ + if (partition_id == SPMD_DIRECT_MSG_ENDPOINT_ID) { + return false; + } + + /* + * Ensure the ID follows the convention to indicate it resides + * in the secure world. + */ + if (!ffa_is_secure_world_id(partition_id)) { + return false; + } + + /* Ensure we don't conflict with the SPMC partition ID. */ + if (partition_id == FFA_SPMC_ID) { + return false; + } + + /* Ensure we do not already have an SP context with this ID. */ + if (spmc_get_sp_ctx(partition_id)) { + return false; + } + + /* Ensure we don't clash with any Logical SP's. */ + for (unsigned int i = 0U; i < EL3_LP_DESCS_COUNT; i++) { + if (el3_lp_descs[i].sp_id == partition_id) { + return false; + } + } + + return true; +} + +/******************************************************************************* + * This function either forwards the request to the other world or returns + * with an ERET depending on the source of the call. + * We can assume that the destination is for an entity at a lower exception + * level as any messages destined for a logical SP resident in EL3 will have + * already been taken care of by the SPMC before entering this function. + ******************************************************************************/ +static uint64_t spmc_smc_return(uint32_t smc_fid, + bool secure_origin, + uint64_t x1, + uint64_t x2, + uint64_t x3, + uint64_t x4, + void *handle, + void *cookie, + uint64_t flags, + uint16_t dst_id) +{ + /* If the destination is in the normal world always go via the SPMD. */ + if (ffa_is_normal_world_id(dst_id)) { + return spmd_smc_handler(smc_fid, x1, x2, x3, x4, + cookie, handle, flags); + } + /* + * If the caller is secure and we want to return to the secure world, + * ERET directly. + */ + else if (secure_origin && ffa_is_secure_world_id(dst_id)) { + SMC_RET5(handle, smc_fid, x1, x2, x3, x4); + } + /* If we originated in the normal world then switch contexts. */ + else if (!secure_origin && ffa_is_secure_world_id(dst_id)) { + return spmd_smc_switch_state(smc_fid, secure_origin, x1, x2, + x3, x4, handle); + } else { + /* Unknown State. */ + panic(); + } + + /* Shouldn't be Reached. */ + return 0; +} + +/******************************************************************************* + * FF-A ABI Handlers. + ******************************************************************************/ + +/******************************************************************************* + * Helper function to validate arg2 as part of a direct message. + ******************************************************************************/ +static inline bool direct_msg_validate_arg2(uint64_t x2) +{ + /* Check message type. */ + if (x2 & FFA_FWK_MSG_BIT) { + /* We have a framework message, ensure it is a known message. */ + if (x2 & ~(FFA_FWK_MSG_MASK | FFA_FWK_MSG_BIT)) { + VERBOSE("Invalid message format 0x%lx.\n", x2); + return false; + } + } else { + /* We have a partition messages, ensure x2 is not set. */ + if (x2 != (uint64_t) 0) { + VERBOSE("Arg2 MBZ for partition messages. (0x%lx).\n", + x2); + return false; + } + } + return true; +} + +/******************************************************************************* + * Helper function to validate the destination ID of a direct response. + ******************************************************************************/ +static bool direct_msg_validate_dst_id(uint16_t dst_id) +{ + struct secure_partition_desc *sp; + + /* Check if we're targeting a normal world partition. */ + if (ffa_is_normal_world_id(dst_id)) { + return true; + } + + /* Or directed to the SPMC itself.*/ + if (dst_id == FFA_SPMC_ID) { + return true; + } + + /* Otherwise ensure the SP exists. */ + sp = spmc_get_sp_ctx(dst_id); + if (sp != NULL) { + return true; + } + + return false; +} + +/******************************************************************************* + * Helper function to validate the response from a Logical Partition. + ******************************************************************************/ +static bool direct_msg_validate_lp_resp(uint16_t origin_id, uint16_t lp_id, + void *handle) +{ + /* Retrieve populated Direct Response Arguments. */ + uint64_t x1 = SMC_GET_GP(handle, CTX_GPREG_X1); + uint64_t x2 = SMC_GET_GP(handle, CTX_GPREG_X2); + uint16_t src_id = ffa_endpoint_source(x1); + uint16_t dst_id = ffa_endpoint_destination(x1); + + if (src_id != lp_id) { + ERROR("Invalid EL3 LP source ID (0x%x).\n", src_id); + return false; + } + + /* + * Check the destination ID is valid and ensure the LP is responding to + * the original request. + */ + if ((!direct_msg_validate_dst_id(dst_id)) || (dst_id != origin_id)) { + ERROR("Invalid EL3 LP destination ID (0x%x).\n", dst_id); + return false; + } + + if (!direct_msg_validate_arg2(x2)) { + ERROR("Invalid EL3 LP message encoding.\n"); + return false; + } + return true; +} + +/******************************************************************************* + * Handle direct request messages and route to the appropriate destination. + ******************************************************************************/ +static uint64_t direct_req_smc_handler(uint32_t smc_fid, + bool secure_origin, + uint64_t x1, + uint64_t x2, + uint64_t x3, + uint64_t x4, + void *cookie, + void *handle, + uint64_t flags) +{ + uint16_t src_id = ffa_endpoint_source(x1); + uint16_t dst_id = ffa_endpoint_destination(x1); + struct el3_lp_desc *el3_lp_descs; + struct secure_partition_desc *sp; + unsigned int idx; + + /* Check if arg2 has been populated correctly based on message type. */ + if (!direct_msg_validate_arg2(x2)) { + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + + /* Validate Sender is either the current SP or from the normal world. */ + if ((secure_origin && src_id != spmc_get_current_sp_ctx()->sp_id) || + (!secure_origin && !ffa_is_normal_world_id(src_id))) { + ERROR("Invalid direct request source ID (0x%x).\n", src_id); + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + + el3_lp_descs = get_el3_lp_array(); + + /* Check if the request is destined for a Logical Partition. */ + for (unsigned int i = 0U; i < MAX_EL3_LP_DESCS_COUNT; i++) { + if (el3_lp_descs[i].sp_id == dst_id) { + uint64_t ret = el3_lp_descs[i].direct_req( + smc_fid, secure_origin, x1, x2, + x3, x4, cookie, handle, flags); + if (!direct_msg_validate_lp_resp(src_id, dst_id, + handle)) { + panic(); + } + + /* Message checks out. */ + return ret; + } + } + + /* + * If the request was not targeted to a LSP and from the secure world + * then it is invalid since a SP cannot call into the Normal world and + * there is no other SP to call into. If there are other SPs in future + * then the partition runtime model would need to be validated as well. + */ + if (secure_origin) { + VERBOSE("Direct request not supported to the Normal World.\n"); + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + + /* Check if the SP ID is valid. */ + sp = spmc_get_sp_ctx(dst_id); + if (sp == NULL) { + VERBOSE("Direct request to unknown partition ID (0x%x).\n", + dst_id); + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + + /* + * Check that the target execution context is in a waiting state before + * forwarding the direct request to it. + */ + idx = get_ec_index(sp); + if (sp->ec[idx].rt_state != RT_STATE_WAITING) { + VERBOSE("SP context on core%u is not waiting (%u).\n", + idx, sp->ec[idx].rt_model); + return spmc_ffa_error_return(handle, FFA_ERROR_BUSY); + } + + /* + * Everything checks out so forward the request to the SP after updating + * its state and runtime model. + */ + sp->ec[idx].rt_state = RT_STATE_RUNNING; + sp->ec[idx].rt_model = RT_MODEL_DIR_REQ; + sp->ec[idx].dir_req_origin_id = src_id; + return spmc_smc_return(smc_fid, secure_origin, x1, x2, x3, x4, + handle, cookie, flags, dst_id); +} + +/******************************************************************************* + * Handle direct response messages and route to the appropriate destination. + ******************************************************************************/ +static uint64_t direct_resp_smc_handler(uint32_t smc_fid, + bool secure_origin, + uint64_t x1, + uint64_t x2, + uint64_t x3, + uint64_t x4, + void *cookie, + void *handle, + uint64_t flags) +{ + uint16_t dst_id = ffa_endpoint_destination(x1); + struct secure_partition_desc *sp; + unsigned int idx; + + /* Check if arg2 has been populated correctly based on message type. */ + if (!direct_msg_validate_arg2(x2)) { + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + + /* Check that the response did not originate from the Normal world. */ + if (!secure_origin) { + VERBOSE("Direct Response not supported from Normal World.\n"); + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + + /* + * Check that the response is either targeted to the Normal world or the + * SPMC e.g. a PM response. + */ + if (!direct_msg_validate_dst_id(dst_id)) { + VERBOSE("Direct response to invalid partition ID (0x%x).\n", + dst_id); + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + + /* Obtain the SP descriptor and update its runtime state. */ + sp = spmc_get_sp_ctx(ffa_endpoint_source(x1)); + if (sp == NULL) { + VERBOSE("Direct response to unknown partition ID (0x%x).\n", + dst_id); + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + + /* Sanity check state is being tracked correctly in the SPMC. */ + idx = get_ec_index(sp); + assert(sp->ec[idx].rt_state == RT_STATE_RUNNING); + + /* Ensure SP execution context was in the right runtime model. */ + if (sp->ec[idx].rt_model != RT_MODEL_DIR_REQ) { + VERBOSE("SP context on core%u not handling direct req (%u).\n", + idx, sp->ec[idx].rt_model); + return spmc_ffa_error_return(handle, FFA_ERROR_DENIED); + } + + if (sp->ec[idx].dir_req_origin_id != dst_id) { + WARN("Invalid direct resp partition ID 0x%x != 0x%x on core%u.\n", + dst_id, sp->ec[idx].dir_req_origin_id, idx); + return spmc_ffa_error_return(handle, FFA_ERROR_DENIED); + } + + /* Update the state of the SP execution context. */ + sp->ec[idx].rt_state = RT_STATE_WAITING; + + /* Clear the ongoing direct request ID. */ + sp->ec[idx].dir_req_origin_id = INV_SP_ID; + + /* + * If the receiver is not the SPMC then forward the response to the + * Normal world. + */ + if (dst_id == FFA_SPMC_ID) { + spmc_sp_synchronous_exit(&sp->ec[idx], x4); + /* Should not get here. */ + panic(); + } + + return spmc_smc_return(smc_fid, secure_origin, x1, x2, x3, x4, + handle, cookie, flags, dst_id); +} + +/******************************************************************************* + * This function handles the FFA_MSG_WAIT SMC to allow an SP to relinquish its + * cycles. + ******************************************************************************/ +static uint64_t msg_wait_handler(uint32_t smc_fid, + bool secure_origin, + uint64_t x1, + uint64_t x2, + uint64_t x3, + uint64_t x4, + void *cookie, + void *handle, + uint64_t flags) +{ + struct secure_partition_desc *sp; + unsigned int idx; + + /* + * Check that the response did not originate from the Normal world as + * only the secure world can call this ABI. + */ + if (!secure_origin) { + VERBOSE("Normal world cannot call FFA_MSG_WAIT.\n"); + return spmc_ffa_error_return(handle, FFA_ERROR_NOT_SUPPORTED); + } + + /* Get the descriptor of the SP that invoked FFA_MSG_WAIT. */ + sp = spmc_get_current_sp_ctx(); + if (sp == NULL) { + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + + /* + * Get the execution context of the SP that invoked FFA_MSG_WAIT. + */ + idx = get_ec_index(sp); + + /* Ensure SP execution context was in the right runtime model. */ + if (sp->ec[idx].rt_model == RT_MODEL_DIR_REQ) { + return spmc_ffa_error_return(handle, FFA_ERROR_DENIED); + } + + /* Sanity check the state is being tracked correctly in the SPMC. */ + assert(sp->ec[idx].rt_state == RT_STATE_RUNNING); + + /* + * Perform a synchronous exit if the partition was initialising. The + * state is updated after the exit. + */ + if (sp->ec[idx].rt_model == RT_MODEL_INIT) { + spmc_sp_synchronous_exit(&sp->ec[idx], x4); + /* Should not get here */ + panic(); + } + + /* Update the state of the SP execution context. */ + sp->ec[idx].rt_state = RT_STATE_WAITING; + + /* Resume normal world if a secure interrupt was handled. */ + if (sp->ec[idx].rt_model == RT_MODEL_INTR) { + /* FFA_MSG_WAIT can only be called from the secure world. */ + unsigned int secure_state_in = SECURE; + unsigned int secure_state_out = NON_SECURE; + + cm_el1_sysregs_context_save(secure_state_in); + cm_el1_sysregs_context_restore(secure_state_out); + cm_set_next_eret_context(secure_state_out); + SMC_RET0(cm_get_context(secure_state_out)); + } + + /* Forward the response to the Normal world. */ + return spmc_smc_return(smc_fid, secure_origin, x1, x2, x3, x4, + handle, cookie, flags, FFA_NWD_ID); +} + +static uint64_t ffa_error_handler(uint32_t smc_fid, + bool secure_origin, + uint64_t x1, + uint64_t x2, + uint64_t x3, + uint64_t x4, + void *cookie, + void *handle, + uint64_t flags) +{ + struct secure_partition_desc *sp; + unsigned int idx; + + /* Check that the response did not originate from the Normal world. */ + if (!secure_origin) { + return spmc_ffa_error_return(handle, FFA_ERROR_NOT_SUPPORTED); + } + + /* Get the descriptor of the SP that invoked FFA_ERROR. */ + sp = spmc_get_current_sp_ctx(); + if (sp == NULL) { + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + + /* Get the execution context of the SP that invoked FFA_ERROR. */ + idx = get_ec_index(sp); + + /* + * We only expect FFA_ERROR to be received during SP initialisation + * otherwise this is an invalid call. + */ + if (sp->ec[idx].rt_model == RT_MODEL_INIT) { + ERROR("SP 0x%x failed to initialize.\n", sp->sp_id); + spmc_sp_synchronous_exit(&sp->ec[idx], x2); + /* Should not get here. */ + panic(); + } + + return spmc_ffa_error_return(handle, FFA_ERROR_NOT_SUPPORTED); +} + +static uint64_t ffa_version_handler(uint32_t smc_fid, + bool secure_origin, + uint64_t x1, + uint64_t x2, + uint64_t x3, + uint64_t x4, + void *cookie, + void *handle, + uint64_t flags) +{ + uint32_t requested_version = x1 & FFA_VERSION_MASK; + + if (requested_version & FFA_VERSION_BIT31_MASK) { + /* Invalid encoding, return an error. */ + SMC_RET1(handle, FFA_ERROR_NOT_SUPPORTED); + /* Execution stops here. */ + } + + /* Determine the caller to store the requested version. */ + if (secure_origin) { + /* + * Ensure that the SP is reporting the same version as + * specified in its manifest. If these do not match there is + * something wrong with the SP. + * TODO: Should we abort the SP? For now assert this is not + * case. + */ + assert(requested_version == + spmc_get_current_sp_ctx()->ffa_version); + } else { + /* + * If this is called by the normal world, record this + * information in its descriptor. + */ + spmc_get_hyp_ctx()->ffa_version = requested_version; + } + + SMC_RET1(handle, MAKE_FFA_VERSION(FFA_VERSION_MAJOR, + FFA_VERSION_MINOR)); +} + +/******************************************************************************* + * Helper function to obtain the FF-A version of the calling partition. + ******************************************************************************/ +uint32_t get_partition_ffa_version(bool secure_origin) +{ + if (secure_origin) { + return spmc_get_current_sp_ctx()->ffa_version; + } else { + return spmc_get_hyp_ctx()->ffa_version; + } +} + +static uint64_t rxtx_map_handler(uint32_t smc_fid, + bool secure_origin, + uint64_t x1, + uint64_t x2, + uint64_t x3, + uint64_t x4, + void *cookie, + void *handle, + uint64_t flags) +{ + int ret; + uint32_t error_code; + uint32_t mem_atts = secure_origin ? MT_SECURE : MT_NS; + struct mailbox *mbox; + uintptr_t tx_address = x1; + uintptr_t rx_address = x2; + uint32_t page_count = x3 & FFA_RXTX_PAGE_COUNT_MASK; /* Bits [5:0] */ + uint32_t buf_size = page_count * FFA_PAGE_SIZE; + + /* + * The SPMC does not support mapping of VM RX/TX pairs to facilitate + * indirect messaging with SPs. Check if the Hypervisor has invoked this + * ABI on behalf of a VM and reject it if this is the case. + */ + if (tx_address == 0 || rx_address == 0) { + WARN("Mapping RX/TX Buffers on behalf of VM not supported.\n"); + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + + /* Ensure the specified buffers are not the same. */ + if (tx_address == rx_address) { + WARN("TX Buffer must not be the same as RX Buffer.\n"); + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + + /* Ensure the buffer size is not 0. */ + if (buf_size == 0U) { + WARN("Buffer size must not be 0\n"); + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + + /* + * Ensure the buffer size is a multiple of the translation granule size + * in TF-A. + */ + if (buf_size % PAGE_SIZE != 0U) { + WARN("Buffer size must be aligned to translation granule.\n"); + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + + /* Obtain the RX/TX buffer pair descriptor. */ + mbox = spmc_get_mbox_desc(secure_origin); + + spin_lock(&mbox->lock); + + /* Check if buffers have already been mapped. */ + if (mbox->rx_buffer != 0 || mbox->tx_buffer != 0) { + WARN("RX/TX Buffers already mapped (%p/%p)\n", + (void *) mbox->rx_buffer, (void *)mbox->tx_buffer); + error_code = FFA_ERROR_DENIED; + goto err; + } + + /* memmap the TX buffer as read only. */ + ret = mmap_add_dynamic_region(tx_address, /* PA */ + tx_address, /* VA */ + buf_size, /* size */ + mem_atts | MT_RO_DATA); /* attrs */ + if (ret != 0) { + /* Return the correct error code. */ + error_code = (ret == -ENOMEM) ? FFA_ERROR_NO_MEMORY : + FFA_ERROR_INVALID_PARAMETER; + WARN("Unable to map TX buffer: %d\n", error_code); + goto err; + } + + /* memmap the RX buffer as read write. */ + ret = mmap_add_dynamic_region(rx_address, /* PA */ + rx_address, /* VA */ + buf_size, /* size */ + mem_atts | MT_RW_DATA); /* attrs */ + + if (ret != 0) { + error_code = (ret == -ENOMEM) ? FFA_ERROR_NO_MEMORY : + FFA_ERROR_INVALID_PARAMETER; + WARN("Unable to map RX buffer: %d\n", error_code); + /* Unmap the TX buffer again. */ + mmap_remove_dynamic_region(tx_address, buf_size); + goto err; + } + + mbox->tx_buffer = (void *) tx_address; + mbox->rx_buffer = (void *) rx_address; + mbox->rxtx_page_count = page_count; + spin_unlock(&mbox->lock); + + SMC_RET1(handle, FFA_SUCCESS_SMC32); + /* Execution stops here. */ +err: + spin_unlock(&mbox->lock); + return spmc_ffa_error_return(handle, error_code); +} + +static uint64_t rxtx_unmap_handler(uint32_t smc_fid, + bool secure_origin, + uint64_t x1, + uint64_t x2, + uint64_t x3, + uint64_t x4, + void *cookie, + void *handle, + uint64_t flags) +{ + struct mailbox *mbox = spmc_get_mbox_desc(secure_origin); + uint32_t buf_size = mbox->rxtx_page_count * FFA_PAGE_SIZE; + + /* + * The SPMC does not support mapping of VM RX/TX pairs to facilitate + * indirect messaging with SPs. Check if the Hypervisor has invoked this + * ABI on behalf of a VM and reject it if this is the case. + */ + if (x1 != 0UL) { + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + + spin_lock(&mbox->lock); + + /* Check if buffers are currently mapped. */ + if (mbox->rx_buffer == 0 || mbox->tx_buffer == 0) { + spin_unlock(&mbox->lock); + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + + /* Unmap RX Buffer */ + if (mmap_remove_dynamic_region((uintptr_t) mbox->rx_buffer, + buf_size) != 0) { + WARN("Unable to unmap RX buffer!\n"); + } + + mbox->rx_buffer = 0; + + /* Unmap TX Buffer */ + if (mmap_remove_dynamic_region((uintptr_t) mbox->tx_buffer, + buf_size) != 0) { + WARN("Unable to unmap TX buffer!\n"); + } + + mbox->tx_buffer = 0; + mbox->rxtx_page_count = 0; + + spin_unlock(&mbox->lock); + SMC_RET1(handle, FFA_SUCCESS_SMC32); +} + +/* + * Helper function to populate the properties field of a Partition Info Get + * descriptor. + */ +static uint32_t +partition_info_get_populate_properties(uint32_t sp_properties, + enum sp_execution_state sp_ec_state) +{ + uint32_t properties = sp_properties; + uint32_t ec_state; + + /* Determine the execution state of the SP. */ + ec_state = sp_ec_state == SP_STATE_AARCH64 ? + FFA_PARTITION_INFO_GET_AARCH64_STATE : + FFA_PARTITION_INFO_GET_AARCH32_STATE; + + properties |= ec_state << FFA_PARTITION_INFO_GET_EXEC_STATE_SHIFT; + + return properties; +} + +/* + * Collate the partition information in a v1.1 partition information + * descriptor format, this will be converter later if required. + */ +static int partition_info_get_handler_v1_1(uint32_t *uuid, + struct ffa_partition_info_v1_1 + *partitions, + uint32_t max_partitions, + uint32_t *partition_count) +{ + uint32_t index; + struct ffa_partition_info_v1_1 *desc; + bool null_uuid = is_null_uuid(uuid); + struct el3_lp_desc *el3_lp_descs = get_el3_lp_array(); + + /* Deal with Logical Partitions. */ + for (index = 0U; index < EL3_LP_DESCS_COUNT; index++) { + if (null_uuid || uuid_match(uuid, el3_lp_descs[index].uuid)) { + /* Found a matching UUID, populate appropriately. */ + if (*partition_count >= max_partitions) { + return FFA_ERROR_NO_MEMORY; + } + + desc = &partitions[*partition_count]; + desc->ep_id = el3_lp_descs[index].sp_id; + desc->execution_ctx_count = PLATFORM_CORE_COUNT; + /* LSPs must be AArch64. */ + desc->properties = + partition_info_get_populate_properties( + el3_lp_descs[index].properties, + SP_STATE_AARCH64); + + if (null_uuid) { + copy_uuid(desc->uuid, el3_lp_descs[index].uuid); + } + (*partition_count)++; + } + } + + /* Deal with physical SP's. */ + for (index = 0U; index < SECURE_PARTITION_COUNT; index++) { + if (null_uuid || uuid_match(uuid, sp_desc[index].uuid)) { + /* Found a matching UUID, populate appropriately. */ + if (*partition_count >= max_partitions) { + return FFA_ERROR_NO_MEMORY; + } + + desc = &partitions[*partition_count]; + desc->ep_id = sp_desc[index].sp_id; + /* + * Execution context count must match No. cores for + * S-EL1 SPs. + */ + desc->execution_ctx_count = PLATFORM_CORE_COUNT; + desc->properties = + partition_info_get_populate_properties( + sp_desc[index].properties, + sp_desc[index].execution_state); + + if (null_uuid) { + copy_uuid(desc->uuid, sp_desc[index].uuid); + } + (*partition_count)++; + } + } + return 0; +} + +/* + * Handle the case where that caller only wants the count of partitions + * matching a given UUID and does not want the corresponding descriptors + * populated. + */ +static uint32_t partition_info_get_handler_count_only(uint32_t *uuid) +{ + uint32_t index = 0; + uint32_t partition_count = 0; + bool null_uuid = is_null_uuid(uuid); + struct el3_lp_desc *el3_lp_descs = get_el3_lp_array(); + + /* Deal with Logical Partitions. */ + for (index = 0U; index < EL3_LP_DESCS_COUNT; index++) { + if (null_uuid || + uuid_match(uuid, el3_lp_descs[index].uuid)) { + (partition_count)++; + } + } + + /* Deal with physical SP's. */ + for (index = 0U; index < SECURE_PARTITION_COUNT; index++) { + if (null_uuid || uuid_match(uuid, sp_desc[index].uuid)) { + (partition_count)++; + } + } + return partition_count; +} + +/* + * If the caller of the PARTITION_INFO_GET ABI was a v1.0 caller, populate + * the corresponding descriptor format from the v1.1 descriptor array. + */ +static uint64_t partition_info_populate_v1_0(struct ffa_partition_info_v1_1 + *partitions, + struct mailbox *mbox, + int partition_count) +{ + uint32_t index; + uint32_t buf_size; + uint32_t descriptor_size; + struct ffa_partition_info_v1_0 *v1_0_partitions = + (struct ffa_partition_info_v1_0 *) mbox->rx_buffer; + + buf_size = mbox->rxtx_page_count * FFA_PAGE_SIZE; + descriptor_size = partition_count * + sizeof(struct ffa_partition_info_v1_0); + + if (descriptor_size > buf_size) { + return FFA_ERROR_NO_MEMORY; + } + + for (index = 0U; index < partition_count; index++) { + v1_0_partitions[index].ep_id = partitions[index].ep_id; + v1_0_partitions[index].execution_ctx_count = + partitions[index].execution_ctx_count; + /* Only report v1.0 properties. */ + v1_0_partitions[index].properties = + (partitions[index].properties & + FFA_PARTITION_INFO_GET_PROPERTIES_V1_0_MASK); + } + return 0; +} + +/* + * Main handler for FFA_PARTITION_INFO_GET which supports both FF-A v1.1 and + * v1.0 implementations. + */ +static uint64_t partition_info_get_handler(uint32_t smc_fid, + bool secure_origin, + uint64_t x1, + uint64_t x2, + uint64_t x3, + uint64_t x4, + void *cookie, + void *handle, + uint64_t flags) +{ + int ret; + uint32_t partition_count = 0; + uint32_t size = 0; + uint32_t ffa_version = get_partition_ffa_version(secure_origin); + struct mailbox *mbox; + uint64_t info_get_flags; + bool count_only; + uint32_t uuid[4]; + + uuid[0] = x1; + uuid[1] = x2; + uuid[2] = x3; + uuid[3] = x4; + + /* Determine if the Partition descriptors should be populated. */ + info_get_flags = SMC_GET_GP(handle, CTX_GPREG_X5); + count_only = (info_get_flags & FFA_PARTITION_INFO_GET_COUNT_FLAG_MASK); + + /* Handle the case where we don't need to populate the descriptors. */ + if (count_only) { + partition_count = partition_info_get_handler_count_only(uuid); + if (partition_count == 0) { + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + } else { + struct ffa_partition_info_v1_1 partitions[MAX_SP_LP_PARTITIONS]; + + /* + * Handle the case where the partition descriptors are required, + * check we have the buffers available and populate the + * appropriate structure version. + */ + + /* Obtain the v1.1 format of the descriptors. */ + ret = partition_info_get_handler_v1_1(uuid, partitions, + MAX_SP_LP_PARTITIONS, + &partition_count); + + /* Check if an error occurred during discovery. */ + if (ret != 0) { + goto err; + } + + /* If we didn't find any matches the UUID is unknown. */ + if (partition_count == 0) { + ret = FFA_ERROR_INVALID_PARAMETER; + goto err; + } + + /* Obtain the partition mailbox RX/TX buffer pair descriptor. */ + mbox = spmc_get_mbox_desc(secure_origin); + + /* + * If the caller has not bothered registering its RX/TX pair + * then return an error code. + */ + spin_lock(&mbox->lock); + if (mbox->rx_buffer == NULL) { + ret = FFA_ERROR_BUSY; + goto err_unlock; + } + + /* Ensure the RX buffer is currently free. */ + if (mbox->state != MAILBOX_STATE_EMPTY) { + ret = FFA_ERROR_BUSY; + goto err_unlock; + } + + /* Zero the RX buffer before populating. */ + (void)memset(mbox->rx_buffer, 0, + mbox->rxtx_page_count * FFA_PAGE_SIZE); + + /* + * Depending on the FF-A version of the requesting partition + * we may need to convert to a v1.0 format otherwise we can copy + * directly. + */ + if (ffa_version == MAKE_FFA_VERSION(U(1), U(0))) { + ret = partition_info_populate_v1_0(partitions, + mbox, + partition_count); + if (ret != 0) { + goto err_unlock; + } + } else { + uint32_t buf_size = mbox->rxtx_page_count * + FFA_PAGE_SIZE; + + /* Ensure the descriptor will fit in the buffer. */ + size = sizeof(struct ffa_partition_info_v1_1); + if (partition_count * size > buf_size) { + ret = FFA_ERROR_NO_MEMORY; + goto err_unlock; + } + memcpy(mbox->rx_buffer, partitions, + partition_count * size); + } + + mbox->state = MAILBOX_STATE_FULL; + spin_unlock(&mbox->lock); + } + SMC_RET4(handle, FFA_SUCCESS_SMC32, 0, partition_count, size); + +err_unlock: + spin_unlock(&mbox->lock); +err: + return spmc_ffa_error_return(handle, ret); +} + +static uint64_t ffa_feature_success(void *handle, uint32_t arg2) +{ + SMC_RET3(handle, FFA_SUCCESS_SMC32, 0, arg2); +} + +static uint64_t ffa_features_retrieve_request(bool secure_origin, + uint32_t input_properties, + void *handle) +{ + /* + * If we're called by the normal world we don't support any + * additional features. + */ + if (!secure_origin) { + if ((input_properties & FFA_FEATURES_RET_REQ_NS_BIT) != 0U) { + return spmc_ffa_error_return(handle, + FFA_ERROR_NOT_SUPPORTED); + } + + } else { + struct secure_partition_desc *sp = spmc_get_current_sp_ctx(); + /* + * If v1.1 the NS bit must be set otherwise it is an invalid + * call. If v1.0 check and store whether the SP has requested + * the use of the NS bit. + */ + if (sp->ffa_version == MAKE_FFA_VERSION(1, 1)) { + if ((input_properties & + FFA_FEATURES_RET_REQ_NS_BIT) == 0U) { + return spmc_ffa_error_return(handle, + FFA_ERROR_NOT_SUPPORTED); + } + return ffa_feature_success(handle, + FFA_FEATURES_RET_REQ_NS_BIT); + } else { + sp->ns_bit_requested = (input_properties & + FFA_FEATURES_RET_REQ_NS_BIT) != + 0U; + } + if (sp->ns_bit_requested) { + return ffa_feature_success(handle, + FFA_FEATURES_RET_REQ_NS_BIT); + } + } + SMC_RET1(handle, FFA_SUCCESS_SMC32); +} + +static uint64_t ffa_features_handler(uint32_t smc_fid, + bool secure_origin, + uint64_t x1, + uint64_t x2, + uint64_t x3, + uint64_t x4, + void *cookie, + void *handle, + uint64_t flags) +{ + uint32_t function_id = (uint32_t) x1; + uint32_t input_properties = (uint32_t) x2; + + /* Check if a Feature ID was requested. */ + if ((function_id & FFA_FEATURES_BIT31_MASK) == 0U) { + /* We currently don't support any additional features. */ + return spmc_ffa_error_return(handle, FFA_ERROR_NOT_SUPPORTED); + } + + /* + * Handle the cases where we have separate handlers due to additional + * properties. + */ + switch (function_id) { + case FFA_MEM_RETRIEVE_REQ_SMC32: + case FFA_MEM_RETRIEVE_REQ_SMC64: + return ffa_features_retrieve_request(secure_origin, + input_properties, + handle); + } + + /* + * We don't currently support additional input properties for these + * other ABIs therefore ensure this value is set to 0. + */ + if (input_properties != 0U) { + return spmc_ffa_error_return(handle, + FFA_ERROR_NOT_SUPPORTED); + } + + /* Report if any other FF-A ABI is supported. */ + switch (function_id) { + /* Supported features from both worlds. */ + case FFA_ERROR: + case FFA_SUCCESS_SMC32: + case FFA_INTERRUPT: + case FFA_SPM_ID_GET: + case FFA_ID_GET: + case FFA_FEATURES: + case FFA_VERSION: + case FFA_RX_RELEASE: + case FFA_MSG_SEND_DIRECT_REQ_SMC32: + case FFA_MSG_SEND_DIRECT_REQ_SMC64: + case FFA_PARTITION_INFO_GET: + case FFA_RXTX_MAP_SMC32: + case FFA_RXTX_MAP_SMC64: + case FFA_RXTX_UNMAP: + case FFA_MEM_FRAG_TX: + case FFA_MSG_RUN: + + /* + * We are relying on the fact that the other registers + * will be set to 0 as these values align with the + * currently implemented features of the SPMC. If this + * changes this function must be extended to handle + * reporting the additional functionality. + */ + + SMC_RET1(handle, FFA_SUCCESS_SMC32); + /* Execution stops here. */ + + /* Supported ABIs only from the secure world. */ + case FFA_SECONDARY_EP_REGISTER_SMC64: + case FFA_MSG_SEND_DIRECT_RESP_SMC32: + case FFA_MSG_SEND_DIRECT_RESP_SMC64: + case FFA_MEM_RELINQUISH: + case FFA_MSG_WAIT: + + if (!secure_origin) { + return spmc_ffa_error_return(handle, + FFA_ERROR_NOT_SUPPORTED); + } + SMC_RET1(handle, FFA_SUCCESS_SMC32); + /* Execution stops here. */ + + /* Supported features only from the normal world. */ + case FFA_MEM_SHARE_SMC32: + case FFA_MEM_SHARE_SMC64: + case FFA_MEM_LEND_SMC32: + case FFA_MEM_LEND_SMC64: + case FFA_MEM_RECLAIM: + case FFA_MEM_FRAG_RX: + + if (secure_origin) { + return spmc_ffa_error_return(handle, + FFA_ERROR_NOT_SUPPORTED); + } + SMC_RET1(handle, FFA_SUCCESS_SMC32); + /* Execution stops here. */ + + default: + return spmc_ffa_error_return(handle, + FFA_ERROR_NOT_SUPPORTED); + } +} + +static uint64_t ffa_id_get_handler(uint32_t smc_fid, + bool secure_origin, + uint64_t x1, + uint64_t x2, + uint64_t x3, + uint64_t x4, + void *cookie, + void *handle, + uint64_t flags) +{ + if (secure_origin) { + SMC_RET3(handle, FFA_SUCCESS_SMC32, 0x0, + spmc_get_current_sp_ctx()->sp_id); + } else { + SMC_RET3(handle, FFA_SUCCESS_SMC32, 0x0, + spmc_get_hyp_ctx()->ns_ep_id); + } +} + +/* + * Enable an SP to query the ID assigned to the SPMC. + */ +static uint64_t ffa_spm_id_get_handler(uint32_t smc_fid, + bool secure_origin, + uint64_t x1, + uint64_t x2, + uint64_t x3, + uint64_t x4, + void *cookie, + void *handle, + uint64_t flags) +{ + assert(x1 == 0UL); + assert(x2 == 0UL); + assert(x3 == 0UL); + assert(x4 == 0UL); + assert(SMC_GET_GP(handle, CTX_GPREG_X5) == 0UL); + assert(SMC_GET_GP(handle, CTX_GPREG_X6) == 0UL); + assert(SMC_GET_GP(handle, CTX_GPREG_X7) == 0UL); + + SMC_RET3(handle, FFA_SUCCESS_SMC32, 0x0, FFA_SPMC_ID); +} + +static uint64_t ffa_run_handler(uint32_t smc_fid, + bool secure_origin, + uint64_t x1, + uint64_t x2, + uint64_t x3, + uint64_t x4, + void *cookie, + void *handle, + uint64_t flags) +{ + struct secure_partition_desc *sp; + uint16_t target_id = FFA_RUN_EP_ID(x1); + uint16_t vcpu_id = FFA_RUN_VCPU_ID(x1); + unsigned int idx; + unsigned int *rt_state; + unsigned int *rt_model; + + /* Can only be called from the normal world. */ + if (secure_origin) { + ERROR("FFA_RUN can only be called from NWd.\n"); + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + + /* Cannot run a Normal world partition. */ + if (ffa_is_normal_world_id(target_id)) { + ERROR("Cannot run a NWd partition (0x%x).\n", target_id); + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + + /* Check that the target SP exists. */ + sp = spmc_get_sp_ctx(target_id); + ERROR("Unknown partition ID (0x%x).\n", target_id); + if (sp == NULL) { + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + + idx = get_ec_index(sp); + if (idx != vcpu_id) { + ERROR("Cannot run vcpu %d != %d.\n", idx, vcpu_id); + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + rt_state = &((sp->ec[idx]).rt_state); + rt_model = &((sp->ec[idx]).rt_model); + if (*rt_state == RT_STATE_RUNNING) { + ERROR("Partition (0x%x) is already running.\n", target_id); + return spmc_ffa_error_return(handle, FFA_ERROR_BUSY); + } + + /* + * Sanity check that if the execution context was not waiting then it + * was either in the direct request or the run partition runtime model. + */ + if (*rt_state == RT_STATE_PREEMPTED || *rt_state == RT_STATE_BLOCKED) { + assert(*rt_model == RT_MODEL_RUN || + *rt_model == RT_MODEL_DIR_REQ); + } + + /* + * If the context was waiting then update the partition runtime model. + */ + if (*rt_state == RT_STATE_WAITING) { + *rt_model = RT_MODEL_RUN; + } + + /* + * Forward the request to the correct SP vCPU after updating + * its state. + */ + *rt_state = RT_STATE_RUNNING; + + return spmc_smc_return(smc_fid, secure_origin, x1, 0, 0, 0, + handle, cookie, flags, target_id); +} + +static uint64_t rx_release_handler(uint32_t smc_fid, + bool secure_origin, + uint64_t x1, + uint64_t x2, + uint64_t x3, + uint64_t x4, + void *cookie, + void *handle, + uint64_t flags) +{ + struct mailbox *mbox = spmc_get_mbox_desc(secure_origin); + + spin_lock(&mbox->lock); + + if (mbox->state != MAILBOX_STATE_FULL) { + spin_unlock(&mbox->lock); + return spmc_ffa_error_return(handle, FFA_ERROR_DENIED); + } + + mbox->state = MAILBOX_STATE_EMPTY; + spin_unlock(&mbox->lock); + + SMC_RET1(handle, FFA_SUCCESS_SMC32); +} + +/* + * Perform initial validation on the provided secondary entry point. + * For now ensure it does not lie within the BL31 Image or the SP's + * RX/TX buffers as these are mapped within EL3. + * TODO: perform validation for additional invalid memory regions. + */ +static int validate_secondary_ep(uintptr_t ep, struct secure_partition_desc *sp) +{ + struct mailbox *mb; + uintptr_t buffer_size; + uintptr_t sp_rx_buffer; + uintptr_t sp_tx_buffer; + uintptr_t sp_rx_buffer_limit; + uintptr_t sp_tx_buffer_limit; + + mb = &sp->mailbox; + buffer_size = (uintptr_t) (mb->rxtx_page_count * FFA_PAGE_SIZE); + sp_rx_buffer = (uintptr_t) mb->rx_buffer; + sp_tx_buffer = (uintptr_t) mb->tx_buffer; + sp_rx_buffer_limit = sp_rx_buffer + buffer_size; + sp_tx_buffer_limit = sp_tx_buffer + buffer_size; + + /* + * Check if the entry point lies within BL31, or the + * SP's RX or TX buffer. + */ + if ((ep >= BL31_BASE && ep < BL31_LIMIT) || + (ep >= sp_rx_buffer && ep < sp_rx_buffer_limit) || + (ep >= sp_tx_buffer && ep < sp_tx_buffer_limit)) { + return -EINVAL; + } + return 0; +} + +/******************************************************************************* + * This function handles the FFA_SECONDARY_EP_REGISTER SMC to allow an SP to + * register an entry point for initialization during a secondary cold boot. + ******************************************************************************/ +static uint64_t ffa_sec_ep_register_handler(uint32_t smc_fid, + bool secure_origin, + uint64_t x1, + uint64_t x2, + uint64_t x3, + uint64_t x4, + void *cookie, + void *handle, + uint64_t flags) +{ + struct secure_partition_desc *sp; + struct sp_exec_ctx *sp_ctx; + + /* This request cannot originate from the Normal world. */ + if (!secure_origin) { + WARN("%s: Can only be called from SWd.\n", __func__); + return spmc_ffa_error_return(handle, FFA_ERROR_NOT_SUPPORTED); + } + + /* Get the context of the current SP. */ + sp = spmc_get_current_sp_ctx(); + if (sp == NULL) { + WARN("%s: Cannot find SP context.\n", __func__); + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + + /* Only an S-EL1 SP should be invoking this ABI. */ + if (sp->runtime_el != S_EL1) { + WARN("%s: Can only be called for a S-EL1 SP.\n", __func__); + return spmc_ffa_error_return(handle, FFA_ERROR_DENIED); + } + + /* Ensure the SP is in its initialization state. */ + sp_ctx = spmc_get_sp_ec(sp); + if (sp_ctx->rt_model != RT_MODEL_INIT) { + WARN("%s: Can only be called during SP initialization.\n", + __func__); + return spmc_ffa_error_return(handle, FFA_ERROR_DENIED); + } + + /* Perform initial validation of the secondary entry point. */ + if (validate_secondary_ep(x1, sp)) { + WARN("%s: Invalid entry point provided (0x%lx).\n", + __func__, x1); + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + + /* + * Update the secondary entrypoint in SP context. + * We don't need a lock here as during partition initialization there + * will only be a single core online. + */ + sp->secondary_ep = x1; + VERBOSE("%s: 0x%lx\n", __func__, sp->secondary_ep); + + SMC_RET1(handle, FFA_SUCCESS_SMC32); +} + +/******************************************************************************* + * This function will parse the Secure Partition Manifest. From manifest, it + * will fetch details for preparing Secure partition image context and secure + * partition image boot arguments if any. + ******************************************************************************/ +static int sp_manifest_parse(void *sp_manifest, int offset, + struct secure_partition_desc *sp, + entry_point_info_t *ep_info, + int32_t *boot_info_reg) +{ + int32_t ret, node; + uint32_t config_32; + + /* + * Look for the mandatory fields that are expected to be present in + * the SP manifests. + */ + node = fdt_path_offset(sp_manifest, "/"); + if (node < 0) { + ERROR("Did not find root node.\n"); + return node; + } + + ret = fdt_read_uint32_array(sp_manifest, node, "uuid", + ARRAY_SIZE(sp->uuid), sp->uuid); + if (ret != 0) { + ERROR("Missing Secure Partition UUID.\n"); + return ret; + } + + ret = fdt_read_uint32(sp_manifest, node, "exception-level", &config_32); + if (ret != 0) { + ERROR("Missing SP Exception Level information.\n"); + return ret; + } + + sp->runtime_el = config_32; + + ret = fdt_read_uint32(sp_manifest, node, "ffa-version", &config_32); + if (ret != 0) { + ERROR("Missing Secure Partition FF-A Version.\n"); + return ret; + } + + sp->ffa_version = config_32; + + ret = fdt_read_uint32(sp_manifest, node, "execution-state", &config_32); + if (ret != 0) { + ERROR("Missing Secure Partition Execution State.\n"); + return ret; + } + + sp->execution_state = config_32; + + ret = fdt_read_uint32(sp_manifest, node, + "messaging-method", &config_32); + if (ret != 0) { + ERROR("Missing Secure Partition messaging method.\n"); + return ret; + } + + /* Validate this entry, we currently only support direct messaging. */ + if ((config_32 & ~(FFA_PARTITION_DIRECT_REQ_RECV | + FFA_PARTITION_DIRECT_REQ_SEND)) != 0U) { + WARN("Invalid Secure Partition messaging method (0x%x)\n", + config_32); + return -EINVAL; + } + + sp->properties = config_32; + + ret = fdt_read_uint32(sp_manifest, node, + "execution-ctx-count", &config_32); + + if (ret != 0) { + ERROR("Missing SP Execution Context Count.\n"); + return ret; + } + + /* + * Ensure this field is set correctly in the manifest however + * since this is currently a hardcoded value for S-EL1 partitions + * we don't need to save it here, just validate. + */ + if (config_32 != PLATFORM_CORE_COUNT) { + ERROR("SP Execution Context Count (%u) must be %u.\n", + config_32, PLATFORM_CORE_COUNT); + return -EINVAL; + } + + /* + * Look for the optional fields that are expected to be present in + * an SP manifest. + */ + ret = fdt_read_uint32(sp_manifest, node, "id", &config_32); + if (ret != 0) { + WARN("Missing Secure Partition ID.\n"); + } else { + if (!is_ffa_secure_id_valid(config_32)) { + ERROR("Invalid Secure Partition ID (0x%x).\n", + config_32); + return -EINVAL; + } + sp->sp_id = config_32; + } + + ret = fdt_read_uint32(sp_manifest, node, + "power-management-messages", &config_32); + if (ret != 0) { + WARN("Missing Power Management Messages entry.\n"); + } else { + /* + * Ensure only the currently supported power messages have + * been requested. + */ + if (config_32 & ~(FFA_PM_MSG_SUB_CPU_OFF | + FFA_PM_MSG_SUB_CPU_SUSPEND | + FFA_PM_MSG_SUB_CPU_SUSPEND_RESUME)) { + ERROR("Requested unsupported PM messages (%x)\n", + config_32); + return -EINVAL; + } + sp->pwr_mgmt_msgs = config_32; + } + + ret = fdt_read_uint32(sp_manifest, node, + "gp-register-num", &config_32); + if (ret != 0) { + WARN("Missing boot information register.\n"); + } else { + /* Check if a register number between 0-3 is specified. */ + if (config_32 < 4) { + *boot_info_reg = config_32; + } else { + WARN("Incorrect boot information register (%u).\n", + config_32); + } + } + + return 0; +} + +/******************************************************************************* + * This function gets the Secure Partition Manifest base and maps the manifest + * region. + * Currently only one Secure Partition manifest is considered which is used to + * prepare the context for the single Secure Partition. + ******************************************************************************/ +static int find_and_prepare_sp_context(void) +{ + void *sp_manifest; + uintptr_t manifest_base; + uintptr_t manifest_base_align; + entry_point_info_t *next_image_ep_info; + int32_t ret, boot_info_reg = -1; + struct secure_partition_desc *sp; + + next_image_ep_info = bl31_plat_get_next_image_ep_info(SECURE); + if (next_image_ep_info == NULL) { + WARN("No Secure Partition image provided by BL2.\n"); + return -ENOENT; + } + + sp_manifest = (void *)next_image_ep_info->args.arg0; + if (sp_manifest == NULL) { + WARN("Secure Partition manifest absent.\n"); + return -ENOENT; + } + + manifest_base = (uintptr_t)sp_manifest; + manifest_base_align = page_align(manifest_base, DOWN); + + /* + * Map the secure partition manifest region in the EL3 translation + * regime. + * Map an area equal to (2 * PAGE_SIZE) for now. During manifest base + * alignment the region of 1 PAGE_SIZE from manifest align base may + * not completely accommodate the secure partition manifest region. + */ + ret = mmap_add_dynamic_region((unsigned long long)manifest_base_align, + manifest_base_align, + PAGE_SIZE * 2, + MT_RO_DATA); + if (ret != 0) { + ERROR("Error while mapping SP manifest (%d).\n", ret); + return ret; + } + + ret = fdt_node_offset_by_compatible(sp_manifest, -1, + "arm,ffa-manifest-1.0"); + if (ret < 0) { + ERROR("Error happened in SP manifest reading.\n"); + return -EINVAL; + } + + /* + * Store the size of the manifest so that it can be used later to pass + * the manifest as boot information later. + */ + next_image_ep_info->args.arg1 = fdt_totalsize(sp_manifest); + INFO("Manifest size = %lu bytes.\n", next_image_ep_info->args.arg1); + + /* + * Select an SP descriptor for initialising the partition's execution + * context on the primary CPU. + */ + sp = spmc_get_current_sp_ctx(); + + /* Initialize entry point information for the SP */ + SET_PARAM_HEAD(next_image_ep_info, PARAM_EP, VERSION_1, + SECURE | EP_ST_ENABLE); + + /* Parse the SP manifest. */ + ret = sp_manifest_parse(sp_manifest, ret, sp, next_image_ep_info, + &boot_info_reg); + if (ret != 0) { + ERROR("Error in Secure Partition manifest parsing.\n"); + return ret; + } + + /* Check that the runtime EL in the manifest was correct. */ + if (sp->runtime_el != S_EL1) { + ERROR("Unexpected runtime EL: %d\n", sp->runtime_el); + return -EINVAL; + } + + /* Perform any common initialisation. */ + spmc_sp_common_setup(sp, next_image_ep_info, boot_info_reg); + + /* Perform any initialisation specific to S-EL1 SPs. */ + spmc_el1_sp_setup(sp, next_image_ep_info); + + /* Initialize the SP context with the required ep info. */ + spmc_sp_common_ep_commit(sp, next_image_ep_info); + + return 0; +} + +/******************************************************************************* + * This function takes an SP context pointer and performs a synchronous entry + * into it. + ******************************************************************************/ +static int32_t logical_sp_init(void) +{ + int32_t rc = 0; + struct el3_lp_desc *el3_lp_descs; + + /* Perform initial validation of the Logical Partitions. */ + rc = el3_sp_desc_validate(); + if (rc != 0) { + ERROR("Logical Partition validation failed!\n"); + return rc; + } + + el3_lp_descs = get_el3_lp_array(); + + INFO("Logical Secure Partition init start.\n"); + for (unsigned int i = 0U; i < EL3_LP_DESCS_COUNT; i++) { + rc = el3_lp_descs[i].init(); + if (rc != 0) { + ERROR("Logical SP (0x%x) Failed to Initialize\n", + el3_lp_descs[i].sp_id); + return rc; + } + VERBOSE("Logical SP (0x%x) Initialized\n", + el3_lp_descs[i].sp_id); + } + + INFO("Logical Secure Partition init completed.\n"); + + return rc; +} + +uint64_t spmc_sp_synchronous_entry(struct sp_exec_ctx *ec) +{ + uint64_t rc; + + assert(ec != NULL); + + /* Assign the context of the SP to this CPU */ + cm_set_context(&(ec->cpu_ctx), SECURE); + + /* Restore the context assigned above */ + cm_el1_sysregs_context_restore(SECURE); + cm_set_next_eret_context(SECURE); + + /* Invalidate TLBs at EL1. */ + tlbivmalle1(); + dsbish(); + + /* Enter Secure Partition */ + rc = spm_secure_partition_enter(&ec->c_rt_ctx); + + /* Save secure state */ + cm_el1_sysregs_context_save(SECURE); + + return rc; +} + +/******************************************************************************* + * SPMC Helper Functions. + ******************************************************************************/ +static int32_t sp_init(void) +{ + uint64_t rc; + struct secure_partition_desc *sp; + struct sp_exec_ctx *ec; + + sp = spmc_get_current_sp_ctx(); + ec = spmc_get_sp_ec(sp); + ec->rt_model = RT_MODEL_INIT; + ec->rt_state = RT_STATE_RUNNING; + + INFO("Secure Partition (0x%x) init start.\n", sp->sp_id); + + rc = spmc_sp_synchronous_entry(ec); + if (rc != 0) { + /* Indicate SP init was not successful. */ + ERROR("SP (0x%x) failed to initialize (%lu).\n", + sp->sp_id, rc); + return 0; + } + + ec->rt_state = RT_STATE_WAITING; + INFO("Secure Partition initialized.\n"); + + return 1; +} + +static void initalize_sp_descs(void) +{ + struct secure_partition_desc *sp; + + for (unsigned int i = 0U; i < SECURE_PARTITION_COUNT; i++) { + sp = &sp_desc[i]; + sp->sp_id = INV_SP_ID; + sp->mailbox.rx_buffer = NULL; + sp->mailbox.tx_buffer = NULL; + sp->mailbox.state = MAILBOX_STATE_EMPTY; + sp->secondary_ep = 0; + } +} + +static void initalize_ns_ep_descs(void) +{ + struct ns_endpoint_desc *ns_ep; + + for (unsigned int i = 0U; i < NS_PARTITION_COUNT; i++) { + ns_ep = &ns_ep_desc[i]; + /* + * Clashes with the Hypervisor ID but will not be a + * problem in practice. + */ + ns_ep->ns_ep_id = 0; + ns_ep->ffa_version = 0; + ns_ep->mailbox.rx_buffer = NULL; + ns_ep->mailbox.tx_buffer = NULL; + ns_ep->mailbox.state = MAILBOX_STATE_EMPTY; + } +} + +/******************************************************************************* + * Initialize SPMC attributes for the SPMD. + ******************************************************************************/ +void spmc_populate_attrs(spmc_manifest_attribute_t *spmc_attrs) +{ + spmc_attrs->major_version = FFA_VERSION_MAJOR; + spmc_attrs->minor_version = FFA_VERSION_MINOR; + spmc_attrs->exec_state = MODE_RW_64; + spmc_attrs->spmc_id = FFA_SPMC_ID; +} + +/******************************************************************************* + * Initialize contexts of all Secure Partitions. + ******************************************************************************/ +int32_t spmc_setup(void) +{ + int32_t ret; + uint32_t flags; + + /* Initialize endpoint descriptors */ + initalize_sp_descs(); + initalize_ns_ep_descs(); + + /* + * Retrieve the information of the datastore for tracking shared memory + * requests allocated by platform code and zero the region if available. + */ + ret = plat_spmc_shmem_datastore_get(&spmc_shmem_obj_state.data, + &spmc_shmem_obj_state.data_size); + if (ret != 0) { + ERROR("Failed to obtain memory descriptor backing store!\n"); + return ret; + } + memset(spmc_shmem_obj_state.data, 0, spmc_shmem_obj_state.data_size); + + /* Setup logical SPs. */ + ret = logical_sp_init(); + if (ret != 0) { + ERROR("Failed to initialize Logical Partitions.\n"); + return ret; + } + + /* Perform physical SP setup. */ + + /* Disable MMU at EL1 (initialized by BL2) */ + disable_mmu_icache_el1(); + + /* Initialize context of the SP */ + INFO("Secure Partition context setup start.\n"); + + ret = find_and_prepare_sp_context(); + if (ret != 0) { + ERROR("Error in SP finding and context preparation.\n"); + return ret; + } + + /* Register power management hooks with PSCI */ + psci_register_spd_pm_hook(&spmc_pm); + + /* + * Register an interrupt handler for S-EL1 interrupts + * when generated during code executing in the + * non-secure state. + */ + flags = 0; + set_interrupt_rm_flag(flags, NON_SECURE); + ret = register_interrupt_type_handler(INTR_TYPE_S_EL1, + spmc_sp_interrupt_handler, + flags); + if (ret != 0) { + ERROR("Failed to register interrupt handler! (%d)\n", ret); + panic(); + } + + /* Register init function for deferred init. */ + bl31_register_bl32_init(&sp_init); + + INFO("Secure Partition setup done.\n"); + + return 0; +} + +/******************************************************************************* + * Secure Partition Manager SMC handler. + ******************************************************************************/ +uint64_t spmc_smc_handler(uint32_t smc_fid, + bool secure_origin, + uint64_t x1, + uint64_t x2, + uint64_t x3, + uint64_t x4, + void *cookie, + void *handle, + uint64_t flags) +{ + switch (smc_fid) { + + case FFA_VERSION: + return ffa_version_handler(smc_fid, secure_origin, x1, x2, x3, + x4, cookie, handle, flags); + + case FFA_SPM_ID_GET: + return ffa_spm_id_get_handler(smc_fid, secure_origin, x1, x2, + x3, x4, cookie, handle, flags); + + case FFA_ID_GET: + return ffa_id_get_handler(smc_fid, secure_origin, x1, x2, x3, + x4, cookie, handle, flags); + + case FFA_FEATURES: + return ffa_features_handler(smc_fid, secure_origin, x1, x2, x3, + x4, cookie, handle, flags); + + case FFA_SECONDARY_EP_REGISTER_SMC64: + return ffa_sec_ep_register_handler(smc_fid, secure_origin, x1, + x2, x3, x4, cookie, handle, + flags); + + case FFA_MSG_SEND_DIRECT_REQ_SMC32: + case FFA_MSG_SEND_DIRECT_REQ_SMC64: + return direct_req_smc_handler(smc_fid, secure_origin, x1, x2, + x3, x4, cookie, handle, flags); + + case FFA_MSG_SEND_DIRECT_RESP_SMC32: + case FFA_MSG_SEND_DIRECT_RESP_SMC64: + return direct_resp_smc_handler(smc_fid, secure_origin, x1, x2, + x3, x4, cookie, handle, flags); + + case FFA_RXTX_MAP_SMC32: + case FFA_RXTX_MAP_SMC64: + return rxtx_map_handler(smc_fid, secure_origin, x1, x2, x3, x4, + cookie, handle, flags); + + case FFA_RXTX_UNMAP: + return rxtx_unmap_handler(smc_fid, secure_origin, x1, x2, x3, + x4, cookie, handle, flags); + + case FFA_PARTITION_INFO_GET: + return partition_info_get_handler(smc_fid, secure_origin, x1, + x2, x3, x4, cookie, handle, + flags); + + case FFA_RX_RELEASE: + return rx_release_handler(smc_fid, secure_origin, x1, x2, x3, + x4, cookie, handle, flags); + + case FFA_MSG_WAIT: + return msg_wait_handler(smc_fid, secure_origin, x1, x2, x3, x4, + cookie, handle, flags); + + case FFA_ERROR: + return ffa_error_handler(smc_fid, secure_origin, x1, x2, x3, x4, + cookie, handle, flags); + + case FFA_MSG_RUN: + return ffa_run_handler(smc_fid, secure_origin, x1, x2, x3, x4, + cookie, handle, flags); + + case FFA_MEM_SHARE_SMC32: + case FFA_MEM_SHARE_SMC64: + case FFA_MEM_LEND_SMC32: + case FFA_MEM_LEND_SMC64: + return spmc_ffa_mem_send(smc_fid, secure_origin, x1, x2, x3, x4, + cookie, handle, flags); + + case FFA_MEM_FRAG_TX: + return spmc_ffa_mem_frag_tx(smc_fid, secure_origin, x1, x2, x3, + x4, cookie, handle, flags); + + case FFA_MEM_FRAG_RX: + return spmc_ffa_mem_frag_rx(smc_fid, secure_origin, x1, x2, x3, + x4, cookie, handle, flags); + + case FFA_MEM_RETRIEVE_REQ_SMC32: + case FFA_MEM_RETRIEVE_REQ_SMC64: + return spmc_ffa_mem_retrieve_req(smc_fid, secure_origin, x1, x2, + x3, x4, cookie, handle, flags); + + case FFA_MEM_RELINQUISH: + return spmc_ffa_mem_relinquish(smc_fid, secure_origin, x1, x2, + x3, x4, cookie, handle, flags); + + case FFA_MEM_RECLAIM: + return spmc_ffa_mem_reclaim(smc_fid, secure_origin, x1, x2, x3, + x4, cookie, handle, flags); + + default: + WARN("Unsupported FF-A call 0x%08x.\n", smc_fid); + break; + } + return spmc_ffa_error_return(handle, FFA_ERROR_NOT_SUPPORTED); +} + +/******************************************************************************* + * This function is the handler registered for S-EL1 interrupts by the SPMC. It + * validates the interrupt and upon success arranges entry into the SP for + * handling the interrupt. + ******************************************************************************/ +static uint64_t spmc_sp_interrupt_handler(uint32_t id, + uint32_t flags, + void *handle, + void *cookie) +{ + struct secure_partition_desc *sp = spmc_get_current_sp_ctx(); + struct sp_exec_ctx *ec; + uint32_t linear_id = plat_my_core_pos(); + + /* Sanity check for a NULL pointer dereference. */ + assert(sp != NULL); + + /* Check the security state when the exception was generated. */ + assert(get_interrupt_src_ss(flags) == NON_SECURE); + + /* Panic if not an S-EL1 Partition. */ + if (sp->runtime_el != S_EL1) { + ERROR("Interrupt received for a non S-EL1 SP on core%u.\n", + linear_id); + panic(); + } + + /* Obtain a reference to the SP execution context. */ + ec = spmc_get_sp_ec(sp); + + /* Ensure that the execution context is in waiting state else panic. */ + if (ec->rt_state != RT_STATE_WAITING) { + ERROR("SP EC on core%u is not waiting (%u), it is (%u).\n", + linear_id, RT_STATE_WAITING, ec->rt_state); + panic(); + } + + /* Update the runtime model and state of the partition. */ + ec->rt_model = RT_MODEL_INTR; + ec->rt_state = RT_STATE_RUNNING; + + VERBOSE("SP (0x%x) interrupt start on core%u.\n", sp->sp_id, linear_id); + + /* + * Forward the interrupt to the S-EL1 SP. The interrupt ID is not + * populated as the SP can determine this by itself. + */ + return spmd_smc_switch_state(FFA_INTERRUPT, false, + FFA_PARAM_MBZ, FFA_PARAM_MBZ, + FFA_PARAM_MBZ, FFA_PARAM_MBZ, + handle); +} diff --git a/services/std_svc/spm/el3_spmc/spmc_pm.c b/services/std_svc/spm/el3_spmc/spmc_pm.c new file mode 100644 index 0000000..c7e864f --- /dev/null +++ b/services/std_svc/spm/el3_spmc/spmc_pm.c @@ -0,0 +1,285 @@ +/* + * Copyright (c) 2022, ARM Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <assert.h> +#include <errno.h> + +#include <lib/el3_runtime/context_mgmt.h> +#include <lib/spinlock.h> +#include <plat/common/common_def.h> +#include <plat/common/platform.h> +#include <services/ffa_svc.h> +#include "spmc.h" + +#include <platform_def.h> + +/******************************************************************************* + * spmc_build_pm_message + * + * Builds an SPMC to SP direct message request. + ******************************************************************************/ +static void spmc_build_pm_message(gp_regs_t *gpregs, + unsigned long long message, + uint8_t pm_msg_type, + uint16_t sp_id) +{ + write_ctx_reg(gpregs, CTX_GPREG_X0, FFA_MSG_SEND_DIRECT_REQ_SMC32); + write_ctx_reg(gpregs, CTX_GPREG_X1, + (FFA_SPMC_ID << FFA_DIRECT_MSG_SOURCE_SHIFT) | + sp_id); + write_ctx_reg(gpregs, CTX_GPREG_X2, FFA_FWK_MSG_BIT | + (pm_msg_type & FFA_FWK_MSG_MASK)); + write_ctx_reg(gpregs, CTX_GPREG_X3, message); +} + +/******************************************************************************* + * This CPU has been turned on. Enter the SP to initialise S-EL1. + ******************************************************************************/ +static void spmc_cpu_on_finish_handler(u_register_t unused) +{ + struct secure_partition_desc *sp = spmc_get_current_sp_ctx(); + struct sp_exec_ctx *ec; + unsigned int linear_id = plat_my_core_pos(); + entry_point_info_t sec_ec_ep_info = {0}; + uint64_t rc; + + /* Sanity check for a NULL pointer dereference. */ + assert(sp != NULL); + + /* Initialize entry point information for the SP. */ + SET_PARAM_HEAD(&sec_ec_ep_info, PARAM_EP, VERSION_1, + SECURE | EP_ST_ENABLE); + + /* + * Check if the primary execution context registered an entry point else + * bail out early. + * TODO: Add support for boot reason in manifest to allow jumping to + * entrypoint into the primary execution context. + */ + if (sp->secondary_ep == 0) { + WARN("%s: No secondary ep on core%u\n", __func__, linear_id); + return; + } + + sec_ec_ep_info.pc = sp->secondary_ep; + + /* + * Setup and initialise the SP execution context on this physical cpu. + */ + spmc_el1_sp_setup(sp, &sec_ec_ep_info); + spmc_sp_common_ep_commit(sp, &sec_ec_ep_info); + + /* Obtain a reference to the SP execution context. */ + ec = spmc_get_sp_ec(sp); + + /* + * TODO: Should we do some PM related state tracking of the SP execution + * context here? + */ + + /* Update the runtime model and state of the partition. */ + ec->rt_model = RT_MODEL_INIT; + ec->rt_state = RT_STATE_RUNNING; + ec->dir_req_origin_id = INV_SP_ID; + + INFO("SP (0x%x) init start on core%u.\n", sp->sp_id, linear_id); + + rc = spmc_sp_synchronous_entry(ec); + if (rc != 0ULL) { + ERROR("%s failed (%lu) on CPU%u\n", __func__, rc, linear_id); + } + + /* Update the runtime state of the partition. */ + ec->rt_state = RT_STATE_WAITING; + + VERBOSE("CPU %u on!\n", linear_id); +} +/******************************************************************************* + * Helper function to send a FF-A power management message to an SP. + ******************************************************************************/ +static int32_t spmc_send_pm_msg(uint8_t pm_msg_type, + unsigned long long psci_event) +{ + struct secure_partition_desc *sp = spmc_get_current_sp_ctx(); + struct sp_exec_ctx *ec; + gp_regs_t *gpregs_ctx; + unsigned int linear_id = plat_my_core_pos(); + u_register_t resp; + uint64_t rc; + + /* Obtain a reference to the SP execution context. */ + ec = spmc_get_sp_ec(sp); + + /* + * TODO: Should we do some PM related state tracking of the SP execution + * context here? + */ + + /* + * Build an SPMC to SP direct message request. + * Note that x4-x6 should be populated with the original PSCI arguments. + */ + spmc_build_pm_message(get_gpregs_ctx(&ec->cpu_ctx), + psci_event, + pm_msg_type, + sp->sp_id); + + /* Sanity check partition state. */ + assert(ec->rt_state == RT_STATE_WAITING); + + /* Update the runtime model and state of the partition. */ + ec->rt_model = RT_MODEL_DIR_REQ; + ec->rt_state = RT_STATE_RUNNING; + ec->dir_req_origin_id = FFA_SPMC_ID; + + rc = spmc_sp_synchronous_entry(ec); + if (rc != 0ULL) { + ERROR("%s failed (%lu) on CPU%u.\n", __func__, rc, linear_id); + assert(false); + return -EINVAL; + } + + /* + * Validate we receive an expected response from the SP. + * TODO: We don't currently support aborting an SP in the scenario + * where it is misbehaving so assert these conditions are not + * met for now. + */ + gpregs_ctx = get_gpregs_ctx(&ec->cpu_ctx); + + /* Expect a direct message response from the SP. */ + resp = read_ctx_reg(gpregs_ctx, CTX_GPREG_X0); + if (resp != FFA_MSG_SEND_DIRECT_RESP_SMC32) { + ERROR("%s invalid SP response (%lx).\n", __func__, resp); + assert(false); + return -EINVAL; + } + + /* Ensure the sender and receiver are populated correctly. */ + resp = read_ctx_reg(gpregs_ctx, CTX_GPREG_X1); + if (!(ffa_endpoint_source(resp) == sp->sp_id && + ffa_endpoint_destination(resp) == FFA_SPMC_ID)) { + ERROR("%s invalid src/dst response (%lx).\n", __func__, resp); + assert(false); + return -EINVAL; + } + + /* Expect a PM message response from the SP. */ + resp = read_ctx_reg(gpregs_ctx, CTX_GPREG_X2); + if ((resp & FFA_FWK_MSG_BIT) == 0U || + ((resp & FFA_FWK_MSG_MASK) != FFA_PM_MSG_PM_RESP)) { + ERROR("%s invalid PM response (%lx).\n", __func__, resp); + assert(false); + return -EINVAL; + } + + /* Update the runtime state of the partition. */ + ec->rt_state = RT_STATE_WAITING; + + /* Return the status code returned by the SP */ + return read_ctx_reg(gpregs_ctx, CTX_GPREG_X3); +} + +/******************************************************************************* + * spmc_cpu_suspend_finish_handler + ******************************************************************************/ +static void spmc_cpu_suspend_finish_handler(u_register_t unused) +{ + struct secure_partition_desc *sp = spmc_get_current_sp_ctx(); + unsigned int linear_id = plat_my_core_pos(); + int32_t rc; + + /* Sanity check for a NULL pointer dereference. */ + assert(sp != NULL); + + /* + * Check if the SP has subscribed for this power management message. + * If not then we don't have anything else to do here. + */ + if ((sp->pwr_mgmt_msgs & FFA_PM_MSG_SUB_CPU_SUSPEND_RESUME) == 0U) { + goto exit; + } + + rc = spmc_send_pm_msg(FFA_PM_MSG_WB_REQ, FFA_WB_TYPE_NOTS2RAM); + if (rc < 0) { + ERROR("%s failed (%d) on CPU%u\n", __func__, rc, linear_id); + return; + } + +exit: + VERBOSE("CPU %u resumed!\n", linear_id); +} + +/******************************************************************************* + * spmc_cpu_suspend_handler + ******************************************************************************/ +static void spmc_cpu_suspend_handler(u_register_t unused) +{ + struct secure_partition_desc *sp = spmc_get_current_sp_ctx(); + unsigned int linear_id = plat_my_core_pos(); + int32_t rc; + + /* Sanity check for a NULL pointer dereference. */ + assert(sp != NULL); + + /* + * Check if the SP has subscribed for this power management message. + * If not then we don't have anything else to do here. + */ + if ((sp->pwr_mgmt_msgs & FFA_PM_MSG_SUB_CPU_SUSPEND) == 0U) { + goto exit; + } + + rc = spmc_send_pm_msg(FFA_FWK_MSG_PSCI, PSCI_CPU_SUSPEND_AARCH64); + if (rc < 0) { + ERROR("%s failed (%d) on CPU%u\n", __func__, rc, linear_id); + return; + } +exit: + VERBOSE("CPU %u suspend!\n", linear_id); +} + +/******************************************************************************* + * spmc_cpu_off_handler + ******************************************************************************/ +static int32_t spmc_cpu_off_handler(u_register_t unused) +{ + struct secure_partition_desc *sp = spmc_get_current_sp_ctx(); + unsigned int linear_id = plat_my_core_pos(); + int32_t ret = 0; + + /* Sanity check for a NULL pointer dereference. */ + assert(sp != NULL); + + /* + * Check if the SP has subscribed for this power management message. + * If not then we don't have anything else to do here. + */ + if ((sp->pwr_mgmt_msgs & FFA_PM_MSG_SUB_CPU_OFF) == 0U) { + goto exit; + } + + ret = spmc_send_pm_msg(FFA_FWK_MSG_PSCI, PSCI_CPU_OFF); + if (ret < 0) { + ERROR("%s failed (%d) on CPU%u\n", __func__, ret, linear_id); + return ret; + } + +exit: + VERBOSE("CPU %u off!\n", linear_id); + return ret; +} + +/******************************************************************************* + * Structure populated by the SPM Core to perform any bookkeeping before + * PSCI executes a power mgmt. operation. + ******************************************************************************/ +const spd_pm_ops_t spmc_pm = { + .svc_on_finish = spmc_cpu_on_finish_handler, + .svc_off = spmc_cpu_off_handler, + .svc_suspend = spmc_cpu_suspend_handler, + .svc_suspend_finish = spmc_cpu_suspend_finish_handler +}; diff --git a/services/std_svc/spm/el3_spmc/spmc_setup.c b/services/std_svc/spm/el3_spmc/spmc_setup.c new file mode 100644 index 0000000..6de25f6 --- /dev/null +++ b/services/std_svc/spm/el3_spmc/spmc_setup.c @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2022, ARM Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <assert.h> +#include <string.h> + +#include <arch.h> +#include <arch_helpers.h> +#include <common/debug.h> +#include <common/fdt_wrappers.h> +#include <context.h> +#include <lib/el3_runtime/context_mgmt.h> +#include <lib/utils.h> +#include <lib/xlat_tables/xlat_tables_v2.h> +#include <libfdt.h> +#include <plat/common/common_def.h> +#include <plat/common/platform.h> +#include <services/ffa_svc.h> +#include "spm_common.h" +#include "spmc.h" +#include <tools_share/firmware_image_package.h> + +#include <platform_def.h> + +/* + * Statically allocate a page of memory for passing boot information to an SP. + */ +static uint8_t ffa_boot_info_mem[PAGE_SIZE] __aligned(PAGE_SIZE); + +/* + * This function creates a initialization descriptor in the memory reserved + * for passing boot information to an SP. It then copies the partition manifest + * into this region and ensures that its reference in the initialization + * descriptor is updated. + */ +static void spmc_create_boot_info(entry_point_info_t *ep_info, + struct secure_partition_desc *sp) +{ + struct ffa_boot_info_header *boot_header; + struct ffa_boot_info_desc *boot_descriptor; + uintptr_t manifest_addr; + + /* + * Calculate the maximum size of the manifest that can be accommodated + * in the boot information memory region. + */ + const unsigned int + max_manifest_sz = sizeof(ffa_boot_info_mem) - + (sizeof(struct ffa_boot_info_header) + + sizeof(struct ffa_boot_info_desc)); + + /* + * The current implementation only supports the FF-A v1.1 + * implementation of the boot protocol, therefore check + * that a v1.0 SP has not requested use of the protocol. + */ + if (sp->ffa_version == MAKE_FFA_VERSION(1, 0)) { + ERROR("FF-A boot protocol not supported for v1.0 clients\n"); + return; + } + + /* + * Check if the manifest will fit into the boot info memory region else + * bail. + */ + if (ep_info->args.arg1 > max_manifest_sz) { + WARN("Unable to copy manifest into boot information. "); + WARN("Max sz = %u bytes. Manifest sz = %lu bytes\n", + max_manifest_sz, ep_info->args.arg1); + return; + } + + /* Zero the memory region before populating. */ + memset(ffa_boot_info_mem, 0, PAGE_SIZE); + + /* + * Populate the ffa_boot_info_header at the start of the boot info + * region. + */ + boot_header = (struct ffa_boot_info_header *) ffa_boot_info_mem; + + /* Position the ffa_boot_info_desc after the ffa_boot_info_header. */ + boot_header->offset_boot_info_desc = + sizeof(struct ffa_boot_info_header); + boot_descriptor = (struct ffa_boot_info_desc *) + (ffa_boot_info_mem + + boot_header->offset_boot_info_desc); + + /* + * We must use the FF-A version corresponding to the version implemented + * by the SP. Currently this can only be v1.1. + */ + boot_header->version = sp->ffa_version; + + /* Populate the boot information header. */ + boot_header->size_boot_info_desc = sizeof(struct ffa_boot_info_desc); + + /* Set the signature "0xFFA". */ + boot_header->signature = FFA_INIT_DESC_SIGNATURE; + + /* Set the count. Currently 1 since only the manifest is specified. */ + boot_header->count_boot_info_desc = 1; + + /* Populate the boot information descriptor for the manifest. */ + boot_descriptor->type = + FFA_BOOT_INFO_TYPE(FFA_BOOT_INFO_TYPE_STD) | + FFA_BOOT_INFO_TYPE_ID(FFA_BOOT_INFO_TYPE_ID_FDT); + + boot_descriptor->flags = + FFA_BOOT_INFO_FLAG_NAME(FFA_BOOT_INFO_FLAG_NAME_UUID) | + FFA_BOOT_INFO_FLAG_CONTENT(FFA_BOOT_INFO_FLAG_CONTENT_ADR); + + /* + * Copy the manifest into boot info region after the boot information + * descriptor. + */ + boot_descriptor->size_boot_info = (uint32_t) ep_info->args.arg1; + + manifest_addr = (uintptr_t) (ffa_boot_info_mem + + boot_header->offset_boot_info_desc + + boot_header->size_boot_info_desc); + + memcpy((void *) manifest_addr, (void *) ep_info->args.arg0, + boot_descriptor->size_boot_info); + + boot_descriptor->content = manifest_addr; + + /* Calculate the size of the total boot info blob. */ + boot_header->size_boot_info_blob = boot_header->offset_boot_info_desc + + boot_descriptor->size_boot_info + + (boot_header->count_boot_info_desc * + boot_header->size_boot_info_desc); + + INFO("SP boot info @ 0x%lx, size: %u bytes.\n", + (uintptr_t) ffa_boot_info_mem, + boot_header->size_boot_info_blob); + INFO("SP manifest @ 0x%lx, size: %u bytes.\n", + boot_descriptor->content, + boot_descriptor->size_boot_info); +} + +/* + * We are assuming that the index of the execution + * context used is the linear index of the current physical cpu. + */ +unsigned int get_ec_index(struct secure_partition_desc *sp) +{ + return plat_my_core_pos(); +} + +/* S-EL1 partition specific initialisation. */ +void spmc_el1_sp_setup(struct secure_partition_desc *sp, + entry_point_info_t *ep_info) +{ + /* Sanity check input arguments. */ + assert(sp != NULL); + assert(ep_info != NULL); + + /* Initialise the SPSR for S-EL1 SPs. */ + ep_info->spsr = SPSR_64(MODE_EL1, MODE_SP_ELX, + DISABLE_ALL_EXCEPTIONS); + + /* + * TF-A Implementation defined behaviour to provide the linear + * core ID in the x4 register. + */ + ep_info->args.arg4 = (uintptr_t) plat_my_core_pos(); + + /* + * Check whether setup is being performed for the primary or a secondary + * execution context. In the latter case, indicate to the SP that this + * is a warm boot. + * TODO: This check would need to be reworked if the same entry point is + * used for both primary and secondary initialisation. + */ + if (sp->secondary_ep != 0U) { + /* + * Sanity check that the secondary entry point is still what was + * originally set. + */ + assert(sp->secondary_ep == ep_info->pc); + ep_info->args.arg0 = FFA_WB_TYPE_S2RAM; + } +} + +/* Common initialisation for all SPs. */ +void spmc_sp_common_setup(struct secure_partition_desc *sp, + entry_point_info_t *ep_info, + int32_t boot_info_reg) +{ + uint16_t sp_id; + + /* Assign FF-A Partition ID if not already assigned. */ + if (sp->sp_id == INV_SP_ID) { + sp_id = FFA_SP_ID_BASE + ACTIVE_SP_DESC_INDEX; + /* + * Ensure we don't clash with previously assigned partition + * IDs. + */ + while (!is_ffa_secure_id_valid(sp_id)) { + sp_id++; + + if (sp_id == FFA_SWD_ID_LIMIT) { + ERROR("Unable to determine valid SP ID.\n"); + panic(); + } + } + sp->sp_id = sp_id; + } + + /* + * We currently only support S-EL1 partitions so ensure this is the + * case. + */ + assert(sp->runtime_el == S_EL1); + + /* Check if the SP wants to use the FF-A boot protocol. */ + if (boot_info_reg >= 0) { + /* + * Create a boot information descriptor and copy the partition + * manifest into the reserved memory region for consumption by + * the SP. + */ + spmc_create_boot_info(ep_info, sp); + + /* + * We have consumed what we need from ep args so we can now + * zero them before we start populating with new information + * specifically for the SP. + */ + zeromem(&ep_info->args, sizeof(ep_info->args)); + + /* + * Pass the address of the boot information in the + * boot_info_reg. + */ + switch (boot_info_reg) { + case 0: + ep_info->args.arg0 = (uintptr_t) ffa_boot_info_mem; + break; + case 1: + ep_info->args.arg1 = (uintptr_t) ffa_boot_info_mem; + break; + case 2: + ep_info->args.arg2 = (uintptr_t) ffa_boot_info_mem; + break; + case 3: + ep_info->args.arg3 = (uintptr_t) ffa_boot_info_mem; + break; + default: + ERROR("Invalid value for \"gp-register-num\" %d.\n", + boot_info_reg); + } + } else { + /* + * We don't need any of the information that was populated + * in ep_args so we can clear them. + */ + zeromem(&ep_info->args, sizeof(ep_info->args)); + } +} + +/* + * Initialise the SP context now we have populated the common and EL specific + * entrypoint information. + */ +void spmc_sp_common_ep_commit(struct secure_partition_desc *sp, + entry_point_info_t *ep_info) +{ + cpu_context_t *cpu_ctx; + + cpu_ctx = &(spmc_get_sp_ec(sp)->cpu_ctx); + print_entry_point_info(ep_info); + cm_setup_context(cpu_ctx, ep_info); +} diff --git a/services/std_svc/spm/el3_spmc/spmc_shared_mem.c b/services/std_svc/spm/el3_spmc/spmc_shared_mem.c new file mode 100644 index 0000000..5263c04 --- /dev/null +++ b/services/std_svc/spm/el3_spmc/spmc_shared_mem.c @@ -0,0 +1,1934 @@ +/* + * Copyright (c) 2022-2023, ARM Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +#include <assert.h> +#include <errno.h> +#include <inttypes.h> + +#include <common/debug.h> +#include <common/runtime_svc.h> +#include <lib/object_pool.h> +#include <lib/spinlock.h> +#include <lib/xlat_tables/xlat_tables_v2.h> +#include <services/ffa_svc.h> +#include "spmc.h" +#include "spmc_shared_mem.h" + +#include <platform_def.h> + +/** + * struct spmc_shmem_obj - Shared memory object. + * @desc_size: Size of @desc. + * @desc_filled: Size of @desc already received. + * @in_use: Number of clients that have called ffa_mem_retrieve_req + * without a matching ffa_mem_relinquish call. + * @desc: FF-A memory region descriptor passed in ffa_mem_share. + */ +struct spmc_shmem_obj { + size_t desc_size; + size_t desc_filled; + size_t in_use; + struct ffa_mtd desc; +}; + +/* + * Declare our data structure to store the metadata of memory share requests. + * The main datastore is allocated on a per platform basis to ensure enough + * storage can be made available. + * The address of the data store will be populated by the SPMC during its + * initialization. + */ + +struct spmc_shmem_obj_state spmc_shmem_obj_state = { + /* Set start value for handle so top 32 bits are needed quickly. */ + .next_handle = 0xffffffc0U, +}; + +/** + * spmc_shmem_obj_size - Convert from descriptor size to object size. + * @desc_size: Size of struct ffa_memory_region_descriptor object. + * + * Return: Size of struct spmc_shmem_obj object. + */ +static size_t spmc_shmem_obj_size(size_t desc_size) +{ + return desc_size + offsetof(struct spmc_shmem_obj, desc); +} + +/** + * spmc_shmem_obj_alloc - Allocate struct spmc_shmem_obj. + * @state: Global state. + * @desc_size: Size of struct ffa_memory_region_descriptor object that + * allocated object will hold. + * + * Return: Pointer to newly allocated object, or %NULL if there not enough space + * left. The returned pointer is only valid while @state is locked, to + * used it again after unlocking @state, spmc_shmem_obj_lookup must be + * called. + */ +static struct spmc_shmem_obj * +spmc_shmem_obj_alloc(struct spmc_shmem_obj_state *state, size_t desc_size) +{ + struct spmc_shmem_obj *obj; + size_t free = state->data_size - state->allocated; + size_t obj_size; + + if (state->data == NULL) { + ERROR("Missing shmem datastore!\n"); + return NULL; + } + + /* Ensure that descriptor size is aligned */ + if (!is_aligned(desc_size, 16)) { + WARN("%s(0x%zx) desc_size not 16-byte aligned\n", + __func__, desc_size); + return NULL; + } + + obj_size = spmc_shmem_obj_size(desc_size); + + /* Ensure the obj size has not overflowed. */ + if (obj_size < desc_size) { + WARN("%s(0x%zx) desc_size overflow\n", + __func__, desc_size); + return NULL; + } + + if (obj_size > free) { + WARN("%s(0x%zx) failed, free 0x%zx\n", + __func__, desc_size, free); + return NULL; + } + obj = (struct spmc_shmem_obj *)(state->data + state->allocated); + obj->desc = (struct ffa_mtd) {0}; + obj->desc_size = desc_size; + obj->desc_filled = 0; + obj->in_use = 0; + state->allocated += obj_size; + return obj; +} + +/** + * spmc_shmem_obj_free - Free struct spmc_shmem_obj. + * @state: Global state. + * @obj: Object to free. + * + * Release memory used by @obj. Other objects may move, so on return all + * pointers to struct spmc_shmem_obj object should be considered invalid, not + * just @obj. + * + * The current implementation always compacts the remaining objects to simplify + * the allocator and to avoid fragmentation. + */ + +static void spmc_shmem_obj_free(struct spmc_shmem_obj_state *state, + struct spmc_shmem_obj *obj) +{ + size_t free_size = spmc_shmem_obj_size(obj->desc_size); + uint8_t *shift_dest = (uint8_t *)obj; + uint8_t *shift_src = shift_dest + free_size; + size_t shift_size = state->allocated - (shift_src - state->data); + + if (shift_size != 0U) { + memmove(shift_dest, shift_src, shift_size); + } + state->allocated -= free_size; +} + +/** + * spmc_shmem_obj_lookup - Lookup struct spmc_shmem_obj by handle. + * @state: Global state. + * @handle: Unique handle of object to return. + * + * Return: struct spmc_shmem_obj_state object with handle matching @handle. + * %NULL, if not object in @state->data has a matching handle. + */ +static struct spmc_shmem_obj * +spmc_shmem_obj_lookup(struct spmc_shmem_obj_state *state, uint64_t handle) +{ + uint8_t *curr = state->data; + + while (curr - state->data < state->allocated) { + struct spmc_shmem_obj *obj = (struct spmc_shmem_obj *)curr; + + if (obj->desc.handle == handle) { + return obj; + } + curr += spmc_shmem_obj_size(obj->desc_size); + } + return NULL; +} + +/** + * spmc_shmem_obj_get_next - Get the next memory object from an offset. + * @offset: Offset used to track which objects have previously been + * returned. + * + * Return: the next struct spmc_shmem_obj_state object from the provided + * offset. + * %NULL, if there are no more objects. + */ +static struct spmc_shmem_obj * +spmc_shmem_obj_get_next(struct spmc_shmem_obj_state *state, size_t *offset) +{ + uint8_t *curr = state->data + *offset; + + if (curr - state->data < state->allocated) { + struct spmc_shmem_obj *obj = (struct spmc_shmem_obj *)curr; + + *offset += spmc_shmem_obj_size(obj->desc_size); + + return obj; + } + return NULL; +} + +/******************************************************************************* + * FF-A memory descriptor helper functions. + ******************************************************************************/ +/** + * spmc_shmem_obj_get_emad - Get the emad from a given index depending on the + * clients FF-A version. + * @desc: The memory transaction descriptor. + * @index: The index of the emad element to be accessed. + * @ffa_version: FF-A version of the provided structure. + * @emad_size: Will be populated with the size of the returned emad + * descriptor. + * Return: A pointer to the requested emad structure. + */ +static void * +spmc_shmem_obj_get_emad(const struct ffa_mtd *desc, uint32_t index, + uint32_t ffa_version, size_t *emad_size) +{ + uint8_t *emad; + + assert(index < desc->emad_count); + + /* + * If the caller is using FF-A v1.0 interpret the descriptor as a v1.0 + * format, otherwise assume it is a v1.1 format. + */ + if (ffa_version == MAKE_FFA_VERSION(1, 0)) { + emad = (uint8_t *)desc + offsetof(struct ffa_mtd_v1_0, emad); + *emad_size = sizeof(struct ffa_emad_v1_0); + } else { + assert(is_aligned(desc->emad_offset, 16)); + emad = ((uint8_t *) desc + desc->emad_offset); + *emad_size = desc->emad_size; + } + + assert(((uint64_t)index * (uint64_t)*emad_size) <= UINT32_MAX); + return (emad + (*emad_size * index)); +} + +/** + * spmc_shmem_obj_get_comp_mrd - Get comp_mrd from a mtd struct based on the + * FF-A version of the descriptor. + * @obj: Object containing ffa_memory_region_descriptor. + * + * Return: struct ffa_comp_mrd object corresponding to the composite memory + * region descriptor. + */ +static struct ffa_comp_mrd * +spmc_shmem_obj_get_comp_mrd(struct spmc_shmem_obj *obj, uint32_t ffa_version) +{ + size_t emad_size; + /* + * The comp_mrd_offset field of the emad descriptor remains consistent + * between FF-A versions therefore we can use the v1.0 descriptor here + * in all cases. + */ + struct ffa_emad_v1_0 *emad = spmc_shmem_obj_get_emad(&obj->desc, 0, + ffa_version, + &emad_size); + + /* Ensure the composite descriptor offset is aligned. */ + if (!is_aligned(emad->comp_mrd_offset, 8)) { + WARN("Unaligned composite memory region descriptor offset.\n"); + return NULL; + } + + return (struct ffa_comp_mrd *) + ((uint8_t *)(&obj->desc) + emad->comp_mrd_offset); +} + +/** + * spmc_shmem_obj_validate_id - Validate a partition ID is participating in + * a given memory transaction. + * @sp_id: Partition ID to validate. + * @obj: The shared memory object containing the descriptor + * of the memory transaction. + * Return: true if ID is valid, else false. + */ +bool spmc_shmem_obj_validate_id(struct spmc_shmem_obj *obj, uint16_t sp_id) +{ + bool found = false; + struct ffa_mtd *desc = &obj->desc; + size_t desc_size = obj->desc_size; + + /* Validate the partition is a valid participant. */ + for (unsigned int i = 0U; i < desc->emad_count; i++) { + size_t emad_size; + struct ffa_emad_v1_0 *emad; + + emad = spmc_shmem_obj_get_emad(desc, i, + MAKE_FFA_VERSION(1, 1), + &emad_size); + /* + * Validate the calculated emad address resides within the + * descriptor. + */ + if ((emad == NULL) || (uintptr_t) emad >= + (uintptr_t)((uint8_t *) desc + desc_size)) { + VERBOSE("Invalid emad.\n"); + break; + } + if (sp_id == emad->mapd.endpoint_id) { + found = true; + break; + } + } + return found; +} + +/* + * Compare two memory regions to determine if any range overlaps with another + * ongoing memory transaction. + */ +static bool +overlapping_memory_regions(struct ffa_comp_mrd *region1, + struct ffa_comp_mrd *region2) +{ + uint64_t region1_start; + uint64_t region1_size; + uint64_t region1_end; + uint64_t region2_start; + uint64_t region2_size; + uint64_t region2_end; + + assert(region1 != NULL); + assert(region2 != NULL); + + if (region1 == region2) { + return true; + } + + /* + * Check each memory region in the request against existing + * transactions. + */ + for (size_t i = 0; i < region1->address_range_count; i++) { + + region1_start = region1->address_range_array[i].address; + region1_size = + region1->address_range_array[i].page_count * + PAGE_SIZE_4KB; + region1_end = region1_start + region1_size; + + for (size_t j = 0; j < region2->address_range_count; j++) { + + region2_start = region2->address_range_array[j].address; + region2_size = + region2->address_range_array[j].page_count * + PAGE_SIZE_4KB; + region2_end = region2_start + region2_size; + + /* Check if regions are not overlapping. */ + if (!((region2_end <= region1_start) || + (region1_end <= region2_start))) { + WARN("Overlapping mem regions 0x%lx-0x%lx & 0x%lx-0x%lx\n", + region1_start, region1_end, + region2_start, region2_end); + return true; + } + } + } + return false; +} + +/******************************************************************************* + * FF-A v1.0 Memory Descriptor Conversion Helpers. + ******************************************************************************/ +/** + * spmc_shm_get_v1_1_descriptor_size - Calculate the required size for a v1.1 + * converted descriptor. + * @orig: The original v1.0 memory transaction descriptor. + * @desc_size: The size of the original v1.0 memory transaction descriptor. + * + * Return: the size required to store the descriptor store in the v1.1 format. + */ +static uint64_t +spmc_shm_get_v1_1_descriptor_size(struct ffa_mtd_v1_0 *orig, size_t desc_size) +{ + uint64_t size = 0; + struct ffa_comp_mrd *mrd; + struct ffa_emad_v1_0 *emad_array = orig->emad; + + /* Get the size of the v1.1 descriptor. */ + size += sizeof(struct ffa_mtd); + + /* Add the size of the emad descriptors. */ + size += orig->emad_count * sizeof(struct ffa_emad_v1_0); + + /* Add the size of the composite mrds. */ + size += sizeof(struct ffa_comp_mrd); + + /* Add the size of the constituent mrds. */ + mrd = (struct ffa_comp_mrd *) ((uint8_t *) orig + + emad_array[0].comp_mrd_offset); + + /* Add the size of the memory region descriptors. */ + size += mrd->address_range_count * sizeof(struct ffa_cons_mrd); + + return size; +} + +/** + * spmc_shm_get_v1_0_descriptor_size - Calculate the required size for a v1.0 + * converted descriptor. + * @orig: The original v1.1 memory transaction descriptor. + * @desc_size: The size of the original v1.1 memory transaction descriptor. + * + * Return: the size required to store the descriptor store in the v1.0 format. + */ +static size_t +spmc_shm_get_v1_0_descriptor_size(struct ffa_mtd *orig, size_t desc_size) +{ + size_t size = 0; + struct ffa_comp_mrd *mrd; + struct ffa_emad_v1_0 *emad_array = (struct ffa_emad_v1_0 *) + ((uint8_t *) orig + + orig->emad_offset); + + /* Get the size of the v1.0 descriptor. */ + size += sizeof(struct ffa_mtd_v1_0); + + /* Add the size of the v1.0 emad descriptors. */ + size += orig->emad_count * sizeof(struct ffa_emad_v1_0); + + /* Add the size of the composite mrds. */ + size += sizeof(struct ffa_comp_mrd); + + /* Add the size of the constituent mrds. */ + mrd = (struct ffa_comp_mrd *) ((uint8_t *) orig + + emad_array[0].comp_mrd_offset); + + /* Check the calculated address is within the memory descriptor. */ + if (((uintptr_t) mrd + sizeof(struct ffa_comp_mrd)) > + (uintptr_t)((uint8_t *) orig + desc_size)) { + return 0; + } + size += mrd->address_range_count * sizeof(struct ffa_cons_mrd); + + return size; +} + +/** + * spmc_shm_convert_shmem_obj_from_v1_0 - Converts a given v1.0 memory object. + * @out_obj: The shared memory object to populate the converted descriptor. + * @orig: The shared memory object containing the v1.0 descriptor. + * + * Return: true if the conversion is successful else false. + */ +static bool +spmc_shm_convert_shmem_obj_from_v1_0(struct spmc_shmem_obj *out_obj, + struct spmc_shmem_obj *orig) +{ + struct ffa_mtd_v1_0 *mtd_orig = (struct ffa_mtd_v1_0 *) &orig->desc; + struct ffa_mtd *out = &out_obj->desc; + struct ffa_emad_v1_0 *emad_array_in; + struct ffa_emad_v1_0 *emad_array_out; + struct ffa_comp_mrd *mrd_in; + struct ffa_comp_mrd *mrd_out; + + size_t mrd_in_offset; + size_t mrd_out_offset; + size_t mrd_size = 0; + + /* Populate the new descriptor format from the v1.0 struct. */ + out->sender_id = mtd_orig->sender_id; + out->memory_region_attributes = mtd_orig->memory_region_attributes; + out->flags = mtd_orig->flags; + out->handle = mtd_orig->handle; + out->tag = mtd_orig->tag; + out->emad_count = mtd_orig->emad_count; + out->emad_size = sizeof(struct ffa_emad_v1_0); + + /* + * We will locate the emad descriptors directly after the ffa_mtd + * struct. This will be 8-byte aligned. + */ + out->emad_offset = sizeof(struct ffa_mtd); + + emad_array_in = mtd_orig->emad; + emad_array_out = (struct ffa_emad_v1_0 *) + ((uint8_t *) out + out->emad_offset); + + /* Copy across the emad structs. */ + for (unsigned int i = 0U; i < out->emad_count; i++) { + /* Bound check for emad array. */ + if (((uint8_t *)emad_array_in + sizeof(struct ffa_emad_v1_0)) > + ((uint8_t *) mtd_orig + orig->desc_size)) { + VERBOSE("%s: Invalid mtd structure.\n", __func__); + return false; + } + memcpy(&emad_array_out[i], &emad_array_in[i], + sizeof(struct ffa_emad_v1_0)); + } + + /* Place the mrd descriptors after the end of the emad descriptors.*/ + mrd_in_offset = emad_array_in->comp_mrd_offset; + mrd_out_offset = out->emad_offset + (out->emad_size * out->emad_count); + mrd_out = (struct ffa_comp_mrd *) ((uint8_t *) out + mrd_out_offset); + + /* Add the size of the composite memory region descriptor. */ + mrd_size += sizeof(struct ffa_comp_mrd); + + /* Find the mrd descriptor. */ + mrd_in = (struct ffa_comp_mrd *) ((uint8_t *) mtd_orig + mrd_in_offset); + + /* Add the size of the constituent memory region descriptors. */ + mrd_size += mrd_in->address_range_count * sizeof(struct ffa_cons_mrd); + + /* + * Update the offset in the emads by the delta between the input and + * output addresses. + */ + for (unsigned int i = 0U; i < out->emad_count; i++) { + emad_array_out[i].comp_mrd_offset = + emad_array_in[i].comp_mrd_offset + + (mrd_out_offset - mrd_in_offset); + } + + /* Verify that we stay within bound of the memory descriptors. */ + if ((uintptr_t)((uint8_t *) mrd_in + mrd_size) > + (uintptr_t)((uint8_t *) mtd_orig + orig->desc_size) || + ((uintptr_t)((uint8_t *) mrd_out + mrd_size) > + (uintptr_t)((uint8_t *) out + out_obj->desc_size))) { + ERROR("%s: Invalid mrd structure.\n", __func__); + return false; + } + + /* Copy the mrd descriptors directly. */ + memcpy(mrd_out, mrd_in, mrd_size); + + return true; +} + +/** + * spmc_shm_convert_mtd_to_v1_0 - Converts a given v1.1 memory object to + * v1.0 memory object. + * @out_obj: The shared memory object to populate the v1.0 descriptor. + * @orig: The shared memory object containing the v1.1 descriptor. + * + * Return: true if the conversion is successful else false. + */ +static bool +spmc_shm_convert_mtd_to_v1_0(struct spmc_shmem_obj *out_obj, + struct spmc_shmem_obj *orig) +{ + struct ffa_mtd *mtd_orig = &orig->desc; + struct ffa_mtd_v1_0 *out = (struct ffa_mtd_v1_0 *) &out_obj->desc; + struct ffa_emad_v1_0 *emad_in; + struct ffa_emad_v1_0 *emad_array_in; + struct ffa_emad_v1_0 *emad_array_out; + struct ffa_comp_mrd *mrd_in; + struct ffa_comp_mrd *mrd_out; + + size_t mrd_in_offset; + size_t mrd_out_offset; + size_t emad_out_array_size; + size_t mrd_size = 0; + size_t orig_desc_size = orig->desc_size; + + /* Populate the v1.0 descriptor format from the v1.1 struct. */ + out->sender_id = mtd_orig->sender_id; + out->memory_region_attributes = mtd_orig->memory_region_attributes; + out->flags = mtd_orig->flags; + out->handle = mtd_orig->handle; + out->tag = mtd_orig->tag; + out->emad_count = mtd_orig->emad_count; + + /* Determine the location of the emad array in both descriptors. */ + emad_array_in = (struct ffa_emad_v1_0 *) + ((uint8_t *) mtd_orig + mtd_orig->emad_offset); + emad_array_out = out->emad; + + /* Copy across the emad structs. */ + emad_in = emad_array_in; + for (unsigned int i = 0U; i < out->emad_count; i++) { + /* Bound check for emad array. */ + if (((uint8_t *)emad_in + sizeof(struct ffa_emad_v1_0)) > + ((uint8_t *) mtd_orig + orig_desc_size)) { + VERBOSE("%s: Invalid mtd structure.\n", __func__); + return false; + } + memcpy(&emad_array_out[i], emad_in, + sizeof(struct ffa_emad_v1_0)); + + emad_in += mtd_orig->emad_size; + } + + /* Place the mrd descriptors after the end of the emad descriptors. */ + emad_out_array_size = sizeof(struct ffa_emad_v1_0) * out->emad_count; + + mrd_out_offset = (uint8_t *) out->emad - (uint8_t *) out + + emad_out_array_size; + + mrd_out = (struct ffa_comp_mrd *) ((uint8_t *) out + mrd_out_offset); + + mrd_in_offset = mtd_orig->emad_offset + + (mtd_orig->emad_size * mtd_orig->emad_count); + + /* Add the size of the composite memory region descriptor. */ + mrd_size += sizeof(struct ffa_comp_mrd); + + /* Find the mrd descriptor. */ + mrd_in = (struct ffa_comp_mrd *) ((uint8_t *) mtd_orig + mrd_in_offset); + + /* Add the size of the constituent memory region descriptors. */ + mrd_size += mrd_in->address_range_count * sizeof(struct ffa_cons_mrd); + + /* + * Update the offset in the emads by the delta between the input and + * output addresses. + */ + emad_in = emad_array_in; + + for (unsigned int i = 0U; i < out->emad_count; i++) { + emad_array_out[i].comp_mrd_offset = emad_in->comp_mrd_offset + + (mrd_out_offset - + mrd_in_offset); + emad_in += mtd_orig->emad_size; + } + + /* Verify that we stay within bound of the memory descriptors. */ + if ((uintptr_t)((uint8_t *) mrd_in + mrd_size) > + (uintptr_t)((uint8_t *) mtd_orig + orig->desc_size) || + ((uintptr_t)((uint8_t *) mrd_out + mrd_size) > + (uintptr_t)((uint8_t *) out + out_obj->desc_size))) { + ERROR("%s: Invalid mrd structure.\n", __func__); + return false; + } + + /* Copy the mrd descriptors directly. */ + memcpy(mrd_out, mrd_in, mrd_size); + + return true; +} + +/** + * spmc_populate_ffa_v1_0_descriptor - Converts a given v1.1 memory object to + * the v1.0 format and populates the + * provided buffer. + * @dst: Buffer to populate v1.0 ffa_memory_region_descriptor. + * @orig_obj: Object containing v1.1 ffa_memory_region_descriptor. + * @buf_size: Size of the buffer to populate. + * @offset: The offset of the converted descriptor to copy. + * @copy_size: Will be populated with the number of bytes copied. + * @out_desc_size: Will be populated with the total size of the v1.0 + * descriptor. + * + * Return: 0 if conversion and population succeeded. + * Note: This function invalidates the reference to @orig therefore + * `spmc_shmem_obj_lookup` must be called if further usage is required. + */ +static uint32_t +spmc_populate_ffa_v1_0_descriptor(void *dst, struct spmc_shmem_obj *orig_obj, + size_t buf_size, size_t offset, + size_t *copy_size, size_t *v1_0_desc_size) +{ + struct spmc_shmem_obj *v1_0_obj; + + /* Calculate the size that the v1.0 descriptor will require. */ + *v1_0_desc_size = spmc_shm_get_v1_0_descriptor_size( + &orig_obj->desc, orig_obj->desc_size); + + if (*v1_0_desc_size == 0) { + ERROR("%s: cannot determine size of descriptor.\n", + __func__); + return FFA_ERROR_INVALID_PARAMETER; + } + + /* Get a new obj to store the v1.0 descriptor. */ + v1_0_obj = spmc_shmem_obj_alloc(&spmc_shmem_obj_state, + *v1_0_desc_size); + + if (!v1_0_obj) { + return FFA_ERROR_NO_MEMORY; + } + + /* Perform the conversion from v1.1 to v1.0. */ + if (!spmc_shm_convert_mtd_to_v1_0(v1_0_obj, orig_obj)) { + spmc_shmem_obj_free(&spmc_shmem_obj_state, v1_0_obj); + return FFA_ERROR_INVALID_PARAMETER; + } + + *copy_size = MIN(v1_0_obj->desc_size - offset, buf_size); + memcpy(dst, (uint8_t *) &v1_0_obj->desc + offset, *copy_size); + + /* + * We're finished with the v1.0 descriptor for now so free it. + * Note that this will invalidate any references to the v1.1 + * descriptor. + */ + spmc_shmem_obj_free(&spmc_shmem_obj_state, v1_0_obj); + + return 0; +} + +static int +spmc_validate_mtd_start(struct ffa_mtd *desc, uint32_t ffa_version, + size_t fragment_length, size_t total_length) +{ + unsigned long long emad_end; + unsigned long long emad_size; + unsigned long long emad_offset; + unsigned int min_desc_size; + + /* Determine the appropriate minimum descriptor size. */ + if (ffa_version == MAKE_FFA_VERSION(1, 0)) { + min_desc_size = sizeof(struct ffa_mtd_v1_0); + } else if (ffa_version == MAKE_FFA_VERSION(1, 1)) { + min_desc_size = sizeof(struct ffa_mtd); + } else { + return FFA_ERROR_INVALID_PARAMETER; + } + if (fragment_length < min_desc_size) { + WARN("%s: invalid length %zu < %u\n", __func__, fragment_length, + min_desc_size); + return FFA_ERROR_INVALID_PARAMETER; + } + + if (desc->emad_count == 0U) { + WARN("%s: unsupported attribute desc count %u.\n", + __func__, desc->emad_count); + return FFA_ERROR_INVALID_PARAMETER; + } + + /* + * If the caller is using FF-A v1.0 interpret the descriptor as a v1.0 + * format, otherwise assume it is a v1.1 format. + */ + if (ffa_version == MAKE_FFA_VERSION(1, 0)) { + emad_offset = emad_size = sizeof(struct ffa_emad_v1_0); + } else { + if (!is_aligned(desc->emad_offset, 16)) { + WARN("%s: Emad offset %" PRIx32 " is not 16-byte aligned.\n", + __func__, desc->emad_offset); + return FFA_ERROR_INVALID_PARAMETER; + } + if (desc->emad_offset < sizeof(struct ffa_mtd)) { + WARN("%s: Emad offset too small: 0x%" PRIx32 " < 0x%zx.\n", + __func__, desc->emad_offset, + sizeof(struct ffa_mtd)); + return FFA_ERROR_INVALID_PARAMETER; + } + emad_offset = desc->emad_offset; + if (desc->emad_size < sizeof(struct ffa_emad_v1_0)) { + WARN("%s: Bad emad size (%" PRIu32 " < %zu).\n", __func__, + desc->emad_size, sizeof(struct ffa_emad_v1_0)); + return FFA_ERROR_INVALID_PARAMETER; + } + if (!is_aligned(desc->emad_size, 16)) { + WARN("%s: Emad size 0x%" PRIx32 " is not 16-byte aligned.\n", + __func__, desc->emad_size); + return FFA_ERROR_INVALID_PARAMETER; + } + emad_size = desc->emad_size; + } + + /* + * Overflow is impossible: the arithmetic happens in at least 64-bit + * precision, but all of the operands are bounded by UINT32_MAX, and + * ((2^32 - 1) * (2^32 - 1) + (2^32 - 1) + (2^32 - 1)) + * = ((2^32 - 1) * ((2^32 - 1) + 1 + 1)) + * = ((2^32 - 1) * (2^32 + 1)) + * = (2^64 - 1). + */ + CASSERT(sizeof(desc->emad_count) == 4, assert_emad_count_max_too_large); + emad_end = (desc->emad_count * (unsigned long long)emad_size) + + (unsigned long long)sizeof(struct ffa_comp_mrd) + + (unsigned long long)emad_offset; + + if (emad_end > total_length) { + WARN("%s: Composite memory region extends beyond descriptor: 0x%llx > 0x%zx\n", + __func__, emad_end, total_length); + return FFA_ERROR_INVALID_PARAMETER; + } + + return 0; +} + +static inline const struct ffa_emad_v1_0 * +emad_advance(const struct ffa_emad_v1_0 *emad, size_t offset) +{ + return (const struct ffa_emad_v1_0 *)((const uint8_t *)emad + offset); +} + +/** + * spmc_shmem_check_obj - Check that counts in descriptor match overall size. + * @obj: Object containing ffa_memory_region_descriptor. + * @ffa_version: FF-A version of the provided descriptor. + * + * Return: 0 if object is valid, FFA_ERROR_INVALID_PARAMETER if + * constituent_memory_region_descriptor offset or count is invalid. + */ +static int spmc_shmem_check_obj(struct spmc_shmem_obj *obj, + uint32_t ffa_version) +{ + unsigned long long total_page_count; + const struct ffa_emad_v1_0 *first_emad; + const struct ffa_emad_v1_0 *end_emad; + size_t emad_size; + uint32_t comp_mrd_offset; + size_t header_emad_size; + size_t size; + size_t count; + size_t expected_size; + const struct ffa_comp_mrd *comp; + + if (obj->desc_filled != obj->desc_size) { + ERROR("BUG: %s called on incomplete object (%zu != %zu)\n", + __func__, obj->desc_filled, obj->desc_size); + panic(); + } + + if (spmc_validate_mtd_start(&obj->desc, ffa_version, + obj->desc_filled, obj->desc_size)) { + ERROR("BUG: %s called on object with corrupt memory region descriptor\n", + __func__); + panic(); + } + + first_emad = spmc_shmem_obj_get_emad(&obj->desc, 0, + ffa_version, &emad_size); + end_emad = emad_advance(first_emad, obj->desc.emad_count * emad_size); + comp_mrd_offset = first_emad->comp_mrd_offset; + + /* Loop through the endpoint descriptors, validating each of them. */ + for (const struct ffa_emad_v1_0 *emad = first_emad; emad < end_emad;) { + ffa_endpoint_id16_t ep_id; + + /* + * If a partition ID resides in the secure world validate that + * the partition ID is for a known partition. Ignore any + * partition ID belonging to the normal world as it is assumed + * the Hypervisor will have validated these. + */ + ep_id = emad->mapd.endpoint_id; + if (ffa_is_secure_world_id(ep_id)) { + if (spmc_get_sp_ctx(ep_id) == NULL) { + WARN("%s: Invalid receiver id 0x%x\n", + __func__, ep_id); + return FFA_ERROR_INVALID_PARAMETER; + } + } + + /* + * The offset provided to the composite memory region descriptor + * should be consistent across endpoint descriptors. + */ + if (comp_mrd_offset != emad->comp_mrd_offset) { + ERROR("%s: mismatching offsets provided, %u != %u\n", + __func__, emad->comp_mrd_offset, comp_mrd_offset); + return FFA_ERROR_INVALID_PARAMETER; + } + + /* Advance to the next endpoint descriptor */ + emad = emad_advance(emad, emad_size); + + /* + * Ensure neither this emad nor any subsequent emads have + * the same partition ID as the previous emad. + */ + for (const struct ffa_emad_v1_0 *other_emad = emad; + other_emad < end_emad; + other_emad = emad_advance(other_emad, emad_size)) { + if (ep_id == other_emad->mapd.endpoint_id) { + WARN("%s: Duplicated endpoint id 0x%x\n", + __func__, emad->mapd.endpoint_id); + return FFA_ERROR_INVALID_PARAMETER; + } + } + } + + header_emad_size = (size_t)((const uint8_t *)end_emad - + (const uint8_t *)&obj->desc); + + /* + * Check that the composite descriptor + * is after the endpoint descriptors. + */ + if (comp_mrd_offset < header_emad_size) { + WARN("%s: invalid object, offset %u < header + emad %zu\n", + __func__, comp_mrd_offset, header_emad_size); + return FFA_ERROR_INVALID_PARAMETER; + } + + /* Ensure the composite descriptor offset is aligned. */ + if (!is_aligned(comp_mrd_offset, 16)) { + WARN("%s: invalid object, unaligned composite memory " + "region descriptor offset %u.\n", + __func__, comp_mrd_offset); + return FFA_ERROR_INVALID_PARAMETER; + } + + size = obj->desc_size; + + /* Check that the composite descriptor is in bounds. */ + if (comp_mrd_offset > size) { + WARN("%s: invalid object, offset %u > total size %zu\n", + __func__, comp_mrd_offset, obj->desc_size); + return FFA_ERROR_INVALID_PARAMETER; + } + size -= comp_mrd_offset; + + /* Check that there is enough space for the composite descriptor. */ + if (size < sizeof(struct ffa_comp_mrd)) { + WARN("%s: invalid object, offset %u, total size %zu, no header space.\n", + __func__, comp_mrd_offset, obj->desc_size); + return FFA_ERROR_INVALID_PARAMETER; + } + size -= sizeof(*comp); + + count = size / sizeof(struct ffa_cons_mrd); + + comp = (const struct ffa_comp_mrd *) + ((const uint8_t *)(&obj->desc) + comp_mrd_offset); + + if (comp->address_range_count != count) { + WARN("%s: invalid object, desc count %u != %zu\n", + __func__, comp->address_range_count, count); + return FFA_ERROR_INVALID_PARAMETER; + } + + /* Ensure that the expected and actual sizes are equal. */ + expected_size = comp_mrd_offset + sizeof(*comp) + + count * sizeof(struct ffa_cons_mrd); + + if (expected_size != obj->desc_size) { + WARN("%s: invalid object, computed size %zu != size %zu\n", + __func__, expected_size, obj->desc_size); + return FFA_ERROR_INVALID_PARAMETER; + } + + total_page_count = 0; + + /* + * comp->address_range_count is 32-bit, so 'count' must fit in a + * uint32_t at this point. + */ + for (size_t i = 0; i < count; i++) { + const struct ffa_cons_mrd *mrd = comp->address_range_array + i; + + if (!is_aligned(mrd->address, PAGE_SIZE)) { + WARN("%s: invalid object, address in region descriptor " + "%zu not 4K aligned (got 0x%016llx)", + __func__, i, (unsigned long long)mrd->address); + } + + /* + * No overflow possible: total_page_count can hold at + * least 2^64 - 1, but will be have at most 2^32 - 1. + * values added to it, each of which cannot exceed 2^32 - 1. + */ + total_page_count += mrd->page_count; + } + + if (comp->total_page_count != total_page_count) { + WARN("%s: invalid object, desc total_page_count %u != %llu\n", + __func__, comp->total_page_count, total_page_count); + return FFA_ERROR_INVALID_PARAMETER; + } + + return 0; +} + +/** + * spmc_shmem_check_state_obj - Check if the descriptor describes memory + * regions that are currently involved with an + * existing memory transactions. This implies that + * the memory is not in a valid state for lending. + * @obj: Object containing ffa_memory_region_descriptor. + * + * Return: 0 if object is valid, FFA_ERROR_INVALID_PARAMETER if invalid memory + * state. + */ +static int spmc_shmem_check_state_obj(struct spmc_shmem_obj *obj, + uint32_t ffa_version) +{ + size_t obj_offset = 0; + struct spmc_shmem_obj *inflight_obj; + + struct ffa_comp_mrd *other_mrd; + struct ffa_comp_mrd *requested_mrd = spmc_shmem_obj_get_comp_mrd(obj, + ffa_version); + + if (requested_mrd == NULL) { + return FFA_ERROR_INVALID_PARAMETER; + } + + inflight_obj = spmc_shmem_obj_get_next(&spmc_shmem_obj_state, + &obj_offset); + + while (inflight_obj != NULL) { + /* + * Don't compare the transaction to itself or to partially + * transmitted descriptors. + */ + if ((obj->desc.handle != inflight_obj->desc.handle) && + (obj->desc_size == obj->desc_filled)) { + other_mrd = spmc_shmem_obj_get_comp_mrd(inflight_obj, + FFA_VERSION_COMPILED); + if (other_mrd == NULL) { + return FFA_ERROR_INVALID_PARAMETER; + } + if (overlapping_memory_regions(requested_mrd, + other_mrd)) { + return FFA_ERROR_INVALID_PARAMETER; + } + } + + inflight_obj = spmc_shmem_obj_get_next(&spmc_shmem_obj_state, + &obj_offset); + } + return 0; +} + +static long spmc_ffa_fill_desc(struct mailbox *mbox, + struct spmc_shmem_obj *obj, + uint32_t fragment_length, + ffa_mtd_flag32_t mtd_flag, + uint32_t ffa_version, + void *smc_handle) +{ + int ret; + uint32_t handle_low; + uint32_t handle_high; + + if (mbox->rxtx_page_count == 0U) { + WARN("%s: buffer pair not registered.\n", __func__); + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_arg; + } + + CASSERT(sizeof(mbox->rxtx_page_count) == 4, assert_bogus_page_count); + if (fragment_length > (uint64_t)mbox->rxtx_page_count * PAGE_SIZE_4KB) { + WARN("%s: bad fragment size %u > %" PRIu64 " buffer size\n", __func__, + fragment_length, (uint64_t)mbox->rxtx_page_count * PAGE_SIZE_4KB); + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_arg; + } + + if (fragment_length > obj->desc_size - obj->desc_filled) { + WARN("%s: bad fragment size %u > %zu remaining\n", __func__, + fragment_length, obj->desc_size - obj->desc_filled); + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_arg; + } + + memcpy((uint8_t *)&obj->desc + obj->desc_filled, + (uint8_t *) mbox->tx_buffer, fragment_length); + + /* Ensure that the sender ID resides in the normal world. */ + if (ffa_is_secure_world_id(obj->desc.sender_id)) { + WARN("%s: Invalid sender ID 0x%x.\n", + __func__, obj->desc.sender_id); + ret = FFA_ERROR_DENIED; + goto err_arg; + } + + /* Ensure the NS bit is set to 0. */ + if ((obj->desc.memory_region_attributes & FFA_MEM_ATTR_NS_BIT) != 0U) { + WARN("%s: NS mem attributes flags MBZ.\n", __func__); + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_arg; + } + + /* + * We don't currently support any optional flags so ensure none are + * requested. + */ + if (obj->desc.flags != 0U && mtd_flag != 0U && + (obj->desc.flags != mtd_flag)) { + WARN("%s: invalid memory transaction flags %u != %u\n", + __func__, obj->desc.flags, mtd_flag); + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_arg; + } + + if (obj->desc_filled == 0U) { + /* First fragment, descriptor header has been copied */ + ret = spmc_validate_mtd_start(&obj->desc, ffa_version, + fragment_length, obj->desc_size); + if (ret != 0) { + goto err_bad_desc; + } + + obj->desc.handle = spmc_shmem_obj_state.next_handle++; + obj->desc.flags |= mtd_flag; + } + + obj->desc_filled += fragment_length; + + handle_low = (uint32_t)obj->desc.handle; + handle_high = obj->desc.handle >> 32; + + if (obj->desc_filled != obj->desc_size) { + SMC_RET8(smc_handle, FFA_MEM_FRAG_RX, handle_low, + handle_high, obj->desc_filled, + (uint32_t)obj->desc.sender_id << 16, 0, 0, 0); + } + + /* The full descriptor has been received, perform any final checks. */ + + ret = spmc_shmem_check_obj(obj, ffa_version); + if (ret != 0) { + goto err_bad_desc; + } + + ret = spmc_shmem_check_state_obj(obj, ffa_version); + if (ret) { + ERROR("%s: invalid memory region descriptor.\n", __func__); + goto err_bad_desc; + } + + /* + * Everything checks out, if the sender was using FF-A v1.0, convert + * the descriptor format to use the v1.1 structures. + */ + if (ffa_version == MAKE_FFA_VERSION(1, 0)) { + struct spmc_shmem_obj *v1_1_obj; + uint64_t mem_handle; + + /* Calculate the size that the v1.1 descriptor will required. */ + uint64_t v1_1_desc_size = + spmc_shm_get_v1_1_descriptor_size((void *) &obj->desc, + obj->desc_size); + + if (v1_1_desc_size > UINT32_MAX) { + ret = FFA_ERROR_NO_MEMORY; + goto err_arg; + } + + /* Get a new obj to store the v1.1 descriptor. */ + v1_1_obj = + spmc_shmem_obj_alloc(&spmc_shmem_obj_state, (size_t)v1_1_desc_size); + + if (!v1_1_obj) { + ret = FFA_ERROR_NO_MEMORY; + goto err_arg; + } + + /* Perform the conversion from v1.0 to v1.1. */ + v1_1_obj->desc_size = (uint32_t)v1_1_desc_size; + v1_1_obj->desc_filled = (uint32_t)v1_1_desc_size; + if (!spmc_shm_convert_shmem_obj_from_v1_0(v1_1_obj, obj)) { + ERROR("%s: Could not convert mtd!\n", __func__); + spmc_shmem_obj_free(&spmc_shmem_obj_state, v1_1_obj); + goto err_arg; + } + + /* + * We're finished with the v1.0 descriptor so free it + * and continue our checks with the new v1.1 descriptor. + */ + mem_handle = obj->desc.handle; + spmc_shmem_obj_free(&spmc_shmem_obj_state, obj); + obj = spmc_shmem_obj_lookup(&spmc_shmem_obj_state, mem_handle); + if (obj == NULL) { + ERROR("%s: Failed to find converted descriptor.\n", + __func__); + ret = FFA_ERROR_INVALID_PARAMETER; + return spmc_ffa_error_return(smc_handle, ret); + } + } + + /* Allow for platform specific operations to be performed. */ + ret = plat_spmc_shmem_begin(&obj->desc); + if (ret != 0) { + goto err_arg; + } + + SMC_RET8(smc_handle, FFA_SUCCESS_SMC32, 0, handle_low, handle_high, 0, + 0, 0, 0); + +err_bad_desc: +err_arg: + spmc_shmem_obj_free(&spmc_shmem_obj_state, obj); + return spmc_ffa_error_return(smc_handle, ret); +} + +/** + * spmc_ffa_mem_send - FFA_MEM_SHARE/LEND implementation. + * @client: Client state. + * @total_length: Total length of shared memory descriptor. + * @fragment_length: Length of fragment of shared memory descriptor passed in + * this call. + * @address: Not supported, must be 0. + * @page_count: Not supported, must be 0. + * @smc_handle: Handle passed to smc call. Used to return + * FFA_MEM_FRAG_RX or SMC_FC_FFA_SUCCESS. + * + * Implements a subset of the FF-A FFA_MEM_SHARE and FFA_MEM_LEND calls needed + * to share or lend memory from non-secure os to secure os (with no stream + * endpoints). + * + * Return: 0 on success, error code on failure. + */ +long spmc_ffa_mem_send(uint32_t smc_fid, + bool secure_origin, + uint64_t total_length, + uint32_t fragment_length, + uint64_t address, + uint32_t page_count, + void *cookie, + void *handle, + uint64_t flags) + +{ + long ret; + struct spmc_shmem_obj *obj; + struct mailbox *mbox = spmc_get_mbox_desc(secure_origin); + ffa_mtd_flag32_t mtd_flag; + uint32_t ffa_version = get_partition_ffa_version(secure_origin); + size_t min_desc_size; + + if (address != 0U || page_count != 0U) { + WARN("%s: custom memory region for message not supported.\n", + __func__); + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + + if (secure_origin) { + WARN("%s: unsupported share direction.\n", __func__); + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + + if (ffa_version == MAKE_FFA_VERSION(1, 0)) { + min_desc_size = sizeof(struct ffa_mtd_v1_0); + } else if (ffa_version == MAKE_FFA_VERSION(1, 1)) { + min_desc_size = sizeof(struct ffa_mtd); + } else { + WARN("%s: bad FF-A version.\n", __func__); + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + + /* Check if the descriptor is too small for the FF-A version. */ + if (fragment_length < min_desc_size) { + WARN("%s: bad first fragment size %u < %zu\n", + __func__, fragment_length, sizeof(struct ffa_mtd_v1_0)); + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + + if ((smc_fid & FUNCID_NUM_MASK) == FFA_FNUM_MEM_SHARE) { + mtd_flag = FFA_MTD_FLAG_TYPE_SHARE_MEMORY; + } else if ((smc_fid & FUNCID_NUM_MASK) == FFA_FNUM_MEM_LEND) { + mtd_flag = FFA_MTD_FLAG_TYPE_LEND_MEMORY; + } else { + WARN("%s: invalid memory management operation.\n", __func__); + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + + spin_lock(&spmc_shmem_obj_state.lock); + obj = spmc_shmem_obj_alloc(&spmc_shmem_obj_state, total_length); + if (obj == NULL) { + ret = FFA_ERROR_NO_MEMORY; + goto err_unlock; + } + + spin_lock(&mbox->lock); + ret = spmc_ffa_fill_desc(mbox, obj, fragment_length, mtd_flag, + ffa_version, handle); + spin_unlock(&mbox->lock); + + spin_unlock(&spmc_shmem_obj_state.lock); + return ret; + +err_unlock: + spin_unlock(&spmc_shmem_obj_state.lock); + return spmc_ffa_error_return(handle, ret); +} + +/** + * spmc_ffa_mem_frag_tx - FFA_MEM_FRAG_TX implementation. + * @client: Client state. + * @handle_low: Handle_low value returned from FFA_MEM_FRAG_RX. + * @handle_high: Handle_high value returned from FFA_MEM_FRAG_RX. + * @fragment_length: Length of fragments transmitted. + * @sender_id: Vmid of sender in bits [31:16] + * @smc_handle: Handle passed to smc call. Used to return + * FFA_MEM_FRAG_RX or SMC_FC_FFA_SUCCESS. + * + * Return: @smc_handle on success, error code on failure. + */ +long spmc_ffa_mem_frag_tx(uint32_t smc_fid, + bool secure_origin, + uint64_t handle_low, + uint64_t handle_high, + uint32_t fragment_length, + uint32_t sender_id, + void *cookie, + void *handle, + uint64_t flags) +{ + long ret; + uint32_t desc_sender_id; + uint32_t ffa_version = get_partition_ffa_version(secure_origin); + struct mailbox *mbox = spmc_get_mbox_desc(secure_origin); + + struct spmc_shmem_obj *obj; + uint64_t mem_handle = handle_low | (((uint64_t)handle_high) << 32); + + spin_lock(&spmc_shmem_obj_state.lock); + + obj = spmc_shmem_obj_lookup(&spmc_shmem_obj_state, mem_handle); + if (obj == NULL) { + WARN("%s: invalid handle, 0x%lx, not a valid handle.\n", + __func__, mem_handle); + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_unlock; + } + + desc_sender_id = (uint32_t)obj->desc.sender_id << 16; + if (sender_id != desc_sender_id) { + WARN("%s: invalid sender_id 0x%x != 0x%x\n", __func__, + sender_id, desc_sender_id); + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_unlock; + } + + if (obj->desc_filled == obj->desc_size) { + WARN("%s: object desc already filled, %zu\n", __func__, + obj->desc_filled); + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_unlock; + } + + spin_lock(&mbox->lock); + ret = spmc_ffa_fill_desc(mbox, obj, fragment_length, 0, ffa_version, + handle); + spin_unlock(&mbox->lock); + + spin_unlock(&spmc_shmem_obj_state.lock); + return ret; + +err_unlock: + spin_unlock(&spmc_shmem_obj_state.lock); + return spmc_ffa_error_return(handle, ret); +} + +/** + * spmc_ffa_mem_retrieve_set_ns_bit - Set the NS bit in the response descriptor + * if the caller implements a version greater + * than FF-A 1.0 or if they have requested + * the functionality. + * TODO: We are assuming that the caller is + * an SP. To support retrieval from the + * normal world this function will need to be + * expanded accordingly. + * @resp: Descriptor populated in callers RX buffer. + * @sp_ctx: Context of the calling SP. + */ +void spmc_ffa_mem_retrieve_set_ns_bit(struct ffa_mtd *resp, + struct secure_partition_desc *sp_ctx) +{ + if (sp_ctx->ffa_version > MAKE_FFA_VERSION(1, 0) || + sp_ctx->ns_bit_requested) { + /* + * Currently memory senders must reside in the normal + * world, and we do not have the functionlaity to change + * the state of memory dynamically. Therefore we can always set + * the NS bit to 1. + */ + resp->memory_region_attributes |= FFA_MEM_ATTR_NS_BIT; + } +} + +/** + * spmc_ffa_mem_retrieve_req - FFA_MEM_RETRIEVE_REQ implementation. + * @smc_fid: FID of SMC + * @total_length: Total length of retrieve request descriptor if this is + * the first call. Otherwise (unsupported) must be 0. + * @fragment_length: Length of fragment of retrieve request descriptor passed + * in this call. Only @fragment_length == @length is + * supported by this implementation. + * @address: Not supported, must be 0. + * @page_count: Not supported, must be 0. + * @smc_handle: Handle passed to smc call. Used to return + * FFA_MEM_RETRIEVE_RESP. + * + * Implements a subset of the FF-A FFA_MEM_RETRIEVE_REQ call. + * Used by secure os to retrieve memory already shared by non-secure os. + * If the data does not fit in a single FFA_MEM_RETRIEVE_RESP message, + * the client must call FFA_MEM_FRAG_RX until the full response has been + * received. + * + * Return: @handle on success, error code on failure. + */ +long +spmc_ffa_mem_retrieve_req(uint32_t smc_fid, + bool secure_origin, + uint32_t total_length, + uint32_t fragment_length, + uint64_t address, + uint32_t page_count, + void *cookie, + void *handle, + uint64_t flags) +{ + int ret; + size_t buf_size; + size_t copy_size = 0; + size_t min_desc_size; + size_t out_desc_size = 0; + + /* + * Currently we are only accessing fields that are the same in both the + * v1.0 and v1.1 mtd struct therefore we can use a v1.1 struct directly + * here. We only need validate against the appropriate struct size. + */ + struct ffa_mtd *resp; + const struct ffa_mtd *req; + struct spmc_shmem_obj *obj = NULL; + struct mailbox *mbox = spmc_get_mbox_desc(secure_origin); + uint32_t ffa_version = get_partition_ffa_version(secure_origin); + struct secure_partition_desc *sp_ctx = spmc_get_current_sp_ctx(); + + if (!secure_origin) { + WARN("%s: unsupported retrieve req direction.\n", __func__); + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + + if (address != 0U || page_count != 0U) { + WARN("%s: custom memory region not supported.\n", __func__); + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + + spin_lock(&mbox->lock); + + req = mbox->tx_buffer; + resp = mbox->rx_buffer; + buf_size = mbox->rxtx_page_count * FFA_PAGE_SIZE; + + if (mbox->rxtx_page_count == 0U) { + WARN("%s: buffer pair not registered.\n", __func__); + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_unlock_mailbox; + } + + if (mbox->state != MAILBOX_STATE_EMPTY) { + WARN("%s: RX Buffer is full! %d\n", __func__, mbox->state); + ret = FFA_ERROR_DENIED; + goto err_unlock_mailbox; + } + + if (fragment_length != total_length) { + WARN("%s: fragmented retrieve request not supported.\n", + __func__); + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_unlock_mailbox; + } + + if (req->emad_count == 0U) { + WARN("%s: unsupported attribute desc count %u.\n", + __func__, obj->desc.emad_count); + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_unlock_mailbox; + } + + /* Determine the appropriate minimum descriptor size. */ + if (ffa_version == MAKE_FFA_VERSION(1, 0)) { + min_desc_size = sizeof(struct ffa_mtd_v1_0); + } else { + min_desc_size = sizeof(struct ffa_mtd); + } + if (total_length < min_desc_size) { + WARN("%s: invalid length %u < %zu\n", __func__, total_length, + min_desc_size); + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_unlock_mailbox; + } + + spin_lock(&spmc_shmem_obj_state.lock); + + obj = spmc_shmem_obj_lookup(&spmc_shmem_obj_state, req->handle); + if (obj == NULL) { + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_unlock_all; + } + + if (obj->desc_filled != obj->desc_size) { + WARN("%s: incomplete object desc filled %zu < size %zu\n", + __func__, obj->desc_filled, obj->desc_size); + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_unlock_all; + } + + if (req->emad_count != 0U && req->sender_id != obj->desc.sender_id) { + WARN("%s: wrong sender id 0x%x != 0x%x\n", + __func__, req->sender_id, obj->desc.sender_id); + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_unlock_all; + } + + if (req->emad_count != 0U && req->tag != obj->desc.tag) { + WARN("%s: wrong tag 0x%lx != 0x%lx\n", + __func__, req->tag, obj->desc.tag); + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_unlock_all; + } + + if (req->emad_count != 0U && req->emad_count != obj->desc.emad_count) { + WARN("%s: mistmatch of endpoint counts %u != %u\n", + __func__, req->emad_count, obj->desc.emad_count); + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_unlock_all; + } + + /* Ensure the NS bit is set to 0 in the request. */ + if ((req->memory_region_attributes & FFA_MEM_ATTR_NS_BIT) != 0U) { + WARN("%s: NS mem attributes flags MBZ.\n", __func__); + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_unlock_all; + } + + if (req->flags != 0U) { + if ((req->flags & FFA_MTD_FLAG_TYPE_MASK) != + (obj->desc.flags & FFA_MTD_FLAG_TYPE_MASK)) { + /* + * If the retrieve request specifies the memory + * transaction ensure it matches what we expect. + */ + WARN("%s: wrong mem transaction flags %x != %x\n", + __func__, req->flags, obj->desc.flags); + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_unlock_all; + } + + if (req->flags != FFA_MTD_FLAG_TYPE_SHARE_MEMORY && + req->flags != FFA_MTD_FLAG_TYPE_LEND_MEMORY) { + /* + * Current implementation does not support donate and + * it supports no other flags. + */ + WARN("%s: invalid flags 0x%x\n", __func__, req->flags); + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_unlock_all; + } + } + + /* Validate the caller is a valid participant. */ + if (!spmc_shmem_obj_validate_id(obj, sp_ctx->sp_id)) { + WARN("%s: Invalid endpoint ID (0x%x).\n", + __func__, sp_ctx->sp_id); + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_unlock_all; + } + + /* Validate that the provided emad offset and structure is valid.*/ + for (size_t i = 0; i < req->emad_count; i++) { + size_t emad_size; + struct ffa_emad_v1_0 *emad; + + emad = spmc_shmem_obj_get_emad(req, i, ffa_version, + &emad_size); + + if ((uintptr_t) emad >= (uintptr_t) + ((uint8_t *) req + total_length)) { + WARN("Invalid emad access.\n"); + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_unlock_all; + } + } + + /* + * Validate all the endpoints match in the case of multiple + * borrowers. We don't mandate that the order of the borrowers + * must match in the descriptors therefore check to see if the + * endpoints match in any order. + */ + for (size_t i = 0; i < req->emad_count; i++) { + bool found = false; + size_t emad_size; + struct ffa_emad_v1_0 *emad; + struct ffa_emad_v1_0 *other_emad; + + emad = spmc_shmem_obj_get_emad(req, i, ffa_version, + &emad_size); + + for (size_t j = 0; j < obj->desc.emad_count; j++) { + other_emad = spmc_shmem_obj_get_emad( + &obj->desc, j, MAKE_FFA_VERSION(1, 1), + &emad_size); + + if (req->emad_count && + emad->mapd.endpoint_id == + other_emad->mapd.endpoint_id) { + found = true; + break; + } + } + + if (!found) { + WARN("%s: invalid receiver id (0x%x).\n", + __func__, emad->mapd.endpoint_id); + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_unlock_all; + } + } + + mbox->state = MAILBOX_STATE_FULL; + + if (req->emad_count != 0U) { + obj->in_use++; + } + + /* + * If the caller is v1.0 convert the descriptor, otherwise copy + * directly. + */ + if (ffa_version == MAKE_FFA_VERSION(1, 0)) { + ret = spmc_populate_ffa_v1_0_descriptor(resp, obj, buf_size, 0, + ©_size, + &out_desc_size); + if (ret != 0U) { + ERROR("%s: Failed to process descriptor.\n", __func__); + goto err_unlock_all; + } + } else { + copy_size = MIN(obj->desc_size, buf_size); + out_desc_size = obj->desc_size; + + memcpy(resp, &obj->desc, copy_size); + } + + /* Set the NS bit in the response if applicable. */ + spmc_ffa_mem_retrieve_set_ns_bit(resp, sp_ctx); + + spin_unlock(&spmc_shmem_obj_state.lock); + spin_unlock(&mbox->lock); + + SMC_RET8(handle, FFA_MEM_RETRIEVE_RESP, out_desc_size, + copy_size, 0, 0, 0, 0, 0); + +err_unlock_all: + spin_unlock(&spmc_shmem_obj_state.lock); +err_unlock_mailbox: + spin_unlock(&mbox->lock); + return spmc_ffa_error_return(handle, ret); +} + +/** + * spmc_ffa_mem_frag_rx - FFA_MEM_FRAG_RX implementation. + * @client: Client state. + * @handle_low: Handle passed to &FFA_MEM_RETRIEVE_REQ. Bit[31:0]. + * @handle_high: Handle passed to &FFA_MEM_RETRIEVE_REQ. Bit[63:32]. + * @fragment_offset: Byte offset in descriptor to resume at. + * @sender_id: Bit[31:16]: Endpoint id of sender if client is a + * hypervisor. 0 otherwise. + * @smc_handle: Handle passed to smc call. Used to return + * FFA_MEM_FRAG_TX. + * + * Return: @smc_handle on success, error code on failure. + */ +long spmc_ffa_mem_frag_rx(uint32_t smc_fid, + bool secure_origin, + uint32_t handle_low, + uint32_t handle_high, + uint32_t fragment_offset, + uint32_t sender_id, + void *cookie, + void *handle, + uint64_t flags) +{ + int ret; + void *src; + size_t buf_size; + size_t copy_size; + size_t full_copy_size; + uint32_t desc_sender_id; + struct mailbox *mbox = spmc_get_mbox_desc(secure_origin); + uint64_t mem_handle = handle_low | (((uint64_t)handle_high) << 32); + struct spmc_shmem_obj *obj; + uint32_t ffa_version = get_partition_ffa_version(secure_origin); + + if (!secure_origin) { + WARN("%s: can only be called from swld.\n", + __func__); + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + + spin_lock(&spmc_shmem_obj_state.lock); + + obj = spmc_shmem_obj_lookup(&spmc_shmem_obj_state, mem_handle); + if (obj == NULL) { + WARN("%s: invalid handle, 0x%lx, not a valid handle.\n", + __func__, mem_handle); + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_unlock_shmem; + } + + desc_sender_id = (uint32_t)obj->desc.sender_id << 16; + if (sender_id != 0U && sender_id != desc_sender_id) { + WARN("%s: invalid sender_id 0x%x != 0x%x\n", __func__, + sender_id, desc_sender_id); + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_unlock_shmem; + } + + if (fragment_offset >= obj->desc_size) { + WARN("%s: invalid fragment_offset 0x%x >= 0x%zx\n", + __func__, fragment_offset, obj->desc_size); + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_unlock_shmem; + } + + spin_lock(&mbox->lock); + + if (mbox->rxtx_page_count == 0U) { + WARN("%s: buffer pair not registered.\n", __func__); + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_unlock_all; + } + + if (mbox->state != MAILBOX_STATE_EMPTY) { + WARN("%s: RX Buffer is full!\n", __func__); + ret = FFA_ERROR_DENIED; + goto err_unlock_all; + } + + buf_size = mbox->rxtx_page_count * FFA_PAGE_SIZE; + + mbox->state = MAILBOX_STATE_FULL; + + /* + * If the caller is v1.0 convert the descriptor, otherwise copy + * directly. + */ + if (ffa_version == MAKE_FFA_VERSION(1, 0)) { + size_t out_desc_size; + + ret = spmc_populate_ffa_v1_0_descriptor(mbox->rx_buffer, obj, + buf_size, + fragment_offset, + ©_size, + &out_desc_size); + if (ret != 0U) { + ERROR("%s: Failed to process descriptor.\n", __func__); + goto err_unlock_all; + } + } else { + full_copy_size = obj->desc_size - fragment_offset; + copy_size = MIN(full_copy_size, buf_size); + + src = &obj->desc; + + memcpy(mbox->rx_buffer, src + fragment_offset, copy_size); + } + + spin_unlock(&mbox->lock); + spin_unlock(&spmc_shmem_obj_state.lock); + + SMC_RET8(handle, FFA_MEM_FRAG_TX, handle_low, handle_high, + copy_size, sender_id, 0, 0, 0); + +err_unlock_all: + spin_unlock(&mbox->lock); +err_unlock_shmem: + spin_unlock(&spmc_shmem_obj_state.lock); + return spmc_ffa_error_return(handle, ret); +} + +/** + * spmc_ffa_mem_relinquish - FFA_MEM_RELINQUISH implementation. + * @client: Client state. + * + * Implements a subset of the FF-A FFA_MEM_RELINQUISH call. + * Used by secure os release previously shared memory to non-secure os. + * + * The handle to release must be in the client's (secure os's) transmit buffer. + * + * Return: 0 on success, error code on failure. + */ +int spmc_ffa_mem_relinquish(uint32_t smc_fid, + bool secure_origin, + uint32_t handle_low, + uint32_t handle_high, + uint32_t fragment_offset, + uint32_t sender_id, + void *cookie, + void *handle, + uint64_t flags) +{ + int ret; + struct mailbox *mbox = spmc_get_mbox_desc(secure_origin); + struct spmc_shmem_obj *obj; + const struct ffa_mem_relinquish_descriptor *req; + struct secure_partition_desc *sp_ctx = spmc_get_current_sp_ctx(); + + if (!secure_origin) { + WARN("%s: unsupported relinquish direction.\n", __func__); + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + + spin_lock(&mbox->lock); + + if (mbox->rxtx_page_count == 0U) { + WARN("%s: buffer pair not registered.\n", __func__); + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_unlock_mailbox; + } + + req = mbox->tx_buffer; + + if (req->flags != 0U) { + WARN("%s: unsupported flags 0x%x\n", __func__, req->flags); + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_unlock_mailbox; + } + + if (req->endpoint_count == 0) { + WARN("%s: endpoint count cannot be 0.\n", __func__); + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_unlock_mailbox; + } + + spin_lock(&spmc_shmem_obj_state.lock); + + obj = spmc_shmem_obj_lookup(&spmc_shmem_obj_state, req->handle); + if (obj == NULL) { + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_unlock_all; + } + + /* + * Validate the endpoint ID was populated correctly. We don't currently + * support proxy endpoints so the endpoint count should always be 1. + */ + if (req->endpoint_count != 1U) { + WARN("%s: unsupported endpoint count %u != 1\n", __func__, + req->endpoint_count); + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_unlock_all; + } + + /* Validate provided endpoint ID matches the partition ID. */ + if (req->endpoint_array[0] != sp_ctx->sp_id) { + WARN("%s: invalid endpoint ID %u != %u\n", __func__, + req->endpoint_array[0], sp_ctx->sp_id); + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_unlock_all; + } + + /* Validate the caller is a valid participant. */ + if (!spmc_shmem_obj_validate_id(obj, sp_ctx->sp_id)) { + WARN("%s: Invalid endpoint ID (0x%x).\n", + __func__, req->endpoint_array[0]); + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_unlock_all; + } + + if (obj->in_use == 0U) { + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_unlock_all; + } + obj->in_use--; + + spin_unlock(&spmc_shmem_obj_state.lock); + spin_unlock(&mbox->lock); + + SMC_RET1(handle, FFA_SUCCESS_SMC32); + +err_unlock_all: + spin_unlock(&spmc_shmem_obj_state.lock); +err_unlock_mailbox: + spin_unlock(&mbox->lock); + return spmc_ffa_error_return(handle, ret); +} + +/** + * spmc_ffa_mem_reclaim - FFA_MEM_RECLAIM implementation. + * @client: Client state. + * @handle_low: Unique handle of shared memory object to reclaim. Bit[31:0]. + * @handle_high: Unique handle of shared memory object to reclaim. + * Bit[63:32]. + * @flags: Unsupported, ignored. + * + * Implements a subset of the FF-A FFA_MEM_RECLAIM call. + * Used by non-secure os reclaim memory previously shared with secure os. + * + * Return: 0 on success, error code on failure. + */ +int spmc_ffa_mem_reclaim(uint32_t smc_fid, + bool secure_origin, + uint32_t handle_low, + uint32_t handle_high, + uint32_t mem_flags, + uint64_t x4, + void *cookie, + void *handle, + uint64_t flags) +{ + int ret; + struct spmc_shmem_obj *obj; + uint64_t mem_handle = handle_low | (((uint64_t)handle_high) << 32); + + if (secure_origin) { + WARN("%s: unsupported reclaim direction.\n", __func__); + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + + if (mem_flags != 0U) { + WARN("%s: unsupported flags 0x%x\n", __func__, mem_flags); + return spmc_ffa_error_return(handle, + FFA_ERROR_INVALID_PARAMETER); + } + + spin_lock(&spmc_shmem_obj_state.lock); + + obj = spmc_shmem_obj_lookup(&spmc_shmem_obj_state, mem_handle); + if (obj == NULL) { + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_unlock; + } + if (obj->in_use != 0U) { + ret = FFA_ERROR_DENIED; + goto err_unlock; + } + + if (obj->desc_filled != obj->desc_size) { + WARN("%s: incomplete object desc filled %zu < size %zu\n", + __func__, obj->desc_filled, obj->desc_size); + ret = FFA_ERROR_INVALID_PARAMETER; + goto err_unlock; + } + + /* Allow for platform specific operations to be performed. */ + ret = plat_spmc_shmem_reclaim(&obj->desc); + if (ret != 0) { + goto err_unlock; + } + + spmc_shmem_obj_free(&spmc_shmem_obj_state, obj); + spin_unlock(&spmc_shmem_obj_state.lock); + + SMC_RET1(handle, FFA_SUCCESS_SMC32); + +err_unlock: + spin_unlock(&spmc_shmem_obj_state.lock); + return spmc_ffa_error_return(handle, ret); +} diff --git a/services/std_svc/spm/el3_spmc/spmc_shared_mem.h b/services/std_svc/spm/el3_spmc/spmc_shared_mem.h new file mode 100644 index 0000000..839f7a1 --- /dev/null +++ b/services/std_svc/spm/el3_spmc/spmc_shared_mem.h @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2022, ARM Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef SPMC_SHARED_MEM_H +#define SPMC_SHARED_MEM_H + +#include <services/el3_spmc_ffa_memory.h> + +/** + * struct ffa_mem_relinquish_descriptor - Relinquish request descriptor. + * @handle: + * Id of shared memory object to relinquish. + * @flags: + * If bit 0 is set clear memory after unmapping from borrower. Must be 0 + * for share. Bit[1]: Time slicing. Not supported, must be 0. All other + * bits are reserved 0. + * @endpoint_count: + * Number of entries in @endpoint_array. + * @endpoint_array: + * Array of endpoint ids. + */ +struct ffa_mem_relinquish_descriptor { + uint64_t handle; + uint32_t flags; + uint32_t endpoint_count; + ffa_endpoint_id16_t endpoint_array[]; +}; +CASSERT(sizeof(struct ffa_mem_relinquish_descriptor) == 16, + assert_ffa_mem_relinquish_descriptor_size_mismatch); + +/** + * struct spmc_shmem_obj_state - Global state. + * @data: Backing store for spmc_shmem_obj objects. + * @data_size: The size allocated for the backing store. + * @allocated: Number of bytes allocated in @data. + * @next_handle: Handle used for next allocated object. + * @lock: Lock protecting all state in this file. + */ +struct spmc_shmem_obj_state { + uint8_t *data; + size_t data_size; + size_t allocated; + uint64_t next_handle; + spinlock_t lock; +}; + +extern struct spmc_shmem_obj_state spmc_shmem_obj_state; +extern int plat_spmc_shmem_begin(struct ffa_mtd *desc); +extern int plat_spmc_shmem_reclaim(struct ffa_mtd *desc); + +long spmc_ffa_mem_send(uint32_t smc_fid, + bool secure_origin, + uint64_t total_length, + uint32_t fragment_length, + uint64_t address, + uint32_t page_count, + void *cookie, + void *handle, + uint64_t flags); + +long spmc_ffa_mem_frag_tx(uint32_t smc_fid, + bool secure_origin, + uint64_t handle_low, + uint64_t handle_high, + uint32_t fragment_length, + uint32_t sender_id, + void *cookie, + void *handle, + uint64_t flags); + +long spmc_ffa_mem_retrieve_req(uint32_t smc_fid, + bool secure_origin, + uint32_t total_length, + uint32_t fragment_length, + uint64_t address, + uint32_t page_count, + void *cookie, + void *handle, + uint64_t flags); + +long spmc_ffa_mem_frag_rx(uint32_t smc_fid, + bool secure_origin, + uint32_t handle_low, + uint32_t handle_high, + uint32_t fragment_offset, + uint32_t sender_id, + void *cookie, + void *handle, + uint64_t flags); + + +int spmc_ffa_mem_relinquish(uint32_t smc_fid, + bool secure_origin, + uint32_t handle_low, + uint32_t handle_high, + uint32_t fragment_offset, + uint32_t sender_id, + void *cookie, + void *handle, + uint64_t flags); + +int spmc_ffa_mem_reclaim(uint32_t smc_fid, + bool secure_origin, + uint32_t handle_low, + uint32_t handle_high, + uint32_t mem_flags, + uint64_t x4, + void *cookie, + void *handle, + uint64_t flags); + +#endif /* SPMC_SHARED_MEM_H */ diff --git a/services/std_svc/spm/spm_mm/spm_mm.mk b/services/std_svc/spm/spm_mm/spm_mm.mk new file mode 100644 index 0000000..cbc7940 --- /dev/null +++ b/services/std_svc/spm/spm_mm/spm_mm.mk @@ -0,0 +1,33 @@ +# +# Copyright (c) 2017-2023, ARM Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +ifneq (${SPD},none) + $(error "Error: SPD and SPM_MM are incompatible build options.") +endif +ifneq (${ARCH},aarch64) + $(error "Error: SPM_MM is only supported on aarch64.") +endif +ifneq (${ENABLE_SVE_FOR_NS},0) + $(error "Error: SPM_MM is not compatible with ENABLE_SVE_FOR_NS") +endif +ifneq (${ENABLE_SME_FOR_NS},0) + $(error "Error: SPM_MM is not compatible with ENABLE_SME_FOR_NS") +endif +ifeq (${CTX_INCLUDE_FPREGS},0) + $(warning "Warning: SPM_MM: CTX_INCLUDE_FPREGS is set to 0") +endif + +SPM_MM_SOURCES := $(addprefix services/std_svc/spm/spm_mm/, \ + spm_mm_main.c \ + spm_mm_setup.c \ + spm_mm_xlat.c) + + +# Let the top-level Makefile know that we intend to include a BL32 image +NEED_BL32 := yes + +# required so that SPM code executing at S-EL0 can access the timer registers +NS_TIMER_SWITCH := 1 diff --git a/services/std_svc/spm/spm_mm/spm_mm_main.c b/services/std_svc/spm/spm_mm/spm_mm_main.c new file mode 100644 index 0000000..1ff7bb7 --- /dev/null +++ b/services/std_svc/spm/spm_mm/spm_mm_main.c @@ -0,0 +1,370 @@ +/* + * Copyright (c) 2017-2022, ARM Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <arch_helpers.h> +#include <assert.h> +#include <errno.h> + +#include <bl31/bl31.h> +#include <bl31/ehf.h> +#include <common/debug.h> +#include <common/runtime_svc.h> +#include <lib/el3_runtime/context_mgmt.h> +#include <lib/smccc.h> +#include <lib/spinlock.h> +#include <lib/utils.h> +#include <lib/xlat_tables/xlat_tables_v2.h> +#include <plat/common/platform.h> +#include <services/spm_mm_partition.h> +#include <services/spm_mm_svc.h> +#include <smccc_helpers.h> + +#include "spm_common.h" +#include "spm_mm_private.h" + +/******************************************************************************* + * Secure Partition context information. + ******************************************************************************/ +static sp_context_t sp_ctx; + +/******************************************************************************* + * Set state of a Secure Partition context. + ******************************************************************************/ +void sp_state_set(sp_context_t *sp_ptr, sp_state_t state) +{ + spin_lock(&(sp_ptr->state_lock)); + sp_ptr->state = state; + spin_unlock(&(sp_ptr->state_lock)); +} + +/******************************************************************************* + * Wait until the state of a Secure Partition is the specified one and change it + * to the desired state. + ******************************************************************************/ +void sp_state_wait_switch(sp_context_t *sp_ptr, sp_state_t from, sp_state_t to) +{ + int success = 0; + + while (success == 0) { + spin_lock(&(sp_ptr->state_lock)); + + if (sp_ptr->state == from) { + sp_ptr->state = to; + + success = 1; + } + + spin_unlock(&(sp_ptr->state_lock)); + } +} + +/******************************************************************************* + * Check if the state of a Secure Partition is the specified one and, if so, + * change it to the desired state. Returns 0 on success, -1 on error. + ******************************************************************************/ +int sp_state_try_switch(sp_context_t *sp_ptr, sp_state_t from, sp_state_t to) +{ + int ret = -1; + + spin_lock(&(sp_ptr->state_lock)); + + if (sp_ptr->state == from) { + sp_ptr->state = to; + + ret = 0; + } + + spin_unlock(&(sp_ptr->state_lock)); + + return ret; +} + +/******************************************************************************* + * This function takes an SP context pointer and performs a synchronous entry + * into it. + ******************************************************************************/ +static uint64_t spm_sp_synchronous_entry(sp_context_t *ctx) +{ + uint64_t rc; + + assert(ctx != NULL); + + /* Assign the context of the SP to this CPU */ + cm_set_context(&(ctx->cpu_ctx), SECURE); + + /* Restore the context assigned above */ + cm_el1_sysregs_context_restore(SECURE); + cm_set_next_eret_context(SECURE); + + /* Invalidate TLBs at EL1. */ + tlbivmalle1(); + dsbish(); + + /* Enter Secure Partition */ + rc = spm_secure_partition_enter(&ctx->c_rt_ctx); + + /* Save secure state */ + cm_el1_sysregs_context_save(SECURE); + + return rc; +} + +/******************************************************************************* + * This function returns to the place where spm_sp_synchronous_entry() was + * called originally. + ******************************************************************************/ +__dead2 static void spm_sp_synchronous_exit(uint64_t rc) +{ + sp_context_t *ctx = &sp_ctx; + + /* + * The SPM must have initiated the original request through a + * synchronous entry into the secure partition. Jump back to the + * original C runtime context with the value of rc in x0; + */ + spm_secure_partition_exit(ctx->c_rt_ctx, rc); + + panic(); +} + +/******************************************************************************* + * Jump to each Secure Partition for the first time. + ******************************************************************************/ +static int32_t spm_init(void) +{ + uint64_t rc; + sp_context_t *ctx; + + INFO("Secure Partition init...\n"); + + ctx = &sp_ctx; + + ctx->state = SP_STATE_RESET; + + rc = spm_sp_synchronous_entry(ctx); + assert(rc == 0); + + ctx->state = SP_STATE_IDLE; + + INFO("Secure Partition initialized.\n"); + + return !rc; +} + +/******************************************************************************* + * Initialize contexts of all Secure Partitions. + ******************************************************************************/ +int32_t spm_mm_setup(void) +{ + sp_context_t *ctx; + + /* Disable MMU at EL1 (initialized by BL2) */ + disable_mmu_icache_el1(); + + /* Initialize context of the SP */ + INFO("Secure Partition context setup start...\n"); + + ctx = &sp_ctx; + + /* Assign translation tables context. */ + ctx->xlat_ctx_handle = spm_get_sp_xlat_context(); + + spm_sp_setup(ctx); + + /* Register init function for deferred init. */ + bl31_register_bl32_init(&spm_init); + + INFO("Secure Partition setup done.\n"); + + return 0; +} + +/******************************************************************************* + * Function to perform a call to a Secure Partition. + ******************************************************************************/ +uint64_t spm_mm_sp_call(uint32_t smc_fid, uint64_t x1, uint64_t x2, uint64_t x3) +{ + uint64_t rc; + sp_context_t *sp_ptr = &sp_ctx; + +#if CTX_INCLUDE_FPREGS + /* + * SP runs to completion, no need to restore FP registers of secure context. + * Save FP registers only for non secure context. + */ + fpregs_context_save(get_fpregs_ctx(cm_get_context(NON_SECURE))); +#endif + + /* Wait until the Secure Partition is idle and set it to busy. */ + sp_state_wait_switch(sp_ptr, SP_STATE_IDLE, SP_STATE_BUSY); + + /* Set values for registers on SP entry */ + cpu_context_t *cpu_ctx = &(sp_ptr->cpu_ctx); + + write_ctx_reg(get_gpregs_ctx(cpu_ctx), CTX_GPREG_X0, smc_fid); + write_ctx_reg(get_gpregs_ctx(cpu_ctx), CTX_GPREG_X1, x1); + write_ctx_reg(get_gpregs_ctx(cpu_ctx), CTX_GPREG_X2, x2); + write_ctx_reg(get_gpregs_ctx(cpu_ctx), CTX_GPREG_X3, x3); + + /* Jump to the Secure Partition. */ + rc = spm_sp_synchronous_entry(sp_ptr); + + /* Flag Secure Partition as idle. */ + assert(sp_ptr->state == SP_STATE_BUSY); + sp_state_set(sp_ptr, SP_STATE_IDLE); + +#if CTX_INCLUDE_FPREGS + /* + * SP runs to completion, no need to save FP registers of secure context. + * Restore only non secure world FP registers. + */ + fpregs_context_restore(get_fpregs_ctx(cm_get_context(NON_SECURE))); +#endif + + return rc; +} + +/******************************************************************************* + * MM_COMMUNICATE handler + ******************************************************************************/ +static uint64_t mm_communicate(uint32_t smc_fid, uint64_t mm_cookie, + uint64_t comm_buffer_address, + uint64_t comm_size_address, void *handle) +{ + uint64_t rc; + + /* Cookie. Reserved for future use. It must be zero. */ + if (mm_cookie != 0U) { + ERROR("MM_COMMUNICATE: cookie is not zero\n"); + SMC_RET1(handle, SPM_MM_INVALID_PARAMETER); + } + + if (comm_buffer_address == 0U) { + ERROR("MM_COMMUNICATE: comm_buffer_address is zero\n"); + SMC_RET1(handle, SPM_MM_INVALID_PARAMETER); + } + + if (comm_size_address != 0U) { + VERBOSE("MM_COMMUNICATE: comm_size_address is not 0 as recommended.\n"); + } + + /* + * The current secure partition design mandates + * - at any point, only a single core can be + * executing in the secure partition. + * - a core cannot be preempted by an interrupt + * while executing in secure partition. + * Raise the running priority of the core to the + * interrupt level configured for secure partition + * so as to block any interrupt from preempting this + * core. + */ + ehf_activate_priority(PLAT_SP_PRI); + + /* Save the Normal world context */ + cm_el1_sysregs_context_save(NON_SECURE); + + rc = spm_mm_sp_call(smc_fid, comm_buffer_address, comm_size_address, + plat_my_core_pos()); + + /* Restore non-secure state */ + cm_el1_sysregs_context_restore(NON_SECURE); + cm_set_next_eret_context(NON_SECURE); + + /* + * Exited from secure partition. This core can take + * interrupts now. + */ + ehf_deactivate_priority(PLAT_SP_PRI); + + SMC_RET1(handle, rc); +} + +/******************************************************************************* + * Secure Partition Manager SMC handler. + ******************************************************************************/ +uint64_t spm_mm_smc_handler(uint32_t smc_fid, + uint64_t x1, + uint64_t x2, + uint64_t x3, + uint64_t x4, + void *cookie, + void *handle, + uint64_t flags) +{ + unsigned int ns; + + /* Determine which security state this SMC originated from */ + ns = is_caller_non_secure(flags); + + if (ns == SMC_FROM_SECURE) { + + /* Handle SMCs from Secure world. */ + + assert(handle == cm_get_context(SECURE)); + + /* Make next ERET jump to S-EL0 instead of S-EL1. */ + cm_set_elr_spsr_el3(SECURE, read_elr_el1(), read_spsr_el1()); + + switch (smc_fid) { + + case SPM_MM_VERSION_AARCH32: + SMC_RET1(handle, SPM_MM_VERSION_COMPILED); + + case MM_SP_EVENT_COMPLETE_AARCH64: + spm_sp_synchronous_exit(x1); + + case MM_SP_MEMORY_ATTRIBUTES_GET_AARCH64: + INFO("Received MM_SP_MEMORY_ATTRIBUTES_GET_AARCH64 SMC\n"); + + if (sp_ctx.state != SP_STATE_RESET) { + WARN("MM_SP_MEMORY_ATTRIBUTES_GET_AARCH64 is available at boot time only\n"); + SMC_RET1(handle, SPM_MM_NOT_SUPPORTED); + } + SMC_RET1(handle, + spm_memory_attributes_get_smc_handler( + &sp_ctx, x1)); + + case MM_SP_MEMORY_ATTRIBUTES_SET_AARCH64: + INFO("Received MM_SP_MEMORY_ATTRIBUTES_SET_AARCH64 SMC\n"); + + if (sp_ctx.state != SP_STATE_RESET) { + WARN("MM_SP_MEMORY_ATTRIBUTES_SET_AARCH64 is available at boot time only\n"); + SMC_RET1(handle, SPM_MM_NOT_SUPPORTED); + } + SMC_RET1(handle, + spm_memory_attributes_set_smc_handler( + &sp_ctx, x1, x2, x3)); + default: + break; + } + } else { + + /* Handle SMCs from Non-secure world. */ + + assert(handle == cm_get_context(NON_SECURE)); + + switch (smc_fid) { + + case MM_VERSION_AARCH32: + SMC_RET1(handle, MM_VERSION_COMPILED); + + case MM_COMMUNICATE_AARCH32: + case MM_COMMUNICATE_AARCH64: + return mm_communicate(smc_fid, x1, x2, x3, handle); + + case MM_SP_MEMORY_ATTRIBUTES_GET_AARCH64: + case MM_SP_MEMORY_ATTRIBUTES_SET_AARCH64: + /* SMC interfaces reserved for secure callers. */ + SMC_RET1(handle, SPM_MM_NOT_SUPPORTED); + + default: + break; + } + } + + SMC_RET1(handle, SMC_UNK); +} diff --git a/services/std_svc/spm/spm_mm/spm_mm_private.h b/services/std_svc/spm/spm_mm/spm_mm_private.h new file mode 100644 index 0000000..3a52a3e --- /dev/null +++ b/services/std_svc/spm/spm_mm/spm_mm_private.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2017-2023, ARM Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef SPM_MM_PRIVATE_H +#define SPM_MM_PRIVATE_H + +#include <context.h> +#include "spm_common.h" + +/******************************************************************************* + * Constants that allow assembler code to preserve callee-saved registers of the + * C runtime context while performing a security state switch. + ******************************************************************************/ +#define SP_C_RT_CTX_X19 0x0 +#define SP_C_RT_CTX_X20 0x8 +#define SP_C_RT_CTX_X21 0x10 +#define SP_C_RT_CTX_X22 0x18 +#define SP_C_RT_CTX_X23 0x20 +#define SP_C_RT_CTX_X24 0x28 +#define SP_C_RT_CTX_X25 0x30 +#define SP_C_RT_CTX_X26 0x38 +#define SP_C_RT_CTX_X27 0x40 +#define SP_C_RT_CTX_X28 0x48 +#define SP_C_RT_CTX_X29 0x50 +#define SP_C_RT_CTX_X30 0x58 + +#define SP_C_RT_CTX_SIZE 0x60 +#define SP_C_RT_CTX_ENTRIES (SP_C_RT_CTX_SIZE >> DWORD_SHIFT) + +#ifndef __ASSEMBLER__ + +#include <stdint.h> + +#include <lib/spinlock.h> +#include <lib/xlat_tables/xlat_tables_v2.h> + +typedef enum sp_state { + SP_STATE_RESET = 0, + SP_STATE_IDLE, + SP_STATE_BUSY +} sp_state_t; + +typedef struct sp_context { + uint64_t c_rt_ctx; + cpu_context_t cpu_ctx; + xlat_ctx_t *xlat_ctx_handle; + + sp_state_t state; + spinlock_t state_lock; +} sp_context_t; + + +void spm_sp_setup(sp_context_t *sp_ctx); + +int32_t spm_memory_attributes_get_smc_handler(sp_context_t *sp_ctx, + uintptr_t base_va); +int spm_memory_attributes_set_smc_handler(sp_context_t *sp_ctx, + u_register_t page_address, + u_register_t pages_count, + u_register_t smc_attributes); + +#endif /* __ASSEMBLER__ */ + +#endif /* SPM_MM_PRIVATE_H */ diff --git a/services/std_svc/spm/spm_mm/spm_mm_setup.c b/services/std_svc/spm/spm_mm/spm_mm_setup.c new file mode 100644 index 0000000..4e65c9c --- /dev/null +++ b/services/std_svc/spm/spm_mm/spm_mm_setup.c @@ -0,0 +1,260 @@ +/* + * Copyright (c) 2017-2023, ARM Limited and Contributors. All rights reserved. + * Copyright (c) 2021, NVIDIA Corporation. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <assert.h> +#include <string.h> + +#include <arch.h> +#include <arch_helpers.h> +#include <context.h> +#include <common/debug.h> +#include <lib/el3_runtime/context_mgmt.h> +#include <lib/xlat_tables/xlat_tables_v2.h> +#include <platform_def.h> +#include <plat/common/common_def.h> +#include <plat/common/platform.h> +#include <services/spm_mm_partition.h> + +#include "spm_common.h" +#include "spm_mm_private.h" +#include "spm_shim_private.h" + +/* Setup context of the Secure Partition */ +void spm_sp_setup(sp_context_t *sp_ctx) +{ + cpu_context_t *ctx = &(sp_ctx->cpu_ctx); + + /* Pointer to the MP information from the platform port. */ + const spm_mm_boot_info_t *sp_boot_info = + plat_get_secure_partition_boot_info(NULL); + + /* + * Initialize CPU context + * ---------------------- + */ + + entry_point_info_t ep_info = {0}; + + SET_PARAM_HEAD(&ep_info, PARAM_EP, VERSION_1, SECURE | EP_ST_ENABLE); + + /* Setup entrypoint and SPSR */ + ep_info.pc = sp_boot_info->sp_image_base; + ep_info.spsr = SPSR_64(MODE_EL0, MODE_SP_EL0, DISABLE_ALL_EXCEPTIONS); + + /* + * X0: Virtual address of a buffer shared between EL3 and Secure EL0. + * The buffer will be mapped in the Secure EL1 translation regime + * with Normal IS WBWA attributes and RO data and Execute Never + * instruction access permissions. + * + * X1: Size of the buffer in bytes + * + * X2: cookie value (Implementation Defined) + * + * X3: cookie value (Implementation Defined) + * + * X4 to X7 = 0 + */ + ep_info.args.arg0 = sp_boot_info->sp_shared_buf_base; + ep_info.args.arg1 = sp_boot_info->sp_shared_buf_size; + ep_info.args.arg2 = PLAT_SPM_COOKIE_0; + ep_info.args.arg3 = PLAT_SPM_COOKIE_1; + + cm_setup_context(ctx, &ep_info); + + /* + * SP_EL0: A non-zero value will indicate to the SP that the SPM has + * initialized the stack pointer for the current CPU through + * implementation defined means. The value will be 0 otherwise. + */ + write_ctx_reg(get_gpregs_ctx(ctx), CTX_GPREG_SP_EL0, + sp_boot_info->sp_stack_base + sp_boot_info->sp_pcpu_stack_size); + + /* + * Setup translation tables + * ------------------------ + */ + +#if ENABLE_ASSERTIONS + + /* Get max granularity supported by the platform. */ + unsigned int max_granule = xlat_arch_get_max_supported_granule_size(); + + VERBOSE("Max translation granule size supported: %u KiB\n", + max_granule / 1024U); + + unsigned int max_granule_mask = max_granule - 1U; + + /* Base must be aligned to the max granularity */ + assert((sp_boot_info->sp_ns_comm_buf_base & max_granule_mask) == 0); + + /* Size must be a multiple of the max granularity */ + assert((sp_boot_info->sp_ns_comm_buf_size & max_granule_mask) == 0); + +#endif /* ENABLE_ASSERTIONS */ + + /* This region contains the exception vectors used at S-EL1. */ + const mmap_region_t sel1_exception_vectors = + MAP_REGION_FLAT(SPM_SHIM_EXCEPTIONS_START, + SPM_SHIM_EXCEPTIONS_SIZE, + MT_CODE | MT_SECURE | MT_PRIVILEGED); + mmap_add_region_ctx(sp_ctx->xlat_ctx_handle, + &sel1_exception_vectors); + + mmap_add_ctx(sp_ctx->xlat_ctx_handle, + plat_get_secure_partition_mmap(NULL)); + + init_xlat_tables_ctx(sp_ctx->xlat_ctx_handle); + + /* + * MMU-related registers + * --------------------- + */ + xlat_ctx_t *xlat_ctx = sp_ctx->xlat_ctx_handle; + + uint64_t mmu_cfg_params[MMU_CFG_PARAM_MAX]; + + setup_mmu_cfg((uint64_t *)&mmu_cfg_params, 0, xlat_ctx->base_table, + xlat_ctx->pa_max_address, xlat_ctx->va_max_address, + EL1_EL0_REGIME); + + write_ctx_reg(get_el1_sysregs_ctx(ctx), CTX_MAIR_EL1, + mmu_cfg_params[MMU_CFG_MAIR]); + + write_ctx_reg(get_el1_sysregs_ctx(ctx), CTX_TCR_EL1, + mmu_cfg_params[MMU_CFG_TCR]); + + write_ctx_reg(get_el1_sysregs_ctx(ctx), CTX_TTBR0_EL1, + mmu_cfg_params[MMU_CFG_TTBR0]); + + /* Setup SCTLR_EL1 */ + u_register_t sctlr_el1 = read_ctx_reg(get_el1_sysregs_ctx(ctx), CTX_SCTLR_EL1); + + sctlr_el1 |= + /*SCTLR_EL1_RES1 |*/ + /* Don't trap DC CVAU, DC CIVAC, DC CVAC, DC CVAP, or IC IVAU */ + SCTLR_UCI_BIT | + /* RW regions at xlat regime EL1&0 are forced to be XN. */ + SCTLR_WXN_BIT | + /* Don't trap to EL1 execution of WFI or WFE at EL0. */ + SCTLR_NTWI_BIT | SCTLR_NTWE_BIT | + /* Don't trap to EL1 accesses to CTR_EL0 from EL0. */ + SCTLR_UCT_BIT | + /* Don't trap to EL1 execution of DZ ZVA at EL0. */ + SCTLR_DZE_BIT | + /* Enable SP Alignment check for EL0 */ + SCTLR_SA0_BIT | + /* Don't change PSTATE.PAN on taking an exception to EL1 */ + SCTLR_SPAN_BIT | + /* Allow cacheable data and instr. accesses to normal memory. */ + SCTLR_C_BIT | SCTLR_I_BIT | + /* Enable MMU. */ + SCTLR_M_BIT + ; + + sctlr_el1 &= ~( + /* Explicit data accesses at EL0 are little-endian. */ + SCTLR_E0E_BIT | + /* + * Alignment fault checking disabled when at EL1 and EL0 as + * the UEFI spec permits unaligned accesses. + */ + SCTLR_A_BIT | + /* Accesses to DAIF from EL0 are trapped to EL1. */ + SCTLR_UMA_BIT + ); + + write_ctx_reg(get_el1_sysregs_ctx(ctx), CTX_SCTLR_EL1, sctlr_el1); + + /* + * Setup other system registers + * ---------------------------- + */ + + /* Shim Exception Vector Base Address */ + write_ctx_reg(get_el1_sysregs_ctx(ctx), CTX_VBAR_EL1, + SPM_SHIM_EXCEPTIONS_PTR); + + write_ctx_reg(get_el1_sysregs_ctx(ctx), CTX_CNTKCTL_EL1, + EL0PTEN_BIT | EL0VTEN_BIT | EL0PCTEN_BIT | EL0VCTEN_BIT); + + /* + * FPEN: Allow the Secure Partition to access FP/SIMD registers. + * Note that SPM will not do any saving/restoring of these registers on + * behalf of the SP. This falls under the SP's responsibility. + * TTA: Enable access to trace registers. + * ZEN (v8.2): Trap SVE instructions and access to SVE registers. + */ + write_ctx_reg(get_el1_sysregs_ctx(ctx), CTX_CPACR_EL1, + CPACR_EL1_FPEN(CPACR_EL1_FP_TRAP_NONE)); + + /* + * Prepare information in buffer shared between EL3 and S-EL0 + * ---------------------------------------------------------- + */ + + void *shared_buf_ptr = (void *) sp_boot_info->sp_shared_buf_base; + + /* Copy the boot information into the shared buffer with the SP. */ + assert((uintptr_t)shared_buf_ptr + sizeof(spm_mm_boot_info_t) + <= (sp_boot_info->sp_shared_buf_base + sp_boot_info->sp_shared_buf_size)); + + assert(sp_boot_info->sp_shared_buf_base <= + (UINTPTR_MAX - sp_boot_info->sp_shared_buf_size + 1)); + + assert(sp_boot_info != NULL); + + memcpy((void *) shared_buf_ptr, (const void *) sp_boot_info, + sizeof(spm_mm_boot_info_t)); + + /* Pointer to the MP information from the platform port. */ + spm_mm_mp_info_t *sp_mp_info = + ((spm_mm_boot_info_t *) shared_buf_ptr)->mp_info; + + assert(sp_mp_info != NULL); + + /* + * Point the shared buffer MP information pointer to where the info will + * be populated, just after the boot info. + */ + ((spm_mm_boot_info_t *) shared_buf_ptr)->mp_info = + (spm_mm_mp_info_t *) ((uintptr_t)shared_buf_ptr + + sizeof(spm_mm_boot_info_t)); + + /* + * Update the shared buffer pointer to where the MP information for the + * payload will be populated + */ + shared_buf_ptr = ((spm_mm_boot_info_t *) shared_buf_ptr)->mp_info; + + /* + * Copy the cpu information into the shared buffer area after the boot + * information. + */ + assert(sp_boot_info->num_cpus <= PLATFORM_CORE_COUNT); + + assert((uintptr_t)shared_buf_ptr + <= (sp_boot_info->sp_shared_buf_base + sp_boot_info->sp_shared_buf_size - + (sp_boot_info->num_cpus * sizeof(*sp_mp_info)))); + + memcpy(shared_buf_ptr, (const void *) sp_mp_info, + sp_boot_info->num_cpus * sizeof(*sp_mp_info)); + + /* + * Calculate the linear indices of cores in boot information for the + * secure partition and flag the primary CPU + */ + sp_mp_info = (spm_mm_mp_info_t *) shared_buf_ptr; + + for (unsigned int index = 0; index < sp_boot_info->num_cpus; index++) { + u_register_t mpidr = sp_mp_info[index].mpidr; + + sp_mp_info[index].linear_id = plat_core_pos_by_mpidr(mpidr); + if (plat_my_core_pos() == sp_mp_info[index].linear_id) + sp_mp_info[index].flags |= MP_INFO_FLAG_PRIMARY_CPU; + } +} diff --git a/services/std_svc/spm/spm_mm/spm_mm_xlat.c b/services/std_svc/spm/spm_mm/spm_mm_xlat.c new file mode 100644 index 0000000..01d95c7 --- /dev/null +++ b/services/std_svc/spm/spm_mm/spm_mm_xlat.c @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2018-2023, ARM Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <arch.h> +#include <arch_helpers.h> +#include <assert.h> +#include <errno.h> +#include <lib/xlat_tables/xlat_tables_v2.h> +#include <platform_def.h> +#include <plat/common/platform.h> +#include <services/spm_mm_partition.h> +#include <services/spm_mm_svc.h> + +#include "spm_mm_private.h" +#include "spm_shim_private.h" + +/* Lock used for SP_MEMORY_ATTRIBUTES_GET and SP_MEMORY_ATTRIBUTES_SET */ +static spinlock_t mem_attr_smc_lock; + +/* + * Attributes are encoded using a different format in the SMC interface than in + * the Trusted Firmware, where the mmap_attr_t enum type is used. This function + * converts an attributes value from the SMC format to the mmap_attr_t format by + * setting MT_RW/MT_RO, MT_USER/MT_PRIVILEGED and MT_EXECUTE/MT_EXECUTE_NEVER. + * The other fields are left as 0 because they are ignored by the function + * xlat_change_mem_attributes_ctx(). + */ +static unsigned int smc_attr_to_mmap_attr(unsigned int attributes) +{ + unsigned int tf_attr = 0U; + + unsigned int access = (attributes & MM_SP_MEMORY_ATTRIBUTES_ACCESS_MASK) + >> MM_SP_MEMORY_ATTRIBUTES_ACCESS_SHIFT; + + if (access == MM_SP_MEMORY_ATTRIBUTES_ACCESS_RW) { + tf_attr |= MT_RW | MT_USER; + } else if (access == MM_SP_MEMORY_ATTRIBUTES_ACCESS_RO) { + tf_attr |= MT_RO | MT_USER; + } else { + /* Other values are reserved. */ + assert(access == MM_SP_MEMORY_ATTRIBUTES_ACCESS_NOACCESS); + /* The only requirement is that there's no access from EL0 */ + tf_attr |= MT_RO | MT_PRIVILEGED; + } + + if ((attributes & MM_SP_MEMORY_ATTRIBUTES_NON_EXEC) == 0) { + tf_attr |= MT_EXECUTE; + } else { + tf_attr |= MT_EXECUTE_NEVER; + } + + return tf_attr; +} + +/* + * This function converts attributes from the Trusted Firmware format into the + * SMC interface format. + */ +static unsigned int smc_mmap_to_smc_attr(unsigned int attr) +{ + unsigned int smc_attr = 0U; + + unsigned int data_access; + + if ((attr & MT_USER) == 0) { + /* No access from EL0. */ + data_access = MM_SP_MEMORY_ATTRIBUTES_ACCESS_NOACCESS; + } else { + if ((attr & MT_RW) != 0) { + assert(MT_TYPE(attr) != MT_DEVICE); + data_access = MM_SP_MEMORY_ATTRIBUTES_ACCESS_RW; + } else { + data_access = MM_SP_MEMORY_ATTRIBUTES_ACCESS_RO; + } + } + + smc_attr |= (data_access & MM_SP_MEMORY_ATTRIBUTES_ACCESS_MASK) + << MM_SP_MEMORY_ATTRIBUTES_ACCESS_SHIFT; + + if ((attr & MT_EXECUTE_NEVER) != 0U) { + smc_attr |= MM_SP_MEMORY_ATTRIBUTES_NON_EXEC; + } + + return smc_attr; +} + +int32_t spm_memory_attributes_get_smc_handler(sp_context_t *sp_ctx, + uintptr_t base_va) +{ + uint32_t attributes; + + spin_lock(&mem_attr_smc_lock); + + int rc = xlat_get_mem_attributes_ctx(sp_ctx->xlat_ctx_handle, + base_va, &attributes); + + spin_unlock(&mem_attr_smc_lock); + + /* Convert error codes of xlat_get_mem_attributes_ctx() into SPM. */ + assert((rc == 0) || (rc == -EINVAL)); + + if (rc == 0) { + return (int32_t) smc_mmap_to_smc_attr(attributes); + } else { + return SPM_MM_INVALID_PARAMETER; + } +} + +int spm_memory_attributes_set_smc_handler(sp_context_t *sp_ctx, + u_register_t page_address, + u_register_t pages_count, + u_register_t smc_attributes) +{ + uintptr_t base_va = (uintptr_t) page_address; + size_t size = (size_t) (pages_count * PAGE_SIZE); + uint32_t attributes = (uint32_t) smc_attributes; + + INFO(" Start address : 0x%lx\n", base_va); + INFO(" Number of pages: %i (%zi bytes)\n", (int) pages_count, size); + INFO(" Attributes : 0x%x\n", attributes); + + spin_lock(&mem_attr_smc_lock); + + int ret = xlat_change_mem_attributes_ctx(sp_ctx->xlat_ctx_handle, + base_va, size, + smc_attr_to_mmap_attr(attributes)); + + spin_unlock(&mem_attr_smc_lock); + + /* Convert error codes of xlat_change_mem_attributes_ctx() into SPM. */ + assert((ret == 0) || (ret == -EINVAL)); + + return (ret == 0) ? SPM_MM_SUCCESS : SPM_MM_INVALID_PARAMETER; +} |