blob: 8aa2a88aa1b3a4ef7b7e85e82e7fc9b091981f73 [file] [log] [blame]
/*
* Copyright (c) 2018-2019, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <arch_helpers.h>
#include <common/debug.h>
#include <common/runtime_svc.h>
#include <lib/el3_runtime/context_mgmt.h>
#include <lib/smccc.h>
#include <lib/utils.h>
#include <plat/common/platform.h>
#include <services/sprt_svc.h>
#include <smccc_helpers.h>
#include "spm_private.h"
/*******************************************************************************
* Functions to manipulate memory regions
******************************************************************************/
/*
* 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 perm = attributes & SPRT_MEMORY_PERM_ATTR_MASK;
if (perm == SPRT_MEMORY_PERM_ATTR_RW) {
return MT_RW | MT_EXECUTE_NEVER | MT_USER;
} else if (perm == SPRT_MEMORY_PERM_ATTR_RO) {
return MT_RO | MT_EXECUTE_NEVER | MT_USER;
} else if (perm == SPRT_MEMORY_PERM_ATTR_RO_EXEC) {
return MT_RO | MT_USER;
} else {
return UINT_MAX;
}
}
/*
* This function converts attributes from the Trusted Firmware format into the
* SMC interface format.
*/
static unsigned int mmap_attr_to_smc_attr(unsigned int attr)
{
unsigned int perm;
/* No access from EL0. */
if ((attr & MT_USER) == 0U)
return UINT_MAX;
if ((attr & MT_RW) != 0) {
assert(MT_TYPE(attr) != MT_DEVICE);
perm = SPRT_MEMORY_PERM_ATTR_RW;
} else {
if ((attr & MT_EXECUTE_NEVER) != 0U) {
perm = SPRT_MEMORY_PERM_ATTR_RO;
} else {
perm = SPRT_MEMORY_PERM_ATTR_RO_EXEC;
}
}
return perm << SPRT_MEMORY_PERM_ATTR_SHIFT;
}
static int32_t sprt_memory_perm_attr_get(sp_context_t *sp_ctx, uintptr_t base_va)
{
uint32_t attributes;
spin_lock(&(sp_ctx->xlat_ctx_lock));
int ret = xlat_get_mem_attributes_ctx(sp_ctx->xlat_ctx_handle,
base_va, &attributes);
spin_unlock(&(sp_ctx->xlat_ctx_lock));
/* Convert error codes of xlat_get_mem_attributes_ctx() into SPM. */
assert((ret == 0) || (ret == -EINVAL));
if (ret != 0)
return SPRT_INVALID_PARAMETER;
unsigned int perm = mmap_attr_to_smc_attr(attributes);
if (perm == UINT_MAX)
return SPRT_INVALID_PARAMETER;
return SPRT_SUCCESS | perm;
}
static int32_t sprt_memory_perm_attr_set(sp_context_t *sp_ctx,
u_register_t page_address, u_register_t pages_count,
u_register_t smc_attributes)
{
int ret;
uintptr_t base_va = (uintptr_t) page_address;
size_t size = pages_count * PAGE_SIZE;
VERBOSE(" Start address : 0x%lx\n", base_va);
VERBOSE(" Number of pages: %i (%zi bytes)\n", (int) pages_count, size);
VERBOSE(" Attributes : 0x%lx\n", smc_attributes);
uint32_t mmap_attr = smc_attr_to_mmap_attr(smc_attributes);
if (mmap_attr == UINT_MAX) {
WARN("%s: Invalid memory attributes: 0x%lx\n", __func__,
smc_attributes);
return SPRT_INVALID_PARAMETER;
}
/*
* Perform some checks before actually trying to change the memory
* attributes.
*/
spin_lock(&(sp_ctx->xlat_ctx_lock));
uint32_t attributes;
ret = xlat_get_mem_attributes_ctx(sp_ctx->xlat_ctx_handle,
base_va, &attributes);
if (ret != 0) {
spin_unlock(&(sp_ctx->xlat_ctx_lock));
return SPRT_INVALID_PARAMETER;
}
if ((attributes & MT_USER) == 0U) {
/* Prohibit changing attributes of S-EL1 regions */
spin_unlock(&(sp_ctx->xlat_ctx_lock));
return SPRT_INVALID_PARAMETER;
}
ret = xlat_change_mem_attributes_ctx(sp_ctx->xlat_ctx_handle,
base_va, size, mmap_attr);
spin_unlock(&(sp_ctx->xlat_ctx_lock));
/* Convert error codes of xlat_change_mem_attributes_ctx() into SPM. */
assert((ret == 0) || (ret == -EINVAL));
return (ret == 0) ? SPRT_SUCCESS : SPRT_INVALID_PARAMETER;
}
/*******************************************************************************
* This function handles all SMCs in the range reserved for SPRT.
******************************************************************************/
static uintptr_t sprt_smc_handler(uint32_t smc_fid, u_register_t x1,
u_register_t x2, u_register_t x3,
u_register_t x4, void *cookie, void *handle,
u_register_t flags)
{
/* SPRT only supported from the Secure world */
if (is_caller_non_secure(flags) == SMC_FROM_NON_SECURE) {
SMC_RET1(handle, SMC_UNK);
}
assert(handle == cm_get_context(SECURE));
/*
* Only S-EL0 partitions are supported for now. Make the next ERET into
* the partition jump directly to S-EL0 instead of S-EL1.
*/
cm_set_elr_spsr_el3(SECURE, read_elr_el1(), read_spsr_el1());
switch (smc_fid) {
case SPRT_VERSION:
SMC_RET1(handle, SPRT_VERSION_COMPILED);
case SPRT_PUT_RESPONSE_AARCH64:
/*
* Registers x1-x3 aren't saved by default to the context,
* but they are needed after spm_sp_synchronous_exit() because
* they hold return values.
*/
SMC_SET_GP(handle, CTX_GPREG_X1, x1);
SMC_SET_GP(handle, CTX_GPREG_X2, x2);
SMC_SET_GP(handle, CTX_GPREG_X3, x3);
spm_sp_synchronous_exit(SPRT_PUT_RESPONSE_AARCH64);
case SPRT_YIELD_AARCH64:
spm_sp_synchronous_exit(SPRT_YIELD_AARCH64);
case SPRT_MEMORY_PERM_ATTR_GET_AARCH64:
{
/* Get context of the SP in use by this CPU. */
unsigned int linear_id = plat_my_core_pos();
sp_context_t *sp_ctx = spm_cpu_get_sp_ctx(linear_id);
SMC_RET1(handle, sprt_memory_perm_attr_get(sp_ctx, x1));
}
case SPRT_MEMORY_PERM_ATTR_SET_AARCH64:
{
/* Get context of the SP in use by this CPU. */
unsigned int linear_id = plat_my_core_pos();
sp_context_t *sp_ctx = spm_cpu_get_sp_ctx(linear_id);
SMC_RET1(handle, sprt_memory_perm_attr_set(sp_ctx, x1, x2, x3));
}
default:
break;
}
WARN("SPRT: Unsupported call 0x%08x\n", smc_fid);
SMC_RET1(handle, SPRT_NOT_SUPPORTED);
}
DECLARE_RT_SVC(
sprt_handler,
OEN_SPRT_START,
OEN_SPRT_END,
SMC_TYPE_FAST,
NULL,
sprt_smc_handler
);