blob: a3752e5cc8bf2068b6f686a4a47cc12dd4ea63a7 [file] [log] [blame]
/*
* SPDX-License-Identifier: BSD-3-Clause
* SPDX-FileCopyrightText: Copyright TF-RMM Contributors.
*/
#include <granule.h>
#include <psci.h>
#include <realm.h>
#include <rec.h>
#include <rsi-handler.h>
#include <smc-rmi.h>
#include <smc.h>
#include <stdint.h>
/*
* Copy @count GPRs from @rec to @rec_exit.
* The remaining @rec_exit.gprs[] values are zero filled.
*/
static void forward_args_to_host(unsigned int count, struct rec *rec,
struct rmi_rec_exit *rec_exit)
{
unsigned int i;
assert(count <= 4U);
for (i = 0U; i < count; ++i) {
rec_exit->gprs[i] = rec->regs[i];
}
for (i = count; i < REC_EXIT_NR_GPRS; ++i) {
rec_exit->gprs[i] = 0UL;
}
}
static void psci_version(struct rsi_result *res)
{
const unsigned long version_1_1 = (1UL << 16) | 1UL;
res->action = UPDATE_REC_RETURN_TO_REALM;
res->smc_res.x[0] = version_1_1;
}
static void psci_cpu_suspend(struct rec *rec, struct rmi_rec_exit *rec_exit,
struct rsi_result *res)
{
res->action = UPDATE_REC_EXIT_TO_HOST;
/*
* We treat all target power states as suspend requests,
* so all we need to do is forward the FID to the NS hypervisor,
* and we can ignore all the parameters.
*/
forward_args_to_host(1U, rec, rec_exit);
/*
* The exit to the Host is just a notification; the Host does not need
* to complete a PSCI request before the next call to RMI_REC_ENTER.
* We therefore update the REC immediately with the results of the PSCI
* command.
*/
res->smc_res.x[0] = PSCI_RETURN_SUCCESS;
}
static void psci_cpu_off(struct rec *rec, struct rmi_rec_exit *rec_exit,
struct rsi_result *res)
{
res->action = UPDATE_REC_EXIT_TO_HOST;
/*
* It should be fine to set this flag without holding a lock on the
* REC or without explicit memory barriers or ordering semantics
* operations, because we already ensure that a REC can only be in an
* executing state once at any given time, and we're in this execution
* context already, and we will be holding a reference count on the
* REC at this point, which will be dropped and re-evaluated with
* proper barriers before any CPU can evaluate the runnable field
* after this change.
*/
rec->runnable = false;
/* Notify the Host, passing the FID only. */
forward_args_to_host(1U, rec, rec_exit);
/*
* The exit to the Host is just a notification; the Host does not need
* to complete a PSCI request before the next call to RMI_REC_ENTER.
* We therefore update the REC immediately with the results of the PSCI
* command.
*/
res->smc_res.x[0] = PSCI_RETURN_SUCCESS;
}
static void psci_reset_rec(struct rec *rec, unsigned long caller_sctlr_el1)
{
/* Set execution level to EL1 (AArch64) and mask exceptions */
rec->pstate = SPSR_EL2_MODE_EL1h |
SPSR_EL2_nRW_AARCH64 |
SPSR_EL2_F_BIT |
SPSR_EL2_I_BIT |
SPSR_EL2_A_BIT |
SPSR_EL2_D_BIT;
/* Disable stage 1 MMU and caches */
rec->sysregs.sctlr_el1 = SCTLR_EL1_FLAGS;
/* Set the endianness of the target to that of the caller */
rec->sysregs.sctlr_el1 |= caller_sctlr_el1 & SCTLR_ELx_EE_BIT;
}
static unsigned long rd_map_read_rec_count(struct granule *g_rd)
{
unsigned long rec_count;
struct rd *rd = granule_map(g_rd, SLOT_RD);
assert(rd != NULL);
rec_count = get_rd_rec_count_unlocked(rd);
buffer_unmap(rd);
return rec_count;
}
static void psci_cpu_on(struct rec *rec, struct rmi_rec_exit *rec_exit,
struct rsi_result *res)
{
unsigned long target_cpu = rec->regs[1];
unsigned long entry_point_address = rec->regs[2];
unsigned long target_rec_idx;
res->action = UPDATE_REC_RETURN_TO_REALM;
/* Check that entry_point_address is a Protected Realm Address */
if (!addr_in_rec_par(rec, entry_point_address)) {
res->smc_res.x[0] = PSCI_RETURN_INVALID_ADDRESS;
return;
}
/* Get REC index from MPIDR */
target_rec_idx = mpidr_to_rec_idx(target_cpu);
/*
* Check that the target_cpu is a valid value.
* Note that the RMM enforces that the REC are created with
* consecutively increasing indexes starting from zero.
*/
if (target_rec_idx >= rd_map_read_rec_count(rec->realm_info.g_rd)) {
res->smc_res.x[0] = PSCI_RETURN_INVALID_PARAMS;
return;
}
/* Check if we're trying to turn ourselves on */
if (target_rec_idx == rec->rec_idx) {
res->smc_res.x[0] = PSCI_RETURN_ALREADY_ON;
return;
}
/* Record that a PSCI request is outstanding */
rec->psci_info.pending = true;
/*
* Notify the Host, passing the FID and MPIDR arguments.
* Leave REC registers unchanged; these will be read and updated by
* psci_complete_request.
*/
forward_args_to_host(2U, rec, rec_exit);
res->action = EXIT_TO_HOST;
}
static void psci_affinity_info(struct rec *rec, struct rmi_rec_exit *rec_exit,
struct rsi_result *res)
{
unsigned long target_affinity = rec->regs[1];
unsigned long lowest_affinity_level = rec->regs[2];
unsigned long target_rec_idx;
res->action = UPDATE_REC_RETURN_TO_REALM;
if (lowest_affinity_level != 0UL) {
res->smc_res.x[0] = PSCI_RETURN_INVALID_PARAMS;
return;
}
/* Get REC index from MPIDR */
target_rec_idx = mpidr_to_rec_idx(target_affinity);
/*
* Check that the target_affinity is a valid value.
* Note that the RMM enforces that the REC are created with
* consecutively increasing indexes starting from zero.
*/
if (target_rec_idx >= rd_map_read_rec_count(rec->realm_info.g_rd)) {
res->smc_res.x[0] = PSCI_RETURN_INVALID_PARAMS;
return;
}
/* Check if the vCPU targets itself */
if (target_rec_idx == rec->rec_idx) {
res->smc_res.x[0] = PSCI_AFFINITY_INFO_ON;
return;
}
/* Record that a PSCI request is outstanding */
rec->psci_info.pending = true;
/*
* Notify the Host, passing the FID and MPIDR arguments.
* Leave REC registers unchanged; these will be read and updated
* by psci_complete_request.
*/
forward_args_to_host(2U, rec, rec_exit);
res->action = EXIT_TO_HOST;
}
/*
* Turning a system off or requesting a reboot of a realm is enforced by the
* RMM by preventing execution of a REC after the function has run. Reboot
* functionality must be provided by the host hypervisor by creating a new
* Realm with associated attestation, measurement etc.
*/
static void system_off_reboot(struct rec *rec)
{
struct rd *rd;
struct granule *g_rd = rec->realm_info.g_rd;
/*
* The RECs (and, consequently, the PSCI calls) run without any
* RMM lock held. Therefore, we cannot cause a deadlock when we acquire
* the rd lock here before we set the Realm's new state.
*/
granule_lock(g_rd, GRANULE_STATE_RD);
rd = granule_map(rec->realm_info.g_rd, SLOT_RD);
assert(rd != NULL);
set_rd_state(rd, REALM_STATE_SYSTEM_OFF);
buffer_unmap(rd);
granule_unlock(g_rd);
/* TODO: Invalidate all stage 2 entris to ensure REC exits */
}
static void psci_system_off_reset(struct rec *rec,
struct rmi_rec_exit *rec_exit,
struct rsi_result *res)
{
system_off_reboot(rec);
/* Notify the Host, passing the FID only */
forward_args_to_host(1U, rec, rec_exit);
res->action = EXIT_TO_HOST;
}
static void psci_features(struct rec *rec, struct rsi_result *res)
{
unsigned int psci_func_id = (unsigned int)rec->regs[1];
switch (psci_func_id) {
case SMC32_PSCI_CPU_SUSPEND:
case SMC64_PSCI_CPU_SUSPEND:
case SMC32_PSCI_CPU_OFF:
case SMC32_PSCI_CPU_ON:
case SMC64_PSCI_CPU_ON:
case SMC32_PSCI_AFFINITY_INFO:
case SMC64_PSCI_AFFINITY_INFO:
case SMC32_PSCI_SYSTEM_OFF:
case SMC32_PSCI_SYSTEM_RESET:
case SMC32_PSCI_FEATURES:
case SMCCC_VERSION:
res->smc_res.x[0] = PSCI_RETURN_SUCCESS;
break;
default:
res->smc_res.x[0] = PSCI_RETURN_NOT_SUPPORTED;
}
res->action = UPDATE_REC_RETURN_TO_REALM;
}
void handle_psci(struct rec *rec,
struct rmi_rec_exit *rec_exit,
struct rsi_result *res)
{
unsigned int function_id = (unsigned int)rec->regs[0];
switch (function_id) {
case SMC32_PSCI_VERSION:
psci_version(res);
break;
case SMC32_PSCI_CPU_SUSPEND:
case SMC64_PSCI_CPU_SUSPEND:
psci_cpu_suspend(rec, rec_exit, res);
break;
case SMC32_PSCI_CPU_OFF:
psci_cpu_off(rec, rec_exit, res);
break;
case SMC32_PSCI_CPU_ON:
case SMC64_PSCI_CPU_ON:
psci_cpu_on(rec, rec_exit, res);
break;
case SMC32_PSCI_AFFINITY_INFO:
case SMC64_PSCI_AFFINITY_INFO:
psci_affinity_info(rec, rec_exit, res);
break;
case SMC32_PSCI_SYSTEM_OFF:
case SMC32_PSCI_SYSTEM_RESET:
psci_system_off_reset(rec, rec_exit, res);
break;
case SMC32_PSCI_FEATURES:
psci_features(rec, res);
break;
default:
res->action = UPDATE_REC_RETURN_TO_REALM;
res->smc_res.x[0] = PSCI_RETURN_NOT_SUPPORTED;
break;
}
if (((unsigned int)res->action & FLAG_EXIT_TO_HOST) != 0U) {
rec_exit->exit_reason = RMI_EXIT_PSCI;
}
}
/*
* In the following two functions, it is only safe to access the runnable field
* on the target_rec once the target_rec is no longer running on another PE and
* all writes performed by the other PE as part of smc_rec_enter is also
* guaranteed to be observed here, which we know when we read a zero refcount
* on the target rec using acquire semantics paired with the release semantics
* on the reference count in smc_rec_enter. If we observe a non-zero refcount
* it simply means that the target_rec is running and we can return the
* corresponding value.
*/
static unsigned long complete_psci_cpu_on(struct rec *target_rec,
unsigned long entry_point_address,
unsigned long caller_sctlr_el1)
{
if ((granule_refcount_read_acquire(target_rec->g_rec) != 0UL) ||
target_rec->runnable) {
return PSCI_RETURN_ALREADY_ON;
}
psci_reset_rec(target_rec, caller_sctlr_el1);
target_rec->pc = entry_point_address;
target_rec->runnable = true;
return PSCI_RETURN_SUCCESS;
}
static unsigned long complete_psci_affinity_info(struct rec *target_rec)
{
if ((granule_refcount_read_acquire(target_rec->g_rec) != 0UL) ||
target_rec->runnable) {
return PSCI_AFFINITY_INFO_ON;
}
return PSCI_AFFINITY_INFO_OFF;
}
unsigned long psci_complete_request(struct rec *calling_rec,
struct rec *target_rec)
{
unsigned long ret = PSCI_RETURN_NOT_SUPPORTED;
unsigned long mpidr = calling_rec->regs[1];
if (!calling_rec->psci_info.pending) {
return RMI_ERROR_INPUT;
}
if (calling_rec->realm_info.g_rd != target_rec->realm_info.g_rd) {
return RMI_ERROR_INPUT;
}
if (mpidr_to_rec_idx(mpidr) != target_rec->rec_idx) {
return RMI_ERROR_INPUT;
}
switch (calling_rec->regs[0]) {
case SMC32_PSCI_CPU_ON:
case SMC64_PSCI_CPU_ON:
ret = complete_psci_cpu_on(target_rec,
calling_rec->regs[2],
calling_rec->sysregs.sctlr_el1);
break;
case SMC32_PSCI_AFFINITY_INFO:
case SMC64_PSCI_AFFINITY_INFO:
ret = complete_psci_affinity_info(target_rec);
break;
default:
assert(false);
}
calling_rec->regs[0] = ret;
calling_rec->regs[1] = 0;
calling_rec->regs[2] = 0;
calling_rec->regs[3] = 0;
calling_rec->psci_info.pending = false;
return RMI_SUCCESS;
}