feat(interrupts): intercept S-EL0 SP direct response message

Since FF-A v1.1 EAC0 spec only allows signaling a virtual secure
interrupt to an S-EL0 SP when in WAITING state, SPMC intercepts
a direct response message from the S-EL0 partition to proactively
signal the pending virtual secure interrupt and resume the vCPU
of the S-EL0 partition.

Further, once the secure interrupt is handled, SPMC resumes the
direct response message from current S-EL0 partition and takes
care of unwinding the call chain.

Change-Id: I55da462b5b6b7813f09a7b57c16fcd378972ac3a
Signed-off-by: Madhukar Pappireddy <madhukar.pappireddy@arm.com>
diff --git a/inc/hf/api.h b/inc/hf/api.h
index 929fe52..9c9b386 100644
--- a/inc/hf/api.h
+++ b/inc/hf/api.h
@@ -133,3 +133,8 @@
 
 struct ffa_value api_ffa_console_log(const struct ffa_value args,
 				     struct vcpu *current);
+
+void api_ffa_resume_direct_resp_target(struct vcpu *current, struct vcpu **next,
+				       ffa_vm_id_t receiver_vm_id,
+				       struct ffa_value to_ret,
+				       bool is_nwd_call_chain);
diff --git a/inc/hf/arch/plat/ffa.h b/inc/hf/arch/plat/ffa.h
index a530af1..1154587 100644
--- a/inc/hf/arch/plat/ffa.h
+++ b/inc/hf/arch/plat/ffa.h
@@ -330,3 +330,8 @@
 struct ffa_value plat_ffa_other_world_mem_reclaim(
 	struct vm *to, ffa_memory_handle_t handle,
 	ffa_memory_region_flags_t flags, struct mpool *page_pool);
+
+bool plat_ffa_intercept_direct_response(struct vcpu_locked current_locked,
+					struct vcpu **next,
+					struct ffa_value to_ret,
+					struct ffa_value *signal_interrupt);
diff --git a/inc/hf/vcpu.h b/inc/hf/vcpu.h
index 1395bcd..179863f 100644
--- a/inc/hf/vcpu.h
+++ b/inc/hf/vcpu.h
@@ -200,6 +200,15 @@
 
 	/** Partition Runtime Model. */
 	enum partition_runtime_model rt_model;
+
+	/**
+	 * Direct response message has been intercepted to handle virtual
+	 * secure interrupt for a S-EL0 partition.
+	 */
+	bool direct_resp_intercepted;
+
+	/** Save direct response message args to be resumed later. */
+	struct ffa_value direct_resp_ffa_value;
 };
 
 /** Encapsulates a vCPU whose lock is held. */
diff --git a/src/api.c b/src/api.c
index 2454a40..94392c7 100644
--- a/src/api.c
+++ b/src/api.c
@@ -2630,6 +2630,43 @@
 }
 
 /**
+ * Resume the target vCPU after the current vCPU sent a direct response.
+ * Current vCPU moves to waiting state.
+ */
+void api_ffa_resume_direct_resp_target(struct vcpu *current, struct vcpu **next,
+				       ffa_vm_id_t receiver_vm_id,
+				       struct ffa_value to_ret,
+				       bool is_nwd_call_chain)
+{
+	if (!vm_id_is_current_world(receiver_vm_id)) {
+		*next = api_switch_to_other_world(current, to_ret,
+						  VCPU_STATE_WAITING);
+
+		/* End of NWd scheduled call chain. */
+		assert(!is_nwd_call_chain ||
+		       (current->call_chain.prev_node == NULL));
+	} else if (receiver_vm_id == HF_PRIMARY_VM_ID) {
+		*next = api_switch_to_primary(current, to_ret,
+					      VCPU_STATE_WAITING);
+
+		/* Removing a node from NWd scheduled call chain. */
+		if (is_nwd_call_chain) {
+			vcpu_call_chain_remove_node(current, *next);
+		}
+	} else if (vm_id_is_current_world(receiver_vm_id)) {
+		/*
+		 * It is expected the receiver_vm_id to be from an SP, otherwise
+		 * 'plat_ffa_is_direct_response_valid' should have
+		 * made function return error before getting to this point.
+		 */
+		*next = api_switch_to_vm(current, to_ret, VCPU_STATE_WAITING,
+					 receiver_vm_id);
+	} else {
+		panic("Invalid direct message response invocation");
+	}
+}
+
+/**
  * Send an FF-A direct message response.
  */
 struct ffa_value api_ffa_msg_send_direct_resp(ffa_vm_id_t sender_vm_id,
@@ -2640,6 +2677,8 @@
 {
 	struct vcpu_locked current_locked;
 	enum vcpu_state next_state = VCPU_STATE_WAITING;
+	struct ffa_value signal_interrupt =
+		(struct ffa_value){.func = FFA_INTERRUPT_32};
 
 	if (!api_ffa_dir_msg_is_arg2_zero(args)) {
 		return ffa_error(FFA_INVALID_PARAMETERS);
@@ -2703,42 +2742,19 @@
 		}
 	}
 
+	if (plat_ffa_intercept_direct_response(current_locked, next, to_ret,
+					       &signal_interrupt)) {
+		vcpu_unlock(&current_locked);
+		return signal_interrupt;
+	}
+
 	/* Clear direct request origin for the caller. */
 	current->direct_request_origin_vm_id = HF_INVALID_VM_ID;
 
 	vcpu_unlock(&current_locked);
 
-	if (!vm_id_is_current_world(receiver_vm_id)) {
-		*next = api_switch_to_other_world(
-			current, to_ret,
-			/*
-			 * Current vcpu sent a direct response. It moves to
-			 * waiting state.
-			 */
-			VCPU_STATE_WAITING);
-	} else if (receiver_vm_id == HF_PRIMARY_VM_ID) {
-		*next = api_switch_to_primary(
-			current, to_ret,
-			/*
-			 * Current vcpu sent a direct response. It moves to
-			 * waiting state.
-			 */
-			VCPU_STATE_WAITING);
-	} else if (vm_id_is_current_world(receiver_vm_id)) {
-		/*
-		 * It is expected the receiver_vm_id to be from an SP, otherwise
-		 * 'plat_ffa_is_direct_response_valid' should have
-		 * made function return error before getting to this point.
-		 */
-		*next = api_switch_to_vm(current, to_ret,
-					 /*
-					  * current vcpu sent a direct response.
-					  * It moves to waiting state.
-					  */
-					 VCPU_STATE_WAITING, receiver_vm_id);
-	} else {
-		panic("Invalid direct message response invocation");
-	}
+	api_ffa_resume_direct_resp_target(current, next, receiver_vm_id, to_ret,
+					  false);
 
 	plat_ffa_unwind_call_chain_ffa_direct_resp(current, *next);
 
diff --git a/src/arch/aarch64/plat/ffa/absent.c b/src/arch/aarch64/plat/ffa/absent.c
index 10fd888..5f62591 100644
--- a/src/arch/aarch64/plat/ffa/absent.c
+++ b/src/arch/aarch64/plat/ffa/absent.c
@@ -493,6 +493,23 @@
 	(void)next;
 }
 
+bool plat_ffa_intercept_direct_response(struct vcpu_locked current_locked,
+					struct vcpu **next,
+					struct ffa_value to_ret,
+					struct ffa_value *signal_interrupt)
+{
+	/*
+	 * Only applicable to SPMC as it signals virtual secure interrupt to
+	 * S-EL0 partitions.
+	 */
+	(void)current_locked;
+	(void)next;
+	(void)to_ret;
+	(void)signal_interrupt;
+
+	return false;
+}
+
 void plat_ffa_enable_virtual_maintenance_interrupts(
 	struct vcpu_locked current_locked)
 {
diff --git a/src/arch/aarch64/plat/ffa/hypervisor.c b/src/arch/aarch64/plat/ffa/hypervisor.c
index 483a060..4c3fd10 100644
--- a/src/arch/aarch64/plat/ffa/hypervisor.c
+++ b/src/arch/aarch64/plat/ffa/hypervisor.c
@@ -1023,6 +1023,23 @@
 	(void)next;
 }
 
+bool plat_ffa_intercept_direct_response(struct vcpu_locked current_locked,
+					struct vcpu **next,
+					struct ffa_value to_ret,
+					struct ffa_value *signal_interrupt)
+{
+	/*
+	 * Only applicable to SPMC as it signals virtual secure interrupt to
+	 * S-EL0 partitions.
+	 */
+	(void)current_locked;
+	(void)next;
+	(void)to_ret;
+	(void)signal_interrupt;
+
+	return false;
+}
+
 void plat_ffa_enable_virtual_maintenance_interrupts(
 	struct vcpu_locked current_locked)
 {
diff --git a/src/arch/aarch64/plat/ffa/spmc.c b/src/arch/aarch64/plat/ffa/spmc.c
index 3bd272c..010a85d 100644
--- a/src/arch/aarch64/plat/ffa/spmc.c
+++ b/src/arch/aarch64/plat/ffa/spmc.c
@@ -2040,6 +2040,134 @@
 	return ret;
 }
 
+static void plat_ffa_signal_interrupt_args(struct ffa_value *args, uint32_t id)
+{
+	assert(args != NULL);
+	args->func = (uint32_t)FFA_INTERRUPT_32;
+	args->arg2 = id;
+}
+
+/**
+ * Run the vCPU in SPMC schedule mode under the runtime model for secure
+ * interrupt handling.
+ */
+static void plat_ffa_run_in_sec_interrupt_rtm(
+	struct vcpu_locked target_vcpu_locked)
+{
+	struct vcpu *target_vcpu;
+
+	target_vcpu = target_vcpu_locked.vcpu;
+
+	/* Mark the registers as unavailable now. */
+	target_vcpu->regs_available = false;
+	target_vcpu->scheduling_mode = SPMC_MODE;
+	target_vcpu->rt_model = RTM_SEC_INTERRUPT;
+	target_vcpu->state = VCPU_STATE_RUNNING;
+}
+
+/**
+ * Intercept a direct response message from current S-EL0 vCPU to signal a
+ * pending virtual secure interrupt and prepare to resume it in SPMC schedule
+ * mode under runtime model for secure interrupt handling.
+ */
+bool plat_ffa_intercept_direct_response(struct vcpu_locked current_locked,
+					struct vcpu **next,
+					struct ffa_value to_ret,
+					struct ffa_value *signal_interrupt)
+{
+	bool is_el0_partition;
+	struct vcpu *current;
+
+	current = current_locked.vcpu;
+	is_el0_partition = current->vm->el0_partition;
+	assert(*next == NULL);
+
+	if (is_el0_partition && vcpu_interrupt_count_get(current_locked) > 0) {
+		dlog_verbose(
+			"Intercepting FFA_MSG_SEND_DIRECT_RESP call to "
+			"signal secure interrupt: %x\n",
+			current->vm->id);
+
+		assert(current->processing_secure_interrupt);
+		current->direct_resp_intercepted = true;
+
+		/* Save direct response message args. */
+		current->direct_resp_ffa_value = to_ret;
+
+		/*
+		 * Prepare to signal virtual secure interrupt to S-EL0 SP. Refer
+		 * to FF-A v1.1 EAC0 Table 8.1 case 1 and Table 12.10.
+		 */
+		plat_ffa_signal_interrupt_args(
+			signal_interrupt, current->current_sec_interrupt_id);
+
+		/*
+		 * Prepare to resume this partition's vCPU in SPMC schedule
+		 * mode to handle virtual secure interrupt.
+		 */
+		plat_ffa_run_in_sec_interrupt_rtm(current_locked);
+
+		/* Resume current vCPU. */
+		*next = NULL;
+
+		return true;
+	}
+
+	return false;
+}
+
+/*
+ * First, all the fields related to secure interrupt handling are reset and
+ * SPMC scheduled call chain is unwound.
+ * Second, the intercepted direct response message is replayed followed by
+ * unwinding of the NWd scheduled call chain.
+ */
+static struct ffa_value plat_ffa_resume_direct_response(struct vcpu *current,
+							struct vcpu **next)
+{
+	ffa_vm_id_t receiver_vm_id;
+	struct ffa_value to_ret;
+	struct vcpu_locked current_vcpu_locked;
+
+	/* Lock current vCPU. */
+	current_vcpu_locked = vcpu_lock(current);
+
+	/* Reset the fields tracking secure interrupt processing. */
+	plat_ffa_reset_secure_interrupt_flags(current_vcpu_locked);
+
+	/* SPMC scheduled call chain is completely unwound. */
+	plat_ffa_exit_spmc_schedule_mode(current_vcpu_locked);
+
+	/* Restore interrupt priority mask. */
+	plat_interrupts_set_priority_mask(current->priority_mask);
+
+	/* Replay the direct response message. */
+	receiver_vm_id = current->direct_request_origin_vm_id;
+	to_ret = current->direct_resp_ffa_value;
+
+	/* Reset the flag now. */
+	current->direct_resp_intercepted = false;
+
+	dlog_verbose(
+		"Resuming intercepted direct response from: %x to: "
+		"%x\n",
+		current->vm->id, receiver_vm_id);
+
+	/* Clear direct request origin for the caller. */
+	current->direct_request_origin_vm_id = HF_INVALID_VM_ID;
+	vcpu_unlock(&current_vcpu_locked);
+
+	api_ffa_resume_direct_resp_target(current, next, receiver_vm_id, to_ret,
+					  true);
+
+	/* Unmask non secure interrupts. */
+	if (current->present_action_ns_interrupts == NS_ACTION_QUEUED) {
+		plat_interrupts_set_priority_mask(current->mask_ns_interrupts);
+	}
+
+	return (struct ffa_value){.func = FFA_MSG_WAIT_32};
+}
+
 /**
  * The invocation of FFA_MSG_WAIT at secure virtual FF-A instance is compliant
  * with FF-A v1.1 EAC0 specification. It only performs the  state transition
@@ -2083,6 +2211,11 @@
 		 */
 		assert(!current->implicit_completion_signal);
 
+		if (current->direct_resp_intercepted) {
+			assert(current->vm->el0_partition);
+			return plat_ffa_resume_direct_response(current, next);
+		}
+
 		/* Secure interrupt pre-empted normal world. */
 		if (current->preempted_vcpu->vm->id == HF_OTHER_WORLD_ID) {
 			return plat_ffa_normal_world_resume(current, next);
diff --git a/src/arch/fake/hypervisor/ffa.c b/src/arch/fake/hypervisor/ffa.c
index 969622d..f197f2f 100644
--- a/src/arch/fake/hypervisor/ffa.c
+++ b/src/arch/fake/hypervisor/ffa.c
@@ -466,6 +466,23 @@
 	(void)next;
 }
 
+bool plat_ffa_intercept_direct_response(struct vcpu_locked current_locked,
+					struct vcpu **next,
+					struct ffa_value to_ret,
+					struct ffa_value *signal_interrupt)
+{
+	/*
+	 * Only applicable to SPMC as it signals virtual secure interrupt to
+	 * S-EL0 partitions.
+	 */
+	(void)current_locked;
+	(void)next;
+	(void)to_ret;
+	(void)signal_interrupt;
+
+	return false;
+}
+
 void plat_ffa_enable_virtual_maintenance_interrupts(
 	struct vcpu_locked current_locked)
 {