feat: handle PSCI framework msg response from SP

SPMC receives the direct response from SP for the direct request
framework message it had sent earlier.

This patch also adds support for SPMC to forward the PSCI CPU_OFF
power managment status code (either DENIED or SUCCESS) to SPMD.

Change-Id: I433ac056af2687259513a92bfb6c45b990659dfd
Signed-off-by: Madhukar Pappireddy <madhukar.pappireddy@arm.com>
diff --git a/src/api.c b/src/api.c
index 7fa5462..e6a1ecf 100644
--- a/src/api.c
+++ b/src/api.c
@@ -3112,6 +3112,12 @@
 		goto out;
 	}
 
+	if (ffa_is_framework_msg(args) &&
+	    ffa_direct_msg_handle_framework_msg_resp(args, &ret, current_locked,
+						     next)) {
+		goto out;
+	}
+
 	if (api_ffa_is_managed_exit_ongoing(current_locked)) {
 		CHECK(current->scheduling_mode != SPMC_MODE);
 
diff --git a/src/arch/fake/hypervisor/ffa.c b/src/arch/fake/hypervisor/ffa.c
index 4a03920..093e6b1 100644
--- a/src/arch/fake/hypervisor/ffa.c
+++ b/src/arch/fake/hypervisor/ffa.c
@@ -651,3 +651,16 @@
 
 	return false;
 }
+
+bool ffa_direct_msg_handle_framework_msg_resp(struct ffa_value args,
+					      struct ffa_value *ret,
+					      struct vcpu_locked current_locked,
+					      struct vcpu **next)
+{
+	(void)args;
+	(void)ret;
+	(void)current_locked;
+	(void)next;
+
+	return false;
+}
diff --git a/src/ffa/absent.c b/src/ffa/absent.c
index 416d20b..63c9a31 100644
--- a/src/ffa/absent.c
+++ b/src/ffa/absent.c
@@ -588,3 +588,16 @@
 
 	return false;
 }
+
+bool ffa_direct_msg_handle_framework_msg_resp(struct ffa_value args,
+					      struct ffa_value *ret,
+					      struct vcpu_locked current_locked,
+					      struct vcpu **next)
+{
+	(void)args;
+	(void)ret;
+	(void)current_locked;
+	(void)next;
+
+	return false;
+}
diff --git a/src/ffa/hypervisor/direct_messaging.c b/src/ffa/hypervisor/direct_messaging.c
index 897b7d3..e3f7d83 100644
--- a/src/ffa/hypervisor/direct_messaging.c
+++ b/src/ffa/hypervisor/direct_messaging.c
@@ -137,3 +137,16 @@
 	(void)vm_id;
 	return false;
 }
+
+bool ffa_direct_msg_handle_framework_msg_resp(struct ffa_value args,
+					      struct ffa_value *ret,
+					      struct vcpu_locked current_locked,
+					      struct vcpu **next)
+{
+	(void)args;
+	(void)ret;
+	(void)current_locked;
+	(void)next;
+
+	return false;
+}
diff --git a/src/ffa/spmc/cpu_cycles.c b/src/ffa/spmc/cpu_cycles.c
index 3dae58b..fd98edf 100644
--- a/src/ffa/spmc/cpu_cycles.c
+++ b/src/ffa/spmc/cpu_cycles.c
@@ -579,6 +579,26 @@
 	struct vcpu_locked current_locked, struct vcpu_locked locked_vcpu,
 	ffa_id_t receiver_vm_id, uint32_t func, enum vcpu_state *next_state)
 {
+	/*
+	 * SPMC denies invocation if the SP's vCPU is processing a PSCI power
+	 * management operation.
+	 */
+	if (current_locked.vcpu->pwr_mgmt_op != PWR_MGMT_NONE) {
+		switch (func) {
+		case FFA_MSG_SEND_DIRECT_REQ_64:
+		case FFA_MSG_SEND_DIRECT_REQ_32:
+		case FFA_MSG_SEND_DIRECT_REQ2_64:
+		case FFA_RUN_32:
+		case FFA_YIELD_32:
+			dlog_verbose(
+				"State transition denied during power "
+				"management operation\n");
+			return false;
+		default:
+			break;
+		}
+	}
+
 	switch (func) {
 	case FFA_MSG_SEND_DIRECT_REQ_64:
 	case FFA_MSG_SEND_DIRECT_REQ_32:
diff --git a/src/ffa/spmc/direct_messaging.c b/src/ffa/spmc/direct_messaging.c
index 0cd6b0d..9a3803b 100644
--- a/src/ffa/spmc/direct_messaging.c
+++ b/src/ffa/spmc/direct_messaging.c
@@ -582,3 +582,130 @@
 {
 	return (vm_id >= EL3_SPMD_LP_ID_START && vm_id <= EL3_SPMD_LP_ID_END);
 }
+
+static struct ffa_value handle_sp_cpu_off_framework_resp(
+	struct ffa_value args, struct vcpu_locked current_locked,
+	struct vcpu **next)
+{
+	struct ffa_value ffa_ret;
+	struct ffa_value ret;
+	uint32_t psci_error_code;
+	struct vcpu *current = current_locked.vcpu;
+
+	/* A placeholder return code. */
+	ret = api_ffa_interrupt_return(0);
+
+	psci_error_code = (uint32_t)args.arg3;
+
+	/*
+	 * Section 18.2.4 of the FF-A v1.2 REL0 spec states that SPMC must
+	 * return DENIED to SPMD even if a single SP returns error to SPMC.
+	 * Upon receiving the DENIED status, SPMD (TF-A) will panic and reset
+	 * the system.
+	 */
+	if (psci_error_code != PSCI_RETURN_SUCCESS) {
+		ffa_ret = ffa_framework_msg_resp(HF_SPMC_VM_ID, HF_SPMD_VM_ID,
+						 FFA_FRAMEWORK_MSG_PSCI_RESP,
+						 PSCI_ERROR_DENIED);
+
+		*next = api_switch_to_vm(current_locked, ffa_ret,
+					 VCPU_STATE_OFF, HF_OTHER_WORLD_ID);
+
+		dlog_verbose(
+			"SP%#x denied CPU_OFF power management operation\n",
+			current->vm->id);
+		return ret;
+	}
+
+	/*
+	 * Reset fields tracking PSCI operation through framework message in
+	 * direct response.
+	 */
+	current->pwr_mgmt_op = PWR_MGMT_NONE;
+	vcpu_dir_req_reset_state(current_locked);
+
+	/* Turn off the vCPU.*/
+	current->state = VCPU_STATE_OFF;
+	dlog_verbose("SPMC turned off vCPU%zu of SP%#x\n",
+		     cpu_index(current->cpu), current->vm->id);
+
+	/*
+	 * No vCPU left that needs to be informed about power management
+	 * operation. Send SUCCESS as status code through direct response
+	 * framework message to SPMD.
+	 */
+	ffa_ret = psci_cpu_off_success_fwk_resp(current->cpu);
+
+	/*
+	 * The framework message is conveyed to SPMD(EL3) from SPMC. So the next
+	 * vCPU's VM id must match the other world VM id.
+	 */
+	*next = api_switch_to_vm(current_locked, ffa_ret, VCPU_STATE_OFF,
+				 HF_OTHER_WORLD_ID);
+
+	dlog_verbose("SPMC: Sent PSCI Success to SPMD\n");
+	return ret;
+}
+
+/**
+ * Handle direct response with PSCI framework message from SP.
+ */
+static struct ffa_value handle_psci_framework_msg_resp(
+	struct ffa_value args, struct vcpu_locked current_locked,
+	struct vcpu **next)
+{
+	struct ffa_value ret;
+	struct vcpu *current = current_locked.vcpu;
+
+	switch (current->pwr_mgmt_op) {
+	case PWR_MGMT_CPU_OFF:
+		return handle_sp_cpu_off_framework_resp(args, current_locked,
+							next);
+	default:
+		/*
+		 * At the moment, SPMC only supports informing Secure Partitions
+		 * about PSCI CPU_OFF operation. Consequently, it does not
+		 * expect to receive framework message through
+		 * FFA_FRAMEWORK_MSG_PSCI_RESP from the SP if the SP's vCPU is
+		 * not known to be processing PSCI CPU_OFF operation.
+		 */
+		dlog_error(
+			"Unexpected framework direct response message received "
+			"from SP%x\n",
+			current->vm->id);
+		ret = ffa_error(FFA_INVALID_PARAMETERS);
+		return ret;
+	}
+}
+
+/**
+ * Handle framework message in a dierct response from an SP.
+ * Returns true if the framework message is completely handled.
+ * Else false if further handling is required.
+ */
+bool ffa_direct_msg_handle_framework_msg_resp(struct ffa_value args,
+					      struct ffa_value *ret,
+					      struct vcpu_locked current_locked,
+					      struct vcpu **next)
+{
+	enum ffa_framework_msg_func func = ffa_framework_msg_get_func(args);
+
+	switch (func) {
+	case FFA_FRAMEWORK_MSG_PSCI_RESP:
+		*ret = handle_psci_framework_msg_resp(args, current_locked,
+						      next);
+		return true;
+	case FFA_FRAMEWORK_MSG_VM_CREATION_RESP:
+	case FFA_FRAMEWORK_MSG_VM_DESTRUCTION_RESP:
+		return false;
+	default:
+		dlog_error(
+			"Unsupported framework message %x sent through direct "
+			"response by SP:%x\n",
+			(uint32_t)func, current_locked.vcpu->vm->id);
+		*ret = ffa_error(FFA_INVALID_PARAMETERS);
+		return true;
+	}
+
+	return false;
+}