VHE: Tests: Add interrupt tests for EL0 partitions
Added interrupt test suite from primary_with_secondaries test suite and
applied it to EL0 partitions. The main difference between a VM and EL0
partition is that there is no irq handler in an EL0 partition. However,
the FFA_INTERRUPT status interface can be used to notify EL0 partitions
that there are interrupts pending, at which the irq handlers are called
manually as opposed to architecturally by the CPU itself.
Also, the wfi test is removed since an interrupt inject from the primary
VM really just sets a bit and not inject interrupts through hcr_el2
since it doesnt apply.
Change-Id: I78b786ab41fc8f03d7a5974807da472a8cdc226c
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 ae66d9d..86cd352 100644
--- a/test/vmapi/el0_partitions/BUILD.gn
+++ b/test/vmapi/el0_partitions/BUILD.gn
@@ -32,6 +32,7 @@
sources = [
"//test/vmapi/primary_with_secondaries/mailbox_common.c",
"boot.c",
+ "interrupts.c",
"mailbox.c",
]
diff --git a/test/vmapi/el0_partitions/interrupts.c b/test/vmapi/el0_partitions/interrupts.c
new file mode 100644
index 0000000..89686f8
--- /dev/null
+++ b/test/vmapi/el0_partitions/interrupts.c
@@ -0,0 +1,334 @@
+/*
+ * 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/std.h"
+
+#include "vmapi/hf/call.h"
+
+#include "primary_with_secondary.h"
+#include "test/hftest.h"
+#include "test/vmapi/ffa.h"
+
+TEAR_DOWN(interrupts)
+{
+ EXPECT_FFA_ERROR(ffa_rx_release(), FFA_DENIED);
+}
+
+/**
+ * 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 ffa_value run_res;
+ struct mailbox_buffers mb = set_up_mailbox();
+
+ SERVICE_SELECT(SERVICE_VM1, "interruptible", 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 wait for a response. */
+ 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(expected_response));
+ EXPECT_EQ(memcmp(mb.recv, expected_response, sizeof(expected_response)),
+ 0);
+ EXPECT_EQ(ffa_rx_release().func, FFA_SUCCESS_32);
+}
+
+/**
+ * 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 ffa_value run_res;
+ struct mailbox_buffers mb = set_up_mailbox();
+
+ SERVICE_SELECT(SERVICE_VM1, "interruptible", 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);
+
+ /* Inject the interrupt and wait for a message. */
+ hf_interrupt_inject(SERVICE_VM1, 0, EXTERNAL_INTERRUPT_ID_A);
+ 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(expected_response));
+ EXPECT_EQ(memcmp(mb.recv, expected_response, sizeof(expected_response)),
+ 0);
+ EXPECT_EQ(ffa_rx_release().func, FFA_SUCCESS_32);
+
+ /* Inject the interrupt again, and wait for the same message. */
+ hf_interrupt_inject(SERVICE_VM1, 0, EXTERNAL_INTERRUPT_ID_A);
+ 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(expected_response));
+ EXPECT_EQ(memcmp(mb.recv, expected_response, sizeof(expected_response)),
+ 0);
+ EXPECT_EQ(ffa_rx_release().func, FFA_SUCCESS_32);
+}
+
+/**
+ * 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 ffa_value run_res;
+ struct mailbox_buffers mb = set_up_mailbox();
+
+ SERVICE_SELECT(SERVICE_VM1, "interruptible", 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);
+
+ /* Inject the interrupt and wait for a message. */
+ hf_interrupt_inject(SERVICE_VM1, 0, EXTERNAL_INTERRUPT_ID_A);
+ 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(expected_response));
+ EXPECT_EQ(memcmp(mb.recv, expected_response, sizeof(expected_response)),
+ 0);
+ EXPECT_EQ(ffa_rx_release().func, FFA_SUCCESS_32);
+
+ /* Inject a different interrupt and wait for a different message. */
+ hf_interrupt_inject(SERVICE_VM1, 0, EXTERNAL_INTERRUPT_ID_B);
+ 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(expected_response_2));
+ EXPECT_EQ(memcmp(mb.recv, expected_response_2,
+ sizeof(expected_response_2)),
+ 0);
+ EXPECT_EQ(ffa_rx_release().func, FFA_SUCCESS_32);
+}
+
+/**
+ * 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 ffa_value run_res;
+ struct mailbox_buffers mb = set_up_mailbox();
+
+ SERVICE_SELECT(SERVICE_VM1, "interruptible", 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);
+
+ /* Inject the interrupt and wait for a message. */
+ hf_interrupt_inject(SERVICE_VM1, 0, EXTERNAL_INTERRUPT_ID_A);
+ 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(expected_response));
+ EXPECT_EQ(memcmp(mb.recv, expected_response, sizeof(expected_response)),
+ 0);
+ EXPECT_EQ(ffa_rx_release().func, FFA_SUCCESS_32);
+
+ run_res = ffa_run(SERVICE_VM1, 0);
+ EXPECT_EQ(run_res.func, FFA_MSG_WAIT_32);
+ EXPECT_EQ(run_res.arg2, FFA_SLEEP_INDEFINITE);
+
+ /* Now send a message to the secondary. */
+ 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(expected_response_2));
+ EXPECT_EQ(memcmp(mb.recv, expected_response_2,
+ sizeof(expected_response_2)),
+ 0);
+ EXPECT_EQ(ffa_rx_release().func, FFA_SUCCESS_32);
+}
+
+/**
+ * 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 ffa_value run_res;
+ struct mailbox_buffers mb = set_up_mailbox();
+
+ SERVICE_SELECT(SERVICE_VM1, "interruptible", mb.send);
+
+ /* Inject the interrupt and expect not to get a message. */
+ hf_interrupt_inject(SERVICE_VM1, 0, EXTERNAL_INTERRUPT_ID_C);
+ run_res = ffa_run(SERVICE_VM1, 0);
+ EXPECT_EQ(run_res.func, FFA_MSG_WAIT_32);
+ EXPECT_EQ(run_res.arg2, FFA_SLEEP_INDEFINITE);
+
+ /*
+ * Now send a message to the secondary to enable the interrupt ID, and
+ * expect the response from the interrupt we sent before.
+ */
+ 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(expected_response));
+ EXPECT_EQ(memcmp(mb.recv, expected_response, sizeof(expected_response)),
+ 0);
+ EXPECT_EQ(ffa_rx_release().func, FFA_SUCCESS_32);
+}
+
+/**
+ * If a secondary VM has an enabled and pending interrupt, even if interrupts
+ * are disabled globally via PSTATE, then hf_mailbox_receive should not block
+ * even if `block` is true.
+ */
+TEST(interrupts, pending_interrupt_no_blocking_receive)
+{
+ const char expected_response[] = "Done waiting";
+ struct ffa_value run_res;
+ struct mailbox_buffers mb = set_up_mailbox();
+
+ SERVICE_SELECT(SERVICE_VM1, "receive_block", mb.send);
+
+ /*
+ * Inject the interrupt and run the VM. It should disable interrupts
+ * globally, enable the specific interrupt, and then send us a message
+ * back after failing to receive a message a few times.
+ */
+ hf_interrupt_inject(SERVICE_VM1, 0, EXTERNAL_INTERRUPT_ID_A);
+ 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(expected_response));
+ EXPECT_EQ(memcmp(mb.recv, expected_response, sizeof(expected_response)),
+ 0);
+ EXPECT_EQ(ffa_rx_release().func, FFA_SUCCESS_32);
+}
+
+/*
+ * Deliver an interrupt and a message to the same vCPU and check that both are
+ * delivered the next time the vCPU is run.
+ */
+TEST(interrupts, deliver_interrupt_and_message)
+{
+ const char message[] = "I\'ll see you again.";
+ struct ffa_value run_res;
+ struct mailbox_buffers mb = set_up_mailbox();
+
+ SERVICE_SELECT(SERVICE_VM1, "interruptible_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);
+
+ 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);
+ hf_interrupt_inject(SERVICE_VM1, 0, EXTERNAL_INTERRUPT_ID_A);
+ 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);
+}
+
+/**
+ * The secondary vCPU is waiting for a direct msg request, but the primary
+ * instead injects an interrupt into it and calls FFA_RUN. The secondary
+ * should get FFA_INTERRUPT_32 returned, as well as the interrupt itself.
+ */
+TEST(interrupts_direct_msg, direct_msg_request_interrupted)
+{
+ struct mailbox_buffers mb = set_up_mailbox();
+ struct ffa_value res;
+
+ SERVICE_SELECT(SERVICE_VM1, "interruptible_echo_direct_msg", mb.send);
+
+ /* Let the secondary get started and wait for a message. */
+ res = ffa_run(SERVICE_VM1, 0);
+ EXPECT_EQ(res.func, FFA_MSG_WAIT_32);
+ EXPECT_EQ(res.arg2, FFA_SLEEP_INDEFINITE);
+
+ /* Send an initial direct message request */
+ res = ffa_msg_send_direct_req(HF_PRIMARY_VM_ID, SERVICE_VM1, 1, 0, 0, 0,
+ 0);
+ EXPECT_EQ(res.func, FFA_MSG_SEND_DIRECT_RESP_32);
+ EXPECT_EQ(res.arg3, 2);
+
+ /* Inject an interrupt to the secondary VM */
+ hf_interrupt_inject(SERVICE_VM1, 0, EXTERNAL_INTERRUPT_ID_A);
+
+ /* Let the secondary VM run */
+ res = ffa_run(SERVICE_VM1, 0);
+ EXPECT_EQ(res.func, FFA_MSG_WAIT_32);
+
+ res = ffa_msg_send_direct_req(HF_PRIMARY_VM_ID, SERVICE_VM1, 3, 0, 0, 0,
+ 0);
+ EXPECT_EQ(res.func, FFA_MSG_SEND_DIRECT_RESP_32);
+ EXPECT_EQ(res.arg3, 4);
+}
+
+/**
+ * The secondary vCPU is waiting for a direct request. The primary injects
+ * an interrupt into it and then calls FFA_MSG_SEND_DIRECT_REQ. The secondary
+ * shall get both the direct request and the interrupt.
+ */
+TEST(interrupts_direct_msg, direct_msg_request_with_interrupt)
+{
+ struct mailbox_buffers mb = set_up_mailbox();
+ struct ffa_value res;
+
+ SERVICE_SELECT(SERVICE_VM1,
+ "interruptible_echo_direct_msg_with_interrupt", mb.send);
+
+ /* Let the secondary get started and wait for a message. */
+ res = ffa_run(SERVICE_VM1, 0);
+ EXPECT_EQ(res.func, FFA_MSG_WAIT_32);
+ EXPECT_EQ(res.arg2, FFA_SLEEP_INDEFINITE);
+
+ /* Inject an interrupt to the secondary VM */
+ hf_interrupt_inject(SERVICE_VM1, 0, EXTERNAL_INTERRUPT_ID_A);
+
+ /*
+ * Send a direct message request. Expect the secondary VM to receive
+ * the message and the interrupt together. The secondary VM then
+ * replies with a direct message response.
+ */
+ res = ffa_msg_send_direct_req(HF_PRIMARY_VM_ID, SERVICE_VM1, 1, 0, 0, 0,
+ 0);
+ EXPECT_EQ(res.func, FFA_MSG_SEND_DIRECT_RESP_32);
+ EXPECT_EQ(res.arg3, 2);
+}
diff --git a/test/vmapi/el0_partitions/services/BUILD.gn b/test/vmapi/el0_partitions/services/BUILD.gn
index 088282c..c117779 100644
--- a/test/vmapi/el0_partitions/services/BUILD.gn
+++ b/test/vmapi/el0_partitions/services/BUILD.gn
@@ -64,6 +64,9 @@
sources = [
"boot.c",
"echo_with_notification.c",
+ "interruptible.c",
+ "interruptible_echo.c",
+ "receive_block.c",
]
deps = [
":hftest_secondary_el0_partition",
diff --git a/test/vmapi/el0_partitions/services/interruptible.c b/test/vmapi/el0_partitions/services/interruptible.c
new file mode 100644
index 0000000..0d00b16
--- /dev/null
+++ b/test/vmapi/el0_partitions/services/interruptible.c
@@ -0,0 +1,94 @@
+/*
+ * 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/arch/irq.h"
+#include "hf/arch/vm/interrupts.h"
+
+#include "hf/dlog.h"
+#include "hf/std.h"
+
+#include "vmapi/hf/call.h"
+#include "vmapi/hf/ffa.h"
+
+#include "primary_with_secondary.h"
+#include "test/hftest.h"
+
+/*
+ * Secondary VM that sends messages in response to interrupts, and interrupts
+ * itself when it receives a message.
+ */
+
+static void irq(void)
+{
+ uint32_t interrupt_id = hf_interrupt_get();
+ if (interrupt_id == HF_INVALID_INTID) {
+ return;
+ }
+ char buffer[] = "Got IRQ xx.";
+ int size = sizeof(buffer);
+ dlog("secondary IRQ %d from current\n", interrupt_id);
+ buffer[8] = '0' + interrupt_id / 10;
+ buffer[9] = '0' + interrupt_id % 10;
+ memcpy_s(SERVICE_SEND_BUFFER(), FFA_MSG_PAYLOAD_MAX, buffer, size);
+ ffa_msg_send(hf_vm_get_id(), HF_PRIMARY_VM_ID, size, 0);
+ dlog("secondary IRQ %d ended\n", interrupt_id);
+}
+
+/**
+ * Try to receive a message from the mailbox, blocking if necessary, and
+ * retrying if interrupted.
+ */
+static struct ffa_value mailbox_receive_retry()
+{
+ struct ffa_value received;
+
+ do {
+ irq();
+ received = ffa_msg_wait();
+ } while (received.func == FFA_ERROR_32 &&
+ ffa_error_code(received) == FFA_INTERRUPTED);
+
+ return received;
+}
+
+TEST_SERVICE(interruptible)
+{
+ ffa_vm_id_t this_vm_id = hf_vm_get_id();
+ void *recv_buf = SERVICE_RECV_BUFFER();
+
+ hf_interrupt_enable(SELF_INTERRUPT_ID, true, INTERRUPT_TYPE_IRQ);
+ hf_interrupt_enable(EXTERNAL_INTERRUPT_ID_A, true, INTERRUPT_TYPE_IRQ);
+ hf_interrupt_enable(EXTERNAL_INTERRUPT_ID_B, true, INTERRUPT_TYPE_IRQ);
+
+ for (;;) {
+ const char ping_message[] = "Ping";
+ const char enable_message[] = "Enable interrupt C";
+
+ struct ffa_value ret = mailbox_receive_retry();
+
+ ASSERT_EQ(ret.func, FFA_MSG_SEND_32);
+ if (ffa_sender(ret) == HF_PRIMARY_VM_ID &&
+ ffa_msg_send_size(ret) == sizeof(ping_message) &&
+ memcmp(recv_buf, ping_message, sizeof(ping_message)) == 0) {
+ /* Interrupt ourselves */
+ hf_interrupt_inject(this_vm_id, 0, SELF_INTERRUPT_ID);
+ } else if (ffa_sender(ret) == HF_PRIMARY_VM_ID &&
+ ffa_msg_send_size(ret) == sizeof(enable_message) &&
+ memcmp(recv_buf, enable_message,
+ sizeof(enable_message)) == 0) {
+ /* Enable interrupt ID C. */
+ hf_interrupt_enable(EXTERNAL_INTERRUPT_ID_C, true,
+ INTERRUPT_TYPE_IRQ);
+ } else {
+ dlog("Got unexpected message from VM %d, size %d.\n",
+ ffa_sender(ret), ffa_msg_send_size(ret));
+ FAIL("Unexpected message");
+ }
+ EXPECT_EQ(ffa_rx_release().func, FFA_SUCCESS_32);
+ }
+}
diff --git a/test/vmapi/el0_partitions/services/interruptible_echo.c b/test/vmapi/el0_partitions/services/interruptible_echo.c
new file mode 100644
index 0000000..b618cf2
--- /dev/null
+++ b/test/vmapi/el0_partitions/services/interruptible_echo.c
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2019 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/arch/irq.h"
+#include "hf/arch/vm/interrupts.h"
+
+#include "hf/dlog.h"
+#include "hf/std.h"
+
+#include "vmapi/hf/call.h"
+
+#include "primary_with_secondary.h"
+#include "test/hftest.h"
+#include "test/vmapi/ffa.h"
+
+volatile uint32_t irq_counter;
+
+static void irq(void)
+{
+ /* Clear the interrupt. */
+ hf_interrupt_get();
+
+ irq_counter++;
+}
+
+/**
+ * Try to receive a message from the mailbox, blocking if necessary, and
+ * retrying if interrupted.
+ */
+static struct ffa_value mailbox_receive_retry()
+{
+ struct ffa_value received;
+
+ do {
+ irq();
+ received = ffa_msg_wait();
+ } while (received.func == FFA_ERROR_32 &&
+ received.arg2 == FFA_INTERRUPTED);
+
+ return received;
+}
+
+TEST_SERVICE(interruptible_echo)
+{
+ hf_interrupt_enable(EXTERNAL_INTERRUPT_ID_A, true, INTERRUPT_TYPE_IRQ);
+
+ EXPECT_EQ(irq_counter, 0);
+ for (;;) {
+ struct ffa_value res = mailbox_receive_retry();
+ void *message = SERVICE_SEND_BUFFER();
+ void *recv_message = SERVICE_RECV_BUFFER();
+
+ ASSERT_EQ(res.func, FFA_MSG_SEND_32);
+ EXPECT_EQ(irq_counter, 1);
+ memcpy_s(message, FFA_MSG_PAYLOAD_MAX, recv_message,
+ ffa_msg_send_size(res));
+
+ EXPECT_EQ(ffa_rx_release().func, FFA_SUCCESS_32);
+ ffa_msg_send(SERVICE_VM1, HF_PRIMARY_VM_ID,
+ ffa_msg_send_size(res), 0);
+ }
+}
+
+/**
+ * Secondary VM gets an interrupt while waiting for a direct
+ * message request.
+ */
+TEST_SERVICE(interruptible_echo_direct_msg)
+{
+ struct ffa_value res;
+
+ hf_interrupt_enable(EXTERNAL_INTERRUPT_ID_A, true, INTERRUPT_TYPE_IRQ);
+
+ res = ffa_msg_wait();
+ EXPECT_EQ(res.func, FFA_MSG_SEND_DIRECT_REQ_32);
+ EXPECT_EQ(res.arg3, 1);
+
+ EXPECT_EQ(irq_counter, 0);
+ res = ffa_msg_send_direct_resp(ffa_receiver(res), ffa_sender(res), 2, 0,
+ 0, 0, 0);
+ EXPECT_EQ(res.func, FFA_INTERRUPT_32);
+
+ irq();
+
+ EXPECT_EQ(irq_counter, 1);
+
+ /* Wait for another direct message request */
+ res = ffa_msg_wait();
+ EXPECT_EQ(res.func, FFA_MSG_SEND_DIRECT_REQ_32);
+ EXPECT_EQ(res.arg3, 3);
+
+ ffa_msg_send_direct_resp(ffa_receiver(res), ffa_sender(res), 4, 0, 0, 0,
+ 0);
+}
+
+/**
+ * The Secondary VM waits for a direct message request. It receives both
+ * a direct message request and an interrupt which it immediately services.
+ * Then it replies straight with a direct message response.
+ */
+TEST_SERVICE(interruptible_echo_direct_msg_with_interrupt)
+{
+ struct ffa_value res;
+
+ hf_interrupt_enable(EXTERNAL_INTERRUPT_ID_A, true, INTERRUPT_TYPE_IRQ);
+
+ EXPECT_EQ(irq_counter, 0);
+
+ dlog("Secondary VM waits for a direct message request.\n");
+
+ res = mailbox_receive_retry();
+ EXPECT_EQ(res.func, FFA_MSG_SEND_DIRECT_REQ_32);
+ EXPECT_EQ(res.arg3, 1);
+ EXPECT_EQ(irq_counter, 1);
+
+ dlog("Secondary VM received direct message request and interrupt.\n");
+
+ ffa_msg_send_direct_resp(ffa_receiver(res), ffa_sender(res), 2, 0, 0, 0,
+ 0);
+}
diff --git a/test/vmapi/el0_partitions/services/receive_block.c b/test/vmapi/el0_partitions/services/receive_block.c
new file mode 100644
index 0000000..05a22f3
--- /dev/null
+++ b/test/vmapi/el0_partitions/services/receive_block.c
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2019 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/arch/irq.h"
+#include "hf/arch/vm/interrupts.h"
+
+#include "hf/dlog.h"
+#include "hf/ffa.h"
+
+#include "vmapi/hf/call.h"
+
+#include "primary_with_secondary.h"
+#include "test/hftest.h"
+#include "test/vmapi/ffa.h"
+
+TEST_SERVICE(receive_block)
+{
+ int32_t i;
+ const char message[] = "Done waiting";
+
+ hf_interrupt_enable(EXTERNAL_INTERRUPT_ID_A, true, INTERRUPT_TYPE_IRQ);
+
+ for (i = 0; i < 10; ++i) {
+ struct ffa_value res = ffa_msg_wait();
+ EXPECT_FFA_ERROR(res, FFA_INTERRUPTED);
+ }
+
+ memcpy_s(SERVICE_SEND_BUFFER(), FFA_MSG_PAYLOAD_MAX, message,
+ sizeof(message));
+
+ ffa_msg_send(hf_vm_get_id(), HF_PRIMARY_VM_ID, sizeof(message), 0);
+}