VHE: Tests: Add mailbox tests to EL0 partitions.

This patch adds the mailbox test suite from the primary_with_secondaries
test suite and applies it to EL0 partitions. Most of the tests remain
exactly the same, except the primary_to_secondary test. This particular
test injects interrupts into a VM, but since interrupt injection is not
allowed, we need to remove the code. The error code expected when the
partition uses a WFE or WFI is different for EL0 partitions as well,
since the partition just yields on use of WFE/WFI. The
echo_with_notification service is also modified to remove
interrupt/exception handling and any other dependencies on interrupts
since those dont apply to EL0 partitions.

Change-Id: I1a82990ed5abdcfc3f5e2e045cbbc3abf1563d49
Signed-off-by: Raghu Krishnamurthy <raghu.ncstate@gmail.com>
diff --git a/test/vmapi/el0_partitions/BUILD.gn b/test/vmapi/el0_partitions/BUILD.gn
index 51514e5..ae66d9d 100644
--- a/test/vmapi/el0_partitions/BUILD.gn
+++ b/test/vmapi/el0_partitions/BUILD.gn
@@ -21,6 +21,7 @@
     "//test/vmapi/primary_with_secondaries/ffa.c",
   ]
 }
+
 vm_kernel("el0_partition_test_vm") {
   testonly = true
   public_configs = [
@@ -29,7 +30,9 @@
   ]
 
   sources = [
+    "//test/vmapi/primary_with_secondaries/mailbox_common.c",
     "boot.c",
+    "mailbox.c",
   ]
 
   deps = [
diff --git a/test/vmapi/el0_partitions/mailbox.c b/test/vmapi/el0_partitions/mailbox.c
new file mode 100644
index 0000000..1285014
--- /dev/null
+++ b/test/vmapi/el0_partitions/mailbox.c
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2021 The Hafnium Authors.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://opensource.org/licenses/BSD-3-Clause.
+ */
+
+#include <stdint.h>
+
+#include "hf/ffa.h"
+#include "hf/std.h"
+
+#include "vmapi/hf/call.h"
+
+#include "primary_with_secondary.h"
+#include "test/hftest.h"
+#include "test/vmapi/ffa.h"
+
+/**
+ * Causes secondary VM to send two messages to primary VM. The second message
+ * will reach the mailbox while it's not writable. Checks that notifications are
+ * properly delivered when mailbox is cleared.
+ */
+TEST(mailbox, primary_to_secondary)
+{
+	char message[] = "not ready echo";
+	struct ffa_value run_res;
+	struct mailbox_buffers mb = set_up_mailbox();
+
+	SERVICE_SELECT(SERVICE_VM1, "echo_with_notification", mb.send);
+
+	run_res = ffa_run(SERVICE_VM1, 0);
+	EXPECT_EQ(run_res.func, FFA_MSG_WAIT_32);
+	EXPECT_EQ(run_res.arg2, FFA_SLEEP_INDEFINITE);
+
+	/* Send a message to echo service, and get response back. */
+	memcpy_s(mb.send, FFA_MSG_PAYLOAD_MAX, message, sizeof(message));
+	EXPECT_EQ(
+		ffa_msg_send(HF_PRIMARY_VM_ID, SERVICE_VM1, sizeof(message), 0)
+			.func,
+		FFA_SUCCESS_32);
+	run_res = ffa_run(SERVICE_VM1, 0);
+	EXPECT_EQ(run_res.func, FFA_MSG_SEND_32);
+	EXPECT_EQ(ffa_msg_send_size(run_res), sizeof(message));
+	EXPECT_EQ(memcmp(mb.recv, message, sizeof(message)), 0);
+
+	/* Let secondary VM continue running so that it will wait again. */
+	run_res = ffa_run(SERVICE_VM1, 0);
+	EXPECT_EQ(run_res.func, FFA_MSG_WAIT_32);
+	EXPECT_EQ(run_res.arg2, FFA_SLEEP_INDEFINITE);
+
+	/* Without clearing our mailbox, send message again. */
+	reverse(message, strnlen_s(message, sizeof(message)));
+	memcpy_s(mb.send, FFA_MSG_PAYLOAD_MAX, message, sizeof(message));
+
+	/* Message should be dropped since the mailbox was not cleared. */
+	EXPECT_EQ(
+		ffa_msg_send(HF_PRIMARY_VM_ID, SERVICE_VM1, sizeof(message), 0)
+			.func,
+		FFA_SUCCESS_32);
+	run_res = ffa_run(SERVICE_VM1, 0);
+	EXPECT_EQ(run_res.func, FFA_YIELD_32);
+	EXPECT_EQ(run_res.arg2, FFA_SLEEP_INDEFINITE);
+
+	/* Clear the mailbox. We expect to be told there are pending waiters. */
+	EXPECT_EQ(ffa_rx_release().func, FFA_RX_RELEASE_32);
+
+	/* Retrieve a single waiter. */
+	EXPECT_EQ(hf_mailbox_waiter_get(HF_PRIMARY_VM_ID), SERVICE_VM1);
+	EXPECT_EQ(hf_mailbox_waiter_get(HF_PRIMARY_VM_ID), -1);
+
+	run_res = ffa_run(SERVICE_VM1, 0);
+	EXPECT_EQ(run_res.func, FFA_MSG_SEND_32);
+	EXPECT_EQ(ffa_msg_send_size(run_res), sizeof(message));
+	EXPECT_EQ(memcmp(mb.recv, message, sizeof(message)), 0);
+	EXPECT_EQ(ffa_rx_release().func, FFA_SUCCESS_32);
+}
diff --git a/test/vmapi/el0_partitions/services/BUILD.gn b/test/vmapi/el0_partitions/services/BUILD.gn
index 2cf7693..088282c 100644
--- a/test/vmapi/el0_partitions/services/BUILD.gn
+++ b/test/vmapi/el0_partitions/services/BUILD.gn
@@ -63,10 +63,13 @@
   ]
   sources = [
     "boot.c",
+    "echo_with_notification.c",
   ]
   deps = [
     ":hftest_secondary_el0_partition",
+    "//test/vmapi/primary_with_secondaries/services:echo",
     "//test/vmapi/primary_with_secondaries/services:ffa_check",
+    "//test/vmapi/primary_with_secondaries/services:relay",
     "//test/vmapi/primary_with_secondaries/services:run_waiting",
   ]
 }
@@ -75,6 +78,7 @@
   testonly = true
   deps = [
     ":hftest_secondary_el0_partition",
+    "//test/vmapi/primary_with_secondaries/services:relay",
   ]
 }
 
diff --git a/test/vmapi/el0_partitions/services/echo_with_notification.c b/test/vmapi/el0_partitions/services/echo_with_notification.c
new file mode 100644
index 0000000..85991d3
--- /dev/null
+++ b/test/vmapi/el0_partitions/services/echo_with_notification.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2021 The Hafnium Authors.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://opensource.org/licenses/BSD-3-Clause.
+ */
+
+#include "hf/ffa.h"
+#include "hf/std.h"
+
+#include "vmapi/hf/call.h"
+
+#include "test/hftest.h"
+
+static void wait_for_vm(uint32_t vmid)
+{
+	uint16_t retry_count = 0;
+	for (;;) {
+		retry_count++;
+		int64_t w = hf_mailbox_writable_get();
+		if (w == vmid) {
+			return;
+		}
+
+		if (w == -1) {
+			__asm__ volatile("wfe");
+		}
+		/*
+		 * On FVP, WFI/WFE done trap to EL2 even though SCTLR_EL2 is
+		 * setup to trap these instructions. The architecture does not
+		 * guarantee that these instructions will be trapped, only that
+		 * it may be trapped if it does not complete in finite time. To
+		 * work around this, if there are more than a threshold number
+		 * of retries, simply call yiled to allow primary VM to get back
+		 * control. Note that on QEMU, WFI/WFE trap just fine.
+		 */
+		if (retry_count > 1000) {
+			ffa_yield();
+			retry_count = 0;
+		}
+	}
+}
+
+TEST_SERVICE(echo_with_notification)
+{
+	/* Loop, echo messages back to the sender. */
+	for (;;) {
+		void *send_buf = SERVICE_SEND_BUFFER();
+		void *recv_buf = SERVICE_RECV_BUFFER();
+		struct ffa_value ret = ffa_msg_wait();
+		ffa_vm_id_t target_vm_id = ffa_receiver(ret);
+		ffa_vm_id_t source_vm_id = ffa_sender(ret);
+
+		memcpy_s(send_buf, FFA_MSG_PAYLOAD_MAX, recv_buf,
+			 ffa_msg_send_size(ret));
+
+		while (ffa_msg_send(target_vm_id, source_vm_id,
+				    ffa_msg_send_size(ret), FFA_MSG_SEND_NOTIFY)
+			       .func != FFA_SUCCESS_32) {
+			wait_for_vm(source_vm_id);
+		}
+
+		EXPECT_EQ(ffa_rx_release().func, FFA_SUCCESS_32);
+	}
+}
diff --git a/test/vmapi/primary_with_secondaries/BUILD.gn b/test/vmapi/primary_with_secondaries/BUILD.gn
index 3bd00c9..55d7861 100644
--- a/test/vmapi/primary_with_secondaries/BUILD.gn
+++ b/test/vmapi/primary_with_secondaries/BUILD.gn
@@ -85,6 +85,7 @@
     "floating_point.c",
     "interrupts.c",
     "mailbox.c",
+    "mailbox_common.c",
     "memory_sharing.c",
     "no_services.c",
     "perfmon.c",
diff --git a/test/vmapi/primary_with_secondaries/inc/primary_with_secondary.h b/test/vmapi/primary_with_secondaries/inc/primary_with_secondary.h
index 043a9cb..598819c 100644
--- a/test/vmapi/primary_with_secondaries/inc/primary_with_secondary.h
+++ b/test/vmapi/primary_with_secondaries/inc/primary_with_secondary.h
@@ -8,6 +8,8 @@
 
 #pragma once
 
+#include <stddef.h>
+
 #define SERVICE_VM1 (HF_VM_ID_OFFSET + 1)
 #define SERVICE_VM2 (HF_VM_ID_OFFSET + 2)
 #define SERVICE_VM3 (HF_VM_ID_OFFSET + 3)
@@ -16,3 +18,5 @@
 #define EXTERNAL_INTERRUPT_ID_A 7
 #define EXTERNAL_INTERRUPT_ID_B 8
 #define EXTERNAL_INTERRUPT_ID_C 9
+
+void reverse(char *s, size_t len);
diff --git a/test/vmapi/primary_with_secondaries/mailbox.c b/test/vmapi/primary_with_secondaries/mailbox.c
index 9681f51..d9597a6 100644
--- a/test/vmapi/primary_with_secondaries/mailbox.c
+++ b/test/vmapi/primary_with_secondaries/mailbox.c
@@ -18,228 +18,6 @@
 #include "test/vmapi/ffa.h"
 
 /**
- * Reverses the order of the elements in the given array.
- */
-static void reverse(char *s, size_t len)
-{
-	size_t i;
-
-	for (i = 0; i < len / 2; i++) {
-		char t = s[i];
-		s[i] = s[len - 1 - i];
-		s[len - 1 - i] = t;
-	}
-}
-
-/**
- * Finds the next lexicographic permutation of the given array, if there is one.
- */
-static void next_permutation(char *s, size_t len)
-{
-	size_t i;
-	size_t j;
-
-	for (i = len - 2; i < len; i--) {
-		const char t = s[i];
-		if (t >= s[i + 1]) {
-			continue;
-		}
-
-		for (j = len - 1; t >= s[j]; j--) {
-		}
-
-		s[i] = s[j];
-		s[j] = t;
-		reverse(s + i + 1, len - i - 1);
-		return;
-	}
-}
-
-TEAR_DOWN(mailbox)
-{
-	EXPECT_FFA_ERROR(ffa_rx_release(), FFA_DENIED);
-}
-
-/**
- * Clearing an empty mailbox is an error.
- */
-TEST(mailbox, clear_empty)
-{
-	EXPECT_FFA_ERROR(ffa_rx_release(), FFA_DENIED);
-	EXPECT_FFA_ERROR(ffa_rx_release(), FFA_DENIED);
-	EXPECT_FFA_ERROR(ffa_rx_release(), FFA_DENIED);
-}
-
-/**
- * Send and receive the same message from the echo VM.
- */
-TEST(mailbox, echo)
-{
-	const char message[] = "Echo this back to me!";
-	struct ffa_value run_res;
-	struct mailbox_buffers mb = set_up_mailbox();
-
-	SERVICE_SELECT(SERVICE_VM1, "echo", mb.send);
-
-	run_res = ffa_run(SERVICE_VM1, 0);
-	EXPECT_EQ(run_res.func, FFA_MSG_WAIT_32);
-	EXPECT_EQ(run_res.arg2, FFA_SLEEP_INDEFINITE);
-
-	/* Set the message, echo it and check it didn't change. */
-	memcpy_s(mb.send, FFA_MSG_PAYLOAD_MAX, message, sizeof(message));
-	EXPECT_EQ(
-		ffa_msg_send(HF_PRIMARY_VM_ID, SERVICE_VM1, sizeof(message), 0)
-			.func,
-		FFA_SUCCESS_32);
-	run_res = ffa_run(SERVICE_VM1, 0);
-	EXPECT_EQ(run_res.func, FFA_MSG_SEND_32);
-	EXPECT_EQ(ffa_msg_send_size(run_res), sizeof(message));
-	EXPECT_EQ(memcmp(mb.recv, message, sizeof(message)), 0);
-	EXPECT_EQ(ffa_rx_release().func, FFA_SUCCESS_32);
-}
-
-/**
- * Repeatedly send a message and receive it back from the echo VM.
- */
-TEST(mailbox, repeated_echo)
-{
-	char message[] = "Echo this back to me!";
-	struct ffa_value run_res;
-	uint8_t i;
-	struct mailbox_buffers mb = set_up_mailbox();
-
-	SERVICE_SELECT(SERVICE_VM1, "echo", mb.send);
-
-	for (i = 0; i < 100; i++) {
-		/* Run secondary until it reaches the wait for messages. */
-		run_res = ffa_run(SERVICE_VM1, 0);
-		EXPECT_EQ(run_res.func, FFA_MSG_WAIT_32);
-		EXPECT_EQ(run_res.arg2, FFA_SLEEP_INDEFINITE);
-
-		/* Set the message, echo it and check it didn't change. */
-		next_permutation(message, sizeof(message) - 1);
-		memcpy_s(mb.send, FFA_MSG_PAYLOAD_MAX, message,
-			 sizeof(message));
-		EXPECT_EQ(ffa_msg_send(HF_PRIMARY_VM_ID, SERVICE_VM1,
-				       sizeof(message), 0)
-				  .func,
-			  FFA_SUCCESS_32);
-		run_res = ffa_run(SERVICE_VM1, 0);
-		EXPECT_EQ(run_res.func, FFA_MSG_SEND_32);
-		EXPECT_EQ(ffa_msg_send_size(run_res), sizeof(message));
-		EXPECT_EQ(memcmp(mb.recv, message, sizeof(message)), 0);
-		EXPECT_EQ(ffa_rx_release().func, FFA_SUCCESS_32);
-	}
-}
-
-/**
- * Send a message to relay_a which will forward it to relay_b where it will be
- * sent back here.
- */
-TEST(mailbox, relay)
-{
-	const char message[] = "Send this round the relay!";
-	struct ffa_value run_res;
-	struct mailbox_buffers mb = set_up_mailbox();
-
-	SERVICE_SELECT(SERVICE_VM1, "relay", mb.send);
-	SERVICE_SELECT(SERVICE_VM2, "relay", mb.send);
-
-	run_res = ffa_run(SERVICE_VM1, 0);
-	EXPECT_EQ(run_res.func, FFA_MSG_WAIT_32);
-	EXPECT_EQ(run_res.arg2, FFA_SLEEP_INDEFINITE);
-	run_res = ffa_run(SERVICE_VM2, 0);
-	EXPECT_EQ(run_res.func, FFA_MSG_WAIT_32);
-	EXPECT_EQ(run_res.arg2, FFA_SLEEP_INDEFINITE);
-
-	/*
-	 * Build the message chain so the message is sent from here to
-	 * SERVICE_VM1, then to SERVICE_VM2 and finally back to here.
-	 */
-	{
-		ffa_vm_id_t *chain = (ffa_vm_id_t *)mb.send;
-		*chain++ = htole32(SERVICE_VM2);
-		*chain++ = htole32(HF_PRIMARY_VM_ID);
-		memcpy_s(chain, FFA_MSG_PAYLOAD_MAX - (2 * sizeof(ffa_vm_id_t)),
-			 message, sizeof(message));
-
-		EXPECT_EQ(
-			ffa_msg_send(
-				HF_PRIMARY_VM_ID, SERVICE_VM1,
-				sizeof(message) + (2 * sizeof(ffa_vm_id_t)), 0)
-				.func,
-			FFA_SUCCESS_32);
-	}
-
-	/* Let SERVICE_VM1 forward the message. */
-	run_res = ffa_run(SERVICE_VM1, 0);
-	EXPECT_EQ(run_res.func, FFA_MSG_SEND_32);
-	EXPECT_EQ(ffa_receiver(run_res), SERVICE_VM2);
-	EXPECT_EQ(ffa_msg_send_size(run_res), 0);
-
-	/* Let SERVICE_VM2 forward the message. */
-	run_res = ffa_run(SERVICE_VM2, 0);
-	EXPECT_EQ(run_res.func, FFA_MSG_SEND_32);
-
-	/* Ensure the message is intact. */
-	EXPECT_EQ(ffa_receiver(run_res), HF_PRIMARY_VM_ID);
-	EXPECT_EQ(ffa_msg_send_size(run_res), sizeof(message));
-	EXPECT_EQ(memcmp(mb.recv, message, sizeof(message)), 0);
-	EXPECT_EQ(ffa_rx_release().func, FFA_SUCCESS_32);
-}
-
-/**
- * Send a message before the secondary VM is configured, but do not register
- * for notification. Ensure we're not notified.
- */
-TEST(mailbox, no_primary_to_secondary_notification_on_configure)
-{
-	struct ffa_value run_res;
-
-	set_up_mailbox();
-
-	EXPECT_FFA_ERROR(ffa_msg_send(HF_PRIMARY_VM_ID, SERVICE_VM1, 0, 0),
-			 FFA_BUSY);
-
-	run_res = ffa_run(SERVICE_VM1, 0);
-	EXPECT_EQ(run_res.func, FFA_MSG_WAIT_32);
-	EXPECT_EQ(run_res.arg2, FFA_SLEEP_INDEFINITE);
-
-	EXPECT_EQ(ffa_msg_send(HF_PRIMARY_VM_ID, SERVICE_VM1, 0, 0).func,
-		  FFA_SUCCESS_32);
-}
-
-/**
- * Send a message before the secondary VM is configured, and receive a
- * notification when it configures.
- */
-TEST(mailbox, secondary_to_primary_notification_on_configure)
-{
-	struct ffa_value run_res;
-
-	set_up_mailbox();
-
-	EXPECT_FFA_ERROR(ffa_msg_send(HF_PRIMARY_VM_ID, SERVICE_VM1, 0,
-				      FFA_MSG_SEND_NOTIFY),
-			 FFA_BUSY);
-
-	/*
-	 * Run first VM for it to configure itself. It should result in
-	 * notifications having to be issued.
-	 */
-	run_res = ffa_run(SERVICE_VM1, 0);
-	EXPECT_EQ(run_res.func, FFA_RX_RELEASE_32);
-
-	/* A single waiter is returned. */
-	EXPECT_EQ(hf_mailbox_waiter_get(SERVICE_VM1), HF_PRIMARY_VM_ID);
-	EXPECT_EQ(hf_mailbox_waiter_get(SERVICE_VM1), -1);
-
-	/* Send should now succeed. */
-	EXPECT_EQ(ffa_msg_send(HF_PRIMARY_VM_ID, SERVICE_VM1, 0, 0).func,
-		  FFA_SUCCESS_32);
-}
-
-/**
  * Causes secondary VM to send two messages to primary VM. The second message
  * will reach the mailbox while it's not writable. Checks that notifications are
  * properly delivered when mailbox is cleared.
@@ -305,52 +83,3 @@
 	EXPECT_EQ(memcmp(mb.recv, message, sizeof(message)), 0);
 	EXPECT_EQ(ffa_rx_release().func, FFA_SUCCESS_32);
 }
-
-/**
- * Sends two messages to secondary VM without letting it run, so second message
- * won't go through. Ensure that a notification is delivered when secondary VM
- * clears the mailbox.
- */
-TEST(mailbox, secondary_to_primary_notification)
-{
-	const char message[] = "not ready echo";
-	struct ffa_value run_res;
-	struct mailbox_buffers mb = set_up_mailbox();
-
-	SERVICE_SELECT(SERVICE_VM1, "echo_with_notification", mb.send);
-
-	run_res = ffa_run(SERVICE_VM1, 0);
-	EXPECT_EQ(run_res.func, FFA_MSG_WAIT_32);
-	EXPECT_EQ(run_res.arg2, FFA_SLEEP_INDEFINITE);
-
-	/* Send a message to echo service twice. The second should fail. */
-	memcpy_s(mb.send, FFA_MSG_PAYLOAD_MAX, message, sizeof(message));
-	EXPECT_EQ(
-		ffa_msg_send(HF_PRIMARY_VM_ID, SERVICE_VM1, sizeof(message), 0)
-			.func,
-		FFA_SUCCESS_32);
-	EXPECT_FFA_ERROR(ffa_msg_send(HF_PRIMARY_VM_ID, SERVICE_VM1,
-				      sizeof(message), FFA_MSG_SEND_NOTIFY),
-			 FFA_BUSY);
-
-	/* Receive a reply for the first message. */
-	run_res = ffa_run(SERVICE_VM1, 0);
-	EXPECT_EQ(run_res.func, FFA_MSG_SEND_32);
-	EXPECT_EQ(ffa_msg_send_size(run_res), sizeof(message));
-	EXPECT_EQ(memcmp(mb.recv, message, sizeof(message)), 0);
-	EXPECT_EQ(ffa_rx_release().func, FFA_SUCCESS_32);
-
-	/* Run VM again so that it clears its mailbox. */
-	run_res = ffa_run(SERVICE_VM1, 0);
-	EXPECT_EQ(run_res.func, FFA_RX_RELEASE_32);
-
-	/* Retrieve a single waiter. */
-	EXPECT_EQ(hf_mailbox_waiter_get(SERVICE_VM1), HF_PRIMARY_VM_ID);
-	EXPECT_EQ(hf_mailbox_waiter_get(SERVICE_VM1), -1);
-
-	/* Send should now succeed. */
-	EXPECT_EQ(
-		ffa_msg_send(HF_PRIMARY_VM_ID, SERVICE_VM1, sizeof(message), 0)
-			.func,
-		FFA_SUCCESS_32);
-}
diff --git a/test/vmapi/primary_with_secondaries/mailbox_common.c b/test/vmapi/primary_with_secondaries/mailbox_common.c
new file mode 100644
index 0000000..64015e7
--- /dev/null
+++ b/test/vmapi/primary_with_secondaries/mailbox_common.c
@@ -0,0 +1,289 @@
+/*
+ * Copyright 2021 The Hafnium Authors.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://opensource.org/licenses/BSD-3-Clause.
+ */
+
+#include <stdint.h>
+
+#include "hf/ffa.h"
+#include "hf/std.h"
+
+#include "vmapi/hf/call.h"
+
+#include "primary_with_secondary.h"
+#include "test/hftest.h"
+#include "test/vmapi/ffa.h"
+
+/**
+ * Reverses the order of the elements in the given array.
+ */
+void reverse(char *s, size_t len)
+{
+	size_t i;
+
+	for (i = 0; i < len / 2; i++) {
+		char t = s[i];
+		s[i] = s[len - 1 - i];
+		s[len - 1 - i] = t;
+	}
+}
+
+/**
+ * Finds the next lexicographic permutation of the given array, if there is one.
+ */
+static void next_permutation(char *s, size_t len)
+{
+	size_t i;
+	size_t j;
+
+	for (i = len - 2; i < len; i--) {
+		const char t = s[i];
+		if (t >= s[i + 1]) {
+			continue;
+		}
+
+		for (j = len - 1; t >= s[j]; j--) {
+		}
+
+		s[i] = s[j];
+		s[j] = t;
+		reverse(s + i + 1, len - i - 1);
+		return;
+	}
+}
+
+TEAR_DOWN(mailbox)
+{
+	EXPECT_FFA_ERROR(ffa_rx_release(), FFA_DENIED);
+}
+
+/**
+ * Clearing an empty mailbox is an error.
+ */
+TEST(mailbox, clear_empty)
+{
+	EXPECT_FFA_ERROR(ffa_rx_release(), FFA_DENIED);
+	EXPECT_FFA_ERROR(ffa_rx_release(), FFA_DENIED);
+	EXPECT_FFA_ERROR(ffa_rx_release(), FFA_DENIED);
+}
+
+/**
+ * Send and receive the same message from the echo VM.
+ */
+TEST(mailbox, echo)
+{
+	const char message[] = "Echo this back to me!";
+	struct ffa_value run_res;
+	struct mailbox_buffers mb = set_up_mailbox();
+
+	SERVICE_SELECT(SERVICE_VM1, "echo", mb.send);
+
+	run_res = ffa_run(SERVICE_VM1, 0);
+	EXPECT_EQ(run_res.func, FFA_MSG_WAIT_32);
+	EXPECT_EQ(run_res.arg2, FFA_SLEEP_INDEFINITE);
+
+	/* Set the message, echo it and check it didn't change. */
+	memcpy_s(mb.send, FFA_MSG_PAYLOAD_MAX, message, sizeof(message));
+	EXPECT_EQ(
+		ffa_msg_send(HF_PRIMARY_VM_ID, SERVICE_VM1, sizeof(message), 0)
+			.func,
+		FFA_SUCCESS_32);
+	run_res = ffa_run(SERVICE_VM1, 0);
+	EXPECT_EQ(run_res.func, FFA_MSG_SEND_32);
+	EXPECT_EQ(ffa_msg_send_size(run_res), sizeof(message));
+	EXPECT_EQ(memcmp(mb.recv, message, sizeof(message)), 0);
+	EXPECT_EQ(ffa_rx_release().func, FFA_SUCCESS_32);
+}
+
+/**
+ * Repeatedly send a message and receive it back from the echo VM.
+ */
+TEST(mailbox, repeated_echo)
+{
+	char message[] = "Echo this back to me!";
+	struct ffa_value run_res;
+	uint8_t i;
+	struct mailbox_buffers mb = set_up_mailbox();
+
+	SERVICE_SELECT(SERVICE_VM1, "echo", mb.send);
+
+	for (i = 0; i < 100; i++) {
+		/* Run secondary until it reaches the wait for messages. */
+		run_res = ffa_run(SERVICE_VM1, 0);
+		EXPECT_EQ(run_res.func, FFA_MSG_WAIT_32);
+		EXPECT_EQ(run_res.arg2, FFA_SLEEP_INDEFINITE);
+
+		/* Set the message, echo it and check it didn't change. */
+		next_permutation(message, sizeof(message) - 1);
+		memcpy_s(mb.send, FFA_MSG_PAYLOAD_MAX, message,
+			 sizeof(message));
+		EXPECT_EQ(ffa_msg_send(HF_PRIMARY_VM_ID, SERVICE_VM1,
+				       sizeof(message), 0)
+				  .func,
+			  FFA_SUCCESS_32);
+		run_res = ffa_run(SERVICE_VM1, 0);
+		EXPECT_EQ(run_res.func, FFA_MSG_SEND_32);
+		EXPECT_EQ(ffa_msg_send_size(run_res), sizeof(message));
+		EXPECT_EQ(memcmp(mb.recv, message, sizeof(message)), 0);
+		EXPECT_EQ(ffa_rx_release().func, FFA_SUCCESS_32);
+	}
+}
+
+/**
+ * Send a message to relay_a which will forward it to relay_b where it will be
+ * sent back here.
+ */
+TEST(mailbox, relay)
+{
+	const char message[] = "Send this round the relay!";
+	struct ffa_value run_res;
+	struct mailbox_buffers mb = set_up_mailbox();
+
+	SERVICE_SELECT(SERVICE_VM1, "relay", mb.send);
+	SERVICE_SELECT(SERVICE_VM2, "relay", mb.send);
+
+	run_res = ffa_run(SERVICE_VM1, 0);
+	EXPECT_EQ(run_res.func, FFA_MSG_WAIT_32);
+	EXPECT_EQ(run_res.arg2, FFA_SLEEP_INDEFINITE);
+	run_res = ffa_run(SERVICE_VM2, 0);
+	EXPECT_EQ(run_res.func, FFA_MSG_WAIT_32);
+	EXPECT_EQ(run_res.arg2, FFA_SLEEP_INDEFINITE);
+
+	/*
+	 * Build the message chain so the message is sent from here to
+	 * SERVICE_VM1, then to SERVICE_VM2 and finally back to here.
+	 */
+	{
+		ffa_vm_id_t *chain = (ffa_vm_id_t *)mb.send;
+		*chain++ = htole32(SERVICE_VM2);
+		*chain++ = htole32(HF_PRIMARY_VM_ID);
+		memcpy_s(chain, FFA_MSG_PAYLOAD_MAX - (2 * sizeof(ffa_vm_id_t)),
+			 message, sizeof(message));
+
+		EXPECT_EQ(
+			ffa_msg_send(
+				HF_PRIMARY_VM_ID, SERVICE_VM1,
+				sizeof(message) + (2 * sizeof(ffa_vm_id_t)), 0)
+				.func,
+			FFA_SUCCESS_32);
+	}
+
+	/* Let SERVICE_VM1 forward the message. */
+	run_res = ffa_run(SERVICE_VM1, 0);
+	EXPECT_EQ(run_res.func, FFA_MSG_SEND_32);
+	EXPECT_EQ(ffa_receiver(run_res), SERVICE_VM2);
+	EXPECT_EQ(ffa_msg_send_size(run_res), 0);
+
+	/* Let SERVICE_VM2 forward the message. */
+	run_res = ffa_run(SERVICE_VM2, 0);
+	EXPECT_EQ(run_res.func, FFA_MSG_SEND_32);
+
+	/* Ensure the message is intact. */
+	EXPECT_EQ(ffa_receiver(run_res), HF_PRIMARY_VM_ID);
+	EXPECT_EQ(ffa_msg_send_size(run_res), sizeof(message));
+	EXPECT_EQ(memcmp(mb.recv, message, sizeof(message)), 0);
+	EXPECT_EQ(ffa_rx_release().func, FFA_SUCCESS_32);
+}
+
+/**
+ * Send a message before the secondary VM is configured, but do not register
+ * for notification. Ensure we're not notified.
+ */
+TEST(mailbox, no_primary_to_secondary_notification_on_configure)
+{
+	struct ffa_value run_res;
+
+	set_up_mailbox();
+
+	EXPECT_FFA_ERROR(ffa_msg_send(HF_PRIMARY_VM_ID, SERVICE_VM1, 0, 0),
+			 FFA_BUSY);
+
+	run_res = ffa_run(SERVICE_VM1, 0);
+	EXPECT_EQ(run_res.func, FFA_MSG_WAIT_32);
+	EXPECT_EQ(run_res.arg2, FFA_SLEEP_INDEFINITE);
+
+	EXPECT_EQ(ffa_msg_send(HF_PRIMARY_VM_ID, SERVICE_VM1, 0, 0).func,
+		  FFA_SUCCESS_32);
+}
+
+/**
+ * Send a message before the secondary VM is configured, and receive a
+ * notification when it configures.
+ */
+TEST(mailbox, secondary_to_primary_notification_on_configure)
+{
+	struct ffa_value run_res;
+
+	set_up_mailbox();
+
+	EXPECT_FFA_ERROR(ffa_msg_send(HF_PRIMARY_VM_ID, SERVICE_VM1, 0,
+				      FFA_MSG_SEND_NOTIFY),
+			 FFA_BUSY);
+
+	/*
+	 * Run first VM for it to configure itself. It should result in
+	 * notifications having to be issued.
+	 */
+	run_res = ffa_run(SERVICE_VM1, 0);
+	EXPECT_EQ(run_res.func, FFA_RX_RELEASE_32);
+
+	/* A single waiter is returned. */
+	EXPECT_EQ(hf_mailbox_waiter_get(SERVICE_VM1), HF_PRIMARY_VM_ID);
+	EXPECT_EQ(hf_mailbox_waiter_get(SERVICE_VM1), -1);
+
+	/* Send should now succeed. */
+	EXPECT_EQ(ffa_msg_send(HF_PRIMARY_VM_ID, SERVICE_VM1, 0, 0).func,
+		  FFA_SUCCESS_32);
+}
+
+/**
+ * Sends two messages to secondary VM without letting it run, so second message
+ * won't go through. Ensure that a notification is delivered when secondary VM
+ * clears the mailbox.
+ */
+TEST(mailbox, secondary_to_primary_notification)
+{
+	const char message[] = "not ready echo";
+	struct ffa_value run_res;
+	struct mailbox_buffers mb = set_up_mailbox();
+
+	SERVICE_SELECT(SERVICE_VM1, "echo_with_notification", mb.send);
+
+	run_res = ffa_run(SERVICE_VM1, 0);
+	EXPECT_EQ(run_res.func, FFA_MSG_WAIT_32);
+	EXPECT_EQ(run_res.arg2, FFA_SLEEP_INDEFINITE);
+
+	/* Send a message to echo service twice. The second should fail. */
+	memcpy_s(mb.send, FFA_MSG_PAYLOAD_MAX, message, sizeof(message));
+	EXPECT_EQ(
+		ffa_msg_send(HF_PRIMARY_VM_ID, SERVICE_VM1, sizeof(message), 0)
+			.func,
+		FFA_SUCCESS_32);
+	EXPECT_FFA_ERROR(ffa_msg_send(HF_PRIMARY_VM_ID, SERVICE_VM1,
+				      sizeof(message), FFA_MSG_SEND_NOTIFY),
+			 FFA_BUSY);
+
+	/* Receive a reply for the first message. */
+	run_res = ffa_run(SERVICE_VM1, 0);
+	EXPECT_EQ(run_res.func, FFA_MSG_SEND_32);
+	EXPECT_EQ(ffa_msg_send_size(run_res), sizeof(message));
+	EXPECT_EQ(memcmp(mb.recv, message, sizeof(message)), 0);
+	EXPECT_EQ(ffa_rx_release().func, FFA_SUCCESS_32);
+
+	/* Run VM again so that it clears its mailbox. */
+	run_res = ffa_run(SERVICE_VM1, 0);
+	EXPECT_EQ(run_res.func, FFA_RX_RELEASE_32);
+
+	/* Retrieve a single waiter. */
+	EXPECT_EQ(hf_mailbox_waiter_get(SERVICE_VM1), HF_PRIMARY_VM_ID);
+	EXPECT_EQ(hf_mailbox_waiter_get(SERVICE_VM1), -1);
+
+	/* Send should now succeed. */
+	EXPECT_EQ(
+		ffa_msg_send(HF_PRIMARY_VM_ID, SERVICE_VM1, sizeof(message), 0)
+			.func,
+		FFA_SUCCESS_32);
+}