Jeenu Viswambharan | bc1a929 | 2017-02-16 14:55:15 +0000 | [diff] [blame] | 1 | /* |
Zelalem Aweke | f92c0cb | 2022-01-31 16:59:42 -0600 | [diff] [blame] | 2 | * Copyright (c) 2017-2022, ARM Limited and Contributors. All rights reserved. |
Jeenu Viswambharan | bc1a929 | 2017-02-16 14:55:15 +0000 | [diff] [blame] | 3 | * |
David Cunado | dedfde5 | 2017-05-11 17:30:06 +0100 | [diff] [blame] | 4 | * SPDX-License-Identifier: BSD-3-Clause |
Jeenu Viswambharan | bc1a929 | 2017-02-16 14:55:15 +0000 | [diff] [blame] | 5 | */ |
| 6 | |
Antonio Nino Diaz | e0f9063 | 2018-12-14 00:18:21 +0000 | [diff] [blame] | 7 | #include <stdbool.h> |
| 8 | #include <string.h> |
| 9 | |
Jeenu Viswambharan | bc1a929 | 2017-02-16 14:55:15 +0000 | [diff] [blame] | 10 | #include <arch_helpers.h> |
Jeenu Viswambharan | bc1a929 | 2017-02-16 14:55:15 +0000 | [diff] [blame] | 11 | #include <context.h> |
Antonio Nino Diaz | e0f9063 | 2018-12-14 00:18:21 +0000 | [diff] [blame] | 12 | #include <lib/el3_runtime/context_mgmt.h> |
| 13 | #include <lib/psci/psci.h> |
| 14 | #include <lib/utils.h> |
Antonio Nino Diaz | bd7b740 | 2019-01-25 14:30:04 +0000 | [diff] [blame] | 15 | #include <plat/arm/common/plat_arm.h> |
Antonio Nino Diaz | 3c817f4 | 2018-03-21 10:49:27 +0000 | [diff] [blame] | 16 | #include <smccc_helpers.h> |
Antonio Nino Diaz | e0f9063 | 2018-12-14 00:18:21 +0000 | [diff] [blame] | 17 | |
Jeenu Viswambharan | bc1a929 | 2017-02-16 14:55:15 +0000 | [diff] [blame] | 18 | /* |
| 19 | * Handle SMC from a lower exception level to switch its execution state |
| 20 | * (either from AArch64 to AArch32, or vice versa). |
| 21 | * |
| 22 | * smc_fid: |
| 23 | * SMC function ID - either ARM_SIP_SVC_STATE_SWITCH_64 or |
| 24 | * ARM_SIP_SVC_STATE_SWITCH_32. |
| 25 | * pc_hi, pc_lo: |
| 26 | * PC upon re-entry to the calling exception level; width dependent on the |
| 27 | * calling exception level. |
| 28 | * cookie_hi, cookie_lo: |
| 29 | * Opaque pointer pairs received from the caller to pass it back, upon |
| 30 | * re-entry. |
| 31 | * handle: |
| 32 | * Handle to saved context. |
| 33 | */ |
| 34 | int arm_execution_state_switch(unsigned int smc_fid, |
| 35 | uint32_t pc_hi, |
| 36 | uint32_t pc_lo, |
| 37 | uint32_t cookie_hi, |
| 38 | uint32_t cookie_lo, |
| 39 | void *handle) |
| 40 | { |
Jeenu Viswambharan | 210f0a8 | 2018-08-02 10:14:12 +0100 | [diff] [blame] | 41 | bool caller_64, thumb = false, from_el2; |
| 42 | unsigned int el, endianness; |
Jeenu Viswambharan | bc1a929 | 2017-02-16 14:55:15 +0000 | [diff] [blame] | 43 | u_register_t spsr, pc, scr, sctlr; |
| 44 | entry_point_info_t ep; |
| 45 | cpu_context_t *ctx = (cpu_context_t *) handle; |
| 46 | el3_state_t *el3_ctx = get_el3state_ctx(ctx); |
| 47 | |
Bence Szépkúti | 16362c6 | 2019-10-24 15:53:23 +0200 | [diff] [blame] | 48 | /* Validate supplied entry point */ |
| 49 | pc = (u_register_t) (((uint64_t) pc_hi << 32) | pc_lo); |
| 50 | if (arm_validate_ns_entrypoint(pc) != 0) |
| 51 | goto invalid_param; |
| 52 | |
Jeenu Viswambharan | bc1a929 | 2017-02-16 14:55:15 +0000 | [diff] [blame] | 53 | /* That the SMC originated from NS is already validated by the caller */ |
| 54 | |
| 55 | /* |
| 56 | * Disallow state switch if any of the secondaries have been brought up. |
| 57 | */ |
Jeenu Viswambharan | 210f0a8 | 2018-08-02 10:14:12 +0100 | [diff] [blame] | 58 | if (psci_secondaries_brought_up() != 0) |
Jeenu Viswambharan | bc1a929 | 2017-02-16 14:55:15 +0000 | [diff] [blame] | 59 | goto exec_denied; |
| 60 | |
| 61 | spsr = read_ctx_reg(el3_ctx, CTX_SPSR_EL3); |
| 62 | caller_64 = (GET_RW(spsr) == MODE_RW_64); |
| 63 | |
| 64 | if (caller_64) { |
| 65 | /* |
| 66 | * If the call originated from AArch64, expect 32-bit pointers when |
| 67 | * switching to AArch32. |
| 68 | */ |
Jeenu Viswambharan | 210f0a8 | 2018-08-02 10:14:12 +0100 | [diff] [blame] | 69 | if ((pc_hi != 0U) || (cookie_hi != 0U)) |
Jeenu Viswambharan | bc1a929 | 2017-02-16 14:55:15 +0000 | [diff] [blame] | 70 | goto invalid_param; |
| 71 | |
| 72 | pc = pc_lo; |
| 73 | |
| 74 | /* Instruction state when entering AArch32 */ |
Jeenu Viswambharan | 210f0a8 | 2018-08-02 10:14:12 +0100 | [diff] [blame] | 75 | thumb = (pc & 1U) != 0U; |
Jeenu Viswambharan | bc1a929 | 2017-02-16 14:55:15 +0000 | [diff] [blame] | 76 | } else { |
| 77 | /* Construct AArch64 PC */ |
| 78 | pc = (((u_register_t) pc_hi) << 32) | pc_lo; |
| 79 | } |
| 80 | |
| 81 | /* Make sure PC is 4-byte aligned, except for Thumb */ |
Jeenu Viswambharan | 210f0a8 | 2018-08-02 10:14:12 +0100 | [diff] [blame] | 82 | if (((pc & 0x3U) != 0U) && !thumb) |
Jeenu Viswambharan | bc1a929 | 2017-02-16 14:55:15 +0000 | [diff] [blame] | 83 | goto invalid_param; |
| 84 | |
| 85 | /* |
| 86 | * EL3 controls register width of the immediate lower EL only. Expect |
| 87 | * this request from EL2/Hyp unless: |
| 88 | * |
| 89 | * - EL2 is not implemented; |
| 90 | * - EL2 is implemented, but was disabled. This can be inferred from |
| 91 | * SCR_EL3.HCE. |
| 92 | */ |
| 93 | from_el2 = caller_64 ? (GET_EL(spsr) == MODE_EL2) : |
| 94 | (GET_M32(spsr) == MODE32_hyp); |
| 95 | scr = read_ctx_reg(el3_ctx, CTX_SCR_EL3); |
| 96 | if (!from_el2) { |
| 97 | /* The call is from NS privilege level other than HYP */ |
| 98 | |
| 99 | /* |
| 100 | * Disallow switching state if there's a Hypervisor in place; |
| 101 | * this request must be taken up with the Hypervisor instead. |
| 102 | */ |
Jeenu Viswambharan | 210f0a8 | 2018-08-02 10:14:12 +0100 | [diff] [blame] | 103 | if ((scr & SCR_HCE_BIT) != 0U) |
Jeenu Viswambharan | bc1a929 | 2017-02-16 14:55:15 +0000 | [diff] [blame] | 104 | goto exec_denied; |
| 105 | } |
| 106 | |
| 107 | /* |
| 108 | * Return to the caller using the same endianness. Extract |
| 109 | * endianness bit from the respective system control register |
| 110 | * directly. |
| 111 | */ |
| 112 | sctlr = from_el2 ? read_sctlr_el2() : read_sctlr_el1(); |
Jeenu Viswambharan | 210f0a8 | 2018-08-02 10:14:12 +0100 | [diff] [blame] | 113 | endianness = ((sctlr & SCTLR_EE_BIT) != 0U) ? 1U : 0U; |
Jeenu Viswambharan | bc1a929 | 2017-02-16 14:55:15 +0000 | [diff] [blame] | 114 | |
| 115 | /* Construct SPSR for the exception state we're about to switch to */ |
| 116 | if (caller_64) { |
Jeenu Viswambharan | 210f0a8 | 2018-08-02 10:14:12 +0100 | [diff] [blame] | 117 | unsigned long long impl; |
Jeenu Viswambharan | bc1a929 | 2017-02-16 14:55:15 +0000 | [diff] [blame] | 118 | |
| 119 | /* |
| 120 | * Switching from AArch64 to AArch32. Ensure this CPU implements |
| 121 | * the target EL in AArch32. |
| 122 | */ |
Antonio Nino Diaz | 864ca6f | 2018-10-31 15:25:35 +0000 | [diff] [blame] | 123 | impl = from_el2 ? el_implemented(2) : el_implemented(1); |
Jeenu Viswambharan | bc1a929 | 2017-02-16 14:55:15 +0000 | [diff] [blame] | 124 | if (impl != EL_IMPL_A64_A32) |
| 125 | goto exec_denied; |
| 126 | |
| 127 | /* Return to the equivalent AArch32 privilege level */ |
| 128 | el = from_el2 ? MODE32_hyp : MODE32_svc; |
Jeenu Viswambharan | 210f0a8 | 2018-08-02 10:14:12 +0100 | [diff] [blame] | 129 | spsr = SPSR_MODE32((u_register_t) el, |
| 130 | thumb ? SPSR_T_THUMB : SPSR_T_ARM, |
Jeenu Viswambharan | bc1a929 | 2017-02-16 14:55:15 +0000 | [diff] [blame] | 131 | endianness, DISABLE_ALL_EXCEPTIONS); |
| 132 | } else { |
| 133 | /* |
| 134 | * Switching from AArch32 to AArch64. Since it's not possible to |
| 135 | * implement an EL as AArch32-only (from which this call was |
| 136 | * raised), it's safe to assume AArch64 is also implemented. |
| 137 | */ |
| 138 | el = from_el2 ? MODE_EL2 : MODE_EL1; |
Jeenu Viswambharan | 210f0a8 | 2018-08-02 10:14:12 +0100 | [diff] [blame] | 139 | spsr = SPSR_64((u_register_t) el, MODE_SP_ELX, |
| 140 | DISABLE_ALL_EXCEPTIONS); |
Jeenu Viswambharan | bc1a929 | 2017-02-16 14:55:15 +0000 | [diff] [blame] | 141 | } |
| 142 | |
| 143 | /* |
| 144 | * Use the context management library to re-initialize the existing |
| 145 | * context with the execution state flipped. Since the library takes |
| 146 | * entry_point_info_t pointer as the argument, construct a dummy one |
| 147 | * with PC, state width, endianness, security etc. appropriately set. |
| 148 | * Other entries in the entry point structure are irrelevant for |
| 149 | * purpose. |
| 150 | */ |
| 151 | zeromem(&ep, sizeof(ep)); |
| 152 | ep.pc = pc; |
Jeenu Viswambharan | 210f0a8 | 2018-08-02 10:14:12 +0100 | [diff] [blame] | 153 | ep.spsr = (uint32_t) spsr; |
Jeenu Viswambharan | bc1a929 | 2017-02-16 14:55:15 +0000 | [diff] [blame] | 154 | SET_PARAM_HEAD(&ep, PARAM_EP, VERSION_1, |
Jeenu Viswambharan | 210f0a8 | 2018-08-02 10:14:12 +0100 | [diff] [blame] | 155 | ((unsigned int) ((endianness != 0U) ? EP_EE_BIG : |
| 156 | EP_EE_LITTLE) |
| 157 | | NON_SECURE | EP_ST_DISABLE)); |
Jeenu Viswambharan | bc1a929 | 2017-02-16 14:55:15 +0000 | [diff] [blame] | 158 | |
| 159 | /* |
| 160 | * Re-initialize the system register context, and exit EL3 as if for the |
| 161 | * first time. State switch is effectively a soft reset of the |
| 162 | * calling EL. |
| 163 | */ |
| 164 | cm_init_my_context(&ep); |
Zelalem Aweke | f92c0cb | 2022-01-31 16:59:42 -0600 | [diff] [blame] | 165 | cm_prepare_el3_exit_ns(); |
Jeenu Viswambharan | bc1a929 | 2017-02-16 14:55:15 +0000 | [diff] [blame] | 166 | |
| 167 | /* |
| 168 | * State switch success. The caller of SMC wouldn't see the SMC |
| 169 | * returning. Instead, execution starts at the supplied entry point, |
| 170 | * with context pointers populated in registers 0 and 1. |
| 171 | */ |
| 172 | SMC_RET2(handle, cookie_hi, cookie_lo); |
| 173 | |
| 174 | invalid_param: |
| 175 | SMC_RET1(handle, STATE_SW_E_PARAM); |
| 176 | |
| 177 | exec_denied: |
Jeenu Viswambharan | bc1a929 | 2017-02-16 14:55:15 +0000 | [diff] [blame] | 178 | /* State switch denied */ |
| 179 | SMC_RET1(handle, STATE_SW_E_DENIED); |
| 180 | } |