summaryrefslogtreecommitdiff
path: root/arch/arm64/kernel/psci.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm64/kernel/psci.c')
-rw-r--r--arch/arm64/kernel/psci.c255
1 files changed, 84 insertions, 171 deletions
diff --git a/arch/arm64/kernel/psci.c b/arch/arm64/kernel/psci.c
index 14f73c445ff5..fabd732d0a2d 100644
--- a/arch/arm64/kernel/psci.c
+++ b/arch/arm64/kernel/psci.c
@@ -1,12 +1,5 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
- * 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.
*
* Copyright (C) 2013 ARM Limited
*
@@ -17,195 +10,115 @@
#include <linux/init.h>
#include <linux/of.h>
+#include <linux/smp.h>
+#include <linux/delay.h>
+#include <linux/psci.h>
+#include <linux/mm.h>
-#include <asm/compiler.h>
-#include <asm/errno.h>
-#include <asm/psci.h>
-
-struct psci_operations psci_ops;
-
-static int (*invoke_psci_fn)(u64, u64, u64, u64);
-
-enum psci_function {
- PSCI_FN_CPU_SUSPEND,
- PSCI_FN_CPU_ON,
- PSCI_FN_CPU_OFF,
- PSCI_FN_MIGRATE,
- PSCI_FN_MAX,
-};
-
-static u32 psci_function_id[PSCI_FN_MAX];
+#include <uapi/linux/psci.h>
-#define PSCI_RET_SUCCESS 0
-#define PSCI_RET_EOPNOTSUPP -1
-#define PSCI_RET_EINVAL -2
-#define PSCI_RET_EPERM -3
+#include <asm/cpu_ops.h>
+#include <asm/errno.h>
+#include <asm/smp_plat.h>
-static int psci_to_linux_errno(int errno)
+static int __init cpu_psci_cpu_init(unsigned int cpu)
{
- switch (errno) {
- case PSCI_RET_SUCCESS:
- return 0;
- case PSCI_RET_EOPNOTSUPP:
- return -EOPNOTSUPP;
- case PSCI_RET_EINVAL:
- return -EINVAL;
- case PSCI_RET_EPERM:
- return -EPERM;
- };
-
- return -EINVAL;
+ return 0;
}
-#define PSCI_POWER_STATE_ID_MASK 0xffff
-#define PSCI_POWER_STATE_ID_SHIFT 0
-#define PSCI_POWER_STATE_TYPE_MASK 0x1
-#define PSCI_POWER_STATE_TYPE_SHIFT 16
-#define PSCI_POWER_STATE_AFFL_MASK 0x3
-#define PSCI_POWER_STATE_AFFL_SHIFT 24
-
-static u32 psci_power_state_pack(struct psci_power_state state)
+static int __init cpu_psci_cpu_prepare(unsigned int cpu)
{
- return ((state.id & PSCI_POWER_STATE_ID_MASK)
- << PSCI_POWER_STATE_ID_SHIFT) |
- ((state.type & PSCI_POWER_STATE_TYPE_MASK)
- << PSCI_POWER_STATE_TYPE_SHIFT) |
- ((state.affinity_level & PSCI_POWER_STATE_AFFL_MASK)
- << PSCI_POWER_STATE_AFFL_SHIFT);
-}
+ if (!psci_ops.cpu_on) {
+ pr_err("no cpu_on method, not booting CPU%d\n", cpu);
+ return -ENODEV;
+ }
-/*
- * The following two functions are invoked via the invoke_psci_fn pointer
- * and will not be inlined, allowing us to piggyback on the AAPCS.
- */
-static noinline int __invoke_psci_fn_hvc(u64 function_id, u64 arg0, u64 arg1,
- u64 arg2)
-{
- asm volatile(
- __asmeq("%0", "x0")
- __asmeq("%1", "x1")
- __asmeq("%2", "x2")
- __asmeq("%3", "x3")
- "hvc #0\n"
- : "+r" (function_id)
- : "r" (arg0), "r" (arg1), "r" (arg2));
-
- return function_id;
+ return 0;
}
-static noinline int __invoke_psci_fn_smc(u64 function_id, u64 arg0, u64 arg1,
- u64 arg2)
+static int cpu_psci_cpu_boot(unsigned int cpu)
{
- asm volatile(
- __asmeq("%0", "x0")
- __asmeq("%1", "x1")
- __asmeq("%2", "x2")
- __asmeq("%3", "x3")
- "smc #0\n"
- : "+r" (function_id)
- : "r" (arg0), "r" (arg1), "r" (arg2));
-
- return function_id;
+ phys_addr_t pa_secondary_entry = __pa_symbol(secondary_entry);
+ int err = psci_ops.cpu_on(cpu_logical_map(cpu), pa_secondary_entry);
+ if (err && err != -EPERM)
+ pr_err("failed to boot CPU%d (%d)\n", cpu, err);
+
+ return err;
}
-static int psci_cpu_suspend(struct psci_power_state state,
- unsigned long entry_point)
+#ifdef CONFIG_HOTPLUG_CPU
+static bool cpu_psci_cpu_can_disable(unsigned int cpu)
{
- int err;
- u32 fn, power_state;
-
- fn = psci_function_id[PSCI_FN_CPU_SUSPEND];
- power_state = psci_power_state_pack(state);
- err = invoke_psci_fn(fn, power_state, entry_point, 0);
- return psci_to_linux_errno(err);
+ return !psci_tos_resident_on(cpu);
}
-static int psci_cpu_off(struct psci_power_state state)
+static int cpu_psci_cpu_disable(unsigned int cpu)
{
- int err;
- u32 fn, power_state;
+ /* Fail early if we don't have CPU_OFF support */
+ if (!psci_ops.cpu_off)
+ return -EOPNOTSUPP;
- fn = psci_function_id[PSCI_FN_CPU_OFF];
- power_state = psci_power_state_pack(state);
- err = invoke_psci_fn(fn, power_state, 0, 0);
- return psci_to_linux_errno(err);
+ /* Trusted OS will deny CPU_OFF */
+ if (psci_tos_resident_on(cpu))
+ return -EPERM;
+
+ return 0;
}
-static int psci_cpu_on(unsigned long cpuid, unsigned long entry_point)
+static void cpu_psci_cpu_die(unsigned int cpu)
{
- int err;
- u32 fn;
-
- fn = psci_function_id[PSCI_FN_CPU_ON];
- err = invoke_psci_fn(fn, cpuid, entry_point, 0);
- return psci_to_linux_errno(err);
+ /*
+ * There are no known implementations of PSCI actually using the
+ * power state field, pass a sensible default for now.
+ */
+ u32 state = PSCI_POWER_STATE_TYPE_POWER_DOWN <<
+ PSCI_0_2_POWER_STATE_TYPE_SHIFT;
+
+ psci_ops.cpu_off(state);
}
-static int psci_migrate(unsigned long cpuid)
+static int cpu_psci_cpu_kill(unsigned int cpu)
{
int err;
- u32 fn;
+ unsigned long start, end;
- fn = psci_function_id[PSCI_FN_MIGRATE];
- err = invoke_psci_fn(fn, cpuid, 0, 0);
- return psci_to_linux_errno(err);
+ if (!psci_ops.affinity_info)
+ return 0;
+ /*
+ * cpu_kill could race with cpu_die and we can
+ * potentially end up declaring this cpu undead
+ * while it is dying. So, try again a few times.
+ */
+
+ start = jiffies;
+ end = start + msecs_to_jiffies(100);
+ do {
+ err = psci_ops.affinity_info(cpu_logical_map(cpu), 0);
+ if (err == PSCI_0_2_AFFINITY_LEVEL_OFF) {
+ pr_info("CPU%d killed (polled %d ms)\n", cpu,
+ jiffies_to_msecs(jiffies - start));
+ return 0;
+ }
+
+ usleep_range(100, 1000);
+ } while (time_before(jiffies, end));
+
+ pr_warn("CPU%d may not have shut down cleanly (AFFINITY_INFO reports %d)\n",
+ cpu, err);
+ return -ETIMEDOUT;
}
-
-static const struct of_device_id psci_of_match[] __initconst = {
- { .compatible = "arm,psci", },
- {},
+#endif
+
+const struct cpu_operations cpu_psci_ops = {
+ .name = "psci",
+ .cpu_init = cpu_psci_cpu_init,
+ .cpu_prepare = cpu_psci_cpu_prepare,
+ .cpu_boot = cpu_psci_cpu_boot,
+#ifdef CONFIG_HOTPLUG_CPU
+ .cpu_can_disable = cpu_psci_cpu_can_disable,
+ .cpu_disable = cpu_psci_cpu_disable,
+ .cpu_die = cpu_psci_cpu_die,
+ .cpu_kill = cpu_psci_cpu_kill,
+#endif
};
-int __init psci_init(void)
-{
- struct device_node *np;
- const char *method;
- u32 id;
- int err = 0;
-
- np = of_find_matching_node(NULL, psci_of_match);
- if (!np)
- return -ENODEV;
-
- pr_info("probing function IDs from device-tree\n");
-
- if (of_property_read_string(np, "method", &method)) {
- pr_warning("missing \"method\" property\n");
- err = -ENXIO;
- goto out_put_node;
- }
-
- if (!strcmp("hvc", method)) {
- invoke_psci_fn = __invoke_psci_fn_hvc;
- } else if (!strcmp("smc", method)) {
- invoke_psci_fn = __invoke_psci_fn_smc;
- } else {
- pr_warning("invalid \"method\" property: %s\n", method);
- err = -EINVAL;
- goto out_put_node;
- }
-
- if (!of_property_read_u32(np, "cpu_suspend", &id)) {
- psci_function_id[PSCI_FN_CPU_SUSPEND] = id;
- psci_ops.cpu_suspend = psci_cpu_suspend;
- }
-
- if (!of_property_read_u32(np, "cpu_off", &id)) {
- psci_function_id[PSCI_FN_CPU_OFF] = id;
- psci_ops.cpu_off = psci_cpu_off;
- }
-
- if (!of_property_read_u32(np, "cpu_on", &id)) {
- psci_function_id[PSCI_FN_CPU_ON] = id;
- psci_ops.cpu_on = psci_cpu_on;
- }
-
- if (!of_property_read_u32(np, "migrate", &id)) {
- psci_function_id[PSCI_FN_MIGRATE] = id;
- psci_ops.migrate = psci_migrate;
- }
-
-out_put_node:
- of_node_put(np);
- return err;
-}