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/BUILD.gn b/BUILD.gn
index 4b21696..43eaf33 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -5,7 +5,8 @@
 
   deps = [
     ":hypervisor",
-    "//test/vm:test_vm_initrd($arch_toolchain)",
+    "//test/vm/primary_only:primary_only_test($arch_toolchain)",
+    "//test/vm/primary_with_secondary:primary_with_secondary_test($arch_toolchain)",
   ]
 }
 
diff --git a/build/image/image.gni b/build/image/image.gni
index a24b81c..464bd30 100644
--- a/build/image/image.gni
+++ b/build/image/image.gni
@@ -138,19 +138,19 @@
     # TODO: is there a less hacky way to do this?
     if (defined(invoker.secondary_vms)) {
       foreach(vm, invoker.secondary_vms) {
-        args += ["--secondary_vm"]
+        args += [ "--secondary_vm" ]
 
         # This is a hack to find the field which names the build target for the
         # VM so it can be added to the dependencies and the image path be
         # passed to the script. The target will be a path or have a semicolon
         # so getting the label name will yield a diferent value. The other
         # fields are simple text so won't change.
-        foreach (field, vm) {
+        foreach(field, vm) {
           if (get_label_info(field, "name") != field) {
-            deps += [field]
-            args += [vm_dir + get_label_info(field, "name") + ".bin"]
+            deps += [ field ]
+            args += [ vm_dir + get_label_info(field, "name") + ".bin" ]
           } else {
-            args += [field]
+            args += [ field ]
           }
         }
       }
diff --git a/inc/vmapi/hf/call.h b/inc/vmapi/hf/call.h
new file mode 100644
index 0000000..6dd1e88
--- /dev/null
+++ b/inc/vmapi/hf/call.h
@@ -0,0 +1,49 @@
+#ifndef _VMAPI_HF_HVC_H
+#define _VMAPI_HF_HVC_H
+
+#include <stddef.h>
+
+/* Keep macro alignment */
+/* clang-format off */
+
+/* Return values for vcpu_run() hypervisor call. */
+#define HF_VCPU_YIELD              0x00
+#define HF_VCPU_WAIT_FOR_INTERRUPT 0x01
+#define HF_VCPU_WAKE_UP            0x02
+#define HF_VCPU_RESPONSE_READY     0x03
+
+/* TODO: Define constants below according to spec. */
+#define HF_VCPU_RUN         0xff00
+#define HF_VM_GET_COUNT     0xff01
+#define HF_VCPU_GET_COUNT   0xff02
+#define HF_VM_CONFIGURE     0xff03
+#define HF_RPC_REQUEST      0xff04
+#define HF_RPC_READ_REQUEST 0xff05
+#define HF_RPC_ACK          0xff06
+#define HF_RPC_REPLY        0xff07
+
+/* clang-format on */
+
+/*
+ * This function must be implemented to trigger the architecture specific call
+ * to the hypervisor.
+ */
+long hf_call(size_t arg0, size_t arg1, size_t arg2, size_t arg3);
+
+/*
+ * Returns the number of secondary VMs.
+ */
+static inline long hf_vm_get_count(void)
+{
+	return hf_call(HF_VM_GET_COUNT, 0, 0, 0);
+}
+
+/*
+ * Returns the number of VCPUs configured in the given secondary VM.
+ */
+static inline long hf_vcpu_get_count(size_t vm_idx)
+{
+	return hf_call(HF_VCPU_GET_COUNT, vm_idx, 0, 0);
+}
+
+#endif /* _VMAPI_HF_HVC_H */
diff --git a/inc/vmapi/hf/hvc.h b/inc/vmapi/hf/hvc.h
deleted file mode 100644
index 377193e..0000000
--- a/inc/vmapi/hf/hvc.h
+++ /dev/null
@@ -1,25 +0,0 @@
-#ifndef _VMAPI_HF_HVC_H
-#define _VMAPI_HF_HVC_H
-
-/* Keep macro alignment */
-/* clang-format off */
-
-/* Return values for vcpu_run() hypervisor call. */
-#define HF_VCPU_YIELD              0x00
-#define HF_VCPU_WAIT_FOR_INTERRUPT 0x01
-#define HF_VCPU_WAKE_UP            0x02
-#define HF_VCPU_RESPONSE_READY     0x03
-
-/* TODO: Define constants below according to spec. */
-#define HF_VCPU_RUN         0xff00
-#define HF_VM_GET_COUNT     0xff01
-#define HF_VCPU_GET_COUNT   0xff02
-#define HF_VM_CONFIGURE     0xff03
-#define HF_RPC_REQUEST      0xff04
-#define HF_RPC_READ_REQUEST 0xff05
-#define HF_RPC_ACK          0xff06
-#define HF_RPC_REPLY        0xff07
-
-/* clang-format on */
-
-#endif /* _VMAPI_HF_HVC_H */
diff --git a/src/api.c b/src/api.c
index 39c90ad..2b7b5d9 100644
--- a/src/api.c
+++ b/src/api.c
@@ -2,7 +2,7 @@
 
 #include "std.h"
 #include "vm.h"
-#include "vmapi/hf/hvc.h"
+#include "vmapi/hf/call.h"
 
 struct vm secondary_vm[MAX_VMS];
 uint32_t secondary_vm_count;
diff --git a/src/arch/aarch64/BUILD.gn b/src/arch/aarch64/BUILD.gn
index 6426f40..1db1e37 100644
--- a/src/arch/aarch64/BUILD.gn
+++ b/src/arch/aarch64/BUILD.gn
@@ -6,6 +6,7 @@
   sources = [
     "exceptions.S",
     "hypervisor_entry.S",
+    "smc.S",
   ]
 
   sources += [
diff --git a/src/arch/aarch64/handler.c b/src/arch/aarch64/handler.c
index 06a8394..7088216 100644
--- a/src/arch/aarch64/handler.c
+++ b/src/arch/aarch64/handler.c
@@ -3,7 +3,7 @@
 #include "dlog.h"
 #include "psci.h"
 #include "vm.h"
-#include "vmapi/hf/hvc.h"
+#include "vmapi/hf/call.h"
 
 #include "msr.h"
 
diff --git a/src/arch/aarch64/hypervisor_entry.S b/src/arch/aarch64/hypervisor_entry.S
index 7d0396a..07747bd 100644
--- a/src/arch/aarch64/hypervisor_entry.S
+++ b/src/arch/aarch64/hypervisor_entry.S
@@ -37,14 +37,8 @@
 	bl cpu_main
 
 	/* Run the vcpu returned by cpu_main. */
-	b vcpu_restore_all_and_run
+	bl vcpu_restore_all_and_run
 
 	/* Loop forever waiting for interrupts. */
-5:	wfi
-	b 5b
-
-/* TODO: Move this elsewhere. */
-.globl smc
-smc:
-	SMC #0
-	ret
+0:	wfi
+	b 0b
diff --git a/src/arch/aarch64/inc/vm/shutdown.h b/src/arch/aarch64/inc/vm/shutdown.h
new file mode 100644
index 0000000..da9e553
--- /dev/null
+++ b/src/arch/aarch64/inc/vm/shutdown.h
@@ -0,0 +1,8 @@
+#ifndef _ARCH_VM_SHUTDOWN_H
+#define _ARCH_VM_SHUTDOWN_H
+
+#include <stdnoreturn.h>
+
+noreturn void shutdown(void);
+
+#endif /* _ARCH_VM_SHUTDOWN_H */
diff --git a/src/arch/aarch64/smc.S b/src/arch/aarch64/smc.S
new file mode 100644
index 0000000..8c817c9
--- /dev/null
+++ b/src/arch/aarch64/smc.S
@@ -0,0 +1,6 @@
+.text
+
+.globl smc
+smc:
+	smc #0
+	ret
diff --git a/src/arch/aarch64/vm/BUILD.gn b/src/arch/aarch64/vm/BUILD.gn
new file mode 100644
index 0000000..53ed1de
--- /dev/null
+++ b/src/arch/aarch64/vm/BUILD.gn
@@ -0,0 +1,19 @@
+# These components are only used by VMs for aarch64 specific actions.
+
+# Make a call to the hypervisor from a VM.
+source_set("hf_call") {
+  sources = [
+    "hf_call.S",
+  ]
+}
+
+# Shutdown the system or exit emulation.
+source_set("shutdown") {
+  sources = [
+    "shutdown.c",
+  ]
+
+  deps = [
+    ":hf_call",
+  ]
+}
diff --git a/src/arch/aarch64/vm/hf_call.S b/src/arch/aarch64/vm/hf_call.S
new file mode 100644
index 0000000..07743ca
--- /dev/null
+++ b/src/arch/aarch64/vm/hf_call.S
@@ -0,0 +1,6 @@
+.text
+
+.global hf_call
+hf_call:
+	hvc #0
+	ret
diff --git a/src/arch/aarch64/vm/shutdown.c b/src/arch/aarch64/vm/shutdown.c
new file mode 100644
index 0000000..8175cdf
--- /dev/null
+++ b/src/arch/aarch64/vm/shutdown.c
@@ -0,0 +1,15 @@
+#include "vm/shutdown.h"
+
+#include "psci.h"
+#include "vmapi/hf/call.h"
+
+/*
+ * Shutdown the system or exit emulation.
+ */
+noreturn void shutdown(void)
+{
+	hf_call(PSCI_SYSTEM_OFF, 0, 0, 0);
+	for (;;) {
+		/* This should never be reached. */
+	}
+}
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