1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * |
4 | * Copyright (C) 2012 ARM Limited |
5 | * |
6 | * Author: Will Deacon <will.deacon@arm.com> |
7 | */ |
8 | |
9 | #include <linux/init.h> |
10 | #include <linux/smp.h> |
11 | #include <linux/of.h> |
12 | #include <linux/delay.h> |
13 | #include <linux/psci.h> |
14 | |
15 | #include <uapi/linux/psci.h> |
16 | |
17 | #include <asm/psci.h> |
18 | #include <asm/smp_plat.h> |
19 | |
20 | /* |
21 | * psci_smp assumes that the following is true about PSCI: |
22 | * |
23 | * cpu_suspend Suspend the execution on a CPU |
24 | * @state we don't currently describe affinity levels, so just pass 0. |
25 | * @entry_point the first instruction to be executed on return |
26 | * returns 0 success, < 0 on failure |
27 | * |
28 | * cpu_off Power down a CPU |
29 | * @state we don't currently describe affinity levels, so just pass 0. |
30 | * no return on successful call |
31 | * |
32 | * cpu_on Power up a CPU |
33 | * @cpuid cpuid of target CPU, as from MPIDR |
34 | * @entry_point the first instruction to be executed on return |
35 | * returns 0 success, < 0 on failure |
36 | * |
37 | * migrate Migrate the context to a different CPU |
38 | * @cpuid cpuid of target CPU, as from MPIDR |
39 | * returns 0 success, < 0 on failure |
40 | * |
41 | */ |
42 | |
43 | extern void secondary_startup(void); |
44 | |
45 | static int psci_boot_secondary(unsigned int cpu, struct task_struct *idle) |
46 | { |
47 | if (psci_ops.cpu_on) |
48 | return psci_ops.cpu_on(cpu_logical_map(cpu), |
49 | virt_to_idmap(&secondary_startup)); |
50 | return -ENODEV; |
51 | } |
52 | |
53 | #ifdef CONFIG_HOTPLUG_CPU |
54 | static int psci_cpu_disable(unsigned int cpu) |
55 | { |
56 | /* Fail early if we don't have CPU_OFF support */ |
57 | if (!psci_ops.cpu_off) |
58 | return -EOPNOTSUPP; |
59 | |
60 | /* Trusted OS will deny CPU_OFF */ |
61 | if (psci_tos_resident_on(cpu)) |
62 | return -EPERM; |
63 | |
64 | return 0; |
65 | } |
66 | |
67 | static void psci_cpu_die(unsigned int cpu) |
68 | { |
69 | u32 state = PSCI_POWER_STATE_TYPE_POWER_DOWN << |
70 | PSCI_0_2_POWER_STATE_TYPE_SHIFT; |
71 | |
72 | if (psci_ops.cpu_off) |
73 | psci_ops.cpu_off(state); |
74 | |
75 | /* We should never return */ |
76 | panic(fmt: "psci: cpu %d failed to shutdown\n" , cpu); |
77 | } |
78 | |
79 | static int psci_cpu_kill(unsigned int cpu) |
80 | { |
81 | int err, i; |
82 | |
83 | if (!psci_ops.affinity_info) |
84 | return 1; |
85 | /* |
86 | * cpu_kill could race with cpu_die and we can |
87 | * potentially end up declaring this cpu undead |
88 | * while it is dying. So, try again a few times. |
89 | */ |
90 | |
91 | for (i = 0; i < 10; i++) { |
92 | err = psci_ops.affinity_info(cpu_logical_map(cpu), 0); |
93 | if (err == PSCI_0_2_AFFINITY_LEVEL_OFF) { |
94 | pr_info("CPU%d killed.\n" , cpu); |
95 | return 1; |
96 | } |
97 | |
98 | msleep(msecs: 10); |
99 | pr_info("Retrying again to check for CPU kill\n" ); |
100 | } |
101 | |
102 | pr_warn("CPU%d may not have shut down cleanly (AFFINITY_INFO reports %d)\n" , |
103 | cpu, err); |
104 | /* Make platform_cpu_kill() fail. */ |
105 | return 0; |
106 | } |
107 | |
108 | #endif |
109 | |
110 | bool __init psci_smp_available(void) |
111 | { |
112 | /* is cpu_on available at least? */ |
113 | return (psci_ops.cpu_on != NULL); |
114 | } |
115 | |
116 | const struct smp_operations psci_smp_ops __initconst = { |
117 | .smp_boot_secondary = psci_boot_secondary, |
118 | #ifdef CONFIG_HOTPLUG_CPU |
119 | .cpu_disable = psci_cpu_disable, |
120 | .cpu_die = psci_cpu_die, |
121 | .cpu_kill = psci_cpu_kill, |
122 | #endif |
123 | }; |
124 | |