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);
+}