feat(indirect message): forward FFA_RX_RELEASE to SPMC

Within FF-A v1.1 indirect messaging, the SPMC owns a VM's RX buffers;
the Hypervisor must acquire ownership through FFA_RX_ACQUIRE ABI when
it needs to deliver a message to a VM, and notify the SPMC when the VM
releases it, by forwarding FFA_RX_RELEASE messages.

SPMC's view of a VM mailbox may not follow current mailbox
FSM (empty->received->read->empty), SPMC may not be aware of VM
message delivery and the consequent received->read mailbox transition.

Change-Id: I391054410573cc80549905f1a4bd2bbd19ac17fe
Signed-off-by: Federico Recanati <federico.recanati@arm.com>
diff --git a/src/api.c b/src/api.c
index f696a14..35ae4c2 100644
--- a/src/api.c
+++ b/src/api.c
@@ -1962,31 +1962,94 @@
  * will overwrite the old and will arrive asynchronously.
  *
  * Returns:
+ *  - FFA_ERROR FFA_INVALID_PARAMETERS if message is forwarded to SPMC but
+ *    there's no buffer pair mapped.
  *  - FFA_ERROR FFA_DENIED on failure, if the mailbox hasn't been read.
  *  - FFA_SUCCESS on success if no further action is needed.
  *  - FFA_RX_RELEASE if it was called by the primary VM and the primary VM now
  *    needs to wake up or kick waiters. Waiters should be retrieved by calling
  *    hf_mailbox_waiter_get.
  */
-struct ffa_value api_ffa_rx_release(struct vcpu *current, struct vcpu **next)
+struct ffa_value api_ffa_rx_release(ffa_vm_id_t receiver_id,
+				    struct vcpu *current, struct vcpu **next)
 {
-	struct vm *vm = current->vm;
-	struct vm_locked locked;
+	struct vm *current_vm = current->vm;
+	struct vm *vm;
+	struct vm_locked vm_locked;
+	ffa_vm_id_t current_vm_id = current_vm->id;
+	ffa_vm_id_t release_vm_id;
 	struct ffa_value ret;
 
-	locked = vm_lock(vm);
+	/* `receiver_id` can be set only at Non-Secure Physical interface. */
+	if (vm_id_is_current_world(current_vm_id) && (receiver_id != 0)) {
+		dlog_error("Invalid `receiver_id`, must be zero.\n");
+		return ffa_error(FFA_INVALID_PARAMETERS);
+	}
+
+	/*
+	 * VM ID to be released: `receiver_id` if message has been forwarded by
+	 * Hypervisor to release a VM's buffer, current VM ID otherwise.
+	 */
+	if (vm_id_is_current_world(current_vm_id) || (receiver_id == 0)) {
+		release_vm_id = current_vm_id;
+	} else {
+		release_vm_id = receiver_id;
+	}
+
+	vm_locked = plat_ffa_vm_find_locked(release_vm_id);
+	vm = vm_locked.vm;
+	if (vm == NULL) {
+		dlog_error("No buffer registered for VM ID %#x.\n",
+			   release_vm_id);
+		return ffa_error(FFA_INVALID_PARAMETERS);
+	}
+
+	if (!plat_ffa_rx_release_forward(vm_locked, &ret)) {
+		dlog_verbose("RX_RELEASE forward failed for VM ID %#x.\n",
+			     release_vm_id);
+		goto out;
+	}
+
+	/*
+	 * When SPMC owns a VM's RX buffer, the Hypervisor's view can be out of
+	 * sync: reset it to empty and exit.
+	 */
+	if (plat_ffa_rx_release_forwarded(vm_locked)) {
+		ret = (struct ffa_value){.func = FFA_SUCCESS_32};
+		goto out;
+	}
+
 	switch (vm->mailbox.state) {
 	case MAILBOX_STATE_EMPTY:
-	case MAILBOX_STATE_RECEIVED:
 		ret = ffa_error(FFA_DENIED);
 		break;
 
+	case MAILBOX_STATE_RECEIVED:
+		if (release_vm_id == current_vm_id) {
+			/*
+			 * VM requesting to release its own RX buffer,
+			 * must be in READ state.
+			 */
+			ret = ffa_error(FFA_DENIED);
+		} else {
+			/*
+			 * Forwarded message from Hypervisor to release a VM
+			 * RX buffer, SPMC's mailbox view can be still in
+			 * RECEIVED state.
+			 */
+			ret = (struct ffa_value){.func = FFA_SUCCESS_32};
+			vm->mailbox.state = MAILBOX_STATE_EMPTY;
+		}
+		break;
+
 	case MAILBOX_STATE_READ:
-		ret = api_waiter_result(locked, current, next);
+		ret = api_waiter_result(vm_locked, current, next);
 		vm->mailbox.state = MAILBOX_STATE_EMPTY;
 		break;
 	}
-	vm_unlock(&locked);
+
+out:
+	vm_unlock(&vm_locked);
 
 	return ret;
 }
diff --git a/src/arch/aarch64/hypervisor/handler.c b/src/arch/aarch64/hypervisor/handler.c
index b30fb8d..0ee37d6 100644
--- a/src/arch/aarch64/hypervisor/handler.c
+++ b/src/arch/aarch64/hypervisor/handler.c
@@ -497,7 +497,7 @@
 		*args = api_ffa_features(args->arg1);
 		return true;
 	case FFA_RX_RELEASE_32:
-		*args = api_ffa_rx_release(current, next);
+		*args = api_ffa_rx_release(ffa_receiver(*args), current, next);
 		return true;
 	case FFA_RXTX_MAP_64:
 		*args = api_ffa_rxtx_map(ipa_init(args->arg1),
diff --git a/src/arch/aarch64/plat/ffa/absent.c b/src/arch/aarch64/plat/ffa/absent.c
index 1dcf5fb..ed19602 100644
--- a/src/arch/aarch64/plat/ffa/absent.c
+++ b/src/arch/aarch64/plat/ffa/absent.c
@@ -129,6 +129,22 @@
 	return false;
 }
 
+bool plat_ffa_rx_release_forward(struct vm_locked vm_locked,
+				 struct ffa_value *ret)
+{
+	(void)vm_locked;
+	(void)ret;
+
+	return false;
+}
+
+bool plat_ffa_rx_release_forwarded(struct vm_locked vm_locked)
+{
+	(void)vm_locked;
+
+	return false;
+}
+
 bool plat_ffa_acquire_receiver_rx(struct vm_locked to_locked,
 				  struct ffa_value *ret)
 {
diff --git a/src/arch/aarch64/plat/ffa/hypervisor.c b/src/arch/aarch64/plat/ffa/hypervisor.c
index 1dcc522..7f0666b 100644
--- a/src/arch/aarch64/plat/ffa/hypervisor.c
+++ b/src/arch/aarch64/plat/ffa/hypervisor.c
@@ -243,6 +243,48 @@
 	return false;
 }
 
+bool plat_ffa_rx_release_forward(struct vm_locked vm_locked,
+				 struct ffa_value *ret)
+{
+	struct vm *vm = vm_locked.vm;
+	ffa_vm_id_t vm_id = vm->id;
+
+	if (!ffa_tee_enabled || (vm->ffa_version < MAKE_FFA_VERSION(1, 1))) {
+		*ret = (struct ffa_value){.func = FFA_SUCCESS_32};
+		return true;
+	}
+
+	CHECK(vm_id_is_current_world(vm_id));
+
+	/* Hypervisor always forward VM's RX_RELEASE to SPMC. */
+	*ret = arch_other_world_call(
+		(struct ffa_value){.func = FFA_RX_RELEASE_32, .arg1 = vm_id});
+
+	return ret->func == FFA_SUCCESS_32;
+}
+
+/**
+ * In FF-A v1.1 with SPMC enabled the SPMC owns the RX buffers for NWd VMs,
+ * hence the SPMC is handling FFA_RX_RELEASE calls for NWd VMs too.
+ * The Hypervisor's view of a VM's RX buffer can be out of sync, reset it to
+ * 'empty' if the FFA_RX_RELEASE call has been successfully forwarded to the
+ * SPMC.
+ */
+bool plat_ffa_rx_release_forwarded(struct vm_locked vm_locked)
+{
+	struct vm *vm = vm_locked.vm;
+
+	if (ffa_tee_enabled && (vm->ffa_version > MAKE_FFA_VERSION(1, 0))) {
+		dlog_verbose(
+			"RX_RELEASE forwarded, reset MB state for VM ID %#x.\n",
+			vm->id);
+		vm->mailbox.state = MAILBOX_STATE_EMPTY;
+		return true;
+	}
+
+	return false;
+}
+
 /**
  * Acquire the RX buffer of a VM from the SPM.
  *
diff --git a/src/arch/aarch64/plat/ffa/spmc.c b/src/arch/aarch64/plat/ffa/spmc.c
index 9729445..4efeb9a 100644
--- a/src/arch/aarch64/plat/ffa/spmc.c
+++ b/src/arch/aarch64/plat/ffa/spmc.c
@@ -141,7 +141,12 @@
 		goto out;
 	}
 
+	/*
+	 * Note: VM struct for Nwd VMs is only partially initialized, to the
+	 * extend of what's currently used by the SPMC (VM ID, waiter list).
+	 */
 	vm_locked.vm->id = vm_id;
+	list_init(&vm_locked.vm->mailbox.waiter_list);
 
 out:
 	nwd_vms_unlock(&nwd_vms_locked);
@@ -349,6 +354,22 @@
 	return false;
 }
 
+bool plat_ffa_rx_release_forward(struct vm_locked vm_locked,
+				 struct ffa_value *ret)
+{
+	(void)vm_locked;
+	(void)ret;
+
+	return true;
+}
+
+bool plat_ffa_rx_release_forwarded(struct vm_locked vm_locked)
+{
+	(void)vm_locked;
+
+	return false;
+}
+
 bool plat_ffa_acquire_receiver_rx(struct vm_locked to_locked,
 				  struct ffa_value *ret)
 {
diff --git a/src/arch/fake/hypervisor/ffa.c b/src/arch/fake/hypervisor/ffa.c
index 2537dc0..252d174 100644
--- a/src/arch/fake/hypervisor/ffa.c
+++ b/src/arch/fake/hypervisor/ffa.c
@@ -99,6 +99,22 @@
 	return false;
 }
 
+bool plat_ffa_rx_release_forward(struct vm_locked vm_locked,
+				 struct ffa_value *ret)
+{
+	(void)vm_locked;
+	(void)ret;
+
+	return false;
+}
+
+bool plat_ffa_rx_release_forwarded(struct vm_locked vm_locked)
+{
+	(void)vm_locked;
+
+	return false;
+}
+
 bool plat_ffa_acquire_receiver_rx(struct vm_locked to_locked,
 				  struct ffa_value *ret)
 {