Commit e790f1deb26a2e23f05dee0b9a5d4f764c3d7ea7
Committed by
Catalin Marinas
1 parent
d329de3f2a
Exists in
smarc-l5.0.0_1.0.0-ga
and in
5 other branches
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(); |