diff options
Diffstat (limited to 'arch/arm64/kernel/psci.c')
-rw-r--r-- | arch/arm64/kernel/psci.c | 231 |
1 files changed, 194 insertions, 37 deletions
diff --git a/arch/arm64/kernel/psci.c b/arch/arm64/kernel/psci.c index ea4828a4aa96..9e9798f91172 100644 --- a/arch/arm64/kernel/psci.c +++ b/arch/arm64/kernel/psci.c | |||
@@ -18,12 +18,17 @@ | |||
18 | #include <linux/init.h> | 18 | #include <linux/init.h> |
19 | #include <linux/of.h> | 19 | #include <linux/of.h> |
20 | #include <linux/smp.h> | 20 | #include <linux/smp.h> |
21 | #include <linux/reboot.h> | ||
22 | #include <linux/pm.h> | ||
23 | #include <linux/delay.h> | ||
24 | #include <uapi/linux/psci.h> | ||
21 | 25 | ||
22 | #include <asm/compiler.h> | 26 | #include <asm/compiler.h> |
23 | #include <asm/cpu_ops.h> | 27 | #include <asm/cpu_ops.h> |
24 | #include <asm/errno.h> | 28 | #include <asm/errno.h> |
25 | #include <asm/psci.h> | 29 | #include <asm/psci.h> |
26 | #include <asm/smp_plat.h> | 30 | #include <asm/smp_plat.h> |
31 | #include <asm/system_misc.h> | ||
27 | 32 | ||
28 | #define PSCI_POWER_STATE_TYPE_STANDBY 0 | 33 | #define PSCI_POWER_STATE_TYPE_STANDBY 0 |
29 | #define PSCI_POWER_STATE_TYPE_POWER_DOWN 1 | 34 | #define PSCI_POWER_STATE_TYPE_POWER_DOWN 1 |
@@ -40,58 +45,52 @@ struct psci_operations { | |||
40 | int (*cpu_off)(struct psci_power_state state); | 45 | int (*cpu_off)(struct psci_power_state state); |
41 | int (*cpu_on)(unsigned long cpuid, unsigned long entry_point); | 46 | int (*cpu_on)(unsigned long cpuid, unsigned long entry_point); |
42 | int (*migrate)(unsigned long cpuid); | 47 | int (*migrate)(unsigned long cpuid); |
48 | int (*affinity_info)(unsigned long target_affinity, | ||
49 | unsigned long lowest_affinity_level); | ||
50 | int (*migrate_info_type)(void); | ||
43 | }; | 51 | }; |
44 | 52 | ||
45 | static struct psci_operations psci_ops; | 53 | static struct psci_operations psci_ops; |
46 | 54 | ||
47 | static int (*invoke_psci_fn)(u64, u64, u64, u64); | 55 | static int (*invoke_psci_fn)(u64, u64, u64, u64); |
56 | typedef int (*psci_initcall_t)(const struct device_node *); | ||
48 | 57 | ||
49 | enum psci_function { | 58 | enum psci_function { |
50 | PSCI_FN_CPU_SUSPEND, | 59 | PSCI_FN_CPU_SUSPEND, |
51 | PSCI_FN_CPU_ON, | 60 | PSCI_FN_CPU_ON, |
52 | PSCI_FN_CPU_OFF, | 61 | PSCI_FN_CPU_OFF, |
53 | PSCI_FN_MIGRATE, | 62 | PSCI_FN_MIGRATE, |
63 | PSCI_FN_AFFINITY_INFO, | ||
64 | PSCI_FN_MIGRATE_INFO_TYPE, | ||
54 | PSCI_FN_MAX, | 65 | PSCI_FN_MAX, |
55 | }; | 66 | }; |
56 | 67 | ||
57 | static u32 psci_function_id[PSCI_FN_MAX]; | 68 | static u32 psci_function_id[PSCI_FN_MAX]; |
58 | 69 | ||
59 | #define PSCI_RET_SUCCESS 0 | ||
60 | #define PSCI_RET_EOPNOTSUPP -1 | ||
61 | #define PSCI_RET_EINVAL -2 | ||
62 | #define PSCI_RET_EPERM -3 | ||
63 | |||
64 | static int psci_to_linux_errno(int errno) | 70 | static int psci_to_linux_errno(int errno) |
65 | { | 71 | { |
66 | switch (errno) { | 72 | switch (errno) { |
67 | case PSCI_RET_SUCCESS: | 73 | case PSCI_RET_SUCCESS: |
68 | return 0; | 74 | return 0; |
69 | case PSCI_RET_EOPNOTSUPP: | 75 | case PSCI_RET_NOT_SUPPORTED: |
70 | return -EOPNOTSUPP; | 76 | return -EOPNOTSUPP; |
71 | case PSCI_RET_EINVAL: | 77 | case PSCI_RET_INVALID_PARAMS: |
72 | return -EINVAL; | 78 | return -EINVAL; |
73 | case PSCI_RET_EPERM: | 79 | case PSCI_RET_DENIED: |
74 | return -EPERM; | 80 | return -EPERM; |
75 | }; | 81 | }; |
76 | 82 | ||
77 | return -EINVAL; | 83 | return -EINVAL; |
78 | } | 84 | } |
79 | 85 | ||
80 | #define PSCI_POWER_STATE_ID_MASK 0xffff | ||
81 | #define PSCI_POWER_STATE_ID_SHIFT 0 | ||
82 | #define PSCI_POWER_STATE_TYPE_MASK 0x1 | ||
83 | #define PSCI_POWER_STATE_TYPE_SHIFT 16 | ||
84 | #define PSCI_POWER_STATE_AFFL_MASK 0x3 | ||
85 | #define PSCI_POWER_STATE_AFFL_SHIFT 24 | ||
86 | |||
87 | static u32 psci_power_state_pack(struct psci_power_state state) | 86 | static u32 psci_power_state_pack(struct psci_power_state state) |
88 | { | 87 | { |
89 | return ((state.id & PSCI_POWER_STATE_ID_MASK) | 88 | return ((state.id << PSCI_0_2_POWER_STATE_ID_SHIFT) |
90 | << PSCI_POWER_STATE_ID_SHIFT) | | 89 | & PSCI_0_2_POWER_STATE_ID_MASK) | |
91 | ((state.type & PSCI_POWER_STATE_TYPE_MASK) | 90 | ((state.type << PSCI_0_2_POWER_STATE_TYPE_SHIFT) |
92 | << PSCI_POWER_STATE_TYPE_SHIFT) | | 91 | & PSCI_0_2_POWER_STATE_TYPE_MASK) | |
93 | ((state.affinity_level & PSCI_POWER_STATE_AFFL_MASK) | 92 | ((state.affinity_level << PSCI_0_2_POWER_STATE_AFFL_SHIFT) |
94 | << PSCI_POWER_STATE_AFFL_SHIFT); | 93 | & PSCI_0_2_POWER_STATE_AFFL_MASK); |
95 | } | 94 | } |
96 | 95 | ||
97 | /* | 96 | /* |
@@ -128,6 +127,14 @@ static noinline int __invoke_psci_fn_smc(u64 function_id, u64 arg0, u64 arg1, | |||
128 | return function_id; | 127 | return function_id; |
129 | } | 128 | } |
130 | 129 | ||
130 | static int psci_get_version(void) | ||
131 | { | ||
132 | int err; | ||
133 | |||
134 | err = invoke_psci_fn(PSCI_0_2_FN_PSCI_VERSION, 0, 0, 0); | ||
135 | return err; | ||
136 | } | ||
137 | |||
131 | static int psci_cpu_suspend(struct psci_power_state state, | 138 | static int psci_cpu_suspend(struct psci_power_state state, |
132 | unsigned long entry_point) | 139 | unsigned long entry_point) |
133 | { | 140 | { |
@@ -171,26 +178,36 @@ static int psci_migrate(unsigned long cpuid) | |||
171 | return psci_to_linux_errno(err); | 178 | return psci_to_linux_errno(err); |
172 | } | 179 | } |
173 | 180 | ||
174 | static const struct of_device_id psci_of_match[] __initconst = { | 181 | static int psci_affinity_info(unsigned long target_affinity, |
175 | { .compatible = "arm,psci", }, | 182 | unsigned long lowest_affinity_level) |
176 | {}, | 183 | { |
177 | }; | 184 | int err; |
185 | u32 fn; | ||
186 | |||
187 | fn = psci_function_id[PSCI_FN_AFFINITY_INFO]; | ||
188 | err = invoke_psci_fn(fn, target_affinity, lowest_affinity_level, 0); | ||
189 | return err; | ||
190 | } | ||
178 | 191 | ||
179 | void __init psci_init(void) | 192 | static int psci_migrate_info_type(void) |
180 | { | 193 | { |
181 | struct device_node *np; | 194 | int err; |
182 | const char *method; | 195 | u32 fn; |
183 | u32 id; | ||
184 | 196 | ||
185 | np = of_find_matching_node(NULL, psci_of_match); | 197 | fn = psci_function_id[PSCI_FN_MIGRATE_INFO_TYPE]; |
186 | if (!np) | 198 | err = invoke_psci_fn(fn, 0, 0, 0); |
187 | return; | 199 | return err; |
200 | } | ||
188 | 201 | ||
189 | pr_info("probing function IDs from device-tree\n"); | 202 | static int get_set_conduit_method(struct device_node *np) |
203 | { | ||
204 | const char *method; | ||
205 | |||
206 | pr_info("probing for conduit method from DT.\n"); | ||
190 | 207 | ||
191 | if (of_property_read_string(np, "method", &method)) { | 208 | if (of_property_read_string(np, "method", &method)) { |
192 | pr_warning("missing \"method\" property\n"); | 209 | pr_warn("missing \"method\" property\n"); |
193 | goto out_put_node; | 210 | return -ENXIO; |
194 | } | 211 | } |
195 | 212 | ||
196 | if (!strcmp("hvc", method)) { | 213 | if (!strcmp("hvc", method)) { |
@@ -198,10 +215,99 @@ void __init psci_init(void) | |||
198 | } else if (!strcmp("smc", method)) { | 215 | } else if (!strcmp("smc", method)) { |
199 | invoke_psci_fn = __invoke_psci_fn_smc; | 216 | invoke_psci_fn = __invoke_psci_fn_smc; |
200 | } else { | 217 | } else { |
201 | pr_warning("invalid \"method\" property: %s\n", method); | 218 | pr_warn("invalid \"method\" property: %s\n", method); |
219 | return -EINVAL; | ||
220 | } | ||
221 | return 0; | ||
222 | } | ||
223 | |||
224 | static void psci_sys_reset(enum reboot_mode reboot_mode, const char *cmd) | ||
225 | { | ||
226 | invoke_psci_fn(PSCI_0_2_FN_SYSTEM_RESET, 0, 0, 0); | ||
227 | } | ||
228 | |||
229 | static void psci_sys_poweroff(void) | ||
230 | { | ||
231 | invoke_psci_fn(PSCI_0_2_FN_SYSTEM_OFF, 0, 0, 0); | ||
232 | } | ||
233 | |||
234 | /* | ||
235 | * PSCI Function IDs for v0.2+ are well defined so use | ||
236 | * standard values. | ||
237 | */ | ||
238 | static int psci_0_2_init(struct device_node *np) | ||
239 | { | ||
240 | int err, ver; | ||
241 | |||
242 | err = get_set_conduit_method(np); | ||
243 | |||
244 | if (err) | ||
245 | goto out_put_node; | ||
246 | |||
247 | ver = psci_get_version(); | ||
248 | |||
249 | if (ver == PSCI_RET_NOT_SUPPORTED) { | ||
250 | /* PSCI v0.2 mandates implementation of PSCI_ID_VERSION. */ | ||
251 | pr_err("PSCI firmware does not comply with the v0.2 spec.\n"); | ||
252 | err = -EOPNOTSUPP; | ||
202 | goto out_put_node; | 253 | goto out_put_node; |
254 | } else { | ||
255 | pr_info("PSCIv%d.%d detected in firmware.\n", | ||
256 | PSCI_VERSION_MAJOR(ver), | ||
257 | PSCI_VERSION_MINOR(ver)); | ||
258 | |||
259 | if (PSCI_VERSION_MAJOR(ver) == 0 && | ||
260 | PSCI_VERSION_MINOR(ver) < 2) { | ||
261 | err = -EINVAL; | ||
262 | pr_err("Conflicting PSCI version detected.\n"); | ||
263 | goto out_put_node; | ||
264 | } | ||
203 | } | 265 | } |
204 | 266 | ||
267 | pr_info("Using standard PSCI v0.2 function IDs\n"); | ||
268 | psci_function_id[PSCI_FN_CPU_SUSPEND] = PSCI_0_2_FN64_CPU_SUSPEND; | ||
269 | psci_ops.cpu_suspend = psci_cpu_suspend; | ||
270 | |||
271 | psci_function_id[PSCI_FN_CPU_OFF] = PSCI_0_2_FN_CPU_OFF; | ||
272 | psci_ops.cpu_off = psci_cpu_off; | ||
273 | |||
274 | psci_function_id[PSCI_FN_CPU_ON] = PSCI_0_2_FN64_CPU_ON; | ||
275 | psci_ops.cpu_on = psci_cpu_on; | ||
276 | |||
277 | psci_function_id[PSCI_FN_MIGRATE] = PSCI_0_2_FN64_MIGRATE; | ||
278 | psci_ops.migrate = psci_migrate; | ||
279 | |||
280 | psci_function_id[PSCI_FN_AFFINITY_INFO] = PSCI_0_2_FN64_AFFINITY_INFO; | ||
281 | psci_ops.affinity_info = psci_affinity_info; | ||
282 | |||
283 | psci_function_id[PSCI_FN_MIGRATE_INFO_TYPE] = | ||
284 | PSCI_0_2_FN_MIGRATE_INFO_TYPE; | ||
285 | psci_ops.migrate_info_type = psci_migrate_info_type; | ||
286 | |||
287 | arm_pm_restart = psci_sys_reset; | ||
288 | |||
289 | pm_power_off = psci_sys_poweroff; | ||
290 | |||
291 | out_put_node: | ||
292 | of_node_put(np); | ||
293 | return err; | ||
294 | } | ||
295 | |||
296 | /* | ||
297 | * PSCI < v0.2 get PSCI Function IDs via DT. | ||
298 | */ | ||
299 | static int psci_0_1_init(struct device_node *np) | ||
300 | { | ||
301 | u32 id; | ||
302 | int err; | ||
303 | |||
304 | err = get_set_conduit_method(np); | ||
305 | |||
306 | if (err) | ||
307 | goto out_put_node; | ||
308 | |||
309 | pr_info("Using PSCI v0.1 Function IDs from DT\n"); | ||
310 | |||
205 | if (!of_property_read_u32(np, "cpu_suspend", &id)) { | 311 | if (!of_property_read_u32(np, "cpu_suspend", &id)) { |
206 | psci_function_id[PSCI_FN_CPU_SUSPEND] = id; | 312 | psci_function_id[PSCI_FN_CPU_SUSPEND] = id; |
207 | psci_ops.cpu_suspend = psci_cpu_suspend; | 313 | psci_ops.cpu_suspend = psci_cpu_suspend; |
@@ -224,7 +330,28 @@ void __init psci_init(void) | |||
224 | 330 | ||
225 | out_put_node: | 331 | out_put_node: |
226 | of_node_put(np); | 332 | of_node_put(np); |
227 | return; | 333 | return err; |
334 | } | ||
335 | |||
336 | static const struct of_device_id psci_of_match[] __initconst = { | ||
337 | { .compatible = "arm,psci", .data = psci_0_1_init}, | ||
338 | { .compatible = "arm,psci-0.2", .data = psci_0_2_init}, | ||
339 | {}, | ||
340 | }; | ||
341 | |||
342 | int __init psci_init(void) | ||
343 | { | ||
344 | struct device_node *np; | ||
345 | const struct of_device_id *matched_np; | ||
346 | psci_initcall_t init_fn; | ||
347 | |||
348 | np = of_find_matching_node_and_match(NULL, psci_of_match, &matched_np); | ||
349 | |||
350 | if (!np) | ||
351 | return -ENODEV; | ||
352 | |||
353 | init_fn = (psci_initcall_t)matched_np->data; | ||
354 | return init_fn(np); | ||
228 | } | 355 | } |
229 | 356 | ||
230 | #ifdef CONFIG_SMP | 357 | #ifdef CONFIG_SMP |
@@ -277,6 +404,35 @@ static void cpu_psci_cpu_die(unsigned int cpu) | |||
277 | 404 | ||
278 | pr_crit("unable to power off CPU%u (%d)\n", cpu, ret); | 405 | pr_crit("unable to power off CPU%u (%d)\n", cpu, ret); |
279 | } | 406 | } |
407 | |||
408 | static int cpu_psci_cpu_kill(unsigned int cpu) | ||
409 | { | ||
410 | int err, i; | ||
411 | |||
412 | if (!psci_ops.affinity_info) | ||
413 | return 1; | ||
414 | /* | ||
415 | * cpu_kill could race with cpu_die and we can | ||
416 | * potentially end up declaring this cpu undead | ||
417 | * while it is dying. So, try again a few times. | ||
418 | */ | ||
419 | |||
420 | for (i = 0; i < 10; i++) { | ||
421 | err = psci_ops.affinity_info(cpu_logical_map(cpu), 0); | ||
422 | if (err == PSCI_0_2_AFFINITY_LEVEL_OFF) { | ||
423 | pr_info("CPU%d killed.\n", cpu); | ||
424 | return 1; | ||
425 | } | ||
426 | |||
427 | msleep(10); | ||
428 | pr_info("Retrying again to check for CPU kill\n"); | ||
429 | } | ||
430 | |||
431 | pr_warn("CPU%d may not have shut down cleanly (AFFINITY_INFO reports %d)\n", | ||
432 | cpu, err); | ||
433 | /* Make op_cpu_kill() fail. */ | ||
434 | return 0; | ||
435 | } | ||
280 | #endif | 436 | #endif |
281 | 437 | ||
282 | const struct cpu_operations cpu_psci_ops = { | 438 | const struct cpu_operations cpu_psci_ops = { |
@@ -287,6 +443,7 @@ const struct cpu_operations cpu_psci_ops = { | |||
287 | #ifdef CONFIG_HOTPLUG_CPU | 443 | #ifdef CONFIG_HOTPLUG_CPU |
288 | .cpu_disable = cpu_psci_cpu_disable, | 444 | .cpu_disable = cpu_psci_cpu_disable, |
289 | .cpu_die = cpu_psci_cpu_die, | 445 | .cpu_die = cpu_psci_cpu_die, |
446 | .cpu_kill = cpu_psci_cpu_kill, | ||
290 | #endif | 447 | #endif |
291 | }; | 448 | }; |
292 | 449 | ||