| /* |
| * Copyright (c) 2018-2020, Arm Limited. All rights reserved. |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| * |
| */ |
| |
| #include <inttypes.h> |
| #include <stdbool.h> |
| #include "psa/client.h" |
| #include "psa/service.h" |
| #include "psa/lifecycle.h" |
| #include "tfm_thread.h" |
| #include "tfm_wait.h" |
| #include "tfm_utils.h" |
| #include "tfm_internal_defines.h" |
| #include "tfm_message_queue.h" |
| #include "tfm_spm_hal.h" |
| #include "tfm_irq_list.h" |
| #include "tfm_api.h" |
| #include "tfm_secure_api.h" |
| #include "tfm_memory_utils.h" |
| #include "spm_api.h" |
| #include "tfm_peripherals_def.h" |
| #include "spm_db.h" |
| #include "tfm_core_utils.h" |
| #include "spm_psa_client_call.h" |
| #include "tfm_rpc.h" |
| #include "tfm_internal.h" |
| #include "tfm_core_trustzone.h" |
| #include "tfm_core_mem_check.h" |
| #include "tfm_list.h" |
| #include "tfm_pools.h" |
| #include "region.h" |
| #include "region_defs.h" |
| #include "tfm/tfm_spm_services_api.h" |
| |
| #include "secure_fw/partitions/tfm_service_list.inc" |
| #include "tfm_spm_db_ipc.inc" |
| |
| /* Extern service variable */ |
| extern struct tfm_spm_service_t service[]; |
| extern const struct tfm_spm_service_db_t service_db[]; |
| |
| /* Pools */ |
| TFM_POOL_DECLARE(conn_handle_pool, sizeof(struct tfm_conn_handle_t), |
| TFM_CONN_HANDLE_MAX_NUM); |
| |
| void tfm_irq_handler(uint32_t partition_id, psa_signal_t signal, |
| IRQn_Type irq_line); |
| |
| #include "tfm_secure_irq_handlers_ipc.inc" |
| |
| /*********************** Connection handle conversion APIs *******************/ |
| |
| /* Set a minimal value here for feature expansion. */ |
| #define CLIENT_HANDLE_VALUE_MIN 32 |
| |
| #define CONVERSION_FACTOR_BITOFFSET 3 |
| #define CONVERSION_FACTOR_VALUE (1 << CONVERSION_FACTOR_BITOFFSET) |
| /* Set 32 as the maximum */ |
| #define CONVERSION_FACTOR_VALUE_MAX 0x20 |
| |
| #if CONVERSION_FACTOR_VALUE > CONVERSION_FACTOR_VALUE_MAX |
| #error "CONVERSION FACTOR OUT OF RANGE" |
| #endif |
| |
| static uint32_t loop_index; |
| |
| /* |
| * A handle instance psa_handle_t allocated inside SPM is actually a memory |
| * address among the handle pool. Return this handle to the client directly |
| * exposes information of secure memory address. In this case, converting the |
| * handle into another value does not represent the memory address to avoid |
| * exposing secure memory directly to clients. |
| * |
| * This function converts the handle instance into another value by scaling the |
| * handle in pool offset, the converted value is named as a user handle. |
| * |
| * The formula: |
| * user_handle = (handle_instance - POOL_START) * CONVERSION_FACTOR_VALUE + |
| * CLIENT_HANDLE_VALUE_MIN + loop_index |
| * where: |
| * CONVERSION_FACTOR_VALUE = 1 << CONVERSION_FACTOR_BITOFFSET, and should not |
| * exceed CONVERSION_FACTOR_VALUE_MAX. |
| * |
| * handle_instance in RANGE[POOL_START, POOL_END] |
| * user_handle in RANGE[CLIENT_HANDLE_VALUE_MIN, 0x3FFFFFFF] |
| * loop_index in RANGE[0, CONVERSION_FACTOR_VALUE - 1] |
| * |
| * note: |
| * loop_index is used to promise same handle instance is converted into |
| * different user handles in short time. |
| */ |
| static psa_handle_t tfm_spm_to_user_handle( |
| struct tfm_conn_handle_t *handle_instance) |
| { |
| psa_handle_t user_handle; |
| |
| loop_index = (loop_index + 1) % CONVERSION_FACTOR_VALUE; |
| user_handle = (psa_handle_t)((((uintptr_t)handle_instance - |
| (uintptr_t)conn_handle_pool) << CONVERSION_FACTOR_BITOFFSET) + |
| CLIENT_HANDLE_VALUE_MIN + loop_index); |
| |
| return user_handle; |
| } |
| |
| /* |
| * This function converts a user handle into a corresponded handle instance. |
| * The converted value is validated before returning, an invalid handle instance |
| * is returned as NULL. |
| * |
| * The formula: |
| * handle_instance = ((user_handle - CLIENT_HANDLE_VALUE_MIN) / |
| * CONVERSION_FACTOR_VALUE) + POOL_START |
| * where: |
| * CONVERSION_FACTOR_VALUE = 1 << CONVERSION_FACTOR_BITOFFSET, and should not |
| * exceed CONVERSION_FACTOR_VALUE_MAX. |
| * |
| * handle_instance in RANGE[POOL_START, POOL_END] |
| * user_handle in RANGE[CLIENT_HANDLE_VALUE_MIN, 0x3FFFFFFF] |
| * loop_index in RANGE[0, CONVERSION_FACTOR_VALUE - 1] |
| */ |
| struct tfm_conn_handle_t *tfm_spm_to_handle_instance(psa_handle_t user_handle) |
| { |
| struct tfm_conn_handle_t *handle_instance; |
| |
| if (user_handle == PSA_NULL_HANDLE) { |
| return NULL; |
| } |
| |
| handle_instance = (struct tfm_conn_handle_t *)((((uintptr_t)user_handle - |
| CLIENT_HANDLE_VALUE_MIN) >> CONVERSION_FACTOR_BITOFFSET) + |
| (uintptr_t)conn_handle_pool); |
| |
| return handle_instance; |
| } |
| |
| /* Service handle management functions */ |
| struct tfm_conn_handle_t *tfm_spm_create_conn_handle( |
| struct tfm_spm_service_t *service, |
| int32_t client_id) |
| { |
| struct tfm_conn_handle_t *p_handle; |
| |
| TFM_CORE_ASSERT(service); |
| |
| /* Get buffer for handle list structure from handle pool */ |
| p_handle = (struct tfm_conn_handle_t *)tfm_pool_alloc(conn_handle_pool); |
| if (!p_handle) { |
| return NULL; |
| } |
| |
| p_handle->service = service; |
| p_handle->status = TFM_HANDLE_STATUS_IDLE; |
| p_handle->client_id = client_id; |
| |
| /* Add handle node to list for next psa functions */ |
| tfm_list_add_tail(&service->handle_list, &p_handle->list); |
| |
| return p_handle; |
| } |
| |
| int32_t tfm_spm_validate_conn_handle( |
| const struct tfm_conn_handle_t *conn_handle, |
| int32_t client_id) |
| { |
| /* Check the handle address is validated */ |
| if (is_valid_chunk_data_in_pool(conn_handle_pool, |
| (uint8_t *)conn_handle) != true) { |
| return IPC_ERROR_GENERIC; |
| } |
| |
| /* Check the handle caller is correct */ |
| if (conn_handle->client_id != client_id) { |
| return IPC_ERROR_GENERIC; |
| } |
| |
| return IPC_SUCCESS; |
| } |
| |
| /** |
| * \brief Free connection handle which not used anymore. |
| * |
| * \param[in] service Target service context pointer |
| * \param[in] conn_handle Connection handle created by |
| * tfm_spm_create_conn_handle() |
| * |
| * \retval IPC_SUCCESS Success |
| * \retval IPC_ERROR_BAD_PARAMETERS Bad parameters input |
| * \retval "Does not return" Panic for not find service by handle |
| */ |
| static int32_t tfm_spm_free_conn_handle(struct tfm_spm_service_t *service, |
| struct tfm_conn_handle_t *conn_handle) |
| { |
| TFM_CORE_ASSERT(service); |
| TFM_CORE_ASSERT(conn_handle != NULL); |
| |
| /* Clear magic as the handler is not used anymore */ |
| conn_handle->internal_msg.magic = 0; |
| |
| /* Remove node from handle list */ |
| tfm_list_del_node(&conn_handle->list); |
| |
| /* Back handle buffer to pool */ |
| tfm_pool_free(conn_handle); |
| return IPC_SUCCESS; |
| } |
| |
| /** |
| * \brief Set reverse handle value for connection. |
| * |
| * \param[in] service Target service context pointer |
| * \param[in] conn_handle Connection handle created by |
| * tfm_spm_create_conn_handle() |
| * \param[in] rhandle rhandle need to save |
| * |
| * \retval IPC_SUCCESS Success |
| * \retval IPC_ERROR_BAD_PARAMETERS Bad parameters input |
| * \retval "Does not return" Panic for not find handle node |
| */ |
| static int32_t tfm_spm_set_rhandle(struct tfm_spm_service_t *service, |
| struct tfm_conn_handle_t *conn_handle, |
| void *rhandle) |
| { |
| TFM_CORE_ASSERT(service); |
| /* Set reverse handle value only be allowed for a connected handle */ |
| TFM_CORE_ASSERT(conn_handle != NULL); |
| |
| conn_handle->rhandle = rhandle; |
| return IPC_SUCCESS; |
| } |
| |
| /** |
| * \brief Get reverse handle value from connection hanlde. |
| * |
| * \param[in] service Target service context pointer |
| * \param[in] conn_handle Connection handle created by |
| * tfm_spm_create_conn_handle() |
| * |
| * \retval void * Success |
| * \retval "Does not return" Panic for those: |
| * service pointer are NULL |
| * hanlde is \ref PSA_NULL_HANDLE |
| * handle node does not be found |
| */ |
| static void *tfm_spm_get_rhandle(struct tfm_spm_service_t *service, |
| struct tfm_conn_handle_t *conn_handle) |
| { |
| TFM_CORE_ASSERT(service); |
| /* Get reverse handle value only be allowed for a connected handle */ |
| TFM_CORE_ASSERT(conn_handle != NULL); |
| |
| return conn_handle->rhandle; |
| } |
| |
| /* Partition management functions */ |
| |
| /** |
| * \brief Get the service context by signal. |
| * |
| * \param[in] partition Partition context pointer |
| * \ref spm_partition_desc_t structures |
| * \param[in] signal Signal associated with inputs to the Secure |
| * Partition, \ref psa_signal_t |
| * |
| * \retval NULL Failed |
| * \retval "Not NULL" Target service context pointer, |
| * \ref tfm_spm_service_t structures |
| */ |
| static struct tfm_spm_service_t * |
| tfm_spm_get_service_by_signal(struct spm_partition_desc_t *partition, |
| psa_signal_t signal) |
| { |
| struct tfm_list_node_t *node, *head; |
| struct tfm_spm_service_t *service; |
| |
| TFM_CORE_ASSERT(partition); |
| |
| if (tfm_list_is_empty(&partition->runtime_data.service_list)) { |
| tfm_core_panic(); |
| } |
| |
| head = &partition->runtime_data.service_list; |
| TFM_LIST_FOR_EACH(node, head) { |
| service = TFM_GET_CONTAINER_PTR(node, struct tfm_spm_service_t, list); |
| if (service->service_db->signal == signal) { |
| return service; |
| } |
| } |
| return NULL; |
| } |
| |
| struct tfm_spm_service_t *tfm_spm_get_service_by_sid(uint32_t sid) |
| { |
| uint32_t i, num; |
| |
| num = sizeof(service) / sizeof(struct tfm_spm_service_t); |
| for (i = 0; i < num; i++) { |
| if (service[i].service_db->sid == sid) { |
| return &service[i]; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| /** |
| * \brief Get the partition context by partition ID. |
| * |
| * \param[in] partition_id Partition identity |
| * |
| * \retval NULL Failed |
| * \retval "Not NULL" Target partition context pointer, |
| * \ref spm_partition_desc_t structures |
| */ |
| static struct spm_partition_desc_t * |
| tfm_spm_get_partition_by_id(int32_t partition_id) |
| { |
| uint32_t idx = get_partition_idx(partition_id); |
| |
| if (idx != SPM_INVALID_PARTITION_IDX) { |
| return &(g_spm_partition_db.partitions[idx]); |
| } |
| return NULL; |
| } |
| |
| struct spm_partition_desc_t *tfm_spm_get_running_partition(void) |
| { |
| uint32_t spid; |
| |
| spid = tfm_spm_partition_get_running_partition_id(); |
| |
| return tfm_spm_get_partition_by_id(spid); |
| } |
| |
| int32_t tfm_spm_check_client_version(struct tfm_spm_service_t *service, |
| uint32_t version) |
| { |
| TFM_CORE_ASSERT(service); |
| |
| switch (service->service_db->version_policy) { |
| case TFM_VERSION_POLICY_RELAXED: |
| if (version > service->service_db->version) { |
| return IPC_ERROR_VERSION; |
| } |
| break; |
| case TFM_VERSION_POLICY_STRICT: |
| if (version != service->service_db->version) { |
| return IPC_ERROR_VERSION; |
| } |
| break; |
| default: |
| return IPC_ERROR_VERSION; |
| } |
| return IPC_SUCCESS; |
| } |
| |
| int32_t tfm_spm_check_authorization(uint32_t sid, |
| struct tfm_spm_service_t *service, |
| bool ns_caller) |
| { |
| struct spm_partition_desc_t *partition = NULL; |
| int32_t i; |
| |
| TFM_CORE_ASSERT(service); |
| |
| if (ns_caller) { |
| if (!service->service_db->non_secure_client) { |
| return IPC_ERROR_GENERIC; |
| } |
| } else { |
| partition = tfm_spm_get_running_partition(); |
| if (!partition) { |
| tfm_core_panic(); |
| } |
| |
| for (i = 0; i < partition->static_data->dependencies_num; i++) { |
| if (partition->static_data->p_dependencies[i] == sid) { |
| break; |
| } |
| } |
| |
| if (i == partition->static_data->dependencies_num) { |
| return IPC_ERROR_GENERIC; |
| } |
| } |
| return IPC_SUCCESS; |
| } |
| |
| /* Message functions */ |
| |
| /** |
| * \brief Get message context by message handle. |
| * |
| * \param[in] msg_handle Message handle which is a reference generated |
| * by the SPM to a specific message. |
| * |
| * \return The message body context pointer |
| * \ref tfm_msg_body_t structures |
| */ |
| static struct tfm_msg_body_t * |
| tfm_spm_get_msg_from_handle(psa_handle_t msg_handle) |
| { |
| /* |
| * The message handler passed by the caller is considered invalid in the |
| * following cases: |
| * 1. Not a valid message handle. (The address of a message is not the |
| * address of a possible handle from the pool |
| * 2. Handle not belongs to the caller partition (The handle is either |
| * unused, or owned by anither partition) |
| * Check the conditions above |
| */ |
| struct tfm_conn_handle_t *connection_handle_address; |
| struct tfm_msg_body_t *msg; |
| uint32_t partition_id; |
| |
| msg = (struct tfm_msg_body_t *)msg_handle; |
| |
| connection_handle_address = |
| TFM_GET_CONTAINER_PTR(msg, struct tfm_conn_handle_t, internal_msg); |
| |
| if (is_valid_chunk_data_in_pool( |
| conn_handle_pool, (uint8_t *)connection_handle_address) != 1) { |
| return NULL; |
| } |
| |
| /* |
| * Check that the magic number is correct. This proves that the message |
| * structure contains an active message. |
| */ |
| if (msg->magic != TFM_MSG_MAGIC) { |
| return NULL; |
| } |
| |
| /* Check that the running partition owns the message */ |
| partition_id = tfm_spm_partition_get_running_partition_id(); |
| if (partition_id != msg->service->partition->static_data->partition_id) { |
| return NULL; |
| } |
| |
| /* |
| * FixMe: For condition 1 it should be checked whether the message belongs |
| * to the service. Skipping this check isn't a security risk as even if the |
| * message belongs to another service, the handle belongs to the calling |
| * partition. |
| */ |
| |
| return msg; |
| } |
| |
| struct tfm_msg_body_t * |
| tfm_spm_get_msg_buffer_from_conn_handle(struct tfm_conn_handle_t *conn_handle) |
| { |
| TFM_CORE_ASSERT(conn_handle != NULL); |
| |
| return &(conn_handle->internal_msg); |
| } |
| |
| void tfm_spm_fill_msg(struct tfm_msg_body_t *msg, |
| struct tfm_spm_service_t *service, |
| struct tfm_conn_handle_t *handle, |
| int32_t type, int32_t client_id, |
| psa_invec *invec, size_t in_len, |
| psa_outvec *outvec, size_t out_len, |
| psa_outvec *caller_outvec) |
| { |
| uint32_t i; |
| |
| TFM_CORE_ASSERT(msg); |
| TFM_CORE_ASSERT(service); |
| TFM_CORE_ASSERT(!(invec == NULL && in_len != 0)); |
| TFM_CORE_ASSERT(!(outvec == NULL && out_len != 0)); |
| TFM_CORE_ASSERT(in_len <= PSA_MAX_IOVEC); |
| TFM_CORE_ASSERT(out_len <= PSA_MAX_IOVEC); |
| TFM_CORE_ASSERT(in_len + out_len <= PSA_MAX_IOVEC); |
| |
| /* Clear message buffer before using it */ |
| tfm_core_util_memset(msg, 0, sizeof(struct tfm_msg_body_t)); |
| |
| tfm_event_init(&msg->ack_evnt); |
| msg->magic = TFM_MSG_MAGIC; |
| msg->service = service; |
| msg->handle = handle; |
| msg->caller_outvec = caller_outvec; |
| msg->msg.client_id = client_id; |
| |
| /* Copy contents */ |
| msg->msg.type = type; |
| |
| for (i = 0; i < in_len; i++) { |
| msg->msg.in_size[i] = invec[i].len; |
| msg->invec[i].base = invec[i].base; |
| } |
| |
| for (i = 0; i < out_len; i++) { |
| msg->msg.out_size[i] = outvec[i].len; |
| msg->outvec[i].base = outvec[i].base; |
| /* Out len is used to record the writed number, set 0 here again */ |
| msg->outvec[i].len = 0; |
| } |
| |
| /* Use message address as handle */ |
| msg->msg.handle = (psa_handle_t)msg; |
| |
| /* For connected handle, set rhandle to every message */ |
| if (handle) { |
| msg->msg.rhandle = tfm_spm_get_rhandle(service, handle); |
| } |
| |
| /* Set the private data of NSPE client caller in multi-core topology */ |
| if (TFM_CLIENT_ID_IS_NS(client_id)) { |
| tfm_rpc_set_caller_data(msg, client_id); |
| } |
| } |
| |
| int32_t tfm_spm_send_event(struct tfm_spm_service_t *service, |
| struct tfm_msg_body_t *msg) |
| { |
| struct spm_partition_runtime_data_t *p_runtime_data = |
| &service->partition->runtime_data; |
| |
| TFM_CORE_ASSERT(service); |
| TFM_CORE_ASSERT(msg); |
| |
| /* Enqueue message to service message queue */ |
| if (tfm_msg_enqueue(&service->msg_queue, msg) != IPC_SUCCESS) { |
| return IPC_ERROR_GENERIC; |
| } |
| |
| /* Messages put. Update signals */ |
| p_runtime_data->signals |= service->service_db->signal; |
| |
| tfm_event_wake(&p_runtime_data->signal_evnt, (p_runtime_data->signals & |
| p_runtime_data->signal_mask)); |
| |
| /* |
| * If it is a NS request via RPC, it is unnecessary to block current |
| * thread. |
| */ |
| if (!is_tfm_rpc_msg(msg)) { |
| tfm_event_wait(&msg->ack_evnt); |
| } |
| |
| return IPC_SUCCESS; |
| } |
| |
| /** |
| * \brief Get bottom of stack region for a partition |
| * |
| * \param[in] partition_idx Partition index |
| * |
| * \return Stack region bottom value |
| * |
| * \note This function doesn't check if partition_idx is valid. |
| */ |
| static uint32_t tfm_spm_partition_get_stack_bottom(uint32_t partition_idx) |
| { |
| return g_spm_partition_db.partitions[partition_idx]. |
| memory_data->stack_bottom; |
| } |
| |
| /** |
| * \brief Get top of stack region for a partition |
| * |
| * \param[in] partition_idx Partition index |
| * |
| * \return Stack region top value |
| * |
| * \note This function doesn't check if partition_idx is valid. |
| */ |
| static uint32_t tfm_spm_partition_get_stack_top(uint32_t partition_idx) |
| { |
| return g_spm_partition_db.partitions[partition_idx].memory_data->stack_top; |
| } |
| |
| uint32_t tfm_spm_partition_get_running_partition_id(void) |
| { |
| struct tfm_core_thread_t *pth = tfm_core_thrd_get_curr_thread(); |
| struct spm_partition_desc_t *partition; |
| struct spm_partition_runtime_data_t *r_data; |
| |
| r_data = TFM_GET_CONTAINER_PTR(pth, struct spm_partition_runtime_data_t, |
| sp_thrd); |
| partition = TFM_GET_CONTAINER_PTR(r_data, struct spm_partition_desc_t, |
| runtime_data); |
| return partition->static_data->partition_id; |
| } |
| |
| static struct tfm_core_thread_t * |
| tfm_spm_partition_get_thread_info(uint32_t partition_idx) |
| { |
| return &g_spm_partition_db.partitions[partition_idx].runtime_data.sp_thrd; |
| } |
| |
| static tfm_core_thrd_entry_t |
| tfm_spm_partition_get_init_func(uint32_t partition_idx) |
| { |
| return (tfm_core_thrd_entry_t)(g_spm_partition_db.partitions[partition_idx]. |
| static_data->partition_init); |
| } |
| |
| static uint32_t tfm_spm_partition_get_priority(uint32_t partition_idx) |
| { |
| return g_spm_partition_db.partitions[partition_idx].static_data-> |
| partition_priority; |
| } |
| |
| int32_t tfm_memory_check(const void *buffer, size_t len, bool ns_caller, |
| enum tfm_memory_access_e access, |
| uint32_t privileged) |
| { |
| enum tfm_status_e err; |
| |
| /* If len is zero, this indicates an empty buffer and base is ignored */ |
| if (len == 0) { |
| return IPC_SUCCESS; |
| } |
| |
| if (!buffer) { |
| return IPC_ERROR_BAD_PARAMETERS; |
| } |
| |
| if ((uintptr_t)buffer > (UINTPTR_MAX - len)) { |
| return IPC_ERROR_MEMORY_CHECK; |
| } |
| |
| if (access == TFM_MEMORY_ACCESS_RW) { |
| err = tfm_core_has_write_access_to_region(buffer, len, ns_caller, |
| privileged); |
| } else { |
| err = tfm_core_has_read_access_to_region(buffer, len, ns_caller, |
| privileged); |
| } |
| if (err == TFM_SUCCESS) { |
| return IPC_SUCCESS; |
| } |
| |
| return IPC_ERROR_MEMORY_CHECK; |
| } |
| |
| uint32_t tfm_spm_init(void) |
| { |
| uint32_t i, j, num; |
| struct spm_partition_desc_t *partition; |
| struct tfm_core_thread_t *pth, *p_ns_entry_thread = NULL; |
| const struct tfm_spm_partition_platform_data_t **platform_data_p; |
| |
| tfm_pool_init(conn_handle_pool, |
| POOL_BUFFER_SIZE(conn_handle_pool), |
| sizeof(struct tfm_conn_handle_t), |
| TFM_CONN_HANDLE_MAX_NUM); |
| |
| /* Init partition first for it will be used when init service */ |
| for (i = 0; i < g_spm_partition_db.partition_count; i++) { |
| partition = &g_spm_partition_db.partitions[i]; |
| |
| /* Check if the PSA framework version matches. */ |
| if (partition->static_data->psa_framework_version != |
| PSA_FRAMEWORK_VERSION) { |
| ERROR_MSG("Warning: PSA Framework Verison is not matched!"); |
| continue; |
| } |
| |
| platform_data_p = partition->platform_data_list; |
| if (platform_data_p != NULL) { |
| while ((*platform_data_p) != NULL) { |
| if (tfm_spm_hal_configure_default_isolation(i, |
| *platform_data_p) != TFM_PLAT_ERR_SUCCESS) { |
| tfm_core_panic(); |
| } |
| ++platform_data_p; |
| } |
| } |
| |
| if ((tfm_spm_partition_get_flags(i) & SPM_PART_FLAG_IPC) == 0) { |
| continue; |
| } |
| |
| /* Add PSA_DOORBELL signal to assigned_signals */ |
| partition->runtime_data.assigned_signals |= PSA_DOORBELL; |
| |
| /* TODO: This can be optimized by generating the assigned signal |
| * in code generation time. |
| */ |
| for (j = 0; j < tfm_core_irq_signals_count; ++j) { |
| if (tfm_core_irq_signals[j].partition_id == |
| partition->static_data->partition_id) { |
| partition->runtime_data.assigned_signals |= |
| tfm_core_irq_signals[j].signal_value; |
| } |
| } |
| |
| tfm_event_init(&partition->runtime_data.signal_evnt); |
| tfm_list_init(&partition->runtime_data.service_list); |
| |
| pth = tfm_spm_partition_get_thread_info(i); |
| if (!pth) { |
| tfm_core_panic(); |
| } |
| |
| tfm_core_thrd_init(pth, |
| tfm_spm_partition_get_init_func(i), |
| NULL, |
| (uintptr_t)tfm_spm_partition_get_stack_top(i), |
| (uintptr_t)tfm_spm_partition_get_stack_bottom(i)); |
| |
| pth->prior = tfm_spm_partition_get_priority(i); |
| |
| if (partition->static_data->partition_id == TFM_SP_NON_SECURE_ID) { |
| p_ns_entry_thread = pth; |
| pth->param = (void *)tfm_spm_hal_get_ns_entry_point(); |
| } |
| |
| /* Kick off */ |
| if (tfm_core_thrd_start(pth) != THRD_SUCCESS) { |
| tfm_core_panic(); |
| } |
| } |
| |
| /* Init Service */ |
| num = sizeof(service) / sizeof(struct tfm_spm_service_t); |
| for (i = 0; i < num; i++) { |
| service[i].service_db = &service_db[i]; |
| partition = |
| tfm_spm_get_partition_by_id(service[i].service_db->partition_id); |
| if (!partition) { |
| tfm_core_panic(); |
| } |
| service[i].partition = partition; |
| partition->runtime_data.assigned_signals |= service[i].service_db->signal; |
| |
| tfm_list_init(&service[i].handle_list); |
| tfm_list_add_tail(&partition->runtime_data.service_list, |
| &service[i].list); |
| } |
| |
| /* |
| * All threads initialized, start the scheduler. |
| * |
| * NOTE: |
| * It is worthy to give the thread object to scheduler if the background |
| * context belongs to one of the threads. Here the background thread is the |
| * initialization thread who calls SPM SVC, which re-uses the non-secure |
| * entry thread's stack. After SPM initialization is done, this stack is |
| * cleaned up and the background context is never going to return. Tell |
| * the scheduler that the current thread is non-secure entry thread. |
| */ |
| tfm_core_thrd_start_scheduler(p_ns_entry_thread); |
| |
| return p_ns_entry_thread->arch_ctx.lr; |
| } |
| |
| void tfm_pendsv_do_schedule(struct tfm_arch_ctx_t *p_actx) |
| { |
| #if TFM_LVL == 2 |
| struct spm_partition_desc_t *p_next_partition; |
| struct spm_partition_runtime_data_t *r_data; |
| uint32_t is_privileged; |
| #endif |
| struct tfm_core_thread_t *pth_next = tfm_core_thrd_get_next_thread(); |
| struct tfm_core_thread_t *pth_curr = tfm_core_thrd_get_curr_thread(); |
| |
| if (pth_next != NULL && pth_curr != pth_next) { |
| #if TFM_LVL == 2 |
| r_data = TFM_GET_CONTAINER_PTR(pth_next, |
| struct spm_partition_runtime_data_t, |
| sp_thrd); |
| p_next_partition = TFM_GET_CONTAINER_PTR(r_data, |
| struct spm_partition_desc_t, |
| runtime_data); |
| |
| if (p_next_partition->static_data->partition_flags & |
| SPM_PART_FLAG_PSA_ROT) { |
| is_privileged = TFM_PARTITION_PRIVILEGED_MODE; |
| } else { |
| is_privileged = TFM_PARTITION_UNPRIVILEGED_MODE; |
| } |
| |
| tfm_spm_partition_change_privilege(is_privileged); |
| #endif |
| |
| tfm_core_thrd_switch_context(p_actx, pth_curr, pth_next); |
| } |
| |
| /* |
| * Handle pending mailbox message from NS in multi-core topology. |
| * Empty operation on single Armv8-M platform. |
| */ |
| tfm_rpc_client_call_handler(); |
| } |
| |
| /*********************** SPM functions for PSA Client APIs *******************/ |
| |
| uint32_t tfm_spm_psa_framework_version(void) |
| { |
| return tfm_spm_client_psa_framework_version(); |
| } |
| |
| uint32_t tfm_spm_psa_version(uint32_t *args, bool ns_caller) |
| { |
| uint32_t sid; |
| |
| TFM_CORE_ASSERT(args != NULL); |
| sid = (uint32_t)args[0]; |
| |
| return tfm_spm_client_psa_version(sid, ns_caller); |
| } |
| |
| psa_status_t tfm_spm_psa_connect(uint32_t *args, bool ns_caller) |
| { |
| uint32_t sid; |
| uint32_t version; |
| |
| TFM_CORE_ASSERT(args != NULL); |
| sid = (uint32_t)args[0]; |
| version = (uint32_t)args[1]; |
| |
| return tfm_spm_client_psa_connect(sid, version, ns_caller); |
| } |
| |
| psa_status_t tfm_spm_psa_call(uint32_t *args, bool ns_caller, uint32_t lr) |
| { |
| psa_handle_t handle; |
| psa_invec *inptr; |
| psa_outvec *outptr; |
| size_t in_num, out_num; |
| struct spm_partition_desc_t *partition = NULL; |
| uint32_t privileged; |
| int32_t type; |
| struct tfm_control_parameter_t ctrl_param; |
| |
| TFM_CORE_ASSERT(args != NULL); |
| handle = (psa_handle_t)args[0]; |
| |
| partition = tfm_spm_get_running_partition(); |
| if (!partition) { |
| tfm_core_panic(); |
| } |
| privileged = tfm_spm_partition_get_privileged_mode( |
| partition->static_data->partition_flags); |
| |
| /* |
| * Read parameters from the arguments. It is a fatal error if the |
| * memory reference for buffer is invalid or not readable. |
| */ |
| if (tfm_memory_check((const void *)args[1], |
| sizeof(struct tfm_control_parameter_t), ns_caller, |
| TFM_MEMORY_ACCESS_RW, privileged) != IPC_SUCCESS) { |
| tfm_core_panic(); |
| } |
| |
| tfm_core_util_memcpy(&ctrl_param, |
| (const void *)args[1], |
| sizeof(ctrl_param)); |
| |
| type = ctrl_param.type; |
| in_num = ctrl_param.in_len; |
| out_num = ctrl_param.out_len; |
| inptr = (psa_invec *)args[2]; |
| outptr = (psa_outvec *)args[3]; |
| |
| /* The request type must be zero or positive. */ |
| if (type < 0) { |
| tfm_core_panic(); |
| } |
| |
| return tfm_spm_client_psa_call(handle, type, inptr, in_num, outptr, out_num, |
| ns_caller, privileged); |
| } |
| |
| void tfm_spm_psa_close(uint32_t *args, bool ns_caller) |
| { |
| psa_handle_t handle; |
| |
| TFM_CORE_ASSERT(args != NULL); |
| handle = args[0]; |
| |
| tfm_spm_client_psa_close(handle, ns_caller); |
| } |
| |
| uint32_t tfm_spm_get_lifecycle_state(void) |
| { |
| /* |
| * FixMe: return PSA_LIFECYCLE_UNKNOWN to the caller directly. It will be |
| * implemented in the future. |
| */ |
| return PSA_LIFECYCLE_UNKNOWN; |
| } |
| |
| /********************* SPM functions for PSA Service APIs ********************/ |
| |
| psa_signal_t tfm_spm_psa_wait(uint32_t *args) |
| { |
| psa_signal_t signal_mask; |
| uint32_t timeout; |
| struct spm_partition_desc_t *partition = NULL; |
| |
| TFM_CORE_ASSERT(args != NULL); |
| signal_mask = (psa_signal_t)args[0]; |
| timeout = args[1]; |
| |
| /* |
| * Timeout[30:0] are reserved for future use. |
| * SPM must ignore the value of RES. |
| */ |
| timeout &= PSA_TIMEOUT_MASK; |
| |
| partition = tfm_spm_get_running_partition(); |
| if (!partition) { |
| tfm_core_panic(); |
| } |
| |
| /* |
| * It is a PROGRAMMER ERROR if the signal_mask does not include any assigned |
| * signals. |
| */ |
| if ((partition->runtime_data.assigned_signals & signal_mask) == 0) { |
| tfm_core_panic(); |
| } |
| |
| /* |
| * Expected signals are included in signal wait mask, ignored signals |
| * should not be set and affect caller thread state. Save this mask for |
| * further checking while signals are ready to be set. |
| */ |
| partition->runtime_data.signal_mask = signal_mask; |
| |
| /* |
| * tfm_event_wait() blocks the caller thread if no signals are available. |
| * In this case, the return value of this function is temporary set into |
| * runtime context. After new signal(s) are available, the return value |
| * is updated with the available signal(s) and blocked thread gets to run. |
| */ |
| if (timeout == PSA_BLOCK && |
| (partition->runtime_data.signals & signal_mask) == 0) { |
| tfm_event_wait(&partition->runtime_data.signal_evnt); |
| } |
| |
| return partition->runtime_data.signals & signal_mask; |
| } |
| |
| psa_status_t tfm_spm_psa_get(uint32_t *args) |
| { |
| psa_signal_t signal; |
| psa_msg_t *msg = NULL; |
| struct tfm_spm_service_t *service = NULL; |
| struct tfm_msg_body_t *tmp_msg = NULL; |
| struct spm_partition_desc_t *partition = NULL; |
| uint32_t privileged; |
| |
| TFM_CORE_ASSERT(args != NULL); |
| signal = (psa_signal_t)args[0]; |
| msg = (psa_msg_t *)args[1]; |
| |
| /* |
| * Only one message could be retrieved every time for psa_get(). It is a |
| * fatal error if the input signal has more than a signal bit set. |
| */ |
| if (!tfm_is_one_bit_set(signal)) { |
| tfm_core_panic(); |
| } |
| |
| partition = tfm_spm_get_running_partition(); |
| if (!partition) { |
| tfm_core_panic(); |
| } |
| privileged = tfm_spm_partition_get_privileged_mode( |
| partition->static_data->partition_flags); |
| |
| /* |
| * Write the message to the service buffer. It is a fatal error if the |
| * input msg pointer is not a valid memory reference or not read-write. |
| */ |
| if (tfm_memory_check(msg, sizeof(psa_msg_t), false, TFM_MEMORY_ACCESS_RW, |
| privileged) != IPC_SUCCESS) { |
| tfm_core_panic(); |
| } |
| |
| /* |
| * It is a fatal error if the caller call psa_get() when no message has |
| * been set. The caller must call this function after an RoT Service signal |
| * is returned by psa_wait(). |
| */ |
| if (partition->runtime_data.signals == 0) { |
| tfm_core_panic(); |
| } |
| |
| /* |
| * It is a fatal error if the RoT Service signal is not currently asserted. |
| */ |
| if ((partition->runtime_data.signals & signal) == 0) { |
| tfm_core_panic(); |
| } |
| |
| /* |
| * Get RoT service by signal from partition. It is a fatal error if getting |
| * failed, which means the input signal is not correspond to an RoT service. |
| */ |
| service = tfm_spm_get_service_by_signal(partition, signal); |
| if (!service) { |
| tfm_core_panic(); |
| } |
| |
| tmp_msg = tfm_msg_dequeue(&service->msg_queue); |
| if (!tmp_msg) { |
| return PSA_ERROR_DOES_NOT_EXIST; |
| } |
| |
| ((struct tfm_conn_handle_t *)(tmp_msg->handle))->status = |
| TFM_HANDLE_STATUS_ACTIVE; |
| |
| tfm_core_util_memcpy(msg, &tmp_msg->msg, sizeof(psa_msg_t)); |
| |
| /* |
| * There may be multiple messages for this RoT Service signal, do not clear |
| * its mask until no remaining message. |
| */ |
| if (tfm_msg_queue_is_empty(&service->msg_queue)) { |
| partition->runtime_data.signals &= ~signal; |
| } |
| |
| return PSA_SUCCESS; |
| } |
| |
| void tfm_spm_psa_set_rhandle(uint32_t *args) |
| { |
| psa_handle_t msg_handle; |
| void *rhandle = NULL; |
| struct tfm_msg_body_t *msg = NULL; |
| |
| TFM_CORE_ASSERT(args != NULL); |
| msg_handle = (psa_handle_t)args[0]; |
| rhandle = (void *)args[1]; |
| |
| /* It is a fatal error if message handle is invalid */ |
| msg = tfm_spm_get_msg_from_handle(msg_handle); |
| if (!msg) { |
| tfm_core_panic(); |
| } |
| |
| msg->msg.rhandle = rhandle; |
| |
| /* Store reverse handle for following client calls. */ |
| tfm_spm_set_rhandle(msg->service, msg->handle, rhandle); |
| } |
| |
| size_t tfm_spm_psa_read(uint32_t *args) |
| { |
| psa_handle_t msg_handle; |
| uint32_t invec_idx; |
| void *buffer = NULL; |
| size_t num_bytes; |
| size_t bytes; |
| struct tfm_msg_body_t *msg = NULL; |
| uint32_t privileged; |
| struct spm_partition_desc_t *partition = NULL; |
| |
| TFM_CORE_ASSERT(args != NULL); |
| msg_handle = (psa_handle_t)args[0]; |
| invec_idx = args[1]; |
| buffer = (void *)args[2]; |
| num_bytes = (size_t)args[3]; |
| |
| /* It is a fatal error if message handle is invalid */ |
| msg = tfm_spm_get_msg_from_handle(msg_handle); |
| if (!msg) { |
| tfm_core_panic(); |
| } |
| |
| partition = msg->service->partition; |
| privileged = tfm_spm_partition_get_privileged_mode( |
| partition->static_data->partition_flags); |
| |
| /* |
| * It is a fatal error if message handle does not refer to a request |
| * message |
| */ |
| if (msg->msg.type < PSA_IPC_CALL) { |
| tfm_core_panic(); |
| } |
| |
| /* |
| * It is a fatal error if invec_idx is equal to or greater than |
| * PSA_MAX_IOVEC |
| */ |
| if (invec_idx >= PSA_MAX_IOVEC) { |
| tfm_core_panic(); |
| } |
| |
| /* There was no remaining data in this input vector */ |
| if (msg->msg.in_size[invec_idx] == 0) { |
| return 0; |
| } |
| |
| /* |
| * Copy the client data to the service buffer. It is a fatal error |
| * if the memory reference for buffer is invalid or not read-write. |
| */ |
| if (tfm_memory_check(buffer, num_bytes, false, |
| TFM_MEMORY_ACCESS_RW, privileged) != IPC_SUCCESS) { |
| tfm_core_panic(); |
| } |
| |
| bytes = num_bytes > msg->msg.in_size[invec_idx] ? |
| msg->msg.in_size[invec_idx] : num_bytes; |
| |
| tfm_core_util_memcpy(buffer, msg->invec[invec_idx].base, bytes); |
| |
| /* There maybe some remaining data */ |
| msg->invec[invec_idx].base = (char *)msg->invec[invec_idx].base + bytes; |
| msg->msg.in_size[invec_idx] -= bytes; |
| |
| return bytes; |
| } |
| |
| size_t tfm_spm_psa_skip(uint32_t *args) |
| { |
| psa_handle_t msg_handle; |
| uint32_t invec_idx; |
| size_t num_bytes; |
| struct tfm_msg_body_t *msg = NULL; |
| |
| TFM_CORE_ASSERT(args != NULL); |
| msg_handle = (psa_handle_t)args[0]; |
| invec_idx = args[1]; |
| num_bytes = (size_t)args[2]; |
| |
| /* It is a fatal error if message handle is invalid */ |
| msg = tfm_spm_get_msg_from_handle(msg_handle); |
| if (!msg) { |
| tfm_core_panic(); |
| } |
| |
| /* |
| * It is a fatal error if message handle does not refer to a request |
| * message |
| */ |
| if (msg->msg.type < PSA_IPC_CALL) { |
| tfm_core_panic(); |
| } |
| |
| /* |
| * It is a fatal error if invec_idx is equal to or greater than |
| * PSA_MAX_IOVEC |
| */ |
| if (invec_idx >= PSA_MAX_IOVEC) { |
| tfm_core_panic(); |
| } |
| |
| /* There was no remaining data in this input vector */ |
| if (msg->msg.in_size[invec_idx] == 0) { |
| return 0; |
| } |
| |
| /* |
| * If num_bytes is greater than the remaining size of the input vector then |
| * the remaining size of the input vector is used. |
| */ |
| if (num_bytes > msg->msg.in_size[invec_idx]) { |
| num_bytes = msg->msg.in_size[invec_idx]; |
| } |
| |
| /* There maybe some remaining data */ |
| msg->invec[invec_idx].base = (char *)msg->invec[invec_idx].base + |
| num_bytes; |
| msg->msg.in_size[invec_idx] -= num_bytes; |
| |
| return num_bytes; |
| } |
| |
| void tfm_spm_psa_write(uint32_t *args) |
| { |
| psa_handle_t msg_handle; |
| uint32_t outvec_idx; |
| void *buffer = NULL; |
| size_t num_bytes; |
| struct tfm_msg_body_t *msg = NULL; |
| uint32_t privileged; |
| struct spm_partition_desc_t *partition = NULL; |
| |
| TFM_CORE_ASSERT(args != NULL); |
| msg_handle = (psa_handle_t)args[0]; |
| outvec_idx = args[1]; |
| buffer = (void *)args[2]; |
| num_bytes = (size_t)args[3]; |
| |
| /* It is a fatal error if message handle is invalid */ |
| msg = tfm_spm_get_msg_from_handle(msg_handle); |
| if (!msg) { |
| tfm_core_panic(); |
| } |
| |
| partition = msg->service->partition; |
| privileged = tfm_spm_partition_get_privileged_mode( |
| partition->static_data->partition_flags); |
| |
| /* |
| * It is a fatal error if message handle does not refer to a request |
| * message |
| */ |
| if (msg->msg.type < PSA_IPC_CALL) { |
| tfm_core_panic(); |
| } |
| |
| /* |
| * It is a fatal error if outvec_idx is equal to or greater than |
| * PSA_MAX_IOVEC |
| */ |
| if (outvec_idx >= PSA_MAX_IOVEC) { |
| tfm_core_panic(); |
| } |
| |
| /* |
| * It is a fatal error if the call attempts to write data past the end of |
| * the client output vector |
| */ |
| if (num_bytes > msg->msg.out_size[outvec_idx] - |
| msg->outvec[outvec_idx].len) { |
| tfm_core_panic(); |
| } |
| |
| /* |
| * Copy the service buffer to client outvecs. It is a fatal error |
| * if the memory reference for buffer is invalid or not readable. |
| */ |
| if (tfm_memory_check(buffer, num_bytes, false, |
| TFM_MEMORY_ACCESS_RO, privileged) != IPC_SUCCESS) { |
| tfm_core_panic(); |
| } |
| |
| tfm_core_util_memcpy((char *)msg->outvec[outvec_idx].base + |
| msg->outvec[outvec_idx].len, buffer, num_bytes); |
| |
| /* Update the write number */ |
| msg->outvec[outvec_idx].len += num_bytes; |
| } |
| |
| static void update_caller_outvec_len(struct tfm_msg_body_t *msg) |
| { |
| uint32_t i; |
| |
| /* |
| * FixeMe: abstract these part into dedicated functions to avoid |
| * accessing thread context in psa layer |
| */ |
| /* If it is a NS request via RPC, the owner of this message is not set */ |
| if (!is_tfm_rpc_msg(msg)) { |
| TFM_CORE_ASSERT(msg->ack_evnt.owner->state == THRD_STATE_BLOCK); |
| } |
| |
| for (i = 0; i < PSA_MAX_IOVEC; i++) { |
| if (msg->msg.out_size[i] == 0) { |
| continue; |
| } |
| |
| TFM_CORE_ASSERT(msg->caller_outvec[i].base == msg->outvec[i].base); |
| |
| msg->caller_outvec[i].len = msg->outvec[i].len; |
| } |
| } |
| |
| void tfm_spm_psa_reply(uint32_t *args) |
| { |
| psa_handle_t msg_handle; |
| psa_status_t status; |
| struct tfm_spm_service_t *service = NULL; |
| struct tfm_msg_body_t *msg = NULL; |
| int32_t ret = PSA_SUCCESS; |
| |
| TFM_CORE_ASSERT(args != NULL); |
| msg_handle = (psa_handle_t)args[0]; |
| status = (psa_status_t)args[1]; |
| |
| /* It is a fatal error if message handle is invalid */ |
| msg = tfm_spm_get_msg_from_handle(msg_handle); |
| if (!msg) { |
| tfm_core_panic(); |
| } |
| |
| /* |
| * RoT Service information is needed in this function, stored it in message |
| * body structure. Only two parameters are passed in this function: handle |
| * and status, so it is useful and simply to do like this. |
| */ |
| service = msg->service; |
| if (!service) { |
| tfm_core_panic(); |
| } |
| |
| /* |
| * Three type of message are passed in this function: CONNECTION, REQUEST, |
| * DISCONNECTION. It needs to process differently for each type. |
| */ |
| switch (msg->msg.type) { |
| case PSA_IPC_CONNECT: |
| /* |
| * Reply to PSA_IPC_CONNECT message. Connect handle is returned if the |
| * input status is PSA_SUCCESS. Others return values are based on the |
| * input status. |
| */ |
| if (status == PSA_SUCCESS) { |
| ret = tfm_spm_to_user_handle(msg->handle); |
| } else if (status == PSA_ERROR_CONNECTION_REFUSED) { |
| /* Refuse the client connection, indicating a permanent error. */ |
| tfm_spm_free_conn_handle(service, msg->handle); |
| ret = PSA_ERROR_CONNECTION_REFUSED; |
| } else if (status == PSA_ERROR_CONNECTION_BUSY) { |
| /* Fail the client connection, indicating a transient error. */ |
| ret = PSA_ERROR_CONNECTION_BUSY; |
| } else { |
| tfm_core_panic(); |
| } |
| break; |
| case PSA_IPC_DISCONNECT: |
| /* Service handle is not used anymore */ |
| tfm_spm_free_conn_handle(service, msg->handle); |
| |
| /* |
| * If the message type is PSA_IPC_DISCONNECT, then the status code is |
| * ignored |
| */ |
| break; |
| default: |
| if (msg->msg.type >= PSA_IPC_CALL) { |
| /* Reply to a request message. Return values are based on status */ |
| ret = status; |
| /* |
| * The total number of bytes written to a single parameter must be |
| * reported to the client by updating the len member of the |
| * psa_outvec structure for the parameter before returning from |
| * psa_call(). |
| */ |
| update_caller_outvec_len(msg); |
| } else { |
| tfm_core_panic(); |
| } |
| } |
| |
| if (ret == PSA_ERROR_PROGRAMMER_ERROR) { |
| /* |
| * If the source of the programmer error is a Secure Partition, the SPM |
| * must panic the Secure Partition in response to a PROGRAMMER ERROR. |
| */ |
| if (TFM_CLIENT_ID_IS_NS(msg->msg.client_id)) { |
| ((struct tfm_conn_handle_t *)(msg->handle))->status = |
| TFM_HANDLE_STATUS_CONNECT_ERROR; |
| } else { |
| tfm_core_panic(); |
| } |
| } else { |
| ((struct tfm_conn_handle_t *)(msg->handle))->status = |
| TFM_HANDLE_STATUS_IDLE; |
| } |
| |
| if (is_tfm_rpc_msg(msg)) { |
| tfm_rpc_client_call_reply(msg, ret); |
| } else { |
| tfm_event_wake(&msg->ack_evnt, ret); |
| } |
| } |
| |
| /** |
| * \brief notify the partition with the signal. |
| * |
| * \param[in] partition_id The ID of the partition to be notified. |
| * \param[in] signal The signal that the partition is to be notified |
| * with. |
| * |
| * \retval void Success. |
| * \retval "Does not return" If partition_id is invalid. |
| */ |
| static void notify_with_signal(int32_t partition_id, psa_signal_t signal) |
| { |
| struct spm_partition_desc_t *partition = NULL; |
| |
| /* |
| * The value of partition_id must be greater than zero as the target of |
| * notification must be a Secure Partition, providing a Non-secure |
| * Partition ID is a fatal error. |
| */ |
| if (!TFM_CLIENT_ID_IS_S(partition_id)) { |
| tfm_core_panic(); |
| } |
| |
| /* |
| * It is a fatal error if partition_id does not correspond to a Secure |
| * Partition. |
| */ |
| partition = tfm_spm_get_partition_by_id(partition_id); |
| if (!partition) { |
| tfm_core_panic(); |
| } |
| |
| partition->runtime_data.signals |= signal; |
| |
| /* |
| * The target partition may be blocked with waiting for signals after |
| * called psa_wait(). Set the return value with the available signals |
| * before wake it up with tfm_event_signal(). |
| */ |
| tfm_event_wake(&partition->runtime_data.signal_evnt, |
| partition->runtime_data.signals & |
| partition->runtime_data.signal_mask); |
| } |
| |
| void tfm_spm_psa_notify(uint32_t *args) |
| { |
| int32_t partition_id; |
| |
| TFM_CORE_ASSERT(args != NULL); |
| partition_id = (int32_t)args[0]; |
| |
| notify_with_signal(partition_id, PSA_DOORBELL); |
| } |
| |
| /** |
| * \brief assert signal for a given IRQ line. |
| * |
| * \param[in] partition_id The ID of the partition which handles this IRQ |
| * \param[in] signal The signal associated with this IRQ |
| * \param[in] irq_line The number of the IRQ line |
| * |
| * \retval void Success. |
| * \retval "Does not return" Partition ID is invalid |
| */ |
| void tfm_irq_handler(uint32_t partition_id, psa_signal_t signal, |
| IRQn_Type irq_line) |
| { |
| tfm_spm_hal_disable_irq(irq_line); |
| notify_with_signal(partition_id, signal); |
| } |
| |
| void tfm_spm_psa_clear(void) |
| { |
| struct spm_partition_desc_t *partition = NULL; |
| |
| partition = tfm_spm_get_running_partition(); |
| if (!partition) { |
| tfm_core_panic(); |
| } |
| |
| /* |
| * It is a fatal error if the Secure Partition's doorbell signal is not |
| * currently asserted. |
| */ |
| if ((partition->runtime_data.signals & PSA_DOORBELL) == 0) { |
| tfm_core_panic(); |
| } |
| partition->runtime_data.signals &= ~PSA_DOORBELL; |
| } |
| |
| void tfm_spm_psa_panic(void) |
| { |
| /* |
| * PSA FF recommends that the SPM causes the system to restart when a secure |
| * partition panics. |
| */ |
| tfm_spm_hal_system_reset(); |
| } |
| |
| /** |
| * \brief Return the IRQ line number associated with a signal |
| * |
| * \param[in] partition_id The ID of the partition in which we look for |
| * the signal. |
| * \param[in] signal The signal we do the query for. |
| * \param[out] irq_line The irq line associated with signal |
| * |
| * \retval IPC_SUCCESS Execution successful, irq_line contains a valid |
| * value. |
| * \retval IPC_ERROR_GENERIC There was an error finding the IRQ line for the |
| * signal. irq_line is unchanged. |
| */ |
| static int32_t get_irq_line_for_signal(int32_t partition_id, |
| psa_signal_t signal, |
| IRQn_Type *irq_line) |
| { |
| size_t i; |
| |
| for (i = 0; i < tfm_core_irq_signals_count; ++i) { |
| if (tfm_core_irq_signals[i].partition_id == partition_id && |
| tfm_core_irq_signals[i].signal_value == signal) { |
| *irq_line = tfm_core_irq_signals[i].irq_line; |
| return IPC_SUCCESS; |
| } |
| } |
| return IPC_ERROR_GENERIC; |
| } |
| |
| void tfm_spm_psa_eoi(uint32_t *args) |
| { |
| psa_signal_t irq_signal; |
| IRQn_Type irq_line = (IRQn_Type) 0; |
| int32_t ret; |
| struct spm_partition_desc_t *partition = NULL; |
| |
| TFM_CORE_ASSERT(args != NULL); |
| irq_signal = (psa_signal_t)args[0]; |
| |
| /* It is a fatal error if passed signal indicates more than one signals. */ |
| if (!tfm_is_one_bit_set(irq_signal)) { |
| tfm_core_panic(); |
| } |
| |
| partition = tfm_spm_get_running_partition(); |
| if (!partition) { |
| tfm_core_panic(); |
| } |
| |
| ret = get_irq_line_for_signal(partition->static_data->partition_id, |
| irq_signal, &irq_line); |
| /* It is a fatal error if passed signal is not an interrupt signal. */ |
| if (ret != IPC_SUCCESS) { |
| tfm_core_panic(); |
| } |
| |
| /* It is a fatal error if passed signal is not currently asserted */ |
| if ((partition->runtime_data.signals & irq_signal) == 0) { |
| tfm_core_panic(); |
| } |
| |
| partition->runtime_data.signals &= ~irq_signal; |
| |
| tfm_spm_hal_clear_pending_irq(irq_line); |
| tfm_spm_hal_enable_irq(irq_line); |
| } |
| |
| void tfm_spm_enable_irq(uint32_t *args) |
| { |
| struct tfm_state_context_t *svc_ctx = (struct tfm_state_context_t *)args; |
| psa_signal_t irq_signal = svc_ctx->r0; |
| IRQn_Type irq_line = (IRQn_Type) 0; |
| int32_t ret; |
| struct spm_partition_desc_t *partition = NULL; |
| |
| /* It is a fatal error if passed signal indicates more than one signals. */ |
| if (!tfm_is_one_bit_set(irq_signal)) { |
| tfm_core_panic(); |
| } |
| |
| partition = tfm_spm_get_running_partition(); |
| if (!partition) { |
| tfm_core_panic(); |
| } |
| |
| ret = get_irq_line_for_signal(partition->static_data->partition_id, |
| irq_signal, &irq_line); |
| /* It is a fatal error if passed signal is not an interrupt signal. */ |
| if (ret != IPC_SUCCESS) { |
| tfm_core_panic(); |
| } |
| |
| tfm_spm_hal_enable_irq(irq_line); |
| } |
| |
| void tfm_spm_disable_irq(uint32_t *args) |
| { |
| struct tfm_state_context_t *svc_ctx = (struct tfm_state_context_t *)args; |
| psa_signal_t irq_signal = svc_ctx->r0; |
| IRQn_Type irq_line = (IRQn_Type) 0; |
| int32_t ret; |
| struct spm_partition_desc_t *partition = NULL; |
| |
| /* It is a fatal error if passed signal indicates more than one signals. */ |
| if (!tfm_is_one_bit_set(irq_signal)) { |
| tfm_core_panic(); |
| } |
| |
| partition = tfm_spm_get_running_partition(); |
| if (!partition) { |
| tfm_core_panic(); |
| } |
| |
| ret = get_irq_line_for_signal(partition->static_data->partition_id, |
| irq_signal, &irq_line); |
| /* It is a fatal error if passed signal is not an interrupt signal. */ |
| if (ret != IPC_SUCCESS) { |
| tfm_core_panic(); |
| } |
| |
| tfm_spm_hal_disable_irq(irq_line); |
| } |
| |
| void tfm_spm_validate_caller(struct spm_partition_desc_t *p_cur_sp, |
| uint32_t *p_ctx, uint32_t exc_return, |
| bool ns_caller) |
| { |
| uintptr_t stacked_ctx_pos; |
| |
| if (ns_caller) { |
| /* |
| * The background IRQ can't be supported, since if SP is executing, |
| * the preempted context of SP can be different with the one who |
| * preempts veneer. |
| */ |
| if (p_cur_sp->static_data->partition_id != TFM_SP_NON_SECURE_ID) { |
| tfm_core_panic(); |
| } |
| |
| /* |
| * It is non-secure caller, check if veneer stack contains |
| * multiple contexts. |
| */ |
| stacked_ctx_pos = (uintptr_t)p_ctx + |
| sizeof(struct tfm_state_context_t) + |
| TFM_VENEER_STACK_GUARD_SIZE; |
| |
| if (is_stack_alloc_fp_space(exc_return)) { |
| #if defined (__FPU_USED) && (__FPU_USED == 1U) |
| if (FPU->FPCCR & FPU_FPCCR_TS_Msk) { |
| stacked_ctx_pos += TFM_ADDTIONAL_FP_CONTEXT_WORDS * |
| sizeof(uint32_t); |
| } |
| #endif |
| stacked_ctx_pos += TFM_BASIC_FP_CONTEXT_WORDS * sizeof(uint32_t); |
| } |
| |
| if (stacked_ctx_pos != p_cur_sp->runtime_data.sp_thrd.stk_top) { |
| tfm_core_panic(); |
| } |
| } else if (p_cur_sp->static_data->partition_id <= 0) { |
| tfm_core_panic(); |
| } |
| } |
| |
| void tfm_spm_request_handler(const struct tfm_state_context_t *svc_ctx) |
| { |
| uint32_t *res_ptr = (uint32_t *)&svc_ctx->r0; |
| uint32_t running_partition_flags = 0; |
| const struct spm_partition_desc_t *partition = NULL; |
| |
| /* Check permissions on request type basis */ |
| |
| switch (svc_ctx->r0) { |
| case TFM_SPM_REQUEST_RESET_VOTE: |
| partition = tfm_spm_get_running_partition(); |
| if (!partition) { |
| tfm_core_panic(); |
| } |
| running_partition_flags = partition->static_data->partition_flags; |
| |
| /* Currently only PSA Root of Trust services are allowed to make Reset |
| * vote request |
| */ |
| if ((running_partition_flags & SPM_PART_FLAG_PSA_ROT) == 0) { |
| *res_ptr = (uint32_t)TFM_ERROR_GENERIC; |
| } |
| |
| /* FixMe: this is a placeholder for checks to be performed before |
| * allowing execution of reset |
| */ |
| *res_ptr = (uint32_t)TFM_SUCCESS; |
| |
| break; |
| default: |
| *res_ptr = (uint32_t)TFM_ERROR_INVALID_PARAMETER; |
| } |
| } |
| |
| enum spm_err_t tfm_spm_db_init(void) |
| { |
| uint32_t i; |
| |
| /* This function initialises partition db */ |
| |
| for (i = 0; i < g_spm_partition_db.partition_count; i++) { |
| g_spm_partition_db.partitions[i].static_data = &static_data_list[i]; |
| g_spm_partition_db.partitions[i].platform_data_list = |
| platform_data_list_list[i]; |
| g_spm_partition_db.partitions[i].memory_data = &memory_data_list[i]; |
| } |
| g_spm_partition_db.is_init = 1; |
| |
| return SPM_ERR_OK; |
| } |