Add Linux Hafnium module socket unit test

Add a Linux module unit test that covers more cases than the basic load/unload.
This test create a secondary VM, and using the socket interface sends a message
and received the same payload as a response.

Bug: 138977432
Change-Id: Ibbc313c1a788924b2f227a28bf0ecd3bf21304a1
diff --git a/driver/linux b/driver/linux
index 5da4b6b..3e669bc 160000
--- a/driver/linux
+++ b/driver/linux
@@ -1 +1 @@
-Subproject commit 5da4b6b4a3a24e7e3eb4dc5aabd0e601d5673aad
+Subproject commit 3e669bc98235cbaf0addc3176711ad28b62a17eb
diff --git a/inc/vmapi/hf/transport.h b/inc/vmapi/hf/transport.h
new file mode 100644
index 0000000..686143c
--- /dev/null
+++ b/inc/vmapi/hf/transport.h
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+/**
+ * Header for Hafnium messages
+ *
+ * NOTE: This is a work in progress.  The final form of a Hafnium message header
+ * is likely to change.
+ */
+struct hf_msg_hdr {
+	uint64_t src_port;
+	uint64_t dst_port;
+};
diff --git a/test/linux/BUILD.gn b/test/linux/BUILD.gn
index 13bd807..8ec6af2 100644
--- a/test/linux/BUILD.gn
+++ b/test/linux/BUILD.gn
@@ -13,8 +13,14 @@
 # limitations under the License.
 
 import("//build/image/image.gni")
+import("//build/toolchain/platform.gni")
 
 executable("test_binary") {
+  include_dirs = [
+    "//driver/linux/inc/uapi",
+    "//third_party/linux/include/uapi/",
+  ]
+
   testonly = true
   sources = [
     "linux.c",
@@ -25,6 +31,14 @@
   output_name = "test_binary"
 }
 
+vm_kernel("socket_vm0") {
+  testonly = true
+
+  deps = [
+    ":hftest_secondary_vm_socket",
+  ]
+}
+
 linux_initrd("linux_test_initrd") {
   testonly = true
 
@@ -45,6 +59,12 @@
 
   primary_vm = "//third_party:linux__prebuilt"
   primary_initrd = ":linux_test_initrd"
+  secondary_vms = [ [
+        "1048576",
+        "1",
+        "socket0",
+        ":socket_vm0",
+      ] ]
 }
 
 group("linux") {
@@ -54,3 +74,23 @@
     ":linux_test",
   ]
 }
+
+# Testing framework for a secondary VM with socket.
+source_set("hftest_secondary_vm_socket") {
+  testonly = true
+
+  configs += [ "//test/hftest:hftest_config" ]
+
+  sources = [
+    "hftest_socket.c",
+  ]
+
+  deps = [
+    "//src:dlog",
+    "//src:panic",
+    "//src:std",
+    "//src/arch/${plat_arch}:entry",
+    "//src/arch/${plat_arch}/hftest:entry",
+    "//src/arch/${plat_arch}/hftest:power_mgmt",
+  ]
+}
diff --git a/test/linux/hftest_socket.c b/test/linux/hftest_socket.c
new file mode 100644
index 0000000..b318a4f
--- /dev/null
+++ b/test/linux/hftest_socket.c
@@ -0,0 +1,109 @@
+/*
+ * 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 <stdalign.h>
+#include <stdint.h>
+
+#include "hf/memiter.h"
+#include "hf/spci.h"
+#include "hf/std.h"
+
+#include "vmapi/hf/call.h"
+#include "vmapi/hf/transport.h"
+
+#include "hftest.h"
+
+alignas(4096) uint8_t kstack[4096];
+
+static alignas(HF_MAILBOX_SIZE) uint8_t send[HF_MAILBOX_SIZE];
+static alignas(HF_MAILBOX_SIZE) uint8_t recv[HF_MAILBOX_SIZE];
+
+static hf_ipaddr_t send_addr = (hf_ipaddr_t)send;
+static hf_ipaddr_t recv_addr = (hf_ipaddr_t)recv;
+
+static struct hftest_context global_context;
+
+struct hftest_context *hftest_get_context(void)
+{
+	return &global_context;
+}
+
+noreturn void abort(void)
+{
+	HFTEST_LOG("Service contained failures.");
+	/* Cause a fault, as a secondary can't power down the machine. */
+	*((volatile uint8_t *)1) = 1;
+
+	/* This should never be reached, but to make the compiler happy... */
+	for (;;) {
+	}
+}
+
+static void swap(uint64_t *a, uint64_t *b)
+{
+	uint64_t t = *a;
+	*a = *b;
+	*b = t;
+}
+
+noreturn void kmain(size_t memory_size)
+{
+	struct hftest_context *ctx;
+
+	/* Prepare the context. */
+
+	/* Set up the mailbox. */
+	hf_vm_configure(send_addr, recv_addr);
+
+	hf_mailbox_clear();
+
+	/* Clean the context. */
+	ctx = hftest_get_context();
+	memset_s(ctx, sizeof(*ctx), 0, sizeof(*ctx));
+	ctx->abort = abort;
+	ctx->send = (struct spci_message *)send;
+	ctx->recv = (struct spci_message *)recv;
+	ctx->memory_size = memory_size;
+
+	/* Pause so the next time cycles are given the service will be run. */
+	spci_yield();
+
+	for (;;) {
+		struct spci_message *send_buf = (struct spci_message *)send;
+		struct spci_message *recv_buf = (struct spci_message *)recv;
+
+		/* Receive the packet. */
+		spci_msg_recv(SPCI_MSG_RECV_BLOCK);
+		EXPECT_LE(recv_buf->length, SPCI_MSG_PAYLOAD_MAX);
+
+		/* Echo the message back to the sender. */
+		memcpy_s(send_buf->payload, SPCI_MSG_PAYLOAD_MAX,
+			 recv_buf->payload, recv_buf->length);
+
+		/* Swap the socket's source and destination ports */
+		struct hf_msg_hdr *hdr = (struct hf_msg_hdr *)send_buf->payload;
+		swap(&(hdr->src_port), &(hdr->dst_port));
+
+		/* Swap the destination and source ids. */
+		spci_vm_id_t dst_id = recv_buf->source_vm_id;
+		spci_vm_id_t src_id = recv_buf->target_vm_id;
+
+		spci_message_init(send_buf, recv_buf->length, dst_id, src_id);
+
+		hf_mailbox_clear();
+		EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
+	}
+}
diff --git a/test/linux/linux.c b/test/linux/linux.c
index 3300799..f0798e9 100644
--- a/test/linux/linux.c
+++ b/test/linux/linux.c
@@ -14,13 +14,23 @@
  * limitations under the License.
  */
 
+#include <errno.h>
 #include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
 #include <unistd.h>
 
 #include "hf/dlog.h"
+#include "hf/socket.h"
 
 #include "hftest.h"
+#include <sys/socket.h>
 #include <sys/syscall.h>
+#include <sys/types.h>
+
+#define MAX_BUF_SIZE 256
 
 static int finit_module(int fd, const char *param_values, int flags)
 {
@@ -48,8 +58,75 @@
 	EXPECT_EQ(delete_module("hafnium", 0), 0);
 }
 
+/**
+ * Loads and unloads the Hafnium kernel module.
+ */
 TEST(linux, load_hafnium)
 {
 	insmod_hafnium();
 	rmmod_hafnium();
 }
+
+/**
+ * Uses the kernel module to send a socket message from the primary VM to a
+ * secondary VM and echoes it back to the primary.
+ */
+TEST(linux, socket_echo_hafnium)
+{
+	spci_vm_id_t vm_id = HF_VM_ID_OFFSET + 1;
+	int port = 10;
+	int socket_id;
+	struct hf_sockaddr addr;
+	const char send_buf[] = "The quick brown fox jumps over the lazy dogs.";
+	size_t send_len = strlen(send_buf);
+	char resp_buf[MAX_BUF_SIZE];
+	ssize_t recv_len;
+
+	ASSERT_LT(send_len, MAX_BUF_SIZE);
+
+	insmod_hafnium();
+
+	/* Create Hafnium socket. */
+	socket_id = socket(PF_HF, SOCK_DGRAM, 0);
+	if (socket_id == -1) {
+		FAIL("Socket creation failed: %s", strerror(errno));
+		return;
+	}
+	HFTEST_LOG("Socket created successfully.");
+
+	/* Connect to requested VM & port. */
+	addr.family = PF_HF;
+	addr.vm_id = vm_id;
+	addr.port = port;
+	if (connect(socket_id, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
+		FAIL("Socket connection failed: %s", strerror(errno));
+		return;
+	}
+	HFTEST_LOG("Socket to secondary VM %d connected on port %d.", vm_id,
+		   port);
+
+	/*
+	 * Send a message to the secondary VM.
+	 * Enable the confirm flag to try again in case port is busy.
+	 */
+	if (send(socket_id, send_buf, send_len, MSG_CONFIRM) < 0) {
+		FAIL("Socket send() failed: %s", strerror(errno));
+		return;
+	}
+	HFTEST_LOG("Packet with length %d sent.", send_len);
+
+	/* Receive a response, which should be an echo of the sent packet. */
+	recv_len = recv(socket_id, resp_buf, sizeof(resp_buf) - 1, 0);
+
+	if (recv_len == -1) {
+		FAIL("Socket recv() failed: %s", strerror(errno));
+		return;
+	}
+	HFTEST_LOG("Packet with length %d received.", recv_len);
+
+	EXPECT_EQ(recv_len, send_len);
+	EXPECT_EQ(memcmp(send_buf, resp_buf, send_len), 0);
+
+	EXPECT_EQ(close(socket_id), 0);
+	rmmod_hafnium();
+}