SPCI: Introduce SPCI_MSG_SEND.

Morph the vmapi service HF_MAILBOX_SEND onto SPCI_MSG_SEND.
The current SPCI_MSG_SEND only allows for implementation defined
messages to be exchanged between VMs.

The new SPCI service returns SPCI_SUCCESS if message is delivered or one
of the following error codes:
        SPCI_INVALID_PARAMETER: one of the parameters in the header does
        not conform;
        SPCI_BUSY: the mailbox was full or the target VM does not exist.

Adapted the tests to this new service.
Added tests specific to spci_message_init and spci_msg_send.

Change-Id: I55adfe68502ddc7bf864432f3e567b6cfe785f92
diff --git a/driver/linux b/driver/linux
index 71f5736..1cc6c75 160000
--- a/driver/linux
+++ b/driver/linux
@@ -1 +1 @@
-Subproject commit 71f57364e99605d8c881649a34bbf4890051ed71
+Subproject commit 1cc6c75d8a5ca147274c31b98f25f33c6a5466c9
diff --git a/inc/hf/api.h b/inc/hf/api.h
index 4604123..e588575 100644
--- a/inc/hf/api.h
+++ b/inc/hf/api.h
@@ -32,8 +32,6 @@
 				       struct vcpu **next);
 int64_t api_vm_configure(ipaddr_t send, ipaddr_t recv, struct vcpu *current,
 			 struct vcpu **next);
-int64_t api_mailbox_send(uint32_t vm_id, size_t size, bool notify,
-			 struct vcpu *current, struct vcpu **next);
 struct hf_mailbox_receive_return api_mailbox_receive(bool block,
 						     struct vcpu *current,
 						     struct vcpu **next);
@@ -53,3 +51,6 @@
 int64_t api_interrupt_inject(uint32_t target_vm_id, uint32_t target_vcpu_idx,
 			     uint32_t intid, struct vcpu *current,
 			     struct vcpu **next);
+
+int32_t api_spci_msg_send(uint32_t attributes, struct vcpu *current,
+			  struct vcpu **next);
diff --git a/inc/hf/vm.h b/inc/hf/vm.h
index b3b7492..069a265 100644
--- a/inc/hf/vm.h
+++ b/inc/hf/vm.h
@@ -22,6 +22,7 @@
 #include "hf/list.h"
 #include "hf/mm.h"
 #include "hf/mpool.h"
+#include "hf/spci.h"
 
 enum mailbox_state {
 	/** There is no message in the mailbox. */
@@ -55,8 +56,8 @@
 	enum mailbox_state state;
 	uint32_t recv_from_id;
 	int16_t recv_bytes;
-	void *recv;
-	const void *send;
+	struct spci_message *recv;
+	const struct spci_message *send;
 
 	/**
 	 * List of wait_entry structs representing VMs that want to be notified
diff --git a/inc/vmapi/hf/call.h b/inc/vmapi/hf/call.h
index 5c8b1c6..8bc9627 100644
--- a/inc/vmapi/hf/call.h
+++ b/inc/vmapi/hf/call.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include "hf/abi.h"
+#include "hf/spci.h"
 #include "hf/types.h"
 
 /* Keep macro alignment */
@@ -29,7 +30,6 @@
 #define HF_VCPU_RUN             0xff03
 #define HF_VCPU_YIELD           0xff04
 #define HF_VM_CONFIGURE         0xff05
-#define HF_MAILBOX_SEND         0xff06
 #define HF_MAILBOX_RECEIVE      0xff07
 #define HF_MAILBOX_CLEAR        0xff08
 #define HF_MAILBOX_WRITABLE_GET 0xff09
@@ -115,11 +115,14 @@
  * If the recipient's receive buffer is busy, it can optionally register the
  * caller to be notified when the recipient's receive buffer becomes available.
  *
- * Returns -1 on failure and 0 on success.
+ * Returns SPCI_SUCCESS if the message is sent, an error code otherwise:
+ *  - INVALID_PARAMETER: one or more of the parameters do not conform.
+ *  - BUSY: the message could not be delivered either because the mailbox
+ *            was full or the target VM does not yet exist.
  */
-static inline int64_t hf_mailbox_send(uint32_t vm_id, size_t size, bool notify)
+static inline int64_t spci_msg_send(uint32_t attributes)
 {
-	return hf_call(HF_MAILBOX_SEND, vm_id, size, notify);
+	return hf_call(SPCI_MSG_SEND_32, attributes, 0, 0);
 }
 
 /**
diff --git a/src/BUILD.gn b/src/BUILD.gn
index 89c476c..d0cbc90 100644
--- a/src/BUILD.gn
+++ b/src/BUILD.gn
@@ -108,6 +108,7 @@
     "fdt_test.cc",
     "mm_test.cc",
     "mpool_test.cc",
+    "spci_test.cc",
   ]
   sources += [ "layout_fake.c" ]
   cflags_cc = [
diff --git a/src/api.c b/src/api.c
index 938d978..1fdb54a 100644
--- a/src/api.c
+++ b/src/api.c
@@ -28,6 +28,7 @@
 #include "hf/vm.h"
 
 #include "vmapi/hf/call.h"
+#include "vmapi/hf/spci.h"
 
 /*
  * To eliminate the risk of deadlocks, we define a partial order for the
@@ -729,43 +730,63 @@
  * If the recipient's receive buffer is busy, it can optionally register the
  * caller to be notified when the recipient's receive buffer becomes available.
  */
-int64_t api_mailbox_send(uint32_t vm_id, size_t size, bool notify,
-			 struct vcpu *current, struct vcpu **next)
+int32_t api_spci_msg_send(uint32_t attributes, struct vcpu *current,
+			  struct vcpu **next)
 {
 	struct vm *from = current->vm;
 	struct vm *to;
-	const void *from_buf;
-	int64_t ret;
 	struct hf_vcpu_run_return primary_ret = {
 		.code = HF_VCPU_RUN_MESSAGE,
 	};
+	struct spci_message from_msg_replica;
+	struct spci_message *to_msg;
+	const struct spci_message *from_msg;
 
-	/* Limit the size of transfer. */
-	if (size > HF_MAILBOX_SIZE) {
-		return -1;
-	}
+	uint32_t size;
 
-	/* Disallow reflexive requests as this suggests an error in the VM. */
-	if (vm_id == from->id) {
-		return -1;
-	}
+	int64_t ret;
+	bool notify = (attributes & SPCI_MSG_SEND_NOTIFY_MASK) ==
+		      SPCI_MSG_SEND_NOTIFY;
 
-	/* Ensure the target VM exists. */
-	to = vm_get(vm_id);
-	if (to == NULL) {
-		return -1;
+	/*
+	 * Check that the sender has configured its send buffer. Copy the
+	 * message header. If the tx mailbox at from_msg is configured (i.e.
+	 * from_msg != NULL) then it can be safely accessed after releasing the
+	 * lock since the tx mailbox address can only be configured once.
+	 */
+	sl_lock(&from->lock);
+	from_msg = from->mailbox.send;
+	sl_unlock(&from->lock);
+
+	if (from_msg == NULL) {
+		return SPCI_INVALID_PARAMETERS;
 	}
 
 	/*
-	 * Check that the sender has configured its send buffer. It is safe to
-	 * use from_buf after releasing the lock because the buffer cannot be
-	 * modified once it's configured.
+	 * Note that the payload is not copied when the message header is.
 	 */
-	sl_lock(&from->lock);
-	from_buf = from->mailbox.send;
-	sl_unlock(&from->lock);
-	if (from_buf == NULL) {
-		return -1;
+	from_msg_replica = *from_msg;
+
+	/* Ensure source VM id corresponds to the current VM. */
+	if (from_msg_replica.source_vm_id != from->id) {
+		return SPCI_INVALID_PARAMETERS;
+	}
+
+	size = from_msg_replica.length;
+	/* Limit the size of transfer. */
+	if (size > HF_MAILBOX_SIZE - sizeof(struct spci_message)) {
+		return SPCI_INVALID_PARAMETERS;
+	}
+
+	/* Disallow reflexive requests as this suggests an error in the VM. */
+	if (from_msg_replica.target_vm_id == from->id) {
+		return SPCI_INVALID_PARAMETERS;
+	}
+
+	/* Ensure the target VM exists. */
+	to = vm_get(from_msg_replica.target_vm_id);
+	if (to == NULL) {
+		return SPCI_INVALID_PARAMETERS;
 	}
 
 	sl_lock(&to->lock);
@@ -778,7 +799,8 @@
 		 */
 		if (notify) {
 			struct wait_entry *entry =
-				&current->vm->wait_entries[vm_id];
+				&current->vm->wait_entries
+					 [from_msg_replica.target_vm_id];
 
 			/* Append waiter only if it's not there yet. */
 			if (list_empty(&entry->wait_links)) {
@@ -787,16 +809,18 @@
 			}
 		}
 
-		ret = -1;
+		ret = SPCI_BUSY;
 		goto out;
 	}
 
 	/* Copy data. */
-	memcpy(to->mailbox.recv, from_buf, size);
+	to_msg = to->mailbox.recv;
+	*to_msg = from_msg_replica;
+	memcpy(to_msg->payload, from->mailbox.send->payload, size);
 	to->mailbox.recv_bytes = size;
 	to->mailbox.recv_from_id = from->id;
 	primary_ret.message.vm_id = to->id;
-	ret = 0;
+	ret = SPCI_SUCCESS;
 
 	/* Messages for the primary VM are delivered directly. */
 	if (to->id == HF_PRIMARY_VM_ID) {
diff --git a/src/arch/aarch64/handler.c b/src/arch/aarch64/handler.c
index eb8f8dc..e29ddf0 100644
--- a/src/arch/aarch64/handler.c
+++ b/src/arch/aarch64/handler.c
@@ -21,6 +21,7 @@
 #include "hf/api.h"
 #include "hf/cpu.h"
 #include "hf/dlog.h"
+#include "hf/spci.h"
 #include "hf/vm.h"
 
 #include "vmapi/hf/call.h"
@@ -404,7 +405,7 @@
 		}
 	}
 
-	switch ((uint32_t)arg0 & ~SMCCC_CONVENTION_MASK) {
+	switch ((uint32_t)arg0) {
 	case HF_VM_GET_ID:
 		ret.user_ret = api_vm_get_id(current());
 		break;
@@ -432,9 +433,8 @@
 						current(), &ret.new);
 		break;
 
-	case HF_MAILBOX_SEND:
-		ret.user_ret =
-			api_mailbox_send(arg1, arg2, arg3, current(), &ret.new);
+	case SPCI_MSG_SEND_32:
+		ret.user_ret = api_spci_msg_send(arg1, current(), &ret.new);
 		break;
 
 	case HF_MAILBOX_RECEIVE:
diff --git a/src/spci_test.cc b/src/spci_test.cc
new file mode 100644
index 0000000..a3191cc
--- /dev/null
+++ b/src/spci_test.cc
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+extern "C" {
+#include "vmapi/hf/spci.h"
+}
+
+#include <gmock/gmock.h>
+
+namespace
+{
+using ::testing::Eq;
+
+/**
+ * Ensure that spci_message_init is correctly setting the expected fields in the
+ * SPCI common message header.
+ */
+TEST(spci, spci_message_init)
+{
+	spci_message header;
+	spci_message compare_header = {
+		.flags = SPCI_MESSAGE_IMPDEF_MASK,
+		.length = 1,
+		.target_vm_id = 2,
+		.source_vm_id = 3,
+	};
+
+	memset(&header, 0xff, sizeof(header));
+	spci_message_init(&header, 1, 2, 3);
+
+	EXPECT_THAT(memcmp(&header, &compare_header, sizeof(header)), 0);
+}
+} /* namespace */
diff --git a/test/hftest/hftest_service.c b/test/hftest/hftest_service.c
index 9850a07..2a1b2a0 100644
--- a/test/hftest/hftest_service.c
+++ b/test/hftest/hftest_service.c
@@ -20,6 +20,7 @@
 #include "hf/arch/std.h"
 
 #include "hf/memiter.h"
+#include "hf/spci.h"
 
 #include "vmapi/hf/call.h"
 
@@ -80,17 +81,18 @@
 {
 	struct memiter args;
 	hftest_test_fn service;
-	struct hf_mailbox_receive_return res;
 	struct hftest_context *ctx;
 
+	struct spci_message *recv_msg = (struct spci_message *)recv;
+
 	/* Prepare the context. */
 
 	/* Set up the mailbox. */
 	hf_vm_configure(send_addr, recv_addr);
 
 	/* Receive the name of the service to run. */
-	res = hf_mailbox_receive(true);
-	memiter_init(&args, recv, res.size);
+	hf_mailbox_receive(true);
+	memiter_init(&args, recv_msg->payload, recv_msg->length);
 	service = find_service(&args);
 	hf_mailbox_clear();
 
@@ -108,8 +110,8 @@
 	ctx = hftest_get_context();
 	memset(ctx, 0, sizeof(*ctx));
 	ctx->abort = abort;
-	ctx->send = send;
-	ctx->recv = recv;
+	ctx->send = (struct spci_message *)send;
+	ctx->recv = (struct spci_message *)recv;
 
 	/* Pause so the next time cycles are given the service will be run. */
 	hf_vcpu_yield();
diff --git a/test/hftest/inc/hftest_impl.h b/test/hftest/inc/hftest_impl.h
index 5778b20..328f664 100644
--- a/test/hftest/inc/hftest_impl.h
+++ b/test/hftest/inc/hftest_impl.h
@@ -20,6 +20,8 @@
 
 #include "hf/arch/std.h"
 
+#include "hf/spci.h"
+
 #define HFTEST_MAX_TESTS 50
 
 /*
@@ -133,8 +135,8 @@
 	noreturn void (*abort)(void);
 
 	/* These are used in services. */
-	void *send;
-	void *recv;
+	struct spci_message *send;
+	struct spci_message *recv;
 };
 
 struct hftest_context *hftest_get_context(void);
@@ -271,6 +273,7 @@
 #define HFTEST_SERVICE_SELECT(vm_id, service, send_buffer)                    \
 	do {                                                                  \
 		struct hf_vcpu_run_return run_res;                            \
+		uint32_t msg_length = strlen(service);                        \
                                                                               \
 		/*                                                            \
 		 * Let the service configure its mailbox and wait for a       \
@@ -281,8 +284,11 @@
 		ASSERT_EQ(run_res.sleep.ns, HF_SLEEP_INDEFINITE);             \
                                                                               \
 		/* Send the selected service to run and let it be handled. */ \
-		memcpy(send_buffer, service, strlen(service));                \
-		ASSERT_EQ(hf_mailbox_send(vm_id, strlen(service), false), 0); \
+		memcpy(send_buffer->payload, service, msg_length);            \
+		spci_message_init(send_buffer, msg_length, vm_id,             \
+				  hf_vm_get_id());                            \
+                                                                              \
+		ASSERT_EQ(spci_msg_send(0), 0);                               \
 		run_res = hf_vcpu_run(vm_id, 0);                              \
 		ASSERT_EQ(run_res.code, HF_VCPU_RUN_YIELD);                   \
 	} while (0)
diff --git a/test/vmapi/gicv3/busy_secondary.c b/test/vmapi/gicv3/busy_secondary.c
index 9ea691f..f694031 100644
--- a/test/vmapi/gicv3/busy_secondary.c
+++ b/test/vmapi/gicv3/busy_secondary.c
@@ -19,6 +19,7 @@
 #include "hf/arch/vm/interrupts_gicv3.h"
 
 #include "hf/dlog.h"
+#include "hf/spci.h"
 
 #include "vmapi/hf/call.h"
 
@@ -38,7 +39,7 @@
 {
 	system_setup();
 	EXPECT_EQ(hf_vm_configure(send_page_addr, recv_page_addr), 0);
-	SERVICE_SELECT(SERVICE_VM0, "busy", send_page);
+	SERVICE_SELECT(SERVICE_VM0, "busy", send_buffer);
 }
 
 TEST(busy_secondary, virtual_timer)
@@ -78,8 +79,10 @@
 
 	/* Let secondary start looping. */
 	dlog("Telling secondary to loop.\n");
-	memcpy(send_page, message, sizeof(message));
-	EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, sizeof(message), false), 0);
+	memcpy(send_buffer->payload, message, sizeof(message));
+	spci_message_init(send_buffer, 0, SERVICE_VM0,
+			  recv_buffer->target_vm_id);
+	EXPECT_EQ(spci_msg_send(0), 0);
 	run_res = hf_vcpu_run(SERVICE_VM0, 0);
 	EXPECT_EQ(run_res.code, HF_VCPU_RUN_PREEMPTED);
 
@@ -133,8 +136,10 @@
 
 	/* Let secondary start looping. */
 	dlog("Telling secondary to loop.\n");
-	memcpy(send_page, message, sizeof(message));
-	EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, sizeof(message), false), 0);
+	memcpy(send_buffer->payload, message, sizeof(message));
+	spci_message_init(send_buffer, 0, SERVICE_VM0,
+			  recv_buffer->target_vm_id);
+	EXPECT_EQ(spci_msg_send(0), 0);
 	run_res = hf_vcpu_run(SERVICE_VM0, 0);
 	EXPECT_EQ(run_res.code, HF_VCPU_RUN_PREEMPTED);
 
diff --git a/test/vmapi/gicv3/gicv3.c b/test/vmapi/gicv3/gicv3.c
index bc1e24d..dc0a081 100644
--- a/test/vmapi/gicv3/gicv3.c
+++ b/test/vmapi/gicv3/gicv3.c
@@ -34,6 +34,9 @@
 hf_ipaddr_t send_page_addr = (hf_ipaddr_t)send_page;
 hf_ipaddr_t recv_page_addr = (hf_ipaddr_t)recv_page;
 
+struct spci_message *send_buffer = (struct spci_message *)send_page;
+struct spci_message *recv_buffer = (struct spci_message *)recv_page;
+
 volatile uint32_t last_interrupt_id = 0;
 
 static void irq(void)
diff --git a/test/vmapi/gicv3/inc/gicv3.h b/test/vmapi/gicv3/inc/gicv3.h
index e4748cb..264dc0c 100644
--- a/test/vmapi/gicv3/inc/gicv3.h
+++ b/test/vmapi/gicv3/inc/gicv3.h
@@ -33,6 +33,9 @@
 extern hf_ipaddr_t send_page_addr;
 extern hf_ipaddr_t recv_page_addr;
 
+extern struct spci_message *send_buffer;
+extern struct spci_message *recv_buffer;
+
 extern volatile uint32_t last_interrupt_id;
 
 void system_setup();
diff --git a/test/vmapi/gicv3/services/busy.c b/test/vmapi/gicv3/services/busy.c
index f311124..4a18d35 100644
--- a/test/vmapi/gicv3/services/busy.c
+++ b/test/vmapi/gicv3/services/busy.c
@@ -31,7 +31,7 @@
 TEST_SERVICE(busy)
 {
 	dlog("Secondary waiting for message...\n");
-	(void)mailbox_receive_retry();
+	mailbox_receive_retry();
 	hf_mailbox_clear();
 	dlog("Secondary received message, looping forever.\n");
 	for (;;) {
diff --git a/test/vmapi/gicv3/services/timer.c b/test/vmapi/gicv3/services/timer.c
index 029c729..79a2b8a 100644
--- a/test/vmapi/gicv3/services/timer.c
+++ b/test/vmapi/gicv3/services/timer.c
@@ -46,8 +46,10 @@
 	}
 	buffer[8] = '0' + interrupt_id / 10;
 	buffer[9] = '0' + interrupt_id % 10;
-	memcpy(SERVICE_SEND_BUFFER(), buffer, size);
-	hf_mailbox_send(HF_PRIMARY_VM_ID, size, false);
+	memcpy(SERVICE_SEND_BUFFER()->payload, buffer, size);
+	spci_message_init(SERVICE_SEND_BUFFER(), size, HF_PRIMARY_VM_ID,
+			  SERVICE_RECV_BUFFER()->target_vm_id);
+	spci_msg_send(0);
 	dlog("secondary IRQ %d ended\n", interrupt_id);
 	event_send_local();
 }
@@ -60,19 +62,22 @@
 
 	for (;;) {
 		const char timer_wfi_message[] = "**** xxxxxxx";
-		char *message = SERVICE_RECV_BUFFER();
+		struct spci_message *message_header = SERVICE_RECV_BUFFER();
+		uint8_t *message;
 		bool wfi, wfe, receive;
 		bool disable_interrupts;
 		uint32_t ticks;
-		struct hf_mailbox_receive_return received_message =
-			mailbox_receive_retry();
+		mailbox_receive_retry();
 
-		if (received_message.vm_id != HF_PRIMARY_VM_ID ||
-		    received_message.size != sizeof(timer_wfi_message)) {
+		if (message_header->source_vm_id != HF_PRIMARY_VM_ID ||
+		    message_header->length != sizeof(timer_wfi_message)) {
 			FAIL("Got unexpected message from VM %d, size %d.\n",
-			     received_message.vm_id, received_message.size);
+			     message_header->source_vm_id,
+			     message_header->length);
 		}
 
+		message = message_header->payload;
+
 		/*
 		 * Start a timer to send the message back: enable it and
 		 * set it for the requested number of ticks.
diff --git a/test/vmapi/gicv3/timer_secondary.c b/test/vmapi/gicv3/timer_secondary.c
index dcb2e87..e554551 100644
--- a/test/vmapi/gicv3/timer_secondary.c
+++ b/test/vmapi/gicv3/timer_secondary.c
@@ -19,6 +19,7 @@
 
 #include "hf/abi.h"
 #include "hf/call.h"
+#include "hf/spci.h"
 
 #include "gicv3.h"
 #include "hftest.h"
@@ -28,7 +29,7 @@
 	system_setup();
 
 	EXPECT_EQ(hf_vm_configure(send_page_addr, recv_page_addr), 0);
-	SERVICE_SELECT(SERVICE_VM0, "timer", send_page);
+	SERVICE_SELECT(SERVICE_VM0, "timer", send_buffer);
 
 	interrupt_enable(VIRTUAL_TIMER_IRQ, true);
 	interrupt_set_edge_triggered(VIRTUAL_TIMER_IRQ, true);
@@ -48,8 +49,10 @@
 	EXPECT_EQ(run_res.sleep.ns, HF_SLEEP_INDEFINITE);
 
 	/* Send the message for the secondary to set a timer. */
-	memcpy(send_page, message, sizeof(message));
-	EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, sizeof(message), false), 0);
+	memcpy(send_buffer->payload, message, sizeof(message));
+	spci_message_init(send_buffer, sizeof(message), SERVICE_VM0,
+			  HF_PRIMARY_VM_ID);
+	EXPECT_EQ(spci_msg_send(0), 0);
 
 	/*
 	 * Let the secondary handle the message and set the timer. It will loop
@@ -71,9 +74,9 @@
 	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(recv_page, expected_response, sizeof(expected_response)),
-		0);
+	EXPECT_EQ(memcmp(recv_buffer->payload, expected_response,
+			 sizeof(expected_response)),
+		  0);
 	EXPECT_EQ(hf_mailbox_clear(), 0);
 }
 
@@ -104,8 +107,10 @@
 	EXPECT_EQ(run_res.sleep.ns, HF_SLEEP_INDEFINITE);
 
 	/* Send the message for the secondary to set a timer. */
-	memcpy(send_page, message, message_length);
-	EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, message_length, false), 0);
+	memcpy(send_buffer->payload, message, message_length);
+	spci_message_init(send_buffer, message_length, SERVICE_VM0,
+			  HF_PRIMARY_VM_ID);
+	EXPECT_EQ(spci_msg_send(0), 0);
 
 	/*
 	 * Let the secondary handle the message and set the timer. Then there's
@@ -152,9 +157,9 @@
 	/* Once we wake it up it should get the timer interrupt and respond. */
 	EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
 	EXPECT_EQ(run_res.message.size, sizeof(expected_response));
-	EXPECT_EQ(
-		memcmp(recv_page, expected_response, sizeof(expected_response)),
-		0);
+	EXPECT_EQ(memcmp(recv_buffer->payload, expected_response,
+			 sizeof(expected_response)),
+		  0);
 	EXPECT_EQ(hf_mailbox_clear(), 0);
 }
 
@@ -240,8 +245,10 @@
 	EXPECT_EQ(run_res.sleep.ns, HF_SLEEP_INDEFINITE);
 
 	/* Send the message for the secondary to set a timer. */
-	memcpy(send_page, message, message_length);
-	EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, message_length, false), 0);
+	memcpy(send_buffer->payload, message, message_length);
+	spci_message_init(send_buffer, message_length, SERVICE_VM0,
+			  HF_PRIMARY_VM_ID);
+	EXPECT_EQ(spci_msg_send(0), 0);
 
 	/*
 	 * Let the secondary handle the message and set the timer.
diff --git a/test/vmapi/primary_with_secondaries/BUILD.gn b/test/vmapi/primary_with_secondaries/BUILD.gn
index b82fa30..34f8857 100644
--- a/test/vmapi/primary_with_secondaries/BUILD.gn
+++ b/test/vmapi/primary_with_secondaries/BUILD.gn
@@ -30,6 +30,7 @@
     "memory_sharing.c",
     "no_services.c",
     "run_race.c",
+    "spci.c",
   ]
 
   sources += [ "util.c" ]
diff --git a/test/vmapi/primary_with_secondaries/inc/util.h b/test/vmapi/primary_with_secondaries/inc/util.h
index b127fae..eca641f 100644
--- a/test/vmapi/primary_with_secondaries/inc/util.h
+++ b/test/vmapi/primary_with_secondaries/inc/util.h
@@ -16,9 +16,11 @@
 
 #pragma once
 
+#include "vmapi/hf/spci.h"
+
 struct mailbox_buffers {
-	void *send;
-	void *recv;
+	struct spci_message *send;
+	struct spci_message *recv;
 };
 
 struct mailbox_buffers set_up_mailbox(void);
diff --git a/test/vmapi/primary_with_secondaries/interrupts.c b/test/vmapi/primary_with_secondaries/interrupts.c
index 655ca54..9429acc 100644
--- a/test/vmapi/primary_with_secondaries/interrupts.c
+++ b/test/vmapi/primary_with_secondaries/interrupts.c
@@ -42,12 +42,15 @@
 	EXPECT_EQ(run_res.sleep.ns, HF_SLEEP_INDEFINITE);
 
 	/* 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);
+	memcpy(mb.send->payload, message, sizeof(message));
+	spci_message_init(mb.send, sizeof(message), SERVICE_VM0,
+			  HF_PRIMARY_VM_ID);
+	EXPECT_EQ(spci_msg_send(0), 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)),
+	EXPECT_EQ(memcmp(mb.recv->payload, expected_response,
+			 sizeof(expected_response)),
 		  0);
 	EXPECT_EQ(hf_mailbox_clear(), 0);
 }
@@ -74,7 +77,8 @@
 	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)),
+	EXPECT_EQ(memcmp(mb.recv->payload, expected_response,
+			 sizeof(expected_response)),
 		  0);
 	EXPECT_EQ(hf_mailbox_clear(), 0);
 
@@ -83,7 +87,8 @@
 	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)),
+	EXPECT_EQ(memcmp(mb.recv->payload, expected_response,
+			 sizeof(expected_response)),
 		  0);
 	EXPECT_EQ(hf_mailbox_clear(), 0);
 }
@@ -110,7 +115,8 @@
 	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)),
+	EXPECT_EQ(memcmp(mb.recv->payload, expected_response,
+			 sizeof(expected_response)),
 		  0);
 	EXPECT_EQ(hf_mailbox_clear(), 0);
 
@@ -119,7 +125,7 @@
 	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,
+	EXPECT_EQ(memcmp(mb.recv->payload, expected_response_2,
 			 sizeof(expected_response_2)),
 		  0);
 	EXPECT_EQ(hf_mailbox_clear(), 0);
@@ -149,7 +155,8 @@
 	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)),
+	EXPECT_EQ(memcmp(mb.recv->payload, expected_response,
+			 sizeof(expected_response)),
 		  0);
 	EXPECT_EQ(hf_mailbox_clear(), 0);
 
@@ -158,12 +165,14 @@
 	EXPECT_EQ(run_res.sleep.ns, HF_SLEEP_INDEFINITE);
 
 	/* Now send a message to the secondary. */
-	memcpy(mb.send, message, sizeof(message));
-	EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, sizeof(message), false), 0);
+	memcpy(mb.send->payload, message, sizeof(message));
+	spci_message_init(mb.send, sizeof(message), SERVICE_VM0,
+			  HF_PRIMARY_VM_ID);
+	EXPECT_EQ(spci_msg_send(0), 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,
+	EXPECT_EQ(memcmp(mb.recv->payload, expected_response_2,
 			 sizeof(expected_response_2)),
 		  0);
 	EXPECT_EQ(hf_mailbox_clear(), 0);
@@ -193,12 +202,15 @@
 	 * 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);
+	memcpy(mb.send->payload, message, sizeof(message));
+	spci_message_init(mb.send, sizeof(message), SERVICE_VM0,
+			  HF_PRIMARY_VM_ID);
+	EXPECT_EQ(spci_msg_send(0), 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)),
+	EXPECT_EQ(memcmp(mb.recv->payload, expected_response,
+			 sizeof(expected_response)),
 		  0);
 	EXPECT_EQ(hf_mailbox_clear(), 0);
 }
@@ -225,7 +237,8 @@
 	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)),
+	EXPECT_EQ(memcmp(mb.recv->payload, expected_response,
+			 sizeof(expected_response)),
 		  0);
 	EXPECT_EQ(hf_mailbox_clear(), 0);
 }
@@ -252,7 +265,8 @@
 	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)),
+	EXPECT_EQ(memcmp(mb.recv->payload, expected_response,
+			 sizeof(expected_response)),
 		  0);
 	EXPECT_EQ(hf_mailbox_clear(), 0);
 }
@@ -273,12 +287,14 @@
 	EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_MESSAGE);
 	EXPECT_EQ(run_res.sleep.ns, HF_SLEEP_INDEFINITE);
 
-	memcpy(mb.send, message, sizeof(message));
-	EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, sizeof(message), false), 0);
+	memcpy(mb.send->payload, message, sizeof(message));
+	spci_message_init(mb.send, sizeof(message), SERVICE_VM0,
+			  HF_PRIMARY_VM_ID);
+	EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
 	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(message));
-	EXPECT_EQ(memcmp(mb.recv, message, sizeof(message)), 0);
+	EXPECT_EQ(memcmp(mb.recv->payload, message, sizeof(message)), 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
index 1e1a4d3..8615957 100644
--- a/test/vmapi/primary_with_secondaries/mailbox.c
+++ b/test/vmapi/primary_with_secondaries/mailbox.c
@@ -18,6 +18,8 @@
 
 #include "hf/arch/std.h"
 
+#include "hf/spci.h"
+
 #include "vmapi/hf/call.h"
 
 #include "hftest.h"
@@ -87,12 +89,14 @@
 	EXPECT_EQ(run_res.sleep.ns, HF_SLEEP_INDEFINITE);
 
 	/* 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);
+	memcpy(mb.send->payload, message, sizeof(message));
+	spci_message_init(mb.send, sizeof(message), SERVICE_VM0,
+			  HF_PRIMARY_VM_ID);
+	EXPECT_EQ(spci_msg_send(0), 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(memcmp(mb.send->payload, message, sizeof(message)), 0);
 	EXPECT_EQ(hf_mailbox_clear(), 0);
 }
 
@@ -116,13 +120,15 @@
 
 		/* 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);
+		memcpy(mb.send->payload, message, sizeof(message));
+		spci_message_init(mb.send, sizeof(message), SERVICE_VM0,
+				  HF_PRIMARY_VM_ID);
+		EXPECT_EQ(spci_msg_send(0), 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(memcmp(mb.recv->payload, message, sizeof(message)),
+			  0);
 		EXPECT_EQ(hf_mailbox_clear(), 0);
 	}
 }
@@ -152,15 +158,15 @@
 	 * SERVICE_VM0, then to SERVICE_VM1 and finally back to here.
 	 */
 	{
-		uint32_t *chain = mb.send;
+		uint32_t *chain = (uint32_t *)mb.send->payload;
 		*chain++ = htole32(SERVICE_VM1);
 		*chain++ = htole32(HF_PRIMARY_VM_ID);
 		memcpy(chain, message, sizeof(message));
-		EXPECT_EQ(hf_mailbox_send(
-				  SERVICE_VM0,
+
+		spci_message_init(mb.send,
 				  sizeof(message) + (2 * sizeof(uint32_t)),
-				  false),
-			  0);
+				  SERVICE_VM0, HF_PRIMARY_VM_ID);
+		EXPECT_EQ(spci_msg_send(0), 0);
 	}
 
 	/* Let SERVICE_VM0 forward the message. */
@@ -176,7 +182,7 @@
 	/* Ensure the message is in tact. */
 	EXPECT_EQ(run_res.message.vm_id, HF_PRIMARY_VM_ID);
 	EXPECT_EQ(run_res.message.size, sizeof(message));
-	EXPECT_EQ(memcmp(mb.recv, message, sizeof(message)), 0);
+	EXPECT_EQ(memcmp(mb.recv->payload, message, sizeof(message)), 0);
 	EXPECT_EQ(hf_mailbox_clear(), 0);
 }
 
@@ -188,15 +194,16 @@
 {
 	struct hf_vcpu_run_return run_res;
 
-	set_up_mailbox();
-
-	EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, 0, false), -1);
+	struct mailbox_buffers mb = set_up_mailbox();
+	spci_message_init(mb.send, 0, SERVICE_VM0, HF_PRIMARY_VM_ID);
+	EXPECT_EQ(spci_msg_send(0), SPCI_BUSY);
 
 	run_res = hf_vcpu_run(SERVICE_VM0, 0);
+	spci_message_init(mb.send, 0, SERVICE_VM0, HF_PRIMARY_VM_ID);
 	EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_MESSAGE);
 	EXPECT_EQ(run_res.sleep.ns, HF_SLEEP_INDEFINITE);
 
-	EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, 0, false), 0);
+	EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
 }
 
 /**
@@ -207,9 +214,10 @@
 {
 	struct hf_vcpu_run_return run_res;
 
-	set_up_mailbox();
+	struct mailbox_buffers mb = set_up_mailbox();
 
-	EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, 0, true), -1);
+	spci_message_init(mb.send, 0, SERVICE_VM0, HF_PRIMARY_VM_ID);
+	EXPECT_EQ(spci_msg_send(SPCI_MSG_SEND_NOTIFY), SPCI_BUSY);
 
 	/*
 	 * Run first VM for it to configure itself. It should result in
@@ -223,7 +231,7 @@
 	EXPECT_EQ(hf_mailbox_waiter_get(SERVICE_VM0), -1);
 
 	/* Send should now succeed. */
-	EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, 0, false), 0);
+	EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
 }
 
 /**
@@ -244,12 +252,14 @@
 	EXPECT_EQ(run_res.sleep.ns, HF_SLEEP_INDEFINITE);
 
 	/* 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);
+	memcpy(mb.send->payload, message, sizeof(message));
+	spci_message_init(mb.send, sizeof(message), SERVICE_VM0,
+			  HF_PRIMARY_VM_ID);
+	EXPECT_EQ(spci_msg_send(0), 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(memcmp(mb.recv->payload, message, sizeof(message)), 0);
 
 	/* Let secondary VM continue running so that it will wait again. */
 	run_res = hf_vcpu_run(SERVICE_VM0, 0);
@@ -258,8 +268,12 @@
 
 	/* 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);
+	memcpy(mb.send->payload, message, sizeof(message));
+	spci_message_init(mb.send, sizeof(message), SERVICE_VM0,
+			  HF_PRIMARY_VM_ID);
+
+	/* Message should be dropped since the mailbox was not cleared. */
+	EXPECT_EQ(spci_msg_send(0), 0);
 	run_res = hf_vcpu_run(SERVICE_VM0, 0);
 	EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
 	EXPECT_EQ(run_res.sleep.ns, HF_SLEEP_INDEFINITE);
@@ -281,7 +295,7 @@
 	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(memcmp(mb.recv->payload, message, sizeof(message)), 0);
 }
 
 /**
@@ -302,15 +316,17 @@
 	EXPECT_EQ(run_res.sleep.ns, HF_SLEEP_INDEFINITE);
 
 	/* 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);
+	memcpy(mb.send->payload, message, sizeof(message));
+	spci_message_init(mb.send, sizeof(message), SERVICE_VM0,
+			  HF_PRIMARY_VM_ID);
+	EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
+	EXPECT_EQ(spci_msg_send(SPCI_MSG_SEND_NOTIFY), SPCI_BUSY);
 
 	/* 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);
+	EXPECT_EQ(memcmp(mb.recv->payload, message, sizeof(message)), 0);
 
 	/* Run VM again so that it clears its mailbox. */
 	run_res = hf_vcpu_run(SERVICE_VM0, 0);
@@ -321,5 +337,5 @@
 	EXPECT_EQ(hf_mailbox_waiter_get(SERVICE_VM0), -1);
 
 	/* Send should now succeed. */
-	EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, 0, false), 0);
+	EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
 }
diff --git a/test/vmapi/primary_with_secondaries/memory_sharing.c b/test/vmapi/primary_with_secondaries/memory_sharing.c
index e44dd93..8d8bb47 100644
--- a/test/vmapi/primary_with_secondaries/memory_sharing.c
+++ b/test/vmapi/primary_with_secondaries/memory_sharing.c
@@ -104,8 +104,9 @@
 	 *       API is still to be agreed on so the address is passed
 	 *       explicitly to test the mechanism.
 	 */
-	memcpy(mb.send, &ptr, sizeof(ptr));
-	EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, sizeof(ptr), false), 0);
+	memcpy(mb.send->payload, &ptr, sizeof(ptr));
+	spci_message_init(mb.send, sizeof(ptr), SERVICE_VM0, HF_PRIMARY_VM_ID);
+	EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
 
 	run_res = hf_vcpu_run(SERVICE_VM0, 0);
 	EXPECT_EQ(run_res.code, HF_VCPU_RUN_YIELD);
@@ -146,8 +147,9 @@
 	 *       API is still to be agreed on so the address is passed
 	 *       explicitly to test the mechanism.
 	 */
-	memcpy(mb.send, &ptr, sizeof(ptr));
-	EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, sizeof(ptr), false), 0);
+	memcpy(mb.send->payload, &ptr, sizeof(ptr));
+	spci_message_init(mb.send, sizeof(ptr), SERVICE_VM0, HF_PRIMARY_VM_ID);
+	EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
 
 	/* Let the memory be returned. */
 	run_res = hf_vcpu_run(SERVICE_VM0, 0);
@@ -183,8 +185,9 @@
 	 *       API is still to be agreed on so the address is passed
 	 *       explicitly to test the mechanism.
 	 */
-	memcpy(mb.send, &ptr, sizeof(ptr));
-	EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, sizeof(ptr), false), 0);
+	memcpy(mb.send->payload, &ptr, sizeof(ptr));
+	spci_message_init(mb.send, sizeof(ptr), SERVICE_VM0, HF_PRIMARY_VM_ID);
+	EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
 
 	/* Let the memory be returned. */
 	run_res = hf_vcpu_run(SERVICE_VM0, 0);
@@ -220,8 +223,9 @@
 	 *       API is still to be agreed on so the address is passed
 	 *       explicitly to test the mechanism.
 	 */
-	memcpy(mb.send, &ptr, sizeof(ptr));
-	EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, sizeof(ptr), false), 0);
+	memcpy(mb.send->payload, &ptr, sizeof(ptr));
+	spci_message_init(mb.send, sizeof(ptr), SERVICE_VM0, HF_PRIMARY_VM_ID);
+	EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
 
 	/* Let the memory be returned. */
 	run_res = hf_vcpu_run(SERVICE_VM0, 0);
@@ -256,8 +260,9 @@
 	 *       API is still to be agreed on so the address is passed
 	 *       explicitly to test the mechanism.
 	 */
-	memcpy(mb.send, &ptr, sizeof(ptr));
-	EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, sizeof(ptr), false), 0);
+	memcpy(mb.send->payload, &ptr, sizeof(ptr));
+	spci_message_init(mb.send, sizeof(ptr), SERVICE_VM0, HF_PRIMARY_VM_ID);
+	EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
 
 	/* Let the memory be returned. */
 	run_res = hf_vcpu_run(SERVICE_VM0, 0);
@@ -295,8 +300,9 @@
 	 *       API is still to be agreed on so the address is passed
 	 *       explicitly to test the mechanism.
 	 */
-	memcpy(mb.send, &ptr, sizeof(ptr));
-	EXPECT_EQ(hf_mailbox_send(SERVICE_VM0, sizeof(ptr), false), 0);
+	memcpy(mb.send->payload, &ptr, sizeof(ptr));
+	spci_message_init(mb.send, sizeof(ptr), SERVICE_VM0, HF_PRIMARY_VM_ID);
+	EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
 
 	/* Let the memory be returned. */
 	run_res = hf_vcpu_run(SERVICE_VM0, 0);
@@ -328,7 +334,7 @@
 	EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
 
 	/* Check the memory was cleared. */
-	memcpy(&ptr, mb.recv, sizeof(ptr));
+	memcpy(&ptr, mb.recv->payload, sizeof(ptr));
 	for (int i = 0; i < PAGE_SIZE; ++i) {
 		ASSERT_EQ(ptr[i], 0);
 	}
@@ -354,7 +360,7 @@
 	EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
 
 	/* Check the memory was cleared. */
-	memcpy(&ptr, mb.recv, sizeof(ptr));
+	memcpy(&ptr, mb.recv->payload, sizeof(ptr));
 	for (int i = 0; i < PAGE_SIZE; ++i) {
 		ASSERT_EQ(ptr[i], 0);
 	}
diff --git a/test/vmapi/primary_with_secondaries/run_race.c b/test/vmapi/primary_with_secondaries/run_race.c
index f77a7b1..2a25c65 100644
--- a/test/vmapi/primary_with_secondaries/run_race.c
+++ b/test/vmapi/primary_with_secondaries/run_race.c
@@ -57,7 +57,7 @@
 
 	/* Copies the contents of the received boolean to the return value. */
 	if (run_res.message.size == sizeof(ok)) {
-		memcpy(&ok, mb->recv, sizeof(ok));
+		memcpy(&ok, mb->recv->payload, sizeof(ok));
 	}
 
 	hf_mailbox_clear();
diff --git a/test/vmapi/primary_with_secondaries/services/BUILD.gn b/test/vmapi/primary_with_secondaries/services/BUILD.gn
index b0ef794..6b15dc7 100644
--- a/test/vmapi/primary_with_secondaries/services/BUILD.gn
+++ b/test/vmapi/primary_with_secondaries/services/BUILD.gn
@@ -125,6 +125,19 @@
   ]
 }
 
+# Service to receive messages in a secondary VM and ensure that the header fields are correctly set.
+source_set("spci_check") {
+  testonly = true
+  public_configs = [
+    "..:config",
+    "//test/hftest:hftest_config",
+  ]
+
+  sources = [
+    "spci_check.c",
+  ]
+}
+
 # Group services together into VMs.
 
 vm_kernel("service_vm0") {
@@ -139,6 +152,7 @@
     ":memory",
     ":receive_block",
     ":relay",
+    ":spci_check",
     ":wfi",
     "//test/hftest:hftest_secondary_vm",
   ]
diff --git a/test/vmapi/primary_with_secondaries/services/check_state.c b/test/vmapi/primary_with_secondaries/services/check_state.c
index 735b8a5..e467f52 100644
--- a/test/vmapi/primary_with_secondaries/services/check_state.c
+++ b/test/vmapi/primary_with_secondaries/services/check_state.c
@@ -21,13 +21,13 @@
 
 #include "hftest.h"
 
-void send_with_retry(uint32_t vm_id, size_t size)
+void send_with_retry()
 {
 	int64_t res;
 
 	do {
-		res = hf_mailbox_send(vm_id, size, false);
-	} while (res == -1);
+		res = spci_msg_send(0);
+	} while (res != SPCI_SUCCESS);
 }
 
 /**
@@ -48,6 +48,9 @@
 	static volatile uintptr_t expected;
 	static volatile uintptr_t actual;
 
+	spci_message_init(SERVICE_SEND_BUFFER(), 0, HF_PRIMARY_VM_ID,
+			  hf_vm_get_id());
+
 	for (i = 0; i < 100000; i++) {
 		/*
 		 * We store the expected/actual values in volatile static
@@ -56,13 +59,15 @@
 		 */
 		expected = i;
 		per_cpu_ptr_set(expected);
-		send_with_retry(HF_PRIMARY_VM_ID, 0);
+		send_with_retry();
 		actual = per_cpu_ptr_get();
 		ok &= expected == actual;
 	}
 
 	/* Send two replies, one for each physical CPU. */
-	memcpy(SERVICE_SEND_BUFFER(), &ok, sizeof(ok));
-	send_with_retry(HF_PRIMARY_VM_ID, sizeof(ok));
-	send_with_retry(HF_PRIMARY_VM_ID, sizeof(ok));
+	memcpy(SERVICE_SEND_BUFFER()->payload, &ok, sizeof(ok));
+	spci_message_init(SERVICE_SEND_BUFFER(), sizeof(ok), HF_PRIMARY_VM_ID,
+			  hf_vm_get_id());
+	send_with_retry();
+	send_with_retry();
 }
diff --git a/test/vmapi/primary_with_secondaries/services/echo.c b/test/vmapi/primary_with_secondaries/services/echo.c
index ffb89df..c58ccfb 100644
--- a/test/vmapi/primary_with_secondaries/services/echo.c
+++ b/test/vmapi/primary_with_secondaries/services/echo.c
@@ -16,6 +16,8 @@
 
 #include "hf/arch/std.h"
 
+#include "hf/spci.h"
+
 #include "vmapi/hf/call.h"
 
 #include "hftest.h"
@@ -24,9 +26,16 @@
 {
 	/* Loop, echo messages back to the sender. */
 	for (;;) {
-		struct hf_mailbox_receive_return res = hf_mailbox_receive(true);
-		memcpy(SERVICE_SEND_BUFFER(), SERVICE_RECV_BUFFER(), res.size);
+		hf_mailbox_receive(true);
+		struct spci_message *send_buf = SERVICE_SEND_BUFFER();
+		struct spci_message *recv_buf = SERVICE_RECV_BUFFER();
+
+		memcpy(send_buf->payload, recv_buf->payload, recv_buf->length);
+		spci_message_init(SERVICE_SEND_BUFFER(), recv_buf->length,
+				  recv_buf->source_vm_id,
+				  recv_buf->target_vm_id);
+
 		hf_mailbox_clear();
-		hf_mailbox_send(res.vm_id, res.size, false);
+		spci_msg_send(0);
 	}
 }
diff --git a/test/vmapi/primary_with_secondaries/services/echo_with_notification.c b/test/vmapi/primary_with_secondaries/services/echo_with_notification.c
index 2e733ae..e6c66c0 100644
--- a/test/vmapi/primary_with_secondaries/services/echo_with_notification.c
+++ b/test/vmapi/primary_with_secondaries/services/echo_with_notification.c
@@ -18,6 +18,8 @@
 #include "hf/arch/std.h"
 #include "hf/arch/vm/interrupts_gicv3.h"
 
+#include "hf/spci.h"
+
 #include "vmapi/hf/call.h"
 
 #include "../msr.h"
@@ -51,11 +53,18 @@
 
 	/* Loop, echo messages back to the sender. */
 	for (;;) {
-		struct hf_mailbox_receive_return res = hf_mailbox_receive(true);
+		hf_mailbox_receive(true);
 
-		memcpy(SERVICE_SEND_BUFFER(), SERVICE_RECV_BUFFER(), res.size);
-		while (hf_mailbox_send(res.vm_id, res.size, true) < 0) {
-			wait_for_vm(res.vm_id);
+		struct spci_message *send_buf = SERVICE_SEND_BUFFER();
+		struct spci_message *recv_buf = SERVICE_RECV_BUFFER();
+
+		memcpy(send_buf->payload, recv_buf->payload, recv_buf->length);
+		spci_message_init(send_buf, recv_buf->length,
+				  recv_buf->source_vm_id,
+				  recv_buf->target_vm_id);
+
+		while (spci_msg_send(SPCI_MSG_SEND_NOTIFY) != SPCI_SUCCESS) {
+			wait_for_vm(recv_buf->source_vm_id);
 		}
 
 		hf_mailbox_clear();
diff --git a/test/vmapi/primary_with_secondaries/services/interruptible.c b/test/vmapi/primary_with_secondaries/services/interruptible.c
index 9b36e8c..c5c67a5 100644
--- a/test/vmapi/primary_with_secondaries/services/interruptible.c
+++ b/test/vmapi/primary_with_secondaries/services/interruptible.c
@@ -38,8 +38,10 @@
 	dlog("secondary IRQ %d from current\n", interrupt_id);
 	buffer[8] = '0' + interrupt_id / 10;
 	buffer[9] = '0' + interrupt_id % 10;
-	memcpy(SERVICE_SEND_BUFFER(), buffer, size);
-	hf_mailbox_send(HF_PRIMARY_VM_ID, size, false);
+	memcpy(SERVICE_SEND_BUFFER()->payload, buffer, size);
+	spci_message_init(SERVICE_SEND_BUFFER(), size, HF_PRIMARY_VM_ID,
+			  hf_vm_get_id());
+	spci_msg_send(0);
 	dlog("secondary IRQ %d ended\n", interrupt_id);
 }
 
@@ -62,6 +64,7 @@
 TEST_SERVICE(interruptible)
 {
 	uint32_t this_vm_id = hf_vm_get_id();
+	struct spci_message *recv_buf = SERVICE_RECV_BUFFER();
 
 	exception_setup(irq);
 	hf_interrupt_enable(SELF_INTERRUPT_ID, true);
@@ -72,23 +75,23 @@
 	for (;;) {
 		const char ping_message[] = "Ping";
 		const char enable_message[] = "Enable interrupt C";
-		struct hf_mailbox_receive_return received_message =
-			mailbox_receive_retry();
-		if (received_message.vm_id == HF_PRIMARY_VM_ID &&
-		    received_message.size == sizeof(ping_message) &&
-		    memcmp(SERVICE_RECV_BUFFER(), ping_message,
+
+		mailbox_receive_retry();
+		if (recv_buf->source_vm_id == HF_PRIMARY_VM_ID &&
+		    recv_buf->length == sizeof(ping_message) &&
+		    memcmp(recv_buf->payload, ping_message,
 			   sizeof(ping_message)) == 0) {
 			/* Interrupt ourselves */
 			hf_interrupt_inject(this_vm_id, 0, SELF_INTERRUPT_ID);
-		} else if (received_message.vm_id == HF_PRIMARY_VM_ID &&
-			   received_message.size == sizeof(enable_message) &&
-			   memcmp(SERVICE_RECV_BUFFER(), enable_message,
+		} else if (recv_buf->source_vm_id == HF_PRIMARY_VM_ID &&
+			   recv_buf->length == sizeof(enable_message) &&
+			   memcmp(recv_buf->payload, enable_message,
 				  sizeof(enable_message)) == 0) {
 			/* Enable interrupt ID C. */
 			hf_interrupt_enable(EXTERNAL_INTERRUPT_ID_C, true);
 		} else {
 			dlog("Got unexpected message from VM %d, size %d.\n",
-			     received_message.vm_id, received_message.size);
+			     recv_buf->source_vm_id, recv_buf->length);
 			FAIL("Unexpected message");
 		}
 		hf_mailbox_clear();
diff --git a/test/vmapi/primary_with_secondaries/services/interruptible_echo.c b/test/vmapi/primary_with_secondaries/services/interruptible_echo.c
index e1eb643..dff3b6d 100644
--- a/test/vmapi/primary_with_secondaries/services/interruptible_echo.c
+++ b/test/vmapi/primary_with_secondaries/services/interruptible_echo.c
@@ -39,6 +39,7 @@
 
 	for (;;) {
 		struct hf_mailbox_receive_return res = hf_mailbox_receive(true);
+		struct spci_message *message = SERVICE_SEND_BUFFER();
 
 		/* Retry if interrupted but made visible with the yield. */
 		while (res.vm_id == HF_INVALID_VM_ID && res.size == 0) {
@@ -46,8 +47,12 @@
 			res = hf_mailbox_receive(true);
 		}
 
-		memcpy(SERVICE_SEND_BUFFER(), SERVICE_RECV_BUFFER(), res.size);
+		memcpy(message->payload, SERVICE_RECV_BUFFER()->payload,
+		       res.size);
+		spci_message_init(message, res.size, HF_PRIMARY_VM_ID,
+				  SERVICE_VM0);
+
 		hf_mailbox_clear();
-		hf_mailbox_send(res.vm_id, res.size, false);
+		spci_msg_send(0);
 	}
 }
diff --git a/test/vmapi/primary_with_secondaries/services/memory.c b/test/vmapi/primary_with_secondaries/services/memory.c
index b85b003..a826184 100644
--- a/test/vmapi/primary_with_secondaries/services/memory.c
+++ b/test/vmapi/primary_with_secondaries/services/memory.c
@@ -28,12 +28,16 @@
 {
 	/* Loop, writing message to the shared memory. */
 	for (;;) {
-		struct hf_mailbox_receive_return res = hf_mailbox_receive(true);
+		hf_mailbox_receive(true);
 		uint8_t *ptr;
 		size_t i;
 
 		/* Check the memory was cleared. */
-		memcpy(&ptr, SERVICE_RECV_BUFFER(), sizeof(ptr));
+		struct spci_message *recv_buf = SERVICE_RECV_BUFFER();
+		memcpy(&ptr, recv_buf->payload, sizeof(ptr));
+		spci_message_init(SERVICE_SEND_BUFFER(), sizeof(ptr),
+				  recv_buf->source_vm_id, hf_vm_get_id());
+
 		for (int i = 0; i < PAGE_SIZE; ++i) {
 			ASSERT_EQ(ptr[i], 0);
 		}
@@ -48,7 +52,7 @@
 
 		/* Signal completion and reset. */
 		hf_mailbox_clear();
-		hf_mailbox_send(res.vm_id, 0, false);
+		spci_msg_send(0);
 	}
 }
 
@@ -56,21 +60,26 @@
 {
 	/* Loop, giving memory back to the sender. */
 	for (;;) {
-		struct hf_mailbox_receive_return res = hf_mailbox_receive(true);
+		hf_mailbox_receive(true);
 		uint8_t *ptr;
 
 		/* Check the memory was cleared. */
-		memcpy(&ptr, SERVICE_RECV_BUFFER(), sizeof(ptr));
+		struct spci_message *recv_buf = SERVICE_RECV_BUFFER();
+		memcpy(&ptr, recv_buf->payload, sizeof(ptr));
+		spci_message_init(SERVICE_SEND_BUFFER(), sizeof(ptr),
+				  recv_buf->source_vm_id, hf_vm_get_id());
+
 		for (int i = 0; i < PAGE_SIZE; ++i) {
 			ASSERT_EQ(ptr[i], 0);
 		}
 
 		/* Give the memory back and notify the sender. */
-		ASSERT_EQ(hf_share_memory(res.vm_id, (hf_ipaddr_t)ptr,
-					  PAGE_SIZE, HF_MEMORY_GIVE),
+		ASSERT_EQ(hf_share_memory(recv_buf->source_vm_id,
+					  (hf_ipaddr_t)ptr, PAGE_SIZE,
+					  HF_MEMORY_GIVE),
 			  0);
 		hf_mailbox_clear();
-		hf_mailbox_send(res.vm_id, 0, false);
+		spci_msg_send(0);
 
 		/*
 		 * Try and access the memory which will cause a fault unless the
@@ -94,8 +103,10 @@
 	 *       API is still to be agreed on so the address is passed
 	 *       explicitly to test the mechanism.
 	 */
-	memcpy(SERVICE_SEND_BUFFER(), &ptr, sizeof(ptr));
-	EXPECT_EQ(hf_mailbox_send(HF_PRIMARY_VM_ID, sizeof(ptr), false), 0);
+	memcpy(SERVICE_SEND_BUFFER()->payload, &ptr, sizeof(ptr));
+	spci_message_init(SERVICE_SEND_BUFFER(), sizeof(ptr), HF_PRIMARY_VM_ID,
+			  hf_vm_get_id());
+	EXPECT_EQ(spci_msg_send(0), 0);
 
 	/* Try using the memory that isn't valid unless it's been returned.  */
 	page[16] = 123;
@@ -115,8 +126,10 @@
 	 *       API is still to be agreed on so the address is passed
 	 *       explicitly to test the mechanism.
 	 */
-	memcpy(SERVICE_SEND_BUFFER(), &ptr, sizeof(ptr));
-	EXPECT_EQ(hf_mailbox_send(HF_PRIMARY_VM_ID, sizeof(ptr), false), 0);
+	memcpy(SERVICE_SEND_BUFFER()->payload, &ptr, sizeof(ptr));
+	spci_message_init(SERVICE_SEND_BUFFER(), sizeof(ptr), HF_PRIMARY_VM_ID,
+			  hf_vm_get_id());
+	EXPECT_EQ(spci_msg_send(0), 0);
 
 	/* Try using the memory that isn't valid unless it's been returned.  */
 	page[633] = 180;
diff --git a/test/vmapi/primary_with_secondaries/services/receive_block.c b/test/vmapi/primary_with_secondaries/services/receive_block.c
index bdfbed6..59a10a5 100644
--- a/test/vmapi/primary_with_secondaries/services/receive_block.c
+++ b/test/vmapi/primary_with_secondaries/services/receive_block.c
@@ -18,6 +18,7 @@
 #include "hf/arch/vm/interrupts_gicv3.h"
 
 #include "hf/dlog.h"
+#include "hf/spci.h"
 
 #include "vmapi/hf/call.h"
 
@@ -50,6 +51,9 @@
 		EXPECT_EQ(res.size, 0);
 	}
 
-	memcpy(SERVICE_SEND_BUFFER(), message, sizeof(message));
-	hf_mailbox_send(HF_PRIMARY_VM_ID, sizeof(message), false);
+	memcpy(SERVICE_SEND_BUFFER()->payload, message, sizeof(message));
+	spci_message_init(SERVICE_SEND_BUFFER(), sizeof(message),
+			  HF_PRIMARY_VM_ID, hf_vm_get_id());
+
+	spci_msg_send(0);
 }
diff --git a/test/vmapi/primary_with_secondaries/services/relay.c b/test/vmapi/primary_with_secondaries/services/relay.c
index f509835..627a461 100644
--- a/test/vmapi/primary_with_secondaries/services/relay.c
+++ b/test/vmapi/primary_with_secondaries/services/relay.c
@@ -36,18 +36,24 @@
 		uint32_t next_message_size;
 
 		/* Receive the message to relay. */
-		struct hf_mailbox_receive_return res = hf_mailbox_receive(true);
-		ASSERT_GE(res.size, sizeof(uint32_t));
+		hf_mailbox_receive(true);
 
 		/* Prepare to relay the message. */
-		chain = SERVICE_RECV_BUFFER();
+		struct spci_message *recv_buf = SERVICE_RECV_BUFFER();
+		struct spci_message *send_buf = SERVICE_SEND_BUFFER();
+		ASSERT_GE(recv_buf->length, sizeof(uint32_t));
+
+		chain = (uint32_t *)recv_buf->payload;
 		next_vm_id = le32toh(*chain);
 		next_message = chain + 1;
-		next_message_size = res.size - sizeof(uint32_t);
+		next_message_size = recv_buf->length - sizeof(uint32_t);
 
 		/* Send the message to the next stage. */
-		memcpy(SERVICE_SEND_BUFFER(), next_message, next_message_size);
+		memcpy(send_buf->payload, next_message, next_message_size);
+		spci_message_init(send_buf, next_message_size, next_vm_id,
+				  hf_vm_get_id());
+
 		hf_mailbox_clear();
-		hf_mailbox_send(next_vm_id, next_message_size, false);
+		spci_msg_send(0);
 	}
 }
diff --git a/test/vmapi/primary_with_secondaries/services/spci_check.c b/test/vmapi/primary_with_secondaries/services/spci_check.c
new file mode 100644
index 0000000..a81fc01
--- /dev/null
+++ b/test/vmapi/primary_with_secondaries/services/spci_check.c
@@ -0,0 +1,66 @@
+/*
+ * 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/std.h"
+
+#include "hf/spci.h"
+
+#include "vmapi/hf/call.h"
+
+#include "hftest.h"
+#include "primary_with_secondary.h"
+
+TEST_SERVICE(spci_check)
+{
+	struct spci_message *recv_buf = SERVICE_RECV_BUFFER();
+	const char message[] = "spci_msg_send";
+	struct spci_message expected_message = {
+		.flags = SPCI_MESSAGE_IMPDEF_MASK,
+		.length = sizeof(message),
+		.target_vm_id = SERVICE_VM0,
+		.source_vm_id = HF_PRIMARY_VM_ID,
+
+		/*
+		 * TODO: Padding fields may be set to MBZ in the next SPCI spec
+		 * versions.
+		 */
+		.reserved_1 = 0,
+		.reserved_2 = 0,
+	};
+
+	/* Wait for single message to be sent by the primary VM. */
+	hf_mailbox_receive(true);
+
+	/* Ensure message header has all fields correctly set. */
+	EXPECT_EQ(recv_buf->flags, expected_message.flags);
+	EXPECT_EQ(recv_buf->length, expected_message.length);
+	EXPECT_EQ(recv_buf->target_vm_id, expected_message.target_vm_id);
+	EXPECT_EQ(recv_buf->source_vm_id, expected_message.source_vm_id);
+
+	/* TODO: Padding fields may be set to MBZ in the next SPCI spec
+	 * versions. */
+	EXPECT_EQ(recv_buf->reserved_1, expected_message.reserved_1);
+	EXPECT_EQ(recv_buf->reserved_2, expected_message.reserved_2);
+
+	/* Ensure message header has all fields correctly set. */
+	EXPECT_EQ(memcmp(recv_buf, &expected_message, sizeof(expected_message)),
+		  0);
+
+	/* Ensure that the payload was correctly transmitted. */
+	EXPECT_EQ(memcmp(recv_buf->payload, message, sizeof(message)), 0);
+
+	hf_vcpu_yield();
+}
diff --git a/test/vmapi/primary_with_secondaries/services/wfi.c b/test/vmapi/primary_with_secondaries/services/wfi.c
index 9c4f66e..6d3a05c 100644
--- a/test/vmapi/primary_with_secondaries/services/wfi.c
+++ b/test/vmapi/primary_with_secondaries/services/wfi.c
@@ -48,6 +48,9 @@
 		interrupt_wait();
 	}
 
-	memcpy(SERVICE_SEND_BUFFER(), message, sizeof(message));
-	hf_mailbox_send(HF_PRIMARY_VM_ID, sizeof(message), false);
+	memcpy(SERVICE_SEND_BUFFER()->payload, message, sizeof(message));
+	spci_message_init(SERVICE_SEND_BUFFER(), sizeof(message),
+			  HF_PRIMARY_VM_ID, hf_vm_get_id());
+
+	spci_msg_send(0);
 }
diff --git a/test/vmapi/primary_with_secondaries/spci.c b/test/vmapi/primary_with_secondaries/spci.c
new file mode 100644
index 0000000..e6d1049
--- /dev/null
+++ b/test/vmapi/primary_with_secondaries/spci.c
@@ -0,0 +1,65 @@
+/*
+ * 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/spci.h"
+
+#include <stdint.h>
+
+#include "hf/arch/std.h"
+
+#include "vmapi/hf/call.h"
+
+#include "hftest.h"
+#include "primary_with_secondary.h"
+#include "util.h"
+
+/**
+ * Send a message to a secondary VM which checks the validity of the received
+ * header.
+ */
+TEST(spci, msg_send)
+{
+	const char message[] = "spci_msg_send";
+	struct hf_vcpu_run_return run_res;
+	struct mailbox_buffers mb = set_up_mailbox();
+
+	SERVICE_SELECT(SERVICE_VM0, "spci_check", mb.send);
+
+	/* Set the payload, init the message header and send the message. */
+	memcpy(mb.send->payload, message, sizeof(message));
+	spci_message_init(mb.send, sizeof(message), SERVICE_VM0,
+			  HF_PRIMARY_VM_ID);
+	EXPECT_EQ(spci_msg_send(0), 0);
+
+	run_res = hf_vcpu_run(SERVICE_VM0, 0);
+	EXPECT_EQ(run_res.code, HF_VCPU_RUN_YIELD);
+}
+
+/**
+ * Send a message to a secondary VM spoofing the source vm id.
+ */
+TEST(spci, msg_send_spoof)
+{
+	const char message[] = "spci_msg_send";
+	struct mailbox_buffers mb = set_up_mailbox();
+
+	SERVICE_SELECT(SERVICE_VM0, "spci_check", mb.send);
+
+	/* Set the payload, init the message header and send the message. */
+	memcpy(mb.send->payload, message, sizeof(message));
+	spci_message_init(mb.send, sizeof(message), SERVICE_VM0, SERVICE_VM1);
+	EXPECT_EQ(spci_msg_send(0), SPCI_INVALID_PARAMETERS);
+}
diff --git a/test/vmapi/primary_with_secondaries/util.c b/test/vmapi/primary_with_secondaries/util.c
index 86830c4..24b2b9e 100644
--- a/test/vmapi/primary_with_secondaries/util.c
+++ b/test/vmapi/primary_with_secondaries/util.c
@@ -17,6 +17,7 @@
 #include "util.h"
 
 #include "hf/mm.h"
+#include "hf/spci.h"
 
 #include "vmapi/hf/call.h"
 
@@ -34,7 +35,7 @@
 {
 	ASSERT_EQ(hf_vm_configure(send_page_addr, recv_page_addr), 0);
 	return (struct mailbox_buffers){
-		.send = send_page,
-		.recv = recv_page,
+		.send = ((struct spci_message *)send_page),
+		.recv = ((struct spci_message *)recv_page),
 	};
 }