feat(spmc): add support for FF-A power mgmt. messages in the EL3 SPMC

This patch adds support for forwarding the following PSCI messages
received by the SPMC at EL3 to the S-EL1 SP if the SP has indicated
that it wishes to receive the appropriate message via its manifest.

1. A PSCI CPU_OFF message in response to a cpu hot unplug request
   from the OS.
2. A message to indicate warm boot of a cpu in response to a cpu
   hot plug request from the OS.
3. A PSCI CPU_SUSPEND message in response to a cpu idle event
   initiated from the OS.
4. A message to indicate warm boot of a cpu from a shallow power
   state in response to a cpu resume power event.

This patch also implements the FFA_SECONDARY_EP_REGISTER function to
enable the SP specify its secondary entrypoint.

Signed-off-by: Achin Gupta <achin.gupta@arm.com>
Signed-off-by: Marc Bonnici <marc.bonnici@arm.com>
Change-Id: I375d0655b2c6fc27445facc39213d1d0678557f4
This commit is contained in:
Marc Bonnici 2022-04-12 17:18:13 +01:00
parent f0143004e5
commit 59bd2ad83c
9 changed files with 465 additions and 16 deletions

View File

@ -151,6 +151,16 @@ Partition Properties
- List of <u32> tuples, identifying the IDs this partition is acting as
proxy for.
- power-management-messages
- value type: <u32>
- Specifies which power management messages a partition subscribes to.
A set bit means the partition should be informed of the power event, clear
bit - should not be informed of event:
- Bit[0]: CPU_OFF
- Bit[1]: CPU_SUSPEND
- Bit[2]: CPU_SUSPEND_RESUME
Memory Regions
--------------

View File

@ -56,6 +56,19 @@
(((blk) & FFA_MSG_SEND_ATTRS_BLK_MASK) \
<< FFA_MSG_SEND_ATTRS_BLK_SHIFT)
/* Defines for FF-A framework messages exchanged using direct messages. */
#define FFA_FWK_MSG_BIT BIT(31)
#define FFA_FWK_MSG_MASK 0xFF
#define FFA_FWK_MSG_PSCI U(0x0)
/* Defines for FF-A power management messages framework messages. */
#define FFA_PM_MSG_WB_REQ U(0x1) /* Warm boot request. */
#define FFA_PM_MSG_PM_RESP U(0x2) /* Response to PSCI or warmboot req. */
/* FF-A warm boot types. */
#define FFA_WB_TYPE_S2RAM 0x0
#define FFA_WB_TYPE_NOTS2RAM 0x1
/* Get FFA fastcall std FID from function number */
#define FFA_FID(smc_cc, func_num) \
((SMC_TYPE_FAST << FUNCID_TYPE_SHIFT) | \

View File

@ -33,10 +33,6 @@
/* Align with Hafnium implementation */
#define INV_SP_ID 0x7FFF
/* FF-A warm boot types. */
#define FFA_WB_TYPE_S2RAM 0
#define FFA_WB_TYPE_NOTS2RAM 1
/* FF-A Related helper macros. */
#define FFA_ID_MASK U(0xFFFF)
#define FFA_PARTITION_ID_SHIFT U(16)
@ -53,6 +49,13 @@
/* Ensure that the page size used by TF-A is 4k aligned. */
CASSERT((PAGE_SIZE % FFA_PAGE_SIZE) == 0, assert_aligned_page_size);
/*
* Defines to allow an SP to subscribe for power management messages
*/
#define FFA_PM_MSG_SUB_CPU_OFF U(1 << 0)
#define FFA_PM_MSG_SUB_CPU_SUSPEND U(1 << 1)
#define FFA_PM_MSG_SUB_CPU_SUSPEND_RESUME U(1 << 2)
/*
* Runtime states of an execution context as per the FF-A v1.1 specification.
*/
@ -162,6 +165,11 @@ struct secure_partition_desc {
/* Secondary entrypoint. Only valid for a S-EL1 SP. */
uintptr_t secondary_ep;
/*
* Store whether the SP has subscribed to any power management messages.
*/
uint16_t pwr_mgmt_msgs;
};
/*
@ -212,6 +220,9 @@ struct ffa_partition_info_v1_1 {
uint32_t uuid[4];
};
/* Reference to power management hooks */
extern const spd_pm_ops_t spmc_pm;
/* Setup Function for different SP types. */
void spmc_sp_common_setup(struct secure_partition_desc *sp,
entry_point_info_t *ep_info);

View File

@ -11,7 +11,8 @@ endif
SPMC_SOURCES := $(addprefix services/std_svc/spm/el3_spmc/, \
spmc_main.c \
spmc_setup.c \
logical_sp.c)
logical_sp.c \
spmc_pm.c)
# Specify platform specific logical partition implementation.
SPMC_LP_SOURCES := $(addprefix ${PLAT_DIR}/, \

View File

@ -234,13 +234,20 @@ static uint64_t spmc_smc_return(uint32_t smc_fid,
******************************************************************************/
static inline bool direct_msg_validate_arg2(uint64_t x2)
{
/*
* We currently only support partition messages, therefore ensure x2 is
* not set.
*/
if (x2 != (uint64_t) 0) {
VERBOSE("Arg2 MBZ for partition messages (0x%lx).\n", x2);
return false;
/* Check message type. */
if (x2 & FFA_FWK_MSG_BIT) {
/* We have a framework message, ensure it is a known message. */
if (x2 & ~(FFA_FWK_MSG_MASK | FFA_FWK_MSG_BIT)) {
VERBOSE("Invalid message format 0x%lx.\n", x2);
return false;
}
} else {
/* We have a partition messages, ensure x2 is not set. */
if (x2 != (uint64_t) 0) {
VERBOSE("Arg2 MBZ for partition messages. (0x%lx).\n",
x2);
return false;
}
}
return true;
}
@ -1032,6 +1039,7 @@ static uint64_t ffa_features_handler(uint32_t smc_fid,
/* Execution stops here. */
/* Supported ABIs only from the secure world. */
case FFA_SECONDARY_EP_REGISTER_SMC64:
case FFA_MSG_SEND_DIRECT_RESP_SMC32:
case FFA_MSG_SEND_DIRECT_RESP_SMC64:
case FFA_MSG_WAIT:
@ -1171,6 +1179,104 @@ static uint64_t rx_release_handler(uint32_t smc_fid,
SMC_RET1(handle, FFA_SUCCESS_SMC32);
}
/*
* Perform initial validation on the provided secondary entry point.
* For now ensure it does not lie within the BL31 Image or the SP's
* RX/TX buffers as these are mapped within EL3.
* TODO: perform validation for additional invalid memory regions.
*/
static int validate_secondary_ep(uintptr_t ep, struct secure_partition_desc *sp)
{
struct mailbox *mb;
uintptr_t buffer_size;
uintptr_t sp_rx_buffer;
uintptr_t sp_tx_buffer;
uintptr_t sp_rx_buffer_limit;
uintptr_t sp_tx_buffer_limit;
mb = &sp->mailbox;
buffer_size = (uintptr_t) (mb->rxtx_page_count * FFA_PAGE_SIZE);
sp_rx_buffer = (uintptr_t) mb->rx_buffer;
sp_tx_buffer = (uintptr_t) mb->tx_buffer;
sp_rx_buffer_limit = sp_rx_buffer + buffer_size;
sp_tx_buffer_limit = sp_tx_buffer + buffer_size;
/*
* Check if the entry point lies within BL31, or the
* SP's RX or TX buffer.
*/
if ((ep >= BL31_BASE && ep < BL31_LIMIT) ||
(ep >= sp_rx_buffer && ep < sp_rx_buffer_limit) ||
(ep >= sp_tx_buffer && ep < sp_tx_buffer_limit)) {
return -EINVAL;
}
return 0;
}
/*******************************************************************************
* This function handles the FFA_SECONDARY_EP_REGISTER SMC to allow an SP to
* register an entry point for initialization during a secondary cold boot.
******************************************************************************/
static uint64_t ffa_sec_ep_register_handler(uint32_t smc_fid,
bool secure_origin,
uint64_t x1,
uint64_t x2,
uint64_t x3,
uint64_t x4,
void *cookie,
void *handle,
uint64_t flags)
{
struct secure_partition_desc *sp;
struct sp_exec_ctx *sp_ctx;
/* This request cannot originate from the Normal world. */
if (!secure_origin) {
WARN("%s: Can only be called from SWd.\n", __func__);
return spmc_ffa_error_return(handle, FFA_ERROR_NOT_SUPPORTED);
}
/* Get the context of the current SP. */
sp = spmc_get_current_sp_ctx();
if (sp == NULL) {
WARN("%s: Cannot find SP context.\n", __func__);
return spmc_ffa_error_return(handle,
FFA_ERROR_INVALID_PARAMETER);
}
/* Only an S-EL1 SP should be invoking this ABI. */
if (sp->runtime_el != S_EL1) {
WARN("%s: Can only be called for a S-EL1 SP.\n", __func__);
return spmc_ffa_error_return(handle, FFA_ERROR_DENIED);
}
/* Ensure the SP is in its initialization state. */
sp_ctx = spmc_get_sp_ec(sp);
if (sp_ctx->rt_model != RT_MODEL_INIT) {
WARN("%s: Can only be called during SP initialization.\n",
__func__);
return spmc_ffa_error_return(handle, FFA_ERROR_DENIED);
}
/* Perform initial validation of the secondary entry point. */
if (validate_secondary_ep(x1, sp)) {
WARN("%s: Invalid entry point provided (0x%lx).\n",
__func__, x1);
return spmc_ffa_error_return(handle,
FFA_ERROR_INVALID_PARAMETER);
}
/*
* Update the secondary entrypoint in SP context.
* We don't need a lock here as during partition initialization there
* will only be a single core online.
*/
sp->secondary_ep = x1;
VERBOSE("%s: 0x%lx\n", __func__, sp->secondary_ep);
SMC_RET1(handle, FFA_SUCCESS_SMC32);
}
/*******************************************************************************
* This function will parse the Secure Partition Manifest. From manifest, it
* will fetch details for preparing Secure partition image context and secure
@ -1276,6 +1382,25 @@ static int sp_manifest_parse(void *sp_manifest, int offset,
sp->sp_id = config_32;
}
ret = fdt_read_uint32(sp_manifest, node,
"power-management-messages", &config_32);
if (ret != 0) {
WARN("Missing Power Management Messages entry.\n");
} else {
/*
* Ensure only the currently supported power messages have
* been requested.
*/
if (config_32 & ~(FFA_PM_MSG_SUB_CPU_OFF |
FFA_PM_MSG_SUB_CPU_SUSPEND |
FFA_PM_MSG_SUB_CPU_SUSPEND_RESUME)) {
ERROR("Requested unsupported PM messages (%x)\n",
config_32);
return -EINVAL;
}
sp->pwr_mgmt_msgs = config_32;
}
return 0;
}
@ -1540,6 +1665,9 @@ int32_t spmc_setup(void)
return ret;
}
/* Register power management hooks with PSCI */
psci_register_spd_pm_hook(&spmc_pm);
/* Register init function for deferred init. */
bl31_register_bl32_init(&sp_init);
@ -1575,6 +1703,11 @@ uint64_t spmc_smc_handler(uint32_t smc_fid,
return ffa_features_handler(smc_fid, secure_origin, x1, x2, x3,
x4, cookie, handle, flags);
case FFA_SECONDARY_EP_REGISTER_SMC64:
return ffa_sec_ep_register_handler(smc_fid, secure_origin, x1,
x2, x3, x4, cookie, handle,
flags);
case FFA_MSG_SEND_DIRECT_REQ_SMC32:
case FFA_MSG_SEND_DIRECT_REQ_SMC64:
return direct_req_smc_handler(smc_fid, secure_origin, x1, x2,

View File

@ -0,0 +1,283 @@
/*
* Copyright (c) 2022, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <errno.h>
#include <lib/el3_runtime/context_mgmt.h>
#include <lib/spinlock.h>
#include <plat/common/common_def.h>
#include <plat/common/platform.h>
#include <services/ffa_svc.h>
#include "spmc.h"
#include <platform_def.h>
/*******************************************************************************
* spmc_build_pm_message
*
* Builds an SPMC to SP direct message request.
******************************************************************************/
static void spmc_build_pm_message(gp_regs_t *gpregs,
unsigned long long message,
uint8_t pm_msg_type,
uint16_t sp_id)
{
write_ctx_reg(gpregs, CTX_GPREG_X0, FFA_MSG_SEND_DIRECT_REQ_SMC32);
write_ctx_reg(gpregs, CTX_GPREG_X1,
(FFA_SPMC_ID << FFA_DIRECT_MSG_SOURCE_SHIFT) |
sp_id);
write_ctx_reg(gpregs, CTX_GPREG_X2, FFA_FWK_MSG_BIT |
(pm_msg_type & FFA_FWK_MSG_MASK));
write_ctx_reg(gpregs, CTX_GPREG_X3, message);
}
/*******************************************************************************
* This CPU has been turned on. Enter the SP to initialise S-EL1.
******************************************************************************/
static void spmc_cpu_on_finish_handler(u_register_t unused)
{
struct secure_partition_desc *sp = spmc_get_current_sp_ctx();
struct sp_exec_ctx *ec;
unsigned int linear_id = plat_my_core_pos();
entry_point_info_t sec_ec_ep_info = {0};
uint64_t rc;
/* Sanity check for a NULL pointer dereference. */
assert(sp != NULL);
/* Initialize entry point information for the SP. */
SET_PARAM_HEAD(&sec_ec_ep_info, PARAM_EP, VERSION_1,
SECURE | EP_ST_ENABLE);
/*
* Check if the primary execution context registered an entry point else
* bail out early.
* TODO: Add support for boot reason in manifest to allow jumping to
* entrypoint into the primary execution context.
*/
if (sp->secondary_ep == 0) {
WARN("%s: No secondary ep on core%u\n", __func__, linear_id);
return;
}
sec_ec_ep_info.pc = sp->secondary_ep;
/*
* Setup and initialise the SP execution context on this physical cpu.
*/
spmc_el1_sp_setup(sp, &sec_ec_ep_info);
spmc_sp_common_ep_commit(sp, &sec_ec_ep_info);
/* Obtain a reference to the SP execution context. */
ec = spmc_get_sp_ec(sp);
/*
* TODO: Should we do some PM related state tracking of the SP execution
* context here?
*/
/* Update the runtime model and state of the partition. */
ec->rt_model = RT_MODEL_INIT;
ec->rt_state = RT_STATE_RUNNING;
INFO("SP (0x%x) init start on core%u.\n", sp->sp_id, linear_id);
rc = spmc_sp_synchronous_entry(ec);
if (rc != 0ULL) {
ERROR("%s failed (%lu) on CPU%u\n", __func__, rc, linear_id);
}
/* Update the runtime state of the partition. */
ec->rt_state = RT_STATE_WAITING;
VERBOSE("CPU %u on!\n", linear_id);
}
/*******************************************************************************
* Helper function to send a FF-A power management message to an SP.
******************************************************************************/
static int32_t spmc_send_pm_msg(uint8_t pm_msg_type,
unsigned long long psci_event)
{
struct secure_partition_desc *sp = spmc_get_current_sp_ctx();
struct sp_exec_ctx *ec;
gp_regs_t *gpregs_ctx;
unsigned int linear_id = plat_my_core_pos();
u_register_t resp;
uint64_t rc;
/* Obtain a reference to the SP execution context. */
ec = spmc_get_sp_ec(sp);
/*
* TODO: Should we do some PM related state tracking of the SP execution
* context here?
*/
/*
* Build an SPMC to SP direct message request.
* Note that x4-x6 should be populated with the original PSCI arguments.
*/
spmc_build_pm_message(get_gpregs_ctx(&ec->cpu_ctx),
psci_event,
pm_msg_type,
sp->sp_id);
/* Sanity check partition state. */
assert(ec->rt_state == RT_STATE_WAITING);
/* Update the runtime model and state of the partition. */
ec->rt_model = RT_MODEL_DIR_REQ;
ec->rt_state = RT_STATE_RUNNING;
rc = spmc_sp_synchronous_entry(ec);
if (rc != 0ULL) {
ERROR("%s failed (%lu) on CPU%u.\n", __func__, rc, linear_id);
assert(false);
return -EINVAL;
}
/*
* Validate we receive an expected response from the SP.
* TODO: We don't currently support aborting an SP in the scenario
* where it is misbehaving so assert these conditions are not
* met for now.
*/
gpregs_ctx = get_gpregs_ctx(&ec->cpu_ctx);
/* Expect a direct message response from the SP. */
resp = read_ctx_reg(gpregs_ctx, CTX_GPREG_X0);
if (resp != FFA_MSG_SEND_DIRECT_RESP_SMC32) {
ERROR("%s invalid SP response (%lx).\n", __func__, resp);
assert(false);
return -EINVAL;
}
/* Ensure the sender and receiver are populated correctly. */
resp = read_ctx_reg(gpregs_ctx, CTX_GPREG_X1);
if (!(ffa_endpoint_source(resp) == sp->sp_id &&
ffa_endpoint_destination(resp) == FFA_SPMC_ID)) {
ERROR("%s invalid src/dst response (%lx).\n", __func__, resp);
assert(false);
return -EINVAL;
}
/* Expect a PM message response from the SP. */
resp = read_ctx_reg(gpregs_ctx, CTX_GPREG_X2);
if ((resp & FFA_FWK_MSG_BIT) == 0U ||
((resp & FFA_FWK_MSG_MASK) != FFA_PM_MSG_PM_RESP)) {
ERROR("%s invalid PM response (%lx).\n", __func__, resp);
assert(false);
return -EINVAL;
}
/* Update the runtime state of the partition. */
ec->rt_state = RT_STATE_WAITING;
/* Return the status code returned by the SP */
return read_ctx_reg(gpregs_ctx, CTX_GPREG_X3);
}
/*******************************************************************************
* spmc_cpu_suspend_finish_handler
******************************************************************************/
static void spmc_cpu_suspend_finish_handler(u_register_t unused)
{
struct secure_partition_desc *sp = spmc_get_current_sp_ctx();
unsigned int linear_id = plat_my_core_pos();
int32_t rc;
/* Sanity check for a NULL pointer dereference. */
assert(sp != NULL);
/*
* Check if the SP has subscribed for this power management message.
* If not then we don't have anything else to do here.
*/
if ((sp->pwr_mgmt_msgs & FFA_PM_MSG_SUB_CPU_SUSPEND_RESUME) == 0U) {
goto exit;
}
rc = spmc_send_pm_msg(FFA_PM_MSG_WB_REQ, FFA_WB_TYPE_NOTS2RAM);
if (rc < 0) {
ERROR("%s failed (%d) on CPU%u\n", __func__, rc, linear_id);
return;
}
exit:
VERBOSE("CPU %u resumed!\n", linear_id);
}
/*******************************************************************************
* spmc_cpu_suspend_handler
******************************************************************************/
static void spmc_cpu_suspend_handler(u_register_t unused)
{
struct secure_partition_desc *sp = spmc_get_current_sp_ctx();
unsigned int linear_id = plat_my_core_pos();
int32_t rc;
/* Sanity check for a NULL pointer dereference. */
assert(sp != NULL);
/*
* Check if the SP has subscribed for this power management message.
* If not then we don't have anything else to do here.
*/
if ((sp->pwr_mgmt_msgs & FFA_PM_MSG_SUB_CPU_SUSPEND) == 0U) {
goto exit;
}
rc = spmc_send_pm_msg(FFA_FWK_MSG_PSCI, PSCI_CPU_SUSPEND_AARCH64);
if (rc < 0) {
ERROR("%s failed (%d) on CPU%u\n", __func__, rc, linear_id);
return;
}
exit:
VERBOSE("CPU %u suspend!\n", linear_id);
}
/*******************************************************************************
* spmc_cpu_off_handler
******************************************************************************/
static int32_t spmc_cpu_off_handler(u_register_t unused)
{
struct secure_partition_desc *sp = spmc_get_current_sp_ctx();
unsigned int linear_id = plat_my_core_pos();
int32_t ret = 0;
/* Sanity check for a NULL pointer dereference. */
assert(sp != NULL);
/*
* Check if the SP has subscribed for this power management message.
* If not then we don't have anything else to do here.
*/
if ((sp->pwr_mgmt_msgs & FFA_PM_MSG_SUB_CPU_OFF) == 0U) {
goto exit;
}
ret = spmc_send_pm_msg(FFA_FWK_MSG_PSCI, PSCI_CPU_OFF);
if (ret < 0) {
ERROR("%s failed (%d) on CPU%u\n", __func__, ret, linear_id);
return ret;
}
exit:
VERBOSE("CPU %u off!\n", linear_id);
return ret;
}
/*******************************************************************************
* Structure populated by the SPM Core to perform any bookkeeping before
* PSCI executes a power mgmt. operation.
******************************************************************************/
const spd_pm_ops_t spmc_pm = {
.svc_on_finish = spmc_cpu_on_finish_handler,
.svc_off = spmc_cpu_off_handler,
.svc_suspend = spmc_cpu_suspend_handler,
.svc_suspend_finish = spmc_cpu_suspend_finish_handler
};

View File

@ -684,7 +684,7 @@ uint64_t spmd_smc_handler(uint32_t smc_fid,
(SMC_GET_GP(gpregs, CTX_GPREG_X0) !=
FFA_MSG_SEND_DIRECT_RESP_SMC32) ||
(SMC_GET_GP(gpregs, CTX_GPREG_X2) !=
(SPMD_FWK_MSG_BIT |
(FFA_FWK_MSG_BIT |
SPMD_FWK_MSG_FFA_VERSION_RESP))) {
ERROR("Failed to forward FFA_VERSION\n");
ret = FFA_ERROR_NOT_SUPPORTED;

View File

@ -123,7 +123,7 @@ static int32_t spmd_cpu_off_handler(u_register_t unused)
/* Build an SPMD to SPMC direct message request. */
spmd_build_spmc_message(get_gpregs_ctx(&ctx->cpu_ctx),
SPMD_FWK_MSG_PSCI, PSCI_CPU_OFF);
FFA_FWK_MSG_PSCI, PSCI_CPU_OFF);
rc = spmd_spm_core_sync_entry(ctx);
if (rc != 0ULL) {

View File

@ -59,8 +59,6 @@ typedef struct spmd_spm_core_context {
#define FFA_NS_ENDPOINT_ID U(0)
/* Define SPMD target function IDs for framework messages to the SPMC */
#define SPMD_FWK_MSG_BIT BIT(31)
#define SPMD_FWK_MSG_PSCI U(0)
#define SPMD_FWK_MSG_FFA_VERSION_REQ U(0x8)
#define SPMD_FWK_MSG_FFA_VERSION_RESP U(0x9)