Initial tests for the primary VM interface.

The tests are kept platform independent and shutdown the system when
they are done. Shutting down results in QEMU being exited when we are in
emulation.

Change-Id: I1b5b0dd6c23ba2ab1e2b1a9d193b65d812f5a5ec
diff --git a/test/vm/BUILD.gn b/test/vm/BUILD.gn
index 82f6f64..fb05a32 100644
--- a/test/vm/BUILD.gn
+++ b/test/vm/BUILD.gn
@@ -1,21 +1,15 @@
 import("//build/image/image.gni")
 
-vm_kernel("test_vm") {
-  testonly = true
-
+source_set("hf_test_vm") {
   sources = [
     "vm_entry.S",
-    "kmain.c",
   ]
 
   deps = [
     "//src:common",
     "//src:common_debug",
     "//src/arch/${arch}:entry",
+    "//src/arch/${arch}/vm:hf_call",
+    "//src/arch/${arch}/vm:shutdown",
   ]
 }
-
-initrd("test_vm_initrd") {
-  testonly = true
-  primary_vm = ":test_vm"
-}
diff --git a/test/vm/hf_test.h b/test/vm/hf_test.h
new file mode 100644
index 0000000..2959ff5
--- /dev/null
+++ b/test/vm/hf_test.h
@@ -0,0 +1,144 @@
+#ifndef _HF_TEST_H
+#define _HF_TEST_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "dlog.h"
+
+/*
+ * Prefixed to log lines from tests for easy filtering in the console.
+ */
+#define HF_TEST_LOG_PREFIX "[hf_test] "
+
+/*
+ * Context for tests.
+ */
+struct hf_test_context {
+	uint32_t failures;
+};
+
+/*
+ * This union can store any of the primitive types supported by the assertion
+ * macros.
+ */
+union hf_test_any {
+	bool b;
+	char c;
+	signed char sc;
+	unsigned char uc;
+	signed short ss;
+	unsigned short us;
+	signed int si;
+	unsigned int ui;
+	signed long int sli;
+	unsigned long int uli;
+	signed long long int slli;
+	unsigned long long int ulli;
+	void *p;
+};
+
+/* _Generic formatting doesn't seem to be supported so doing this manually. */
+/* clang-format off */
+
+/* Select the union member to match the type of the expression. */
+#define hf_test_any_get(any, x)                     \
+	_Generic((x),                               \
+		bool:                   (any).b,    \
+		char:                   (any).c,    \
+		signed char:            (any).sc,   \
+		unsigned char:          (any).uc,   \
+		signed short:           (any).ss,   \
+		unsigned short:         (any).us,   \
+		signed int:             (any).si,   \
+		unsigned int:           (any).ui,   \
+		signed long int:        (any).sli,  \
+		unsigned long int:      (any).uli,  \
+		signed long long int:   (any).slli, \
+		unsigned long long int: (any).ulli, \
+		void *:                 (any).p)
+
+/*
+ * dlog format specifier for types. Note, these aren't the standard specifiers
+ * for the types.
+ */
+#define hf_test_dlog_format(x)                \
+	_Generic((x),                         \
+		bool:                   "%u", \
+		char:                   "%c", \
+		signed char:            "%d", \
+		unsigned char:          "%u", \
+		signed short:           "%d", \
+		unsigned short:         "%u", \
+		signed int:             "%d", \
+		unsigned int:           "%u", \
+		signed long int:        "%d", \
+		unsigned long int:      "%u", \
+		signed long long int:   "%d", \
+		unsigned long long int: "%u", \
+		void *:                 "%p")
+
+/* clang-format on */
+
+#define ASSERT_OP(lhs, rhs, op, fatal)                                     \
+	do {                                                               \
+		union hf_test_any lhs_value;                               \
+		union hf_test_any rhs_value;                               \
+		hf_test_any_get(lhs_value, lhs) = (lhs);                   \
+		hf_test_any_get(rhs_value, rhs) = (rhs);                   \
+		if (!(hf_test_any_get(lhs_value, lhs)                      \
+			      op hf_test_any_get(rhs_value, rhs))) {       \
+			++hf_test_ctx->failures;                           \
+			dlog(HF_TEST_LOG_PREFIX "  %s:%u: Failure\n",      \
+			     __FILE__, __LINE__);                          \
+			dlog(HF_TEST_LOG_PREFIX "    %s %s %s (%s=", #lhs, \
+			     #op, #rhs, #lhs);                             \
+			dlog(hf_test_dlog_format(lhs),                     \
+			     hf_test_any_get(lhs_value, lhs));             \
+			dlog(", %s=", #rhs);                               \
+			dlog(hf_test_dlog_format(rhs),                     \
+			     hf_test_any_get(rhs_value, rhs));             \
+			dlog(")\n");                                       \
+			if (fatal) {                                       \
+				return;                                    \
+			}                                                  \
+		}                                                          \
+	} while (0)
+
+#define ASSERT_EQ(x, y) ASSERT_OP(x, y, ==, true)
+#define ASSERT_NE(x, y) ASSERT_OP(x, y, !=, true)
+#define ASSERT_LE(x, y) ASSERT_OP(x, y, <=, true)
+#define ASSERT_LT(x, y) ASSERT_OP(x, y, <, true)
+#define ASSERT_GE(x, y) ASSERT_OP(x, y, >=, true)
+#define ASSERT_GT(x, y) ASSERT_OP(x, y, >, true)
+
+#define EXPECT_EQ(x, y) ASSERT_OP(x, y, ==, false)
+#define EXPECT_NE(x, y) ASSERT_OP(x, y, !=, false)
+#define EXPECT_LE(x, y) ASSERT_OP(x, y, <=, false)
+#define EXPECT_LT(x, y) ASSERT_OP(x, y, <, false)
+#define EXPECT_GE(x, y) ASSERT_OP(x, y, >=, false)
+#define EXPECT_GT(x, y) ASSERT_OP(x, y, >, false)
+
+/*
+ * Declare a test case.
+ */
+#define TEST(name) static void name(struct hf_test_context *hf_test_ctx)
+
+/*
+ * Run a test case.
+ */
+#define RUN_TEST(test)                                       \
+	do {                                                 \
+		struct hf_test_context ctx = {               \
+			.failures = 0,                       \
+		};                                           \
+		dlog(HF_TEST_LOG_PREFIX "RUN %s\n", #test);  \
+		test(&ctx);                                  \
+		if (ctx.failures) {                          \
+			dlog(HF_TEST_LOG_PREFIX "FAILED\n"); \
+		} else {                                     \
+			dlog(HF_TEST_LOG_PREFIX "OK\n");     \
+		}                                            \
+	} while (0)
+
+#endif /* _HF_TEST_H */
diff --git a/test/vm/kmain.c b/test/vm/kmain.c
deleted file mode 100644
index 4f7e301..0000000
--- a/test/vm/kmain.c
+++ /dev/null
@@ -1,14 +0,0 @@
-#include <stddef.h>
-#include <stdint.h>
-
-#include "dlog.h"
-
-uint8_t kstack[4096] __attribute__((aligned(4096)));
-
-void kmain(void)
-{
-	dlog("Here we go!\n");
-	while (1) {
-		/* Do nothing */
-	}
-}
diff --git a/test/vm/primary_only/BUILD.gn b/test/vm/primary_only/BUILD.gn
new file mode 100644
index 0000000..b4e1212
--- /dev/null
+++ b/test/vm/primary_only/BUILD.gn
@@ -0,0 +1,18 @@
+import("//build/image/image.gni")
+
+vm_kernel("primary_only_test_vm") {
+  testonly = true
+
+  sources = [
+    "primary.c",
+  ]
+
+  deps = [
+    "//test/vm:hf_test_vm",
+  ]
+}
+
+initrd("primary_only_test") {
+  testonly = true
+  primary_vm = ":primary_only_test_vm"
+}
diff --git a/test/vm/primary_only/primary.c b/test/vm/primary_only/primary.c
new file mode 100644
index 0000000..e9f70ee
--- /dev/null
+++ b/test/vm/primary_only/primary.c
@@ -0,0 +1,28 @@
+#include <stdint.h>
+
+#include "../hf_test.h"
+#include "vmapi/hf/call.h"
+
+uint8_t kstack[4096] __attribute__((aligned(4096)));
+
+TEST(vm_get_count)
+{
+	EXPECT_EQ(hf_vm_get_count(), 0);
+}
+
+TEST(vcpu_get_count_when_no_secondary_vm)
+{
+	EXPECT_EQ(hf_vcpu_get_count(0), -1);
+}
+
+TEST(vcpu_get_count_for_large_invalid_vm_index)
+{
+	EXPECT_EQ(hf_vcpu_get_count(0xffffffff), -1);
+}
+
+void kmain(void)
+{
+	RUN_TEST(vm_get_count);
+	RUN_TEST(vcpu_get_count_when_no_secondary_vm);
+	RUN_TEST(vcpu_get_count_for_large_invalid_vm_index);
+}
diff --git a/test/vm/primary_with_secondary/BUILD.gn b/test/vm/primary_with_secondary/BUILD.gn
new file mode 100644
index 0000000..9d9aff1
--- /dev/null
+++ b/test/vm/primary_with_secondary/BUILD.gn
@@ -0,0 +1,33 @@
+import("//build/image/image.gni")
+
+vm_kernel("primary_with_secondary_test_primary_vm") {
+  testonly = true
+
+  sources = [
+    "primary.c",
+  ]
+
+  deps = [
+    "//test/vm:hf_test_vm",
+  ]
+}
+
+vm_kernel("primary_with_secondary_test_secondary_vm") {
+  testonly = true
+
+  sources = [
+    "secondary.c",
+  ]
+
+  deps = [
+    "//test/vm:hf_test_vm",
+  ]
+}
+
+initrd("primary_with_secondary_test") {
+  testonly = true
+  primary_vm = ":primary_with_secondary_test_primary_vm"
+  secondary_vms = [
+    ["1048576", "1", "secondary", ":primary_with_secondary_test_secondary_vm"],
+  ]
+}
diff --git a/test/vm/primary_with_secondary/primary.c b/test/vm/primary_with_secondary/primary.c
new file mode 100644
index 0000000..22b84aa
--- /dev/null
+++ b/test/vm/primary_with_secondary/primary.c
@@ -0,0 +1,28 @@
+#include <stdint.h>
+
+#include "../hf_test.h"
+#include "vmapi/hf/call.h"
+
+uint8_t kstack[4096] __attribute__((aligned(4096)));
+
+TEST(vm_get_count)
+{
+	EXPECT_EQ(hf_vm_get_count(), 1);
+}
+
+TEST(vcpu_get_count_when_no_secondary_vm)
+{
+	EXPECT_EQ(hf_vcpu_get_count(0), 1);
+}
+
+TEST(vcpu_get_count_for_large_invalid_vm_index)
+{
+	EXPECT_EQ(hf_vcpu_get_count(0xffffffff), -1);
+}
+
+void kmain(void)
+{
+	RUN_TEST(vm_get_count);
+	RUN_TEST(vcpu_get_count_when_no_secondary_vm);
+	RUN_TEST(vcpu_get_count_for_large_invalid_vm_index);
+}
diff --git a/test/vm/primary_with_secondary/secondary.c b/test/vm/primary_with_secondary/secondary.c
new file mode 100644
index 0000000..bf6a5c7
--- /dev/null
+++ b/test/vm/primary_with_secondary/secondary.c
@@ -0,0 +1,13 @@
+#include <stdint.h>
+
+#include "../hf_test.h"
+#include "vmapi/hf/call.h"
+
+uint8_t kstack[4096] __attribute__((aligned(4096)));
+
+void kmain(void)
+{
+	for (;;) {
+		/* Do nothing. */
+	}
+}
diff --git a/test/vm/vm_entry.S b/test/vm/vm_entry.S
index 99ceb26..61c9a20 100644
--- a/test/vm/vm_entry.S
+++ b/test/vm/vm_entry.S
@@ -2,9 +2,16 @@
 
 .global image_entry
 image_entry:
-    adr         x0, kstack + 4096
-    mov         sp, x0
-2:
-    bl          kmain
-3:
-    b           3b
+	/* Prepare the stack. */
+	adr x0, kstack + 4096
+	mov sp, x0
+
+	/* Call into C code. */
+	bl kmain
+
+	/* If the VM returns, shutdown the system. */
+	bl shutdown
+
+	/* Loop forever waiting for interrupts. */
+0:	wfi
+	b 0b