diff options
Diffstat (limited to 'arch/arm64/kernel/psci.c')
| -rw-r--r-- | arch/arm64/kernel/psci.c | 255 |
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; -} |
