hf_mailbox_receive should not block if there is a pending interrupt.

This matches the behaviour of WFI, which only traps (and so causes Hafnium
to block the vCPU) if there is not currently a pending interrupt, ignoring
PSTATE.

Bug: 127686530
Change-Id: I06ef4513d3b9f5adf7988f6f77c178cba40b1762
diff --git a/test/vmapi/gicv3/services/timer.c b/test/vmapi/gicv3/services/timer.c
index f39237b..16ccccb 100644
--- a/test/vmapi/gicv3/services/timer.c
+++ b/test/vmapi/gicv3/services/timer.c
@@ -61,6 +61,7 @@
 	for (;;) {
 		const char timer_wfi_message[] = "WFI  xxxxxxx";
 		const char timer_wfe_message[] = "WFE  xxxxxxx";
+		const char timer_receive_message[] = "RECV xxxxxxx";
 		struct hf_mailbox_receive_return received_message =
 			mailbox_receive_retry();
 		if (received_message.vm_id == HF_PRIMARY_VM_ID &&
@@ -72,6 +73,8 @@
 			char *message = SERVICE_RECV_BUFFER();
 			bool wfi = memcmp(message, timer_wfi_message, 5) == 0;
 			bool wfe = memcmp(message, timer_wfe_message, 5) == 0;
+			bool receive =
+				memcmp(message, timer_receive_message, 5) == 0;
 			int32_t ticks = (message[5] - '0') * 1000000 +
 					(message[6] - '0') * 100000 +
 					(message[7] - '0') * 10000 +
@@ -80,7 +83,7 @@
 					(message[10] - '0') * 10 +
 					(message[11] - '0');
 			dlog("Starting timer for %d ticks.\n", ticks);
-			if (wfi) {
+			if (wfi || receive) {
 				arch_irq_disable();
 			}
 			timer_set(ticks);
@@ -95,6 +98,20 @@
 				while (!timer_fired) {
 					event_wait();
 				}
+			} else if (receive) {
+				/*
+				 * Block on hf_mailbox_receive until timer
+				 * fires.
+				 */
+				struct hf_mailbox_receive_return received =
+					hf_mailbox_receive(true);
+				/*
+				 * Expect to be interrupted, not to actually
+				 * receive a message.
+				 */
+				EXPECT_EQ(received.vm_id, HF_INVALID_VM_ID);
+				EXPECT_EQ(received.size, 0);
+				arch_irq_enable();
 			} else {
 				/* Busy wait until the timer fires. */
 				while (!timer_fired) {
diff --git a/test/vmapi/gicv3/timer_secondary.c b/test/vmapi/gicv3/timer_secondary.c
index 658b2d7..9baa24e 100644
--- a/test/vmapi/gicv3/timer_secondary.c
+++ b/test/vmapi/gicv3/timer_secondary.c
@@ -216,6 +216,26 @@
 	timer_wfi_secondary("WFE  0099999", true);
 }
 
+TEST(timer_secondary, receive_short)
+{
+	/*
+	 * Run the test twice in a row, to check that the state doesn't get
+	 * messed up.
+	 */
+	timer_wfi_secondary("RECV 0000001", false);
+	timer_wfi_secondary("RECV 0000001", false);
+}
+
+TEST(timer_secondary, receive_long)
+{
+	/*
+	 * Run the test twice in a row, to check that the state doesn't get
+	 * messed up.
+	 */
+	timer_wfi_secondary("RECV 0099999", false);
+	timer_wfi_secondary("RECV 0099999", false);
+}
+
 /**
  * Set the timer for a very long time, and expect that it doesn't fire.
  */
diff --git a/test/vmapi/primary_with_secondaries/interrupts.c b/test/vmapi/primary_with_secondaries/interrupts.c
index 0193f69..5f12565 100644
--- a/test/vmapi/primary_with_secondaries/interrupts.c
+++ b/test/vmapi/primary_with_secondaries/interrupts.c
@@ -195,3 +195,30 @@
 		  0);
 	EXPECT_EQ(hf_mailbox_clear(), 0);
 }
+
+/**
+ * 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 hf_vcpu_run_return run_res;
+	struct mailbox_buffers mb = set_up_mailbox();
+
+	SERVICE_SELECT(SERVICE_VM0, "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_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);
+}
diff --git a/test/vmapi/primary_with_secondaries/services/BUILD.gn b/test/vmapi/primary_with_secondaries/services/BUILD.gn
index 4a45dfb..923ad75 100644
--- a/test/vmapi/primary_with_secondaries/services/BUILD.gn
+++ b/test/vmapi/primary_with_secondaries/services/BUILD.gn
@@ -89,6 +89,19 @@
   ]
 }
 
+# Service to check that hf_mailbox_receive can't block when there are pending
+# interrupts.
+source_set("receive_block") {
+  testonly = true
+  public_configs = [
+    "..:config",
+    "//test/hftest:hftest_config",
+  ]
+  sources = [
+    "receive_block.c",
+  ]
+}
+
 # Service to listen for messages and forward them on to another.
 source_set("relay") {
   testonly = true
@@ -111,6 +124,7 @@
     ":echo_with_notification",
     ":interruptible",
     ":memory",
+    ":receive_block",
     ":relay",
     "//test/hftest:hftest_secondary_vm",
   ]
diff --git a/test/vmapi/primary_with_secondaries/services/receive_block.c b/test/vmapi/primary_with_secondaries/services/receive_block.c
new file mode 100644
index 0000000..e593f5b
--- /dev/null
+++ b/test/vmapi/primary_with_secondaries/services/receive_block.c
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2019 The Hafnium Authors.
+ *
+ * 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 "hf/arch/cpu.h"
+#include "hf/arch/vm/interrupts_gicv3.h"
+
+#include "hf/dlog.h"
+
+#include "vmapi/hf/call.h"
+
+#include "hftest.h"
+#include "primary_with_secondary.h"
+
+/*
+ * Secondary VM that enables an interrupt, disables interrupts globally, and
+ * calls hf_mailbox_receive with block=true but expects it to fail.
+ */
+
+static void irq(void)
+{
+	uint32_t interrupt_id = hf_interrupt_get();
+	dlog("Unexpected secondary IRQ %d from current\n", interrupt_id);
+	FAIL("Unexpected secondary IRQ");
+}
+
+TEST_SERVICE(receive_block)
+{
+	int32_t i;
+	const char message[] = "Done waiting";
+
+	exception_setup(irq);
+	arch_irq_disable();
+	hf_interrupt_enable(EXTERNAL_INTERRUPT_ID_A, true);
+
+	for (i = 0; i < 10; ++i) {
+		struct hf_mailbox_receive_return res = hf_mailbox_receive(true);
+		EXPECT_EQ(res.vm_id, HF_INVALID_VM_ID);
+		EXPECT_EQ(res.size, 0);
+	}
+
+	memcpy(SERVICE_SEND_BUFFER(), message, sizeof(message));
+	hf_mailbox_send(HF_PRIMARY_VM_ID, sizeof(message), false);
+}