refactor(indirect message): mailbox state as per FF-A v1.1

The maibox_state structure was refactored to represent
the mailbox state, and achieve ownership rules as defined by
the FF-A v1.1 specification.
The code was changed to reflect the new state transitions.

Kept compatibility with FF-A v1.0 indirect messaging to avoid
breaking legacy tests.

Change-Id: I1f2353f97d5d46436a3f81b5abf4a032f43745e8
Signed-off-by: J-Alves <joao.alves@arm.com>
diff --git a/src/api.c b/src/api.c
index 9ceb743..3156fe7 100644
--- a/src/api.c
+++ b/src/api.c
@@ -362,6 +362,12 @@
 	uint32_t buffer_size;
 	struct ffa_value ret;
 
+	/* Acquire receiver's RX buffer. */
+	if (!plat_ffa_acquire_receiver_rx(vm_locked, &ret)) {
+		dlog_verbose("Failed to acquire RX buffer for VM %x\n", vm->id);
+		return ret;
+	}
+
 	if (vm_is_mailbox_busy(vm_locked)) {
 		/*
 		 * Can't retrieve memory information if the mailbox is not
@@ -371,12 +377,6 @@
 		return ffa_error(FFA_BUSY);
 	}
 
-	/* Acquire receiver's RX buffer. */
-	if (!plat_ffa_acquire_receiver_rx(vm_locked, &ret)) {
-		dlog_verbose("Failed to acquire RX buffer for VM %x\n", vm->id);
-		return ret;
-	}
-
 	if (version == MAKE_FFA_VERSION(1, 0)) {
 		struct ffa_partition_info_v1_0 *recv_mailbox = vm->mailbox.recv;
 
@@ -425,7 +425,7 @@
 	/* Sender is Hypervisor in the normal world (TEE in secure world). */
 	vm->mailbox.recv_sender = HF_VM_ID_BASE;
 	vm->mailbox.recv_func = FFA_PARTITION_INFO_GET_32;
-	vm->mailbox.state = MAILBOX_STATE_READ;
+	vm->mailbox.state = MAILBOX_STATE_FULL;
 
 	/*
 	 * Return the count of partition information descriptors in w2
@@ -863,10 +863,65 @@
 	}
 }
 
+/**
+ * Change the state of mailbox to empty, such that the ownership is given to the
+ * Partition manager.
+ * Returns true if the mailbox was reset successfully, false otherwise.
+ */
+static bool api_release_mailbox(struct vm_locked vm_locked, int32_t *error_code)
+{
+	ffa_vm_id_t vm_id = vm_locked.vm->id;
+	int32_t error_code_to_ret = 0;
+	bool ret = false;
+
+	switch (vm_locked.vm->mailbox.state) {
+	case MAILBOX_STATE_EMPTY:
+		dlog_verbose("Mailbox of %x is empty.\n", vm_id);
+		error_code_to_ret = FFA_DENIED;
+		goto out;
+	case MAILBOX_STATE_FULL:
+		/* Check it doesn't have pending RX full notifications. */
+		if (vm_are_fwk_notifications_pending(vm_locked)) {
+			dlog_verbose(
+				"Mailbox of endpoint %x has pending "
+				"messages.\n",
+				vm_id);
+			error_code_to_ret = FFA_DENIED;
+			goto out;
+		}
+		break;
+	case MAILBOX_STATE_OTHER_WORLD_OWNED:
+		/*
+		 * The SPMC shouldn't let SP's mailbox get into this state.
+		 * For the Hypervisor, the VM may call FFA_RX_RELEASE, whilst
+		 * the mailbox is in this state. In that case, we should report
+		 * error.
+		 */
+		if (vm_id_is_current_world(vm_id)) {
+			dlog_verbose(
+				"Mailbox of endpoint %x in a wrongful state.\n",
+				vm_id);
+			error_code_to_ret = FFA_ABORTED;
+			goto out;
+		}
+		break;
+	}
+
+	vm_locked.vm->mailbox.state = MAILBOX_STATE_EMPTY;
+	ret = true;
+out:
+	if (error_code != NULL) {
+		*error_code = error_code_to_ret;
+	}
+
+	return ret;
+}
+
 struct ffa_value api_ffa_msg_wait(struct vcpu *current, struct vcpu **next,
 				  struct ffa_value *args)
 {
 	enum vcpu_state next_state = VCPU_STATE_WAITING;
+	struct ffa_value ret;
 
 	if (args->arg1 != 0U || args->arg2 != 0U || args->arg3 != 0U ||
 	    args->arg4 != 0U || args->arg5 != 0U || args->arg6 != 0U ||
@@ -882,7 +937,16 @@
 
 	assert(next_state == VCPU_STATE_WAITING);
 
-	return plat_ffa_msg_wait_prepare(current, next);
+	ret = plat_ffa_msg_wait_prepare(current, next);
+
+	if (ret.func != FFA_ERROR_32) {
+		struct vm_locked vm_locked = vm_lock(current->vm);
+
+		api_release_mailbox(vm_locked, NULL);
+		vm_unlock(&vm_locked);
+	}
+
+	return ret;
 }
 
 /**
@@ -1004,12 +1068,9 @@
 		 * A pending message allows the vCPU to run so the message can
 		 * be delivered directly.
 		 */
-		if (vcpu->vm->mailbox.state == MAILBOX_STATE_RECEIVED) {
+		if (vcpu->vm->mailbox.state == MAILBOX_STATE_FULL) {
 			arch_regs_set_retval(&vcpu->regs,
 					     ffa_msg_recv_return(vcpu->vm));
-			if (vcpu->vm->mailbox.recv_func == FFA_MSG_SEND_32) {
-				vcpu->vm->mailbox.state = MAILBOX_STATE_READ;
-			}
 			break;
 		}
 
@@ -1215,41 +1276,6 @@
 }
 
 /**
- * Determines the value to be returned by api_ffa_rxtx_map and
- * api_ffa_rx_release after they've succeeded. If a secondary VM is running and
- * there are waiters, it also switches back to the primary VM for it to wake
- * waiters up.
- */
-static struct ffa_value api_waiter_result(struct vm_locked locked_vm,
-					  struct vcpu *current,
-					  struct vcpu **next)
-{
-	struct vm *vm = locked_vm.vm;
-
-	CHECK(list_empty(&vm->mailbox.waiter_list));
-
-	if (list_empty(&vm->mailbox.waiter_list)) {
-		/* No waiters, nothing else to do. */
-		return (struct ffa_value){.func = FFA_SUCCESS_32};
-	}
-
-	if (vm->id == HF_PRIMARY_VM_ID) {
-		/* The caller is the primary VM. Tell it to wake up waiters. */
-		return (struct ffa_value){.func = FFA_RX_RELEASE_32};
-	}
-
-	/*
-	 * Switch back to the primary VM, informing it that there are waiters
-	 * that need to be notified.
-	 */
-	*next = api_switch_to_primary(
-		current, (struct ffa_value){.func = FFA_RX_RELEASE_32},
-		VCPU_STATE_WAITING);
-
-	return (struct ffa_value){.func = FFA_SUCCESS_32};
-}
-
-/**
  * Configures the hypervisor's stage-1 view of the send and receive pages.
  */
 static bool api_vm_configure_stage1(struct mm_stage1_locked mm_stage1_locked,
@@ -1768,8 +1794,7 @@
 		goto out;
 	}
 
-	if (to->mailbox.state != MAILBOX_STATE_EMPTY ||
-	    to->mailbox.recv == NULL) {
+	if (vm_is_mailbox_busy(to_locked)) {
 		dlog_error(
 			"Cannot deliver message to VM %#x, RX buffer not "
 			"ready.\n",
@@ -1798,7 +1823,7 @@
 	to->mailbox.recv_size = msg_size;
 	to->mailbox.recv_sender = sender_id;
 	to->mailbox.recv_func = FFA_MSG_SEND2_32;
-	to->mailbox.state = MAILBOX_STATE_RECEIVED;
+	to->mailbox.state = MAILBOX_STATE_FULL;
 
 	rx_buffer_full = plat_ffa_is_vm_id(sender_id)
 				 ? FFA_NOTIFICATION_HYP_BUFFER_FULL_MASK
@@ -1886,10 +1911,10 @@
 	sl_lock(&vm->lock);
 
 	/* Return pending messages without blocking. */
-	if (vm->mailbox.state == MAILBOX_STATE_RECEIVED) {
+	if (vm->mailbox.state == MAILBOX_STATE_FULL) {
 		return_code = ffa_msg_recv_return(vm);
 		if (return_code.func == FFA_MSG_SEND_32) {
-			vm->mailbox.state = MAILBOX_STATE_READ;
+			vm->mailbox.state = MAILBOX_STATE_EMPTY;
 		}
 		goto out;
 	}
@@ -2025,7 +2050,7 @@
  *    hf_mailbox_waiter_get.
  */
 struct ffa_value api_ffa_rx_release(ffa_vm_id_t receiver_id,
-				    struct vcpu *current, struct vcpu **next)
+				    struct vcpu *current)
 {
 	struct vm *current_vm = current->vm;
 	struct vm *vm;
@@ -2033,6 +2058,7 @@
 	ffa_vm_id_t current_vm_id = current_vm->id;
 	ffa_vm_id_t release_vm_id;
 	struct ffa_value ret;
+	int32_t error_code;
 
 	/* `receiver_id` can be set only at Non-Secure Physical interface. */
 	if (vm_id_is_current_world(current_vm_id) && (receiver_id != 0)) {
@@ -2058,49 +2084,16 @@
 		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);
+	if (plat_ffa_rx_release_forward(vm_locked, &ret)) {
 		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};
+	if (!api_release_mailbox(vm_locked, &error_code)) {
+		ret = ffa_error(error_code);
 		goto out;
 	}
 
-	switch (vm->mailbox.state) {
-	case MAILBOX_STATE_EMPTY:
-		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(vm_locked, current, next);
-		vm->mailbox.state = MAILBOX_STATE_EMPTY;
-		break;
-	}
+	ret = (struct ffa_value){.func = FFA_SUCCESS_32};
 
 out:
 	vm_unlock(&vm_locked);
@@ -2152,7 +2145,7 @@
 		goto out;
 	}
 
-	receiver->mailbox.state = MAILBOX_STATE_RECEIVED;
+	receiver->mailbox.state = MAILBOX_STATE_OTHER_WORLD_OWNED;
 
 	ret = (struct ffa_value){.func = FFA_SUCCESS_32};
 
@@ -3073,10 +3066,12 @@
 	 */
 	memcpy_s(retrieve_request, message_buffer_size, to_msg, length);
 
-	if (vm_is_mailbox_busy(to_locked)) {
+	if ((vm_is_mailbox_other_world_owned(to_locked) &&
+	     !plat_ffa_acquire_receiver_rx(to_locked, &ret)) ||
+	    vm_is_mailbox_busy(to_locked)) {
 		/*
-		 * Can't retrieve memory information if the mailbox is not
-		 * available.
+		 * Can't retrieve memory information if the mailbox is
+		 * not available.
 		 */
 		dlog_verbose("%s: RX buffer not ready.\n", __func__);
 		ret = ffa_error(FFA_BUSY);