blob: 00b79038bd786fdd8059f154402c0d51a4160cf6 [file] [edit]
/*
* Copyright (c) 2013-2026, Arm Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <arch.h>
#include <arch_helpers.h>
#include <arch_features.h>
#include <bl31/interrupt_mgmt.h>
#include <bl31/sync_handle.h>
#include <bl31/ea_handle.h>
#include <common/debug.h>
#include <common/runtime_svc.h>
#include <context.h>
#include <lib/cpus/cpu_ops.h>
#include <plat/common/platform.h>
/*******************************************************************************
* The 'rt_svc_descs' array holds the runtime service descriptors exported by
* services by placing them in the 'rt_svc_descs' linker section.
* The 'rt_svc_descs_indices' array holds the index of a descriptor in the
* 'rt_svc_descs' array. When an SMC arrives, the OEN[29:24] bits and the call
* type[31] bit in the function id are combined to get an index into the
* 'rt_svc_descs_indices' array. This gives the index of the descriptor in the
* 'rt_svc_descs' array which contains the SMC handler.
******************************************************************************/
uint8_t rt_svc_descs_indices[MAX_RT_SVCS];
void __dead2 report_unhandled_exception(void);
#define RT_SVC_DECS_NUM ((RT_SVC_DESCS_END - RT_SVC_DESCS_START)\
/ sizeof(rt_svc_desc_t))
static bool get_handler_for_smc_fid(uint32_t smc_fid, rt_svc_handle_t *handler)
{
unsigned int index;
unsigned int idx;
const rt_svc_desc_t *rt_svc_descs;
idx = get_unique_oen_from_smc_fid(smc_fid);
assert(idx < MAX_RT_SVCS);
index = rt_svc_descs_indices[idx];
if (index >= RT_SVC_DECS_NUM)
return false;
rt_svc_descs = (rt_svc_desc_t *) RT_SVC_DESCS_START;
assert(handler != NULL);
*handler = rt_svc_descs[index].handle;
assert(*handler != NULL);
return true;
}
#ifdef __aarch64__
#include <lib/extensions/ras_arch.h>
#if FFH_SUPPORT
static void ea_proceed(uint32_t ea_reason, u_register_t esr_el3, u_register_t scr_el3, cpu_context_t *ctx)
{
/*
* If it is a double fault invoke platform handler. Double fault
* scenario would arise when platform is handling a fault in lower EL
* using plat_ea_handler() and another fault happens which would trap
* into EL3 as FFH_SUPPORT is enabled for the platform.
*/
el3_state_t *state = get_el3state_ctx(ctx);
if (read_ctx_reg(state, CTX_DOUBLE_FAULT_ESR) != 0) {
return plat_handle_double_fault(ea_reason, esr_el3);
}
/*
* Save CTX_DOUBLE_FAULT_ESR, so that if another fault happens in lower
* EL, we catch it as DoubleFault in next invocation of ea_proceed()
* along with preserving original ESR_EL3.
*/
write_ctx_reg(state, CTX_DOUBLE_FAULT_ESR, esr_el3);
/* Call platform External Abort handler. */
plat_ea_handler(ea_reason, esr_el3, NULL, ctx, scr_el3 & SCR_NS_BIT);
/* Clear Double Fault storage */
write_ctx_reg(state, CTX_DOUBLE_FAULT_ESR, 0);
}
/*
* This function handles SErrors from lower ELs.
*
* It delegates the handling of the EA to platform handler, and upon
* successfully handling the EA, exits EL3
*/
void handler_lower_el_async_ea(cpu_context_t *ctx)
{
el3_state_t *state = get_el3state_ctx(ctx);
u_register_t scr_el3 = read_ctx_reg(state, CTX_SCR_EL3);
u_register_t esr_el3 = read_esr_el3();
if (is_feat_ras_supported()) {
/* should only be invoked for SError */
assert(EXTRACT(ESR_EC, esr_el3) == EC_SERROR);
/*
* Check for Implementation Defined Syndrome. If so, skip
* checking Uncontainable error type from the syndrome as the
* format is unknown.
*/
if ((esr_el3 & SERROR_IDS_BIT) != 0) {
/* AET only valid when DFSC is 0x11. Route to platform fatal
* error handler if it is an uncontainable error type */
if (EXTRACT(EABORT_DFSC, esr_el3) == DFSC_SERROR &&
EXTRACT(EABORT_AET, esr_el3) == ERROR_STATUS_UET_UC) {
return plat_handle_uncontainable_ea();
}
}
}
return ea_proceed(ERROR_EA_ASYNC, esr_el3, scr_el3, ctx);
}
#endif /* FFH_SUPPORT */
/*
* This function handles FIQ or IRQ interrupts i.e. EL3, S-EL1 and NS
* interrupts.
*/
void handler_interrupt_exception(cpu_context_t *ctx)
{
el3_state_t *state = get_el3state_ctx(ctx);
u_register_t scr_el3 = read_ctx_reg(state, CTX_SCR_EL3);
/*
* Find out whether this is a valid interrupt type.
* If the interrupt controller reports a spurious interrupt then return
* to where we came from.
*/
uint32_t type = plat_ic_get_pending_interrupt_type();
if (type == INTR_TYPE_INVAL) {
return;
}
/*
* Get the registered handler for this interrupt type.
* A NULL return value could be 'cause of the following conditions:
*
* a. An interrupt of a type was routed correctly but a handler for its
* type was not registered.
*
* b. An interrupt of a type was not routed correctly so a handler for
* its type was not registered.
*
* c. An interrupt of a type was routed correctly to EL3, but was
* deasserted before its pending state could be read. Another
* interrupt of a different type pended at the same time and its
* type was reported as pending instead. However, a handler for this
* type was not registered.
*
* a. and b. can only happen due to a programming error. The
* occurrence of c. could be beyond the control of Trusted Firmware.
* It makes sense to return from this exception instead of reporting an
* error.
*/
interrupt_type_handler_t handler = get_interrupt_type_handler(type);
if (handler == NULL) {
return;
}
handler(INTR_ID_UNAVAILABLE, scr_el3 & SCR_NS_BIT, ctx, NULL);
}
static void smc_unknown(cpu_context_t *ctx)
{
/*
* Unknown SMC call. Populate return value with SMC_UNK and call
* el3_exit() which will restore the remaining architectural state
* i.e., SYS, GP and PAuth registers(if any) prior to issuing the ERET
* to the desired lower EL.
*/
write_ctx_reg(get_gpregs_ctx(ctx), CTX_GPREG_X0, SMC_UNK);
}
static u_register_t get_flags(uint32_t smc_fid, u_register_t scr_el3)
{
u_register_t flags = 0;
/* Copy SCR_EL3.NS bit to the flag to indicate caller's security */
flags |= scr_el3 & SCR_NS_BIT;
#if ENABLE_RMM
/* Copy SCR_EL3.NSE bit to the flag to indicate caller's security Shift
* copied SCR_EL3.NSE bit by 5 to create space for SCR_EL3.NS bit. Bit 5
* of the flag corresponds to the SCR_EL3.NSE bit.
*/
flags |= ((scr_el3 & SCR_NSE_BIT) >> SCR_NSE_SHIFT) << 5;
#endif /* ENABLE_RMM */
/*
* Per SMCCCv1.3 a caller can set the SVE hint bit in the SMC FID passed
* through x0. Copy the SVE hint bit to flags and mask the bit in
* smc_fid passed to the standard service dispatcher. A
* service/dispatcher can retrieve the SVE hint bit state from flags
* using the appropriate helper.
*/
flags |= smc_fid & MASK(FUNCID_SVE_HINT);
return flags;
}
static void sync_handler(cpu_context_t *ctx, uint32_t smc_fid, u_register_t scr_el3)
{
rt_svc_handle_t handler;
/*
* Per SMCCC documentation, bits [23:17] must be zero for Fast SMCs.
* Other values are reserved for future use. Ensure that these bits are
* zeroes, if not report as unknown SMC.
*/
if (EXTRACT(FUNCID_TYPE, smc_fid) == SMC_TYPE_FAST &&
EXTRACT(FUNCID_FC_RESERVED, smc_fid) != 0) {
return smc_unknown(ctx);
}
smc_fid &= ~MASK(FUNCID_SVE_HINT);
/* Get the descriptor using the index */
if (!get_handler_for_smc_fid(smc_fid, &handler)) {
return smc_unknown(ctx);
}
u_register_t x1, x2, x3, x4;
get_smc_params_from_ctx(ctx, x1, x2, x3, x4);
handler(smc_fid, x1, x2, x3, x4, NULL, ctx, get_flags(smc_fid, scr_el3));
}
void handler_sync_exception(cpu_context_t *ctx)
{
uint32_t smc_fid = read_ctx_reg(get_gpregs_ctx(ctx), CTX_GPREG_X0);
u_register_t esr_el3 = read_esr_el3();
u_register_t exc_class = EXTRACT(ESR_EC, esr_el3);
el3_state_t *state = get_el3state_ctx(ctx);
u_register_t scr_el3 = read_ctx_reg(state, CTX_SCR_EL3);
if (exc_class == EC_AARCH64_SMC) {
return sync_handler(ctx, smc_fid, scr_el3);
#if CTX_INCLUDE_AARCH32_REGS
} else if (exc_class == EC_AARCH32_SMC) {
if (EXTRACT(FUNCID_CC, smc_fid) != 0) {
return smc_unknown(ctx);
}
return sync_handler(ctx, smc_fid, scr_el3);
#endif /* CTX_INCLUDE_AARCH32_REGS */
} else if (exc_class == EC_AARCH64_SYS) {
int ret = handle_sysreg_trap(esr_el3, ctx, get_flags(smc_fid, scr_el3));
if (ret == TRAP_RET_CONTINUE) {
/* advance the PC to continue after the instruction */
write_ctx_reg(state, CTX_ELR_EL3, read_ctx_reg(state, CTX_ELR_EL3) + 4);
return;
} else if (ret == TRAP_RET_REPEAT) {
/* continue at the same instruction */
return;
}
#if FFH_SUPPORT
/* If FFH Support then try to handle lower EL EA exceptions. */
} else if ((exc_class == EC_IABORT_LOWER_EL || exc_class == EC_DABORT_LOWER_EL)
&& ((scr_el3 & SCR_EA_BIT) != 0UL)) {
/*
* Check for Uncontainable error type. If so, route to the
* platform fatal error handler rather than the generic EA one.
*/
if (is_feat_ras_supported() &&
(EXTRACT(EABORT_SET, esr_el3) == ERROR_STATUS_SET_UC ||
EXTRACT(EABORT_DFSC, esr_el3) == SYNC_EA_FSC)) {
return plat_handle_uncontainable_ea();
}
/* Setup exception class and syndrome arguments for platform handler */
ea_proceed(ERROR_EA_SYNC, esr_el3, scr_el3, ctx);
/* Platforms that support SDEI should use that for lower EL
* communication. This allows for SDEI to be omitted. */
#if !SDEI_SUPPORT
inject_sync_ea64(state, esr_el3);
#endif /* !SDEI_SUPPORT */
return;
#endif /* FFH_SUPPORT */
}
#if CTX_INCLUDE_AARCH32_REGS
/* unhandled trap, UNDEF injection not provided for lower EL in AArch32 mode. */
if (read_spsr_el3() & MASK(SPSR_M)) {
ERROR("Trapped an instruction from AArch32 %s mode\n",
get_mode_str((unsigned int)GET_M32(read_spsr_el3())));
ERROR("at address 0x%lx, reason 0x%lx\n", read_elr_el3(), esr_el3);
report_unhandled_exception();
}
#endif /* CTX_INCLUDE_AARCH32_REGS */
/*
* UNDEF injection by default for AArch64 on any otherwise unhandled
* trap. This is expected to include:
* 0b000111 - SME/SVE instructions and registers
* 0b001001 - PAUTH instructions
* 0b001010 - FEAT_LS64/catch all instructions
* 0b010100 - MSRR/MRSS
* 0b011000 - MSR/MRS
* 0b011001 - SVE instructions and registers
* 0b011101 - SME instructions and illegal ZA execution
* 0b101100 - floating point
*
* Exception syndromes not listed are either unreachable or the erronous
* UNDEF injection is accepted as a better alternative to a panic at EL3.
*/
inject_undef64(ctx);
}
#endif /* __aarch64__ */
/*******************************************************************************
* Function to invoke the registered `handle` corresponding to the smc_fid in
* AArch32 mode.
******************************************************************************/
uintptr_t handle_runtime_svc(uint32_t smc_fid,
void *cookie,
void *handle,
unsigned int flags)
{
u_register_t x1, x2, x3, x4;
rt_svc_handle_t handler;
assert(handle != NULL);
if (!get_handler_for_smc_fid(smc_fid, &handler)) {
SMC_RET1(handle, SMC_UNK);
}
get_smc_params_from_ctx(handle, x1, x2, x3, x4);
return handler(smc_fid, x1, x2, x3, x4, cookie, handle, flags);
}
/*******************************************************************************
* Simple routine to sanity check a runtime service descriptor before using it
******************************************************************************/
static int32_t validate_rt_svc_desc(const rt_svc_desc_t *desc)
{
if (desc == NULL) {
return -EINVAL;
}
if (desc->start_oen > desc->end_oen) {
return -EINVAL;
}
if (desc->end_oen >= OEN_LIMIT) {
return -EINVAL;
}
if ((desc->call_type != SMC_TYPE_FAST) &&
(desc->call_type != SMC_TYPE_YIELD)) {
return -EINVAL;
}
/* A runtime service having no init or handle function doesn't make sense */
if ((desc->init == NULL) && (desc->handle == NULL)) {
return -EINVAL;
}
return 0;
}
/*******************************************************************************
* This function calls the initialisation routine in the descriptor exported by
* a runtime service. Once a descriptor has been validated, its start & end
* owning entity numbers and the call type are combined to form a unique oen.
* The unique oen is used as an index into the 'rt_svc_descs_indices' array.
* The index of the runtime service descriptor is stored at this index.
******************************************************************************/
void __init runtime_svc_init(void)
{
int rc = 0;
uint8_t index, start_idx, end_idx;
rt_svc_desc_t *rt_svc_descs;
/* Assert the number of descriptors detected are less than maximum indices */
assert((RT_SVC_DESCS_END >= RT_SVC_DESCS_START) &&
(RT_SVC_DECS_NUM < MAX_RT_SVCS));
/* If no runtime services are implemented then simply bail out */
if (RT_SVC_DECS_NUM == 0U) {
return;
}
/* Initialise internal variables to invalid state */
(void)memset(rt_svc_descs_indices, -1, sizeof(rt_svc_descs_indices));
rt_svc_descs = (rt_svc_desc_t *) RT_SVC_DESCS_START;
for (index = 0U; index < RT_SVC_DECS_NUM; index++) {
rt_svc_desc_t *service = &rt_svc_descs[index];
/*
* An invalid descriptor is an error condition since it is
* difficult to predict the system behaviour in the absence
* of this service.
*/
rc = validate_rt_svc_desc(service);
if (rc != 0) {
ERROR("Invalid runtime service descriptor %p\n",
(void *) service);
panic();
}
/*
* The runtime service may have separate rt_svc_desc_t
* for its fast smc and yielding smc. Since the service itself
* need to be initialized only once, only one of them will have
* an initialisation routine defined. Call the initialisation
* routine for this runtime service, if it is defined.
*/
if (service->init != NULL) {
rc = service->init();
if (rc != 0) {
ERROR("Error initializing runtime service %s\n",
service->name);
continue;
}
}
/*
* Fill the indices corresponding to the start and end
* owning entity numbers with the index of the
* descriptor which will handle the SMCs for this owning
* entity range.
*/
start_idx = (uint8_t)get_unique_oen(service->start_oen,
service->call_type);
end_idx = (uint8_t)get_unique_oen(service->end_oen,
service->call_type);
assert(start_idx <= end_idx);
assert(end_idx < MAX_RT_SVCS);
for (; start_idx <= end_idx; start_idx++) {
rt_svc_descs_indices[start_idx] = index;
}
}
}