Commit e790f1deb26a2e23f05dee0b9a5d4f764c3d7ea7

Authored by Will Deacon
Committed by Catalin Marinas
1 parent d329de3f2a

arm64: psci: add support for PSCI invocations from the kernel

This patch adds support for the Power State Coordination Interface
defined by ARM, allowing Linux to request CPU-centric power-management
operations from firmware implementing the PSCI protocol.

Signed-off-by: Will Deacon <will.deacon@arm.com>
[Marc: s/u32/u64/ in the relevant spots, and switch from an initcall
 to an simpler init function]
Signed-off-by: Marc Zyngier <marc.zyngier@arm.com>
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>

Showing 4 changed files with 253 additions and 1 deletions Side-by-side Diff

arch/arm64/include/asm/psci.h
  1 +/*
  2 + * This program is free software; you can redistribute it and/or modify
  3 + * it under the terms of the GNU General Public License version 2 as
  4 + * published by the Free Software Foundation.
  5 + *
  6 + * This program is distributed in the hope that it will be useful,
  7 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  8 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  9 + * GNU General Public License for more details.
  10 + *
  11 + * Copyright (C) 2013 ARM Limited
  12 + */
  13 +
  14 +#ifndef __ASM_PSCI_H
  15 +#define __ASM_PSCI_H
  16 +
  17 +#define PSCI_POWER_STATE_TYPE_STANDBY 0
  18 +#define PSCI_POWER_STATE_TYPE_POWER_DOWN 1
  19 +
  20 +struct psci_power_state {
  21 + u16 id;
  22 + u8 type;
  23 + u8 affinity_level;
  24 +};
  25 +
  26 +struct psci_operations {
  27 + int (*cpu_suspend)(struct psci_power_state state,
  28 + unsigned long entry_point);
  29 + int (*cpu_off)(struct psci_power_state state);
  30 + int (*cpu_on)(unsigned long cpuid, unsigned long entry_point);
  31 + int (*migrate)(unsigned long cpuid);
  32 +};
  33 +
  34 +extern struct psci_operations psci_ops;
  35 +
  36 +int psci_init(void);
  37 +
  38 +#endif /* __ASM_PSCI_H */
arch/arm64/kernel/Makefile
... ... @@ -9,7 +9,7 @@
9 9 arm64-obj-y := cputable.o debug-monitors.o entry.o irq.o fpsimd.o \
10 10 entry-fpsimd.o process.o ptrace.o setup.o signal.o \
11 11 sys.o stacktrace.o time.o traps.o io.o vdso.o \
12   - hyp-stub.o
  12 + hyp-stub.o psci.o
13 13  
14 14 arm64-obj-$(CONFIG_COMPAT) += sys32.o kuser32.o signal32.o \
15 15 sys_compat.o
arch/arm64/kernel/psci.c
  1 +/*
  2 + * This program is free software; you can redistribute it and/or modify
  3 + * it under the terms of the GNU General Public License version 2 as
  4 + * published by the Free Software Foundation.
  5 + *
  6 + * This program is distributed in the hope that it will be useful,
  7 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  8 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  9 + * GNU General Public License for more details.
  10 + *
  11 + * Copyright (C) 2013 ARM Limited
  12 + *
  13 + * Author: Will Deacon <will.deacon@arm.com>
  14 + */
  15 +
  16 +#define pr_fmt(fmt) "psci: " fmt
  17 +
  18 +#include <linux/init.h>
  19 +#include <linux/of.h>
  20 +
  21 +#include <asm/compiler.h>
  22 +#include <asm/errno.h>
  23 +#include <asm/psci.h>
  24 +
  25 +struct psci_operations psci_ops;
  26 +
  27 +static int (*invoke_psci_fn)(u64, u64, u64, u64);
  28 +
  29 +enum psci_function {
  30 + PSCI_FN_CPU_SUSPEND,
  31 + PSCI_FN_CPU_ON,
  32 + PSCI_FN_CPU_OFF,
  33 + PSCI_FN_MIGRATE,
  34 + PSCI_FN_MAX,
  35 +};
  36 +
  37 +static u32 psci_function_id[PSCI_FN_MAX];
  38 +
  39 +#define PSCI_RET_SUCCESS 0
  40 +#define PSCI_RET_EOPNOTSUPP -1
  41 +#define PSCI_RET_EINVAL -2
  42 +#define PSCI_RET_EPERM -3
  43 +
  44 +static int psci_to_linux_errno(int errno)
  45 +{
  46 + switch (errno) {
  47 + case PSCI_RET_SUCCESS:
  48 + return 0;
  49 + case PSCI_RET_EOPNOTSUPP:
  50 + return -EOPNOTSUPP;
  51 + case PSCI_RET_EINVAL:
  52 + return -EINVAL;
  53 + case PSCI_RET_EPERM:
  54 + return -EPERM;
  55 + };
  56 +
  57 + return -EINVAL;
  58 +}
  59 +
  60 +#define PSCI_POWER_STATE_ID_MASK 0xffff
  61 +#define PSCI_POWER_STATE_ID_SHIFT 0
  62 +#define PSCI_POWER_STATE_TYPE_MASK 0x1
  63 +#define PSCI_POWER_STATE_TYPE_SHIFT 16
  64 +#define PSCI_POWER_STATE_AFFL_MASK 0x3
  65 +#define PSCI_POWER_STATE_AFFL_SHIFT 24
  66 +
  67 +static u32 psci_power_state_pack(struct psci_power_state state)
  68 +{
  69 + return ((state.id & PSCI_POWER_STATE_ID_MASK)
  70 + << PSCI_POWER_STATE_ID_SHIFT) |
  71 + ((state.type & PSCI_POWER_STATE_TYPE_MASK)
  72 + << PSCI_POWER_STATE_TYPE_SHIFT) |
  73 + ((state.affinity_level & PSCI_POWER_STATE_AFFL_MASK)
  74 + << PSCI_POWER_STATE_AFFL_SHIFT);
  75 +}
  76 +
  77 +/*
  78 + * The following two functions are invoked via the invoke_psci_fn pointer
  79 + * and will not be inlined, allowing us to piggyback on the AAPCS.
  80 + */
  81 +static noinline int __invoke_psci_fn_hvc(u64 function_id, u64 arg0, u64 arg1,
  82 + u64 arg2)
  83 +{
  84 + asm volatile(
  85 + __asmeq("%0", "x0")
  86 + __asmeq("%1", "x1")
  87 + __asmeq("%2", "x2")
  88 + __asmeq("%3", "x3")
  89 + "hvc #0\n"
  90 + : "+r" (function_id)
  91 + : "r" (arg0), "r" (arg1), "r" (arg2));
  92 +
  93 + return function_id;
  94 +}
  95 +
  96 +static noinline int __invoke_psci_fn_smc(u64 function_id, u64 arg0, u64 arg1,
  97 + u64 arg2)
  98 +{
  99 + asm volatile(
  100 + __asmeq("%0", "x0")
  101 + __asmeq("%1", "x1")
  102 + __asmeq("%2", "x2")
  103 + __asmeq("%3", "x3")
  104 + "smc #0\n"
  105 + : "+r" (function_id)
  106 + : "r" (arg0), "r" (arg1), "r" (arg2));
  107 +
  108 + return function_id;
  109 +}
  110 +
  111 +static int psci_cpu_suspend(struct psci_power_state state,
  112 + unsigned long entry_point)
  113 +{
  114 + int err;
  115 + u32 fn, power_state;
  116 +
  117 + fn = psci_function_id[PSCI_FN_CPU_SUSPEND];
  118 + power_state = psci_power_state_pack(state);
  119 + err = invoke_psci_fn(fn, power_state, entry_point, 0);
  120 + return psci_to_linux_errno(err);
  121 +}
  122 +
  123 +static int psci_cpu_off(struct psci_power_state state)
  124 +{
  125 + int err;
  126 + u32 fn, power_state;
  127 +
  128 + fn = psci_function_id[PSCI_FN_CPU_OFF];
  129 + power_state = psci_power_state_pack(state);
  130 + err = invoke_psci_fn(fn, power_state, 0, 0);
  131 + return psci_to_linux_errno(err);
  132 +}
  133 +
  134 +static int psci_cpu_on(unsigned long cpuid, unsigned long entry_point)
  135 +{
  136 + int err;
  137 + u32 fn;
  138 +
  139 + fn = psci_function_id[PSCI_FN_CPU_ON];
  140 + err = invoke_psci_fn(fn, cpuid, entry_point, 0);
  141 + return psci_to_linux_errno(err);
  142 +}
  143 +
  144 +static int psci_migrate(unsigned long cpuid)
  145 +{
  146 + int err;
  147 + u32 fn;
  148 +
  149 + fn = psci_function_id[PSCI_FN_MIGRATE];
  150 + err = invoke_psci_fn(fn, cpuid, 0, 0);
  151 + return psci_to_linux_errno(err);
  152 +}
  153 +
  154 +static const struct of_device_id psci_of_match[] __initconst = {
  155 + { .compatible = "arm,psci", },
  156 + {},
  157 +};
  158 +
  159 +int __init psci_init(void)
  160 +{
  161 + struct device_node *np;
  162 + const char *method;
  163 + u32 id;
  164 + int err = 0;
  165 +
  166 + np = of_find_matching_node(NULL, psci_of_match);
  167 + if (!np)
  168 + return -ENODEV;
  169 +
  170 + pr_info("probing function IDs from device-tree\n");
  171 +
  172 + if (of_property_read_string(np, "method", &method)) {
  173 + pr_warning("missing \"method\" property\n");
  174 + err = -ENXIO;
  175 + goto out_put_node;
  176 + }
  177 +
  178 + if (!strcmp("hvc", method)) {
  179 + invoke_psci_fn = __invoke_psci_fn_hvc;
  180 + } else if (!strcmp("smc", method)) {
  181 + invoke_psci_fn = __invoke_psci_fn_smc;
  182 + } else {
  183 + pr_warning("invalid \"method\" property: %s\n", method);
  184 + err = -EINVAL;
  185 + goto out_put_node;
  186 + }
  187 +
  188 + if (!of_property_read_u32(np, "cpu_suspend", &id)) {
  189 + psci_function_id[PSCI_FN_CPU_SUSPEND] = id;
  190 + psci_ops.cpu_suspend = psci_cpu_suspend;
  191 + }
  192 +
  193 + if (!of_property_read_u32(np, "cpu_off", &id)) {
  194 + psci_function_id[PSCI_FN_CPU_OFF] = id;
  195 + psci_ops.cpu_off = psci_cpu_off;
  196 + }
  197 +
  198 + if (!of_property_read_u32(np, "cpu_on", &id)) {
  199 + psci_function_id[PSCI_FN_CPU_ON] = id;
  200 + psci_ops.cpu_on = psci_cpu_on;
  201 + }
  202 +
  203 + if (!of_property_read_u32(np, "migrate", &id)) {
  204 + psci_function_id[PSCI_FN_MIGRATE] = id;
  205 + psci_ops.migrate = psci_migrate;
  206 + }
  207 +
  208 +out_put_node:
  209 + of_node_put(np);
  210 + return err;
  211 +}
arch/arm64/kernel/setup.c
... ... @@ -50,6 +50,7 @@
50 50 #include <asm/tlbflush.h>
51 51 #include <asm/traps.h>
52 52 #include <asm/memblock.h>
  53 +#include <asm/psci.h>
53 54  
54 55 unsigned int processor_id;
55 56 EXPORT_SYMBOL(processor_id);
... ... @@ -260,6 +261,8 @@
260 261 request_standard_resources();
261 262  
262 263 unflatten_device_tree();
  264 +
  265 + psci_init();
263 266  
264 267 #ifdef CONFIG_SMP
265 268 smp_init_cpus();