arm64: add spin table support

There was a hacky and one-off spin table support in tegra132.
Make this support generic for all arm64 chips.

BUG=chrome-os-partner:32082
BRANCH=None
TEST=Ran with and without secure monitor booting smp into the kernel.

Change-Id: I3425ab0c30983d4c74d0aa465dda38bb2c91c83b
Signed-off-by: Patrick Georgi <pgeorgi@chromium.org>
Original-Commit-Id: 024dc3f3e5262433a56ed14934db837b5feb1748
Original-Change-Id: If12083a9afc3b2be663d36cfeed10f9b74bae3c8
Original-Signed-off-by: Aaron Durbin <adurbin@chromium.org>
Original-Reviewed-on: https://chromium-review.googlesource.com/218654
Original-Reviewed-by: Furquan Shaikh <furquan@chromium.org>
Reviewed-on: http://review.coreboot.org/9084
Tested-by: build bot (Jenkins)
Reviewed-by: Stefan Reinauer <stefan.reinauer@coreboot.org>
diff --git a/src/arch/arm64/Kconfig b/src/arch/arm64/Kconfig
index a070b90..2465bb1 100644
--- a/src/arch/arm64/Kconfig
+++ b/src/arch/arm64/Kconfig
@@ -20,4 +20,9 @@
 	default n
 	select RELOCATABLE_MODULES
 
+config ARCH_SPINTABLE
+	bool
+	default n
+	depends on ARCH_RAMSTAGE_ARM64
+
 source src/arch/arm64/armv8/Kconfig
diff --git a/src/arch/arm64/Makefile.inc b/src/arch/arm64/Makefile.inc
index 1dfaa96..c2caf64 100644
--- a/src/arch/arm64/Makefile.inc
+++ b/src/arch/arm64/Makefile.inc
@@ -123,6 +123,7 @@
 ramstage-y += ../../lib/memcpy.c
 ramstage-y += ../../lib/memmove.c
 ramstage-y += stage_entry.S
+ramstage-$(CONFIG_ARCH_SPINTABLE) += spintable.c spintable_asm.S
 ramstage-y += transition.c transition_asm.S
 
 rmodules_arm64-y += ../../lib/memset.c
diff --git a/src/arch/arm64/armv8/secmon_loader.c b/src/arch/arm64/armv8/secmon_loader.c
index 066f1c1..4d83764 100644
--- a/src/arch/arm64/armv8/secmon_loader.c
+++ b/src/arch/arm64/armv8/secmon_loader.c
@@ -24,6 +24,7 @@
 
 #include <arch/lib_helpers.h>
 #include <arch/secmon.h>
+#include <arch/spintable.h>
 #include <console/console.h>
 #include <rmodule.h>
 #include <string.h>
@@ -75,30 +76,24 @@
 	return rmodule_entry(&secmon_mod);
 }
 
-void secmon_run(void (*entry)(void *), void *cb_tables)
+struct secmon_runit {
+	secmon_entry_t entry;
+	struct secmon_params bsp_params;
+	struct secmon_params secondary_params;
+};
+
+static void secmon_start(void *arg)
 {
-	struct secmon_params params;
 	uint32_t scr;
+	struct secmon_params *p = NULL;
+	struct secmon_runit *r = arg;
 
-	printk(BIOS_SPEW, "payload jump @ %p\n", entry);
+	if (cpu_is_bsp())
+		p = &r->bsp_params;
+	else if (r->secondary_params.entry != NULL)
+		p = &r->secondary_params;
 
-	if (get_current_el() != EL3) {
-		printk(BIOS_DEBUG, "Secmon Error: Can only be loaded in EL3\n");
-		return;
-	}
-
-	secmon_entry_t doit = secmon_load_rmodule();
-
-	if (doit == NULL)
-		die("ARM64 Error: secmon load error");
-
-	printk(BIOS_DEBUG, "ARM64: Loaded the el3 monitor...jumping to %p\n",
-	       doit);
-
-	params.entry = entry;
-	params.arg = cb_tables;
-	params.elx_el = EL2;
-	params.elx_mode = SPSR_USE_L;
+	printk(BIOS_DEBUG, "CPU%x entering secure monitor.\n", cpu_info()->id);
 
 	/* We want to enforce the following policies:
 	 * NS bit is set for lower EL
@@ -107,5 +102,47 @@
 	scr |= SCR_NS;
 	raw_write_scr_el3(scr);
 
-	doit(&params);
+	r->entry(p);
+}
+
+void secmon_run(void (*entry)(void *), void *cb_tables)
+{
+	const struct spintable_attributes *spin_attrs;
+	static struct secmon_runit runit;
+	struct cpu_action action = {
+		.run = secmon_start,
+		.arg = &runit,
+	};
+
+	printk(BIOS_SPEW, "payload jump @ %p\n", entry);
+
+	if (get_current_el() != EL3) {
+		printk(BIOS_DEBUG, "Secmon Error: Can only be loaded in EL3\n");
+		return;
+	}
+
+	runit.entry = secmon_load_rmodule();
+
+	if (runit.entry == NULL)
+		die("ARM64 Error: secmon load error");
+
+	printk(BIOS_DEBUG, "ARM64: Loaded the el3 monitor...jumping to %p\n",
+	       runit.entry);
+
+	runit.bsp_params.entry = entry;
+	runit.bsp_params.arg = cb_tables;
+	runit.bsp_params.elx_el = EL2;
+	runit.bsp_params.elx_mode = SPSR_USE_L;
+	runit.secondary_params.elx_el = EL2;
+	runit.secondary_params.elx_mode = SPSR_USE_L;
+
+	spin_attrs = spintable_get_attributes();
+
+	if (spin_attrs != NULL) {
+		runit.secondary_params.entry = spin_attrs->entry;
+		runit.secondary_params.arg = spin_attrs->addr;
+	}
+
+	arch_run_on_all_cpus_but_self_async(&action);
+	secmon_start(&runit);
 }
diff --git a/src/arch/arm64/boot.c b/src/arch/arm64/boot.c
index 6307e60..01630f3 100644
--- a/src/arch/arm64/boot.c
+++ b/src/arch/arm64/boot.c
@@ -21,6 +21,7 @@
 #include <arch/lib_helpers.h>
 #include <arch/secmon.h>
 #include <arch/stages.h>
+#include <arch/spintable.h>
 #include <arch/transition.h>
 #include <cbmem.h>
 #include <console/console.h>
@@ -38,6 +39,9 @@
 
 	secmon_run(payload_entry, cb_tables);
 
+	/* Start the other CPUs spinning. */
+	spintable_start();
+
 	/* If current EL is not EL3, jump to payload at same EL. */
 	if (current_el != EL3) {
 		cache_sync_instructions();
diff --git a/src/arch/arm64/include/arch/spintable.h b/src/arch/arm64/include/arch/spintable.h
new file mode 100644
index 0000000..b583ddb
--- /dev/null
+++ b/src/arch/arm64/include/arch/spintable.h
@@ -0,0 +1,50 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright 2014 Google, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+#ifndef __ARCH_SPINTABLE_H__
+#define __ARCH_SPINTABLE_H__
+
+struct spintable_attributes {
+	void (*entry)(void *);
+	void *addr;
+};
+
+#if IS_ENABLED(CONFIG_ARCH_SPINTABLE)
+
+/* Initialize spintable with provided monitor address. */
+void spintable_init(void *monitor_address);
+
+/* Start spinning on the non-boot CPUS. */
+void spintable_start(void);
+
+/* Return NULL on failure, otherwise the spintable info. */
+const struct spintable_attributes *spintable_get_attributes(void);
+
+#else /* IS_ENABLED(CONFIG_SPINTABLE) */
+
+static inline void spintable_init(void *monitor_address) {}
+static inline void spintable_start(void) {}
+static inline const struct spintable_attributes *spintable_get_attributes(void)
+{
+	return NULL;
+}
+
+#endif /* IS_ENABLED(CONFIG_SPINTABLE) */
+
+#endif /* __ARCH_SPINTABLE_H__ */
diff --git a/src/arch/arm64/spintable.c b/src/arch/arm64/spintable.c
new file mode 100644
index 0000000..7a1ab7f
--- /dev/null
+++ b/src/arch/arm64/spintable.c
@@ -0,0 +1,100 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright 2014 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA, 02110-1301 USA
+ */
+
+#include <arch/cache.h>
+#include <arch/spintable.h>
+#include <arch/transition.h>
+#include <console/console.h>
+#include <cpu/cpu.h>
+#include <cbmem.h>
+#include <string.h>
+
+static struct spintable_attributes spin_attrs;
+
+void spintable_init(void *monitor_address)
+{
+	extern void __wait_for_spin_table_request(void);
+	const size_t code_size = 4096;
+
+	if (monitor_address == NULL) {
+		printk(BIOS_ERR, "spintable: NULL address to monitor.\n");
+		return;
+	}
+
+	spin_attrs.entry = cbmem_add(CBMEM_ID_SPINTABLE, code_size);
+
+	if (spin_attrs.entry == NULL)
+		return;
+
+	spin_attrs.addr = monitor_address;
+
+	printk(BIOS_INFO, "spintable @ %p will monitor %p\n",
+		spin_attrs.entry, spin_attrs.addr);
+
+	/* Ensure the memory location is zero'd out. */
+	*(uint64_t *)monitor_address = 0;
+
+	memcpy(spin_attrs.entry, __wait_for_spin_table_request, code_size);
+
+	dcache_clean_invalidate_by_mva(monitor_address, sizeof(uint64_t));
+	dcache_clean_invalidate_by_mva(spin_attrs.entry, code_size);
+}
+
+static void spintable_enter(void *unused)
+{
+	struct exc_state state;
+	const struct spintable_attributes *attrs;
+	int current_el;
+
+	attrs = spintable_get_attributes();
+
+	current_el = get_current_el();
+
+	if (current_el != EL3)
+		attrs->entry(attrs->addr);
+
+	memset(&state, 0, sizeof(state));
+	state.elx.spsr = get_eret_el(EL2, SPSR_USE_L);
+
+	transition_with_entry(attrs->entry, attrs->addr, &state);
+}
+
+const struct spintable_attributes *spintable_get_attributes(void)
+{
+	if (spin_attrs.entry == NULL) {
+		printk(BIOS_ERR, "spintable: monitor code not present.\n");
+		return NULL;
+	}
+
+	return &spin_attrs;
+}
+
+void spintable_start(void)
+{
+	struct cpu_action action = {
+		.run = spintable_enter,
+	};
+
+	if (spintable_get_attributes() == NULL)
+		return;
+
+	printk(BIOS_INFO, "All non-boot CPUs to enter spintable.\n");
+
+	arch_run_on_all_cpus_but_self_async(&action);
+}
diff --git a/src/arch/arm64/spintable_asm.S b/src/arch/arm64/spintable_asm.S
new file mode 100644
index 0000000..3066b7e
--- /dev/null
+++ b/src/arch/arm64/spintable_asm.S
@@ -0,0 +1,38 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright 2014 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <arch/asm.h>
+
+ENTRY(__wait_for_spin_table_request)
+	/* Entry here is in EL2 with the magic address in x0. */
+	mov	x28, x0
+1:
+	ldr	x27, [x28]
+	cmp	x27, xzr
+	b.ne	2f
+	wfe
+	b	1b
+2:
+	/* Entry into the kernel. */
+	mov	x0, xzr
+	mov	x1, xzr
+	mov	x2, xzr
+	mov	x3, xzr
+	br	x27
+ENDPROC(__wait_for_spin_table_request)