fix(interrupts): check for pending interrupts during direct response

A secure interrupt might trigger while the target SP is currently
running to send a direct response. SPMC would then inject virtual
interrupt to vCPU of target SP and resume it.
However, it is possible that the S-EL1 SP could have its interrupts
masked and hence might not handle the virtual interrupt before
sending direct response message. In such a scenario, SPMC must
return an error with code FFA_INTERRUPTED to inform the S-EL1 SP of
a pending interrupt and allow it to be handled before sending the
direct response.

Change-Id: I8a4517a929799f29e9eba01dab0fd9b8b538b2db
Signed-off-by: Madhukar Pappireddy <madhukar.pappireddy@arm.com>
diff --git a/src/api.c b/src/api.c
index 461fdd3..2c6fa76 100644
--- a/src/api.c
+++ b/src/api.c
@@ -2706,6 +2706,10 @@
 		return ffa_error(FFA_DENIED);
 	}
 
+	if (plat_ffa_is_direct_response_interrupted(current)) {
+		return ffa_error(FFA_INTERRUPTED);
+	}
+
 	assert(next_state == VCPU_STATE_WAITING);
 	current_locked = vcpu_lock(current);
 
diff --git a/src/arch/aarch64/plat/ffa/absent.c b/src/arch/aarch64/plat/ffa/absent.c
index e08d332..b667336 100644
--- a/src/arch/aarch64/plat/ffa/absent.c
+++ b/src/arch/aarch64/plat/ffa/absent.c
@@ -517,6 +517,12 @@
 	(void)vm_locked;
 }
 
+bool plat_ffa_is_direct_response_interrupted(struct vcpu *current)
+{
+	(void)current;
+	return false;
+}
+
 struct ffa_value plat_ffa_other_world_mem_send(
 	struct vm *from, struct ffa_memory_region *memory_region,
 	uint32_t length, uint32_t fragment_length, struct mpool *page_pool)
diff --git a/src/arch/aarch64/plat/ffa/hypervisor.c b/src/arch/aarch64/plat/ffa/hypervisor.c
index 6d847cf..9b601f6 100644
--- a/src/arch/aarch64/plat/ffa/hypervisor.c
+++ b/src/arch/aarch64/plat/ffa/hypervisor.c
@@ -1064,6 +1064,12 @@
 	}
 }
 
+bool plat_ffa_is_direct_response_interrupted(struct vcpu *current)
+{
+	(void)current;
+	return false;
+}
+
 /** Forwards a memory send message on to the other world. */
 static struct ffa_value memory_send_other_world_forward(
 	struct vm_locked other_world_locked, ffa_vm_id_t sender_vm_id,
diff --git a/src/arch/aarch64/plat/ffa/spmc.c b/src/arch/aarch64/plat/ffa/spmc.c
index 150f5acd..eca471a 100644
--- a/src/arch/aarch64/plat/ffa/spmc.c
+++ b/src/arch/aarch64/plat/ffa/spmc.c
@@ -2597,3 +2597,62 @@
 
 	return ffa_error(FFA_INVALID_PARAMETERS);
 }
+
+bool plat_ffa_is_direct_response_interrupted(struct vcpu *current)
+{
+	struct vcpu_locked current_locked;
+	bool ret;
+
+	/*
+	 * A secure interrupt might trigger while the target SP is currently
+	 * running to send a direct response. SPMC would then inject virtual
+	 * interrupt to vCPU of target SP and resume it.
+	 * However, it is possible that the S-EL1 SP could have its interrupts
+	 * masked and hence might not handle the virtual interrupt before
+	 * sending direct response message. In such a scenario, SPMC must
+	 * return an error with code FFA_INTERRUPTED to inform the S-EL1 SP of
+	 * a pending interrupt and allow it to be handled before sending the
+	 * direct response.
+	 */
+	current_locked = vcpu_lock(current);
+
+	/*
+	 * An S-EL0 partition can handle virtual secure interrupt only in
+	 * WAITING state. Hence it is likely for an S-EL0 SP to have a pending
+	 * virtual interrupt during FFA_MSG_SEND_DIRECT_RESP invocation.
+	 */
+	if (current->vm->el0_partition) {
+		ret = false;
+		goto out;
+	}
+
+	/*
+	 * Check if there are any pending virtual secure interrupts to be
+	 * handled.
+	 */
+	if (vcpu_interrupt_count_get(current_locked) > 0 &&
+	    current->processing_secure_interrupt) {
+		assert(current->implicit_completion_signal);
+
+		dlog_verbose(
+			"Secure virtual interrupt not yet serviced by "
+			"SP %x. FFA_MSG_SEND_DIRECT_RESP interrupted\n",
+			current->vm->id);
+		ret = true;
+	} else {
+		/*
+		 * SP must have completed handling of virtual interrupt and
+		 * performed de-activation. All the fields corresponding to
+		 * secure interrupt handling must have been cleared. Refer to
+		 * plat_ffa_interrupt_deactivate().
+		 */
+		assert(!current->processing_secure_interrupt);
+		assert(!current->secure_interrupt_deactivated);
+		assert(!current->implicit_completion_signal);
+
+		ret = false;
+	}
+out:
+	vcpu_unlock(&current_locked);
+	return ret;
+}
diff --git a/src/arch/fake/hypervisor/ffa.c b/src/arch/fake/hypervisor/ffa.c
index ffa87cd..9b74a98 100644
--- a/src/arch/fake/hypervisor/ffa.c
+++ b/src/arch/fake/hypervisor/ffa.c
@@ -490,6 +490,12 @@
 	(void)vm_locked;
 }
 
+bool plat_ffa_is_direct_response_interrupted(struct vcpu *current)
+{
+	(void)current;
+	return false;
+}
+
 struct ffa_value plat_ffa_other_world_mem_send(
 	struct vm *from, uint32_t share_func,
 	struct ffa_memory_region **memory_region, uint32_t length,