Add test for starting cpus.

Change-Id: Ia1959e6cf1018722ae140eeed4ffb1fb166ceb0e
diff --git a/src/arch/aarch64/inc/hf/arch/vm/shutdown.h b/src/arch/aarch64/inc/hf/arch/vm/power_mgmt.h
similarity index 76%
copy from src/arch/aarch64/inc/hf/arch/vm/shutdown.h
copy to src/arch/aarch64/inc/hf/arch/vm/power_mgmt.h
index cc179c5..7128b06 100644
--- a/src/arch/aarch64/inc/hf/arch/vm/shutdown.h
+++ b/src/arch/aarch64/inc/hf/arch/vm/power_mgmt.h
@@ -16,6 +16,14 @@
 
 #pragma once
 
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
 #include <stdnoreturn.h>
 
 noreturn void shutdown(void);
+
+bool cpu_start(uintptr_t id, void *stack, size_t stack_size,
+	       void (*entry)(uintptr_t arg), uintptr_t arg);
+
+noreturn void cpu_stop(void);
diff --git a/src/arch/aarch64/vm/BUILD.gn b/src/arch/aarch64/vm/BUILD.gn
index 8171764..b6dee03 100644
--- a/src/arch/aarch64/vm/BUILD.gn
+++ b/src/arch/aarch64/vm/BUILD.gn
@@ -28,10 +28,11 @@
   ]
 }
 
-# Shutdown the system or exit emulation.
-source_set("shutdown") {
+# Shutdown the system or exit emulation, start/stop CPUs.
+source_set("power_mgmt") {
   sources = [
-    "shutdown.c",
+    "cpu_entry.S",
+    "power_mgmt.c",
   ]
 
   deps = [
diff --git a/src/arch/aarch64/inc/hf/arch/vm/shutdown.h b/src/arch/aarch64/vm/cpu_entry.S
similarity index 77%
rename from src/arch/aarch64/inc/hf/arch/vm/shutdown.h
rename to src/arch/aarch64/vm/cpu_entry.S
index cc179c5..1d800c7 100644
--- a/src/arch/aarch64/inc/hf/arch/vm/shutdown.h
+++ b/src/arch/aarch64/vm/cpu_entry.S
@@ -14,8 +14,11 @@
  * limitations under the License.
  */
 
-#pragma once
+.globl vm_cpu_entry_raw
+vm_cpu_entry_raw:
+	/* Initialise stack from the cpu_start_state struct. */
+	ldr x1, [x0]
+	mov sp, x1
 
-#include <stdnoreturn.h>
-
-noreturn void shutdown(void);
+	/* Jump to C entry point. */
+	b vm_cpu_entry
diff --git a/src/arch/aarch64/vm/power_mgmt.c b/src/arch/aarch64/vm/power_mgmt.c
new file mode 100644
index 0000000..50b5e47
--- /dev/null
+++ b/src/arch/aarch64/vm/power_mgmt.c
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2018 Google LLC
+ *
+ * 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/vm/power_mgmt.h"
+
+#include "hf/spinlock.h"
+
+#include "vmapi/hf/call.h"
+
+#include "../psci.h"
+
+/**
+ * Holds temporary state used to set up the environment on which CPUs will
+ * start executing.
+ *
+ * vm_cpu_entry_raw requires that the first field of cpu_start_state be the
+ * initial stack pointer.
+ */
+struct cpu_start_state {
+	uintptr_t initial_sp;
+	void (*entry)(uintptr_t arg);
+	uintreg_t arg;
+	struct spinlock lock;
+};
+
+/**
+ * Releases the given cpu_start_state struct by releasing its lock, then calls
+ * the entry point specified by the caller of cpu_start.
+ */
+void vm_cpu_entry(struct cpu_start_state *s)
+{
+	struct cpu_start_state local = *(volatile struct cpu_start_state *)s;
+	sl_unlock(&s->lock);
+
+	local.entry(local.arg);
+
+	/* Turn off CPU if the entry point ever returns. */
+	cpu_stop();
+}
+
+/**
+ * Starts the CPU with the given ID. It will start at the provided entry point
+ * with the provided argument.
+ */
+bool cpu_start(uintptr_t id, void *stack, size_t stack_size,
+	       void (*entry)(uintptr_t arg), uintptr_t arg)
+{
+	struct cpu_start_state s;
+	void vm_cpu_entry_raw(uintptr_t arg);
+
+	/* Initialise the temporary state we'll hold on the stack. */
+	sl_init(&s.lock);
+	sl_lock(&s.lock);
+	s.initial_sp = (uintptr_t)stack + stack_size;
+	s.entry = entry;
+	s.arg = arg;
+
+	/* Try to start the CPU. */
+	if (hf_call(PSCI_CPU_ON, id, (size_t)&vm_cpu_entry_raw, (size_t)&s) !=
+	    PSCI_RETURN_SUCCESS) {
+		return false;
+	}
+
+	/*
+	 * Wait for the starting cpu to release the spin lock, which indicates
+	 * that it won't touch the state we hold on the stack anymore.
+	 */
+	sl_lock(&s.lock);
+
+	return true;
+}
+
+/**
+ * Stops the current CPU.
+ */
+noreturn void cpu_stop(void)
+{
+	hf_call(PSCI_CPU_OFF, 0, 0, 0);
+	for (;;) {
+		/* This should never be reached. */
+	}
+}
+
+/**
+ * Shuts down the system or exits emulation.
+ */
+noreturn void shutdown(void)
+{
+	hf_call(PSCI_SYSTEM_OFF, 0, 0, 0);
+	for (;;) {
+		/* This should never be reached. */
+	}
+}
diff --git a/src/arch/aarch64/vm/shutdown.c b/src/arch/aarch64/vm/shutdown.c
deleted file mode 100644
index 4bc48cd..0000000
--- a/src/arch/aarch64/vm/shutdown.c
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 2018 Google LLC
- *
- * 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/vm/shutdown.h"
-
-#include "vmapi/hf/call.h"
-
-#include "../psci.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 0ac7cde..7fe6e6a 100644
--- a/test/vm/BUILD.gn
+++ b/test/vm/BUILD.gn
@@ -40,7 +40,7 @@
     "//src:memiter",
     "//src/arch/${plat_arch}:entry",
     "//src/arch/${plat_arch}/vm:hf_call",
-    "//src/arch/${plat_arch}/vm:shutdown",
+    "//src/arch/${plat_arch}/vm:power_mgmt",
     "//src/arch/${plat_arch}/vm:vm_entry",
   ]
 }
@@ -54,7 +54,7 @@
     "//src:dlog",
     "//src/arch/${plat_arch}:entry",
     "//src/arch/${plat_arch}/vm:hf_call",
-    "//src/arch/${plat_arch}/vm:shutdown",
+    "//src/arch/${plat_arch}/vm:power_mgmt",
     "//src/arch/${plat_arch}/vm:vm_entry",
   ]
 }
diff --git a/test/vm/hftest.py b/test/vm/hftest.py
index ac0f247..d80d26b 100755
--- a/test/vm/hftest.py
+++ b/test/vm/hftest.py
@@ -36,7 +36,7 @@
     qemu_args = [
         "timeout", "--foreground", "5s",
         "./prebuilts/linux-x64/qemu/qemu-system-aarch64", "-M", "virt,gic_version=3", "-cpu",
-        "cortex-a57", "-m", "16M", "-machine", "virtualization=true",
+        "cortex-a57", "-smp", "4", "-m", "16M", "-machine", "virtualization=true",
         "-nographic", "-nodefaults", "-serial", "stdio", "-kernel", hafnium,
         "-initrd", initrd
     ]
diff --git a/test/vm/primary_only.c b/test/vm/primary_only.c
index dd7ab48..ba9e6d7 100644
--- a/test/vm/primary_only.c
+++ b/test/vm/primary_only.c
@@ -14,6 +14,12 @@
  * limitations under the License.
  */
 
+#include <stdalign.h>
+
+#include "hf/arch/vm/power_mgmt.h"
+
+#include "hf/spinlock.h"
+
 #include "vmapi/hf/call.h"
 
 #include "hftest.h"
@@ -49,3 +55,29 @@
 	struct hf_vcpu_run_return res = hf_vcpu_run(1, 0);
 	EXPECT_EQ(res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
 }
+
+/**
+ * Releases the lock passed in.
+ */
+static void vm_cpu_entry(uintptr_t arg)
+{
+	struct spinlock *lock = (struct spinlock *)arg;
+
+	dlog("Second CPU started.\n");
+	sl_unlock(lock);
+}
+
+TEST(cpus, start)
+{
+	struct spinlock lock = SPINLOCK_INIT;
+	alignas(4096) static char other_stack[4096];
+
+	/* Start secondary while holding lock. */
+	sl_lock(&lock);
+	EXPECT_EQ(cpu_start(1, other_stack, sizeof(other_stack), vm_cpu_entry,
+			    (uintptr_t)&lock),
+		  true);
+
+	/* Wait for CPU to release the lock. */
+	sl_lock(&lock);
+}