Split test suites into thier own files.
Change-Id: I04b1cc92872a1eda90610bf6cdde3cd0aee5866f
diff --git a/test/vmapi/primary_with_secondaries/BUILD.gn b/test/vmapi/primary_with_secondaries/BUILD.gn
index 8126d67..8822959 100644
--- a/test/vmapi/primary_with_secondaries/BUILD.gn
+++ b/test/vmapi/primary_with_secondaries/BUILD.gn
@@ -25,10 +25,11 @@
sources = [
"abort.c",
+ "interrupts.c",
+ "mailbox.c",
"memory_sharing.c",
"no_services.c",
"run_race.c",
- "with_services.c",
]
sources += [ "util.c" ]
diff --git a/test/vmapi/primary_with_secondaries/interrupts.c b/test/vmapi/primary_with_secondaries/interrupts.c
new file mode 100644
index 0000000..c18c8c9
--- /dev/null
+++ b/test/vmapi/primary_with_secondaries/interrupts.c
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+
+#include "hf/std.h"
+
+#include "vmapi/hf/call.h"
+
+#include "hftest.h"
+#include "primary_with_secondary.h"
+
+/**
+ * Send a message to the interruptible VM, which will interrupt itself to send a
+ * response back.
+ */
+TEST(interrupts, interrupt_self)
+{
+ const char message[] = "Ping";
+ const char expected_response[] = "Got IRQ 05.";
+ struct hf_vcpu_run_return run_res;
+ struct mailbox_buffers mb = set_up_mailbox();
+
+ SERVICE_SELECT(SERVICE_VM0, "interruptible", mb.send);
+
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
+
+ /* Set the message, echo it and wait for a response. */
+ memcpy(mb.send, message, sizeof(message));
+ EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, sizeof(message), false), 0);
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
+ EXPECT_EQ(run_res.message.size, sizeof(expected_response));
+ EXPECT_EQ(memcmp(mb.recv, expected_response, sizeof(expected_response)),
+ 0);
+ EXPECT_EQ(hf_mailbox_clear(), 0);
+}
+
+/**
+ * Inject an interrupt to the interrupt VM, which will send a message back.
+ * Repeat this twice to make sure it doesn't get into a bad state after the
+ * first one.
+ */
+TEST(interrupts, inject_interrupt_twice)
+{
+ const char expected_response[] = "Got IRQ 07.";
+ struct hf_vcpu_run_return run_res;
+ struct mailbox_buffers mb = set_up_mailbox();
+
+ SERVICE_SELECT(SERVICE_VM0, "interruptible", mb.send);
+
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
+
+ /* Inject the interrupt and wait for a message. */
+ hf_interrupt_inject(SERVICE_VM0, 0, EXTERNAL_INTERRUPT_ID_A);
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
+ EXPECT_EQ(run_res.message.size, sizeof(expected_response));
+ EXPECT_EQ(memcmp(mb.recv, expected_response, sizeof(expected_response)),
+ 0);
+ EXPECT_EQ(hf_mailbox_clear(), 0);
+
+ /* Inject the interrupt again, and wait for the same message. */
+ hf_interrupt_inject(SERVICE_VM0, 0, EXTERNAL_INTERRUPT_ID_A);
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
+ EXPECT_EQ(run_res.message.size, sizeof(expected_response));
+ EXPECT_EQ(memcmp(mb.recv, expected_response, sizeof(expected_response)),
+ 0);
+ EXPECT_EQ(hf_mailbox_clear(), 0);
+}
+
+/**
+ * Inject two different interrupts to the interrupt VM, which will send a
+ * message back each time.
+ */
+TEST(interrupts, inject_two_interrupts)
+{
+ const char expected_response[] = "Got IRQ 07.";
+ const char expected_response_2[] = "Got IRQ 08.";
+ struct hf_vcpu_run_return run_res;
+ struct mailbox_buffers mb = set_up_mailbox();
+
+ SERVICE_SELECT(SERVICE_VM0, "interruptible", mb.send);
+
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
+
+ /* Inject the interrupt and wait for a message. */
+ hf_interrupt_inject(SERVICE_VM0, 0, EXTERNAL_INTERRUPT_ID_A);
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
+ EXPECT_EQ(run_res.message.size, sizeof(expected_response));
+ EXPECT_EQ(memcmp(mb.recv, expected_response, sizeof(expected_response)),
+ 0);
+ EXPECT_EQ(hf_mailbox_clear(), 0);
+
+ /* Inject a different interrupt and wait for a different message. */
+ hf_interrupt_inject(SERVICE_VM0, 0, EXTERNAL_INTERRUPT_ID_B);
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
+ EXPECT_EQ(run_res.message.size, sizeof(expected_response_2));
+ EXPECT_EQ(memcmp(mb.recv, expected_response_2,
+ sizeof(expected_response_2)),
+ 0);
+ EXPECT_EQ(hf_mailbox_clear(), 0);
+}
+
+/**
+ * Inject an interrupt then send a message to the interrupt VM, which will send
+ * a message back each time. This is to test that interrupt injection doesn't
+ * interfere with message reception.
+ */
+TEST(interrupts, inject_interrupt_message)
+{
+ const char expected_response[] = "Got IRQ 07.";
+ const char message[] = "Ping";
+ const char expected_response_2[] = "Got IRQ 05.";
+ struct hf_vcpu_run_return run_res;
+ struct mailbox_buffers mb = set_up_mailbox();
+
+ SERVICE_SELECT(SERVICE_VM0, "interruptible", mb.send);
+
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
+
+ /* Inject the interrupt and wait for a message. */
+ hf_interrupt_inject(SERVICE_VM0, 0, EXTERNAL_INTERRUPT_ID_A);
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
+ EXPECT_EQ(run_res.message.size, sizeof(expected_response));
+ EXPECT_EQ(memcmp(mb.recv, expected_response, sizeof(expected_response)),
+ 0);
+ EXPECT_EQ(hf_mailbox_clear(), 0);
+
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
+
+ /* Now send a message to the secondary. */
+ memcpy(mb.send, message, sizeof(message));
+ EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, sizeof(message), false), 0);
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
+ EXPECT_EQ(run_res.message.size, sizeof(expected_response_2));
+ EXPECT_EQ(memcmp(mb.recv, expected_response_2,
+ sizeof(expected_response_2)),
+ 0);
+ EXPECT_EQ(hf_mailbox_clear(), 0);
+}
+
+/**
+ * Inject an interrupt which the target VM has not enabled, and then send a
+ * message telling it to enable that interrupt ID. It should then (and only
+ * then) send a message back.
+ */
+TEST(interrupts, inject_interrupt_disabled)
+{
+ const char expected_response[] = "Got IRQ 09.";
+ const char message[] = "Enable interrupt C";
+ struct hf_vcpu_run_return run_res;
+ struct mailbox_buffers mb = set_up_mailbox();
+
+ SERVICE_SELECT(SERVICE_VM0, "interruptible", mb.send);
+
+ /* Inject the interrupt and expect not to get a message. */
+ hf_interrupt_inject(SERVICE_VM0, 0, EXTERNAL_INTERRUPT_ID_C);
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
+ EXPECT_EQ(hf_mailbox_clear(), -1);
+
+ /*
+ * Now send a message to the secondary to enable the interrupt ID, and
+ * expect the response from the interrupt we sent before.
+ */
+ memcpy(mb.send, message, sizeof(message));
+ EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, sizeof(message), false), 0);
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
+ EXPECT_EQ(run_res.message.size, sizeof(expected_response));
+ EXPECT_EQ(memcmp(mb.recv, expected_response, sizeof(expected_response)),
+ 0);
+ EXPECT_EQ(hf_mailbox_clear(), 0);
+}
diff --git a/test/vmapi/primary_with_secondaries/mailbox.c b/test/vmapi/primary_with_secondaries/mailbox.c
new file mode 100644
index 0000000..c54cdb4
--- /dev/null
+++ b/test/vmapi/primary_with_secondaries/mailbox.c
@@ -0,0 +1,304 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+
+#include "hf/std.h"
+
+#include "vmapi/hf/call.h"
+
+#include "hftest.h"
+#include "primary_with_secondary.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, 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;
+ }
+}
+
+/**
+ * Send and receive the same message from the echo VM.
+ */
+TEST(mailbox, echo)
+{
+ const char message[] = "Echo this back to me!";
+ struct hf_vcpu_run_return run_res;
+ struct mailbox_buffers mb = set_up_mailbox();
+
+ SERVICE_SELECT(SERVICE_VM0, "echo", mb.send);
+
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
+
+ /* Set the message, echo it and check it didn't change. */
+ memcpy(mb.send, message, sizeof(message));
+ EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, sizeof(message), false), 0);
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
+ EXPECT_EQ(run_res.message.size, sizeof(message));
+ EXPECT_EQ(memcmp(mb.recv, message, sizeof(message)), 0);
+ EXPECT_EQ(hf_mailbox_clear(), 0);
+}
+
+/**
+ * Repeatedly send a message and receive it back from the echo VM.
+ */
+TEST(mailbox, repeated_echo)
+{
+ char message[] = "Echo this back to me!";
+ struct hf_vcpu_run_return run_res;
+ uint8_t i;
+ struct mailbox_buffers mb = set_up_mailbox();
+
+ SERVICE_SELECT(SERVICE_VM0, "echo", mb.send);
+
+ for (i = 0; i < 100; i++) {
+ /* Run secondary until it reaches the wait for messages. */
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
+
+ /* Set the message, echo it and check it didn't change. */
+ next_permutation(message, sizeof(message) - 1);
+ memcpy(mb.send, message, sizeof(message));
+ EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, sizeof(message), false),
+ 0);
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
+ EXPECT_EQ(run_res.message.size, sizeof(message));
+ EXPECT_EQ(memcmp(mb.recv, message, sizeof(message)), 0);
+ EXPECT_EQ(hf_mailbox_clear(), 0);
+ }
+}
+
+/**
+ * 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 hf_vcpu_run_return run_res;
+ struct mailbox_buffers mb = set_up_mailbox();
+
+ SERVICE_SELECT(SERVICE_VM0, "relay", mb.send);
+ SERVICE_SELECT(SERVICE_VM1, "relay", mb.send);
+
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
+ run_res = hf_vcpu_run(SERVICE_VM1, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
+
+ /*
+ * Build the message chain so the message is sent from here to
+ * SERVICE_VM0, then to SERVICE_VM1 and finally back to here.
+ */
+ {
+ uint32_t *chain = mb.send;
+ *chain++ = htole32(SERVICE_VM1);
+ *chain++ = htole32(HF_PRIMARY_VM_ID);
+ memcpy(chain, message, sizeof(message));
+ EXPECT_EQ(hf_mailbox_send(
+ SERVICE_VM0,
+ sizeof(message) + (2 * sizeof(uint32_t)),
+ false),
+ 0);
+ }
+
+ /* Let SERVICE_VM0 forward the message. */
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAKE_UP);
+ EXPECT_EQ(run_res.wake_up.vm_id, SERVICE_VM1);
+ EXPECT_EQ(run_res.wake_up.vcpu, 0);
+
+ /* Let SERVICE_VM1 forward the message. */
+ run_res = hf_vcpu_run(SERVICE_VM1, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
+
+ /* Ensure the message is in tact. */
+ EXPECT_EQ(run_res.message.size, sizeof(message));
+ EXPECT_EQ(memcmp(mb.recv, message, sizeof(message)), 0);
+ EXPECT_EQ(hf_mailbox_clear(), 0);
+}
+
+/**
+ * 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 hf_vcpu_run_return run_res;
+
+ set_up_mailbox();
+
+ EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, 0, false), -1);
+
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
+
+ EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, 0, false), 0);
+}
+
+/**
+ * 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 hf_vcpu_run_return run_res;
+
+ set_up_mailbox();
+
+ EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, 0, true), -1);
+
+ /*
+ * Run first VM for it to configure itself. It should result in
+ * notifications having to be issued.
+ */
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_NOTIFY_WAITERS);
+
+ /* A single waiter is returned. */
+ EXPECT_EQ(hf_mailbox_waiter_get(SERVICE_VM0), HF_PRIMARY_VM_ID);
+ EXPECT_EQ(hf_mailbox_waiter_get(SERVICE_VM0), -1);
+
+ /* Send should succeed now, though no vCPU is blocked waiting for it. */
+ EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, 0, false), HF_INVALID_VCPU);
+}
+
+/**
+ * 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 hf_vcpu_run_return run_res;
+ struct mailbox_buffers mb = set_up_mailbox();
+
+ SERVICE_SELECT(SERVICE_VM0, "echo_with_notification", mb.send);
+
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
+
+ /* Send a message to echo service, and get response back. */
+ memcpy(mb.send, message, sizeof(message));
+ EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, sizeof(message), false), 0);
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
+ EXPECT_EQ(run_res.message.size, sizeof(message));
+ EXPECT_EQ(memcmp(mb.recv, message, sizeof(message)), 0);
+
+ /* Let secondary VM continue running so that it will wait again. */
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
+
+ /* Without clearing our mailbox, send message again. */
+ reverse(message, strlen(message));
+ memcpy(mb.send, message, sizeof(message));
+ EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, sizeof(message), false), 0);
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
+
+ /* Clear the mailbox. We expect to be told there are pending waiters. */
+ EXPECT_EQ(hf_mailbox_clear(), 1);
+
+ /* Retrieve a single waiter. */
+ EXPECT_EQ(hf_mailbox_waiter_get(HF_PRIMARY_VM_ID), SERVICE_VM0);
+ EXPECT_EQ(hf_mailbox_waiter_get(HF_PRIMARY_VM_ID), -1);
+
+ /*
+ * Inject interrupt into VM and let it run again. We should receive
+ * the echoed message.
+ */
+ EXPECT_EQ(
+ hf_interrupt_inject(SERVICE_VM0, 0, HF_MAILBOX_WRITABLE_INTID),
+ 1);
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
+ EXPECT_EQ(run_res.message.size, sizeof(message));
+ EXPECT_EQ(memcmp(mb.recv, message, sizeof(message)), 0);
+}
+
+/**
+ * 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 hf_vcpu_run_return run_res;
+ struct mailbox_buffers mb = set_up_mailbox();
+
+ SERVICE_SELECT(SERVICE_VM0, "echo_with_notification", mb.send);
+
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
+
+ /* Send a message to echo service twice. The second should fail. */
+ memcpy(mb.send, message, sizeof(message));
+ EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, sizeof(message), false), 0);
+ EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, sizeof(message), true), -1);
+
+ /* Receive a reply for the first message. */
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
+ EXPECT_EQ(run_res.message.size, sizeof(message));
+ EXPECT_EQ(memcmp(mb.recv, message, sizeof(message)), 0);
+
+ /* Run VM again so that it clears its mailbox. */
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_NOTIFY_WAITERS);
+
+ /* Retrieve a single waiter. */
+ EXPECT_EQ(hf_mailbox_waiter_get(SERVICE_VM0), HF_PRIMARY_VM_ID);
+ EXPECT_EQ(hf_mailbox_waiter_get(SERVICE_VM0), -1);
+
+ /* Send should succeed now, though no vCPU is blocked waiting for it. */
+ EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, 0, false), HF_INVALID_VCPU);
+}
diff --git a/test/vmapi/primary_with_secondaries/with_services.c b/test/vmapi/primary_with_secondaries/with_services.c
deleted file mode 100644
index bc52550..0000000
--- a/test/vmapi/primary_with_secondaries/with_services.c
+++ /dev/null
@@ -1,479 +0,0 @@
-/*
- * Copyright 2018 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <stdalign.h>
-#include <stdint.h>
-
-#include "hf/std.h"
-
-#include "vmapi/hf/call.h"
-
-#include "hftest.h"
-#include "primary_with_secondary.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.
- */
-void next_permutation(char *s, size_t len)
-{
- size_t i, 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;
- }
-}
-
-/**
- * Send and receive the same message from the echo VM.
- */
-TEST(mailbox, echo)
-{
- const char message[] = "Echo this back to me!";
- struct hf_vcpu_run_return run_res;
- struct mailbox_buffers mb = set_up_mailbox();
-
- SERVICE_SELECT(SERVICE_VM0, "echo", mb.send);
-
- run_res = hf_vcpu_run(SERVICE_VM0, 0);
- EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
-
- /* Set the message, echo it and check it didn't change. */
- memcpy(mb.send, message, sizeof(message));
- EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, sizeof(message), false), 0);
- run_res = hf_vcpu_run(SERVICE_VM0, 0);
- EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
- EXPECT_EQ(run_res.message.size, sizeof(message));
- EXPECT_EQ(memcmp(mb.recv, message, sizeof(message)), 0);
- EXPECT_EQ(hf_mailbox_clear(), 0);
-}
-
-/**
- * Repeatedly send a message and receive it back from the echo VM.
- */
-TEST(mailbox, repeated_echo)
-{
- char message[] = "Echo this back to me!";
- struct hf_vcpu_run_return run_res;
- uint8_t i;
- struct mailbox_buffers mb = set_up_mailbox();
-
- SERVICE_SELECT(SERVICE_VM0, "echo", mb.send);
-
- for (i = 0; i < 100; i++) {
- /* Run secondary until it reaches the wait for messages. */
- run_res = hf_vcpu_run(SERVICE_VM0, 0);
- EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
-
- /* Set the message, echo it and check it didn't change. */
- next_permutation(message, sizeof(message) - 1);
- memcpy(mb.send, message, sizeof(message));
- EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, sizeof(message), false),
- 0);
- run_res = hf_vcpu_run(SERVICE_VM0, 0);
- EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
- EXPECT_EQ(run_res.message.size, sizeof(message));
- EXPECT_EQ(memcmp(mb.recv, message, sizeof(message)), 0);
- EXPECT_EQ(hf_mailbox_clear(), 0);
- }
-}
-
-/**
- * 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 hf_vcpu_run_return run_res;
- struct mailbox_buffers mb = set_up_mailbox();
-
- SERVICE_SELECT(SERVICE_VM0, "relay", mb.send);
- SERVICE_SELECT(SERVICE_VM1, "relay", mb.send);
-
- run_res = hf_vcpu_run(SERVICE_VM0, 0);
- EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
- run_res = hf_vcpu_run(SERVICE_VM1, 0);
- EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
-
- /*
- * Build the message chain so the message is sent from here to
- * SERVICE_VM0, then to SERVICE_VM1 and finally back to here.
- */
- {
- uint32_t *chain = mb.send;
- *chain++ = htole32(SERVICE_VM1);
- *chain++ = htole32(HF_PRIMARY_VM_ID);
- memcpy(chain, message, sizeof(message));
- EXPECT_EQ(hf_mailbox_send(
- SERVICE_VM0,
- sizeof(message) + (2 * sizeof(uint32_t)),
- false),
- 0);
- }
-
- /* Let SERVICE_VM0 forward the message. */
- run_res = hf_vcpu_run(SERVICE_VM0, 0);
- EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAKE_UP);
- EXPECT_EQ(run_res.wake_up.vm_id, SERVICE_VM1);
- EXPECT_EQ(run_res.wake_up.vcpu, 0);
-
- /* Let SERVICE_VM1 forward the message. */
- run_res = hf_vcpu_run(SERVICE_VM1, 0);
- EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
-
- /* Ensure the message is in tact. */
- EXPECT_EQ(run_res.message.size, sizeof(message));
- EXPECT_EQ(memcmp(mb.recv, message, sizeof(message)), 0);
- EXPECT_EQ(hf_mailbox_clear(), 0);
-}
-
-/**
- * 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 hf_vcpu_run_return run_res;
-
- set_up_mailbox();
-
- EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, 0, false), -1);
-
- run_res = hf_vcpu_run(SERVICE_VM0, 0);
- EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
-
- EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, 0, false), 0);
-}
-
-/**
- * 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 hf_vcpu_run_return run_res;
-
- set_up_mailbox();
-
- EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, 0, true), -1);
-
- /*
- * Run first VM for it to configure itself. It should result in
- * notifications having to be issued.
- */
- run_res = hf_vcpu_run(SERVICE_VM0, 0);
- EXPECT_EQ(run_res.code, HF_VCPU_RUN_NOTIFY_WAITERS);
-
- /* A single waiter is returned. */
- EXPECT_EQ(hf_mailbox_waiter_get(SERVICE_VM0), HF_PRIMARY_VM_ID);
- EXPECT_EQ(hf_mailbox_waiter_get(SERVICE_VM0), -1);
-
- /* Send should succeed now, though no vCPU is blocked waiting for it. */
- EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, 0, false), HF_INVALID_VCPU);
-}
-
-/**
- * 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 hf_vcpu_run_return run_res;
- struct mailbox_buffers mb = set_up_mailbox();
-
- SERVICE_SELECT(SERVICE_VM0, "echo_with_notification", mb.send);
-
- run_res = hf_vcpu_run(SERVICE_VM0, 0);
- EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
-
- /* Send a message to echo service, and get response back. */
- memcpy(mb.send, message, sizeof(message));
- EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, sizeof(message), false), 0);
- run_res = hf_vcpu_run(SERVICE_VM0, 0);
- EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
- EXPECT_EQ(run_res.message.size, sizeof(message));
- EXPECT_EQ(memcmp(mb.recv, message, sizeof(message)), 0);
-
- /* Let secondary VM continue running so that it will wait again. */
- run_res = hf_vcpu_run(SERVICE_VM0, 0);
- EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
-
- /* Without clearing our mailbox, send message again. */
- reverse(message, strlen(message));
- memcpy(mb.send, message, sizeof(message));
- EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, sizeof(message), false), 0);
- run_res = hf_vcpu_run(SERVICE_VM0, 0);
- EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
-
- /* Clear the mailbox. We expect to be told there are pending waiters. */
- EXPECT_EQ(hf_mailbox_clear(), 1);
-
- /* Retrieve a single waiter. */
- EXPECT_EQ(hf_mailbox_waiter_get(HF_PRIMARY_VM_ID), SERVICE_VM0);
- EXPECT_EQ(hf_mailbox_waiter_get(HF_PRIMARY_VM_ID), -1);
-
- /*
- * Inject interrupt into VM and let it run again. We should receive
- * the echoed message.
- */
- EXPECT_EQ(
- hf_interrupt_inject(SERVICE_VM0, 0, HF_MAILBOX_WRITABLE_INTID),
- 1);
- run_res = hf_vcpu_run(SERVICE_VM0, 0);
- EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
- EXPECT_EQ(run_res.message.size, sizeof(message));
- EXPECT_EQ(memcmp(mb.recv, message, sizeof(message)), 0);
-}
-
-/**
- * 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 hf_vcpu_run_return run_res;
- struct mailbox_buffers mb = set_up_mailbox();
-
- SERVICE_SELECT(SERVICE_VM0, "echo_with_notification", mb.send);
-
- run_res = hf_vcpu_run(SERVICE_VM0, 0);
- EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
-
- /* Send a message to echo service twice. The second should fail. */
- memcpy(mb.send, message, sizeof(message));
- EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, sizeof(message), false), 0);
- EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, sizeof(message), true), -1);
-
- /* Receive a reply for the first message. */
- run_res = hf_vcpu_run(SERVICE_VM0, 0);
- EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
- EXPECT_EQ(run_res.message.size, sizeof(message));
- EXPECT_EQ(memcmp(mb.recv, message, sizeof(message)), 0);
-
- /* Run VM again so that it clears its mailbox. */
- run_res = hf_vcpu_run(SERVICE_VM0, 0);
- EXPECT_EQ(run_res.code, HF_VCPU_RUN_NOTIFY_WAITERS);
-
- /* Retrieve a single waiter. */
- EXPECT_EQ(hf_mailbox_waiter_get(SERVICE_VM0), HF_PRIMARY_VM_ID);
- EXPECT_EQ(hf_mailbox_waiter_get(SERVICE_VM0), -1);
-
- /* Send should succeed now, though no vCPU is blocked waiting for it. */
- EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, 0, false), HF_INVALID_VCPU);
-}
-
-/**
- * Send a message to the interruptible VM, which will interrupt itself to send a
- * response back.
- */
-TEST(interrupts, interrupt_self)
-{
- const char message[] = "Ping";
- const char expected_response[] = "Got IRQ 05.";
- struct hf_vcpu_run_return run_res;
- struct mailbox_buffers mb = set_up_mailbox();
-
- SERVICE_SELECT(SERVICE_VM0, "interruptible", mb.send);
-
- run_res = hf_vcpu_run(SERVICE_VM0, 0);
- EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
-
- /* Set the message, echo it and wait for a response. */
- memcpy(mb.send, message, sizeof(message));
- EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, sizeof(message), false), 0);
- run_res = hf_vcpu_run(SERVICE_VM0, 0);
- EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
- EXPECT_EQ(run_res.message.size, sizeof(expected_response));
- EXPECT_EQ(memcmp(mb.recv, expected_response, sizeof(expected_response)),
- 0);
- EXPECT_EQ(hf_mailbox_clear(), 0);
-}
-
-/**
- * Inject an interrupt to the interrupt VM, which will send a message back.
- * Repeat this twice to make sure it doesn't get into a bad state after the
- * first one.
- */
-TEST(interrupts, inject_interrupt_twice)
-{
- const char expected_response[] = "Got IRQ 07.";
- struct hf_vcpu_run_return run_res;
- struct mailbox_buffers mb = set_up_mailbox();
-
- SERVICE_SELECT(SERVICE_VM0, "interruptible", mb.send);
-
- run_res = hf_vcpu_run(SERVICE_VM0, 0);
- EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
-
- /* Inject the interrupt and wait for a message. */
- hf_interrupt_inject(SERVICE_VM0, 0, EXTERNAL_INTERRUPT_ID_A);
- run_res = hf_vcpu_run(SERVICE_VM0, 0);
- EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
- EXPECT_EQ(run_res.message.size, sizeof(expected_response));
- EXPECT_EQ(memcmp(mb.recv, expected_response, sizeof(expected_response)),
- 0);
- EXPECT_EQ(hf_mailbox_clear(), 0);
-
- /* Inject the interrupt again, and wait for the same message. */
- hf_interrupt_inject(SERVICE_VM0, 0, EXTERNAL_INTERRUPT_ID_A);
- run_res = hf_vcpu_run(SERVICE_VM0, 0);
- EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
- EXPECT_EQ(run_res.message.size, sizeof(expected_response));
- EXPECT_EQ(memcmp(mb.recv, expected_response, sizeof(expected_response)),
- 0);
- EXPECT_EQ(hf_mailbox_clear(), 0);
-}
-
-/**
- * Inject two different interrupts to the interrupt VM, which will send a
- * message back each time.
- */
-TEST(interrupts, inject_two_interrupts)
-{
- const char expected_response[] = "Got IRQ 07.";
- const char expected_response_2[] = "Got IRQ 08.";
- struct hf_vcpu_run_return run_res;
- struct mailbox_buffers mb = set_up_mailbox();
-
- SERVICE_SELECT(SERVICE_VM0, "interruptible", mb.send);
-
- run_res = hf_vcpu_run(SERVICE_VM0, 0);
- EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
-
- /* Inject the interrupt and wait for a message. */
- hf_interrupt_inject(SERVICE_VM0, 0, EXTERNAL_INTERRUPT_ID_A);
- run_res = hf_vcpu_run(SERVICE_VM0, 0);
- EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
- EXPECT_EQ(run_res.message.size, sizeof(expected_response));
- EXPECT_EQ(memcmp(mb.recv, expected_response, sizeof(expected_response)),
- 0);
- EXPECT_EQ(hf_mailbox_clear(), 0);
-
- /* Inject a different interrupt and wait for a different message. */
- hf_interrupt_inject(SERVICE_VM0, 0, EXTERNAL_INTERRUPT_ID_B);
- run_res = hf_vcpu_run(SERVICE_VM0, 0);
- EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
- EXPECT_EQ(run_res.message.size, sizeof(expected_response_2));
- EXPECT_EQ(memcmp(mb.recv, expected_response_2,
- sizeof(expected_response_2)),
- 0);
- EXPECT_EQ(hf_mailbox_clear(), 0);
-}
-
-/**
- * Inject an interrupt then send a message to the interrupt VM, which will send
- * a message back each time. This is to test that interrupt injection doesn't
- * interfere with message reception.
- */
-TEST(interrupts, inject_interrupt_message)
-{
- const char expected_response[] = "Got IRQ 07.";
- const char message[] = "Ping";
- const char expected_response_2[] = "Got IRQ 05.";
- struct hf_vcpu_run_return run_res;
- struct mailbox_buffers mb = set_up_mailbox();
-
- SERVICE_SELECT(SERVICE_VM0, "interruptible", mb.send);
-
- run_res = hf_vcpu_run(SERVICE_VM0, 0);
- EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
-
- /* Inject the interrupt and wait for a message. */
- hf_interrupt_inject(SERVICE_VM0, 0, EXTERNAL_INTERRUPT_ID_A);
- run_res = hf_vcpu_run(SERVICE_VM0, 0);
- EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
- EXPECT_EQ(run_res.message.size, sizeof(expected_response));
- EXPECT_EQ(memcmp(mb.recv, expected_response, sizeof(expected_response)),
- 0);
- EXPECT_EQ(hf_mailbox_clear(), 0);
-
- run_res = hf_vcpu_run(SERVICE_VM0, 0);
- EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
-
- /* Now send a message to the secondary. */
- memcpy(mb.send, message, sizeof(message));
- EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, sizeof(message), false), 0);
- run_res = hf_vcpu_run(SERVICE_VM0, 0);
- EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
- EXPECT_EQ(run_res.message.size, sizeof(expected_response_2));
- EXPECT_EQ(memcmp(mb.recv, expected_response_2,
- sizeof(expected_response_2)),
- 0);
- EXPECT_EQ(hf_mailbox_clear(), 0);
-}
-
-/**
- * Inject an interrupt which the target VM has not enabled, and then send a
- * message telling it to enable that interrupt ID. It should then (and only
- * then) send a message back.
- */
-TEST(interrupts, inject_interrupt_disabled)
-{
- const char expected_response[] = "Got IRQ 09.";
- const char message[] = "Enable interrupt C";
- struct hf_vcpu_run_return run_res;
- struct mailbox_buffers mb = set_up_mailbox();
-
- SERVICE_SELECT(SERVICE_VM0, "interruptible", mb.send);
-
- /* Inject the interrupt and expect not to get a message. */
- hf_interrupt_inject(SERVICE_VM0, 0, EXTERNAL_INTERRUPT_ID_C);
- run_res = hf_vcpu_run(SERVICE_VM0, 0);
- EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
- EXPECT_EQ(hf_mailbox_clear(), -1);
-
- /*
- * Now send a message to the secondary to enable the interrupt ID, and
- * expect the response from the interrupt we sent before.
- */
- memcpy(mb.send, message, sizeof(message));
- EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, sizeof(message), false), 0);
- run_res = hf_vcpu_run(SERVICE_VM0, 0);
- EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
- EXPECT_EQ(run_res.message.size, sizeof(expected_response));
- EXPECT_EQ(memcmp(mb.recv, expected_response, sizeof(expected_response)),
- 0);
- EXPECT_EQ(hf_mailbox_clear(), 0);
-}