[RFC PATCH 6/7] arm64: kernel: Apple CPU start driver

From: Mohamed Mediouni
Date: Wed Jan 20 2021 - 08:58:54 EST


From: Stan Skowronek <stan@xxxxxxxxxxxxx>

This driver is needed to spawn CPUs for SMP
on Apple Silicon platforms.

Signed-off-by: Stan Skowronek <stan@xxxxxxxxxxxxx>
Signed-off-by: Mohamed Mediouni <mohamed.mediouni@xxxxxxxxxxxx>
---
.../devicetree/bindings/arm/cpus.yaml | 1 +
arch/arm64/kernel/Makefile | 1 +
arch/arm64/kernel/apple_cpustart.c | 153 ++++++++++++++++++
3 files changed, 155 insertions(+)
create mode 100644 arch/arm64/kernel/apple_cpustart.c

diff --git a/Documentation/devicetree/bindings/arm/cpus.yaml b/Documentation/devicetree/bindings/arm/cpus.yaml
index 14cd727d3c4b..a6ff8cb3db1e 100644
--- a/Documentation/devicetree/bindings/arm/cpus.yaml
+++ b/Documentation/devicetree/bindings/arm/cpus.yaml
@@ -176,6 +176,7 @@ properties:
oneOf:
# On ARM v8 64-bit this property is required
- enum:
+ - apple
- psci
- spin-table
# On ARM 32-bit systems this property is optional
diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile
index 86364ab6f13f..497f43ca7f0f 100644
--- a/arch/arm64/kernel/Makefile
+++ b/arch/arm64/kernel/Makefile
@@ -56,6 +56,7 @@ obj-$(CONFIG_ARM64_RELOC_TEST) += arm64-reloc-test.o
arm64-reloc-test-y := reloc_test_core.o reloc_test_syms.o
obj-$(CONFIG_CRASH_DUMP) += crash_dump.o
obj-$(CONFIG_CRASH_CORE) += crash_core.o
+obj-$(CONFIG_ARCH_APPLE) += apple_cpustart.o
obj-$(CONFIG_ARM_SDE_INTERFACE) += sdei.o
obj-$(CONFIG_ARM64_PTR_AUTH) += pointer_auth.o
obj-$(CONFIG_ARM64_MTE) += mte.o
diff --git a/arch/arm64/kernel/apple_cpustart.c b/arch/arm64/kernel/apple_cpustart.c
new file mode 100644
index 000000000000..41d049eaaec7
--- /dev/null
+++ b/arch/arm64/kernel/apple_cpustart.c
@@ -0,0 +1,153 @@
+/* SPDX-License-Identifier: (GPL-2.0 or BSD-3-Clause) */
+/*
+ * Copyright (C) 2020 Corellium LLC
+ */
+
+#include <linux/init.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/smp.h>
+#include <linux/delay.h>
+#include <linux/mm.h>
+
+#include <asm/cpu_ops.h>
+#include <asm/errno.h>
+#include <asm/smp_plat.h>
+#include <asm/io.h>
+
+#define MAGIC_UNLOCK 0xc5acce55
+
+struct cpu_apple_start_info {
+ void __iomem *pmgr_start;
+ u64 pmgr_start_size;
+ void __iomem *cputrc_rvbar;
+ void __iomem *dbg_unlock;
+};
+
+extern void apple_aic_cpu_prepare(unsigned int cpu);
+
+static int cpu_apple_start0_unlocked = 0;
+static DEFINE_PER_CPU(struct cpu_apple_start_info, cpu_apple_start_info);
+
+static int __init cpu_apple_start_init(unsigned int cpu)
+{
+ return 0;
+}
+
+static int cpu_apple_start_prepare(unsigned int cpu)
+{
+ struct device_node *node;
+ struct cpu_apple_start_info *info;
+
+ info = per_cpu_ptr(&cpu_apple_start_info, cpu);
+
+ if (info->pmgr_start && info->cputrc_rvbar && info->dbg_unlock)
+ return 0;
+
+ node = of_find_compatible_node(NULL, NULL, "apple,startcpu");
+ if (!node) {
+ pr_err("%s: missing startcpu node in device tree.\n", __func__);
+ return -EINVAL;
+ }
+
+ if (!info->pmgr_start) {
+ info->pmgr_start = of_iomap(node, cpu * 3);
+ if (!info->pmgr_start) {
+ pr_err("%s: failed to map start register for CPU %d.\n",
+ __func__, cpu);
+ return -EINVAL;
+ }
+ if (!of_get_address(node, cpu * 3, &info->pmgr_start_size,
+ NULL))
+ info->pmgr_start_size = 8;
+ }
+
+ if (!info->cputrc_rvbar) {
+ info->cputrc_rvbar = of_iomap(node, cpu * 3 + 1);
+ if (!info->cputrc_rvbar) {
+ pr_err("%s: failed to map reset address register for CPU %d.\n",
+ __func__, cpu);
+ return -EINVAL;
+ }
+ }
+
+ if (!info->dbg_unlock) {
+ info->dbg_unlock = of_iomap(node, cpu * 3 + 2);
+ if (!info->dbg_unlock) {
+ pr_err("%s: failed to map unlock register for CPU %d.\n",
+ __func__, cpu);
+ return -EINVAL;
+ }
+ }
+
+ if (cpu)
+ apple_aic_cpu_prepare(cpu);
+
+ return 0;
+}
+
+static int cpu_apple_start_boot(unsigned int cpu)
+{
+ struct cpu_apple_start_info *info;
+ unsigned long addr;
+
+ if (!cpu_apple_start0_unlocked) {
+ if (!cpu_apple_start_prepare(0)) {
+ info = per_cpu_ptr(&cpu_apple_start_info, 0);
+ writel(MAGIC_UNLOCK, info->dbg_unlock);
+ cpu_apple_start0_unlocked = 1;
+ } else
+ pr_err("%s: failed to unlock boot CPU\n", __func__);
+ }
+
+ info = per_cpu_ptr(&cpu_apple_start_info, cpu);
+
+ if (!info->pmgr_start || !info->cputrc_rvbar || !info->dbg_unlock)
+ return -EINVAL;
+
+ writeq(__pa_symbol(secondary_entry) | 1, info->cputrc_rvbar);
+ readq(info->cputrc_rvbar);
+ writeq(__pa_symbol(secondary_entry) | 1, info->cputrc_rvbar);
+ addr = readq(info->cputrc_rvbar) & 0xFFFFFFFFFul;
+ dsb(sy);
+
+ if (addr != (__pa_symbol(secondary_entry) | 1))
+ pr_err("%s: CPU%d reset address: 0x%lx, failed to set to 0x%lx.\n",
+ __func__, cpu, addr,
+ (unsigned long)(__pa_symbol(secondary_entry) | 1));
+
+ writel(MAGIC_UNLOCK, info->dbg_unlock);
+
+ writel(1 << cpu, info->pmgr_start);
+ if (info->pmgr_start_size >= 12) {
+ if (cpu < 4) {
+ writel(1 << cpu, info->pmgr_start + 4);
+ writel(0, info->pmgr_start + 8);
+ } else {
+ writel(0, info->pmgr_start + 4);
+ writel(1 << (cpu - 4), info->pmgr_start + 8);
+ }
+ } else
+ writel(1 << cpu, info->pmgr_start + 4);
+
+ dsb(sy);
+ sev();
+
+ return 0;
+}
+
+static void cpu_apple_wfi(void)
+{
+ /* can't do a proper WFI, because the CPU tends to lose state; will need
+ a proper wrapper sequence */
+ dsb(sy);
+ wfe();
+}
+
+const struct cpu_operations cpu_apple_start_ops = {
+ .name = "apple",
+ .cpu_init = cpu_apple_start_init,
+ .cpu_prepare = cpu_apple_start_prepare,
+ .cpu_boot = cpu_apple_start_boot,
+ .cpu_wfi = cpu_apple_wfi,
+};
--
2.29.2