diff mbox series

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

Message ID 20210120132717.395873-7-mohamed.mediouni@caramail.com (mailing list archive)
State New, archived
Headers show
Series Linux on Apple Silicon | expand

Commit Message

Mohamed Mediouni Jan. 20, 2021, 1:27 p.m. UTC
From: Stan Skowronek <stan@corellium.com>

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

Signed-off-by: Stan Skowronek <stan@corellium.com>
Signed-off-by: Mohamed Mediouni <mohamed.mediouni@caramail.com>
---
 .../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

--
2.29.2

Comments

Arnd Bergmann Jan. 21, 2021, 11:14 a.m. UTC | #1
On Wed, Jan 20, 2021 at 2:27 PM Mohamed Mediouni
<mohamed.mediouni@caramail.com> wrote:
> 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

This uses a very generic identifier for doing something that may
be very specific to a particular SoC generation. It's going to be hard
to decide what the right abstraction will be for long-term maintenance,
so I'd recommend starting with a boot loader that implements
spin-table for secondary startup, and getting back to this after more
of the basic stuff works.

> +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;
> +       }

Where is the binding documentation for this? The way you do a separate
of_iomap() for each CPU suggests that this is not a great binding to
start with. Are these perhaps just individual registers within a larger IP
block in the end?

      Arnd
diff mbox series

Patch

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,
+};