1 /* $OpenBSD: psci.c,v 1.17 2024/07/10 11:01:24 kettenis Exp $ */
2
3 /*
4 * Copyright (c) 2016 Jonathan Gray <jsg@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 #include <sys/param.h>
20 #include <sys/device.h>
21 #include <sys/systm.h>
22
23 #include <machine/bus.h>
24 #include <machine/fdt.h>
25
26 #include <dev/ofw/openfirm.h>
27 #include <dev/ofw/fdt.h>
28
29 #include <dev/fdt/pscivar.h>
30
31 extern void (*cpuresetfn)(void);
32 extern void (*powerdownfn)(void);
33
34 #define SMCCC_VERSION 0x80000000
35 #define SMCCC_ARCH_FEATURES 0x80000001
36 #define SMCCC_ARCH_WORKAROUND_1 0x80008000
37 #define SMCCC_ARCH_WORKAROUND_2 0x80007fff
38 #define SMCCC_ARCH_WORKAROUND_3 0x80003fff
39
40 struct psci_softc {
41 struct device sc_dev;
42 register_t (*sc_callfn)(register_t, register_t, register_t,
43 register_t);
44 uint32_t sc_psci_version;
45 uint32_t sc_system_off;
46 uint32_t sc_system_reset;
47 uint32_t sc_system_suspend;
48 uint32_t sc_cpu_on;
49 uint32_t sc_cpu_off;
50 uint32_t sc_cpu_suspend;
51
52 uint32_t sc_smccc_version;
53 uint32_t sc_method;
54 };
55
56 struct psci_softc *psci_sc;
57
58 int psci_match(struct device *, void *, void *);
59 void psci_attach(struct device *, struct device *, void *);
60 void psci_reset(void);
61 void psci_powerdown(void);
62
63 extern register_t hvc_call(register_t, register_t, register_t, register_t);
64 extern register_t smc_call(register_t, register_t, register_t, register_t);
65
66 int32_t smccc_version(void);
67 int32_t smccc_arch_features(uint32_t);
68
69 uint32_t psci_version(void);
70 int32_t psci_features(uint32_t);
71
72 const struct cfattach psci_ca = {
73 sizeof(struct psci_softc), psci_match, psci_attach
74 };
75
76 struct cfdriver psci_cd = {
77 NULL, "psci", DV_DULL
78 };
79
80 int
psci_match(struct device * parent,void * match,void * aux)81 psci_match(struct device *parent, void *match, void *aux)
82 {
83 struct fdt_attach_args *faa = aux;
84
85 return OF_is_compatible(faa->fa_node, "arm,psci") ||
86 OF_is_compatible(faa->fa_node, "arm,psci-0.2") ||
87 OF_is_compatible(faa->fa_node, "arm,psci-1.0");
88 }
89
90 void
psci_attach(struct device * parent,struct device * self,void * aux)91 psci_attach(struct device *parent, struct device *self, void *aux)
92 {
93 struct psci_softc *sc = (struct psci_softc *)self;
94 struct fdt_attach_args *faa = aux;
95 char method[128];
96 uint32_t version;
97
98 if (OF_getprop(faa->fa_node, "method", method, sizeof(method))) {
99 if (strcmp(method, "hvc") == 0) {
100 sc->sc_callfn = hvc_call;
101 sc->sc_method = PSCI_METHOD_HVC;
102 } else if (strcmp(method, "smc") == 0) {
103 sc->sc_callfn = smc_call;
104 sc->sc_method = PSCI_METHOD_SMC;
105 }
106 }
107
108 /*
109 * The function IDs are only to be parsed for the old specification
110 * (as in version 0.1). All newer implementations are supposed to
111 * use the specified values.
112 */
113 if (OF_is_compatible(faa->fa_node, "arm,psci-0.2") ||
114 OF_is_compatible(faa->fa_node, "arm,psci-1.0")) {
115 sc->sc_psci_version = PSCI_VERSION;
116 sc->sc_system_off = SYSTEM_OFF;
117 sc->sc_system_reset = SYSTEM_RESET;
118 sc->sc_cpu_on = CPU_ON;
119 sc->sc_cpu_off = CPU_OFF;
120 sc->sc_cpu_suspend = CPU_SUSPEND;
121 } else if (OF_is_compatible(faa->fa_node, "arm,psci")) {
122 sc->sc_system_off = OF_getpropint(faa->fa_node,
123 "system_off", 0);
124 sc->sc_system_reset = OF_getpropint(faa->fa_node,
125 "system_reset", 0);
126 sc->sc_cpu_on = OF_getpropint(faa->fa_node, "cpu_on", 0);
127 sc->sc_cpu_off = OF_getpropint(faa->fa_node, "cpu_off", 0);
128 sc->sc_cpu_suspend = OF_getpropint(faa->fa_node,
129 "cpu_suspend", 0);
130 }
131
132 psci_sc = sc;
133
134 version = psci_version();
135 printf(": PSCI %d.%d", version >> 16, version & 0xffff);
136
137 if (version >= 0x10000) {
138 if (psci_features(SMCCC_VERSION) == PSCI_SUCCESS) {
139 sc->sc_smccc_version = smccc_version();
140 printf(", SMCCC %d.%d", sc->sc_smccc_version >> 16,
141 sc->sc_smccc_version & 0xffff);
142 }
143 if (psci_features(SYSTEM_SUSPEND) == PSCI_SUCCESS) {
144 sc->sc_system_suspend = SYSTEM_SUSPEND;
145 printf(", SYSTEM_SUSPEND");
146 }
147 }
148
149 printf("\n");
150
151 if (sc->sc_system_off != 0)
152 powerdownfn = psci_powerdown;
153 if (sc->sc_system_reset != 0)
154 cpuresetfn = psci_reset;
155 }
156
157 void
psci_reset(void)158 psci_reset(void)
159 {
160 struct psci_softc *sc = psci_sc;
161
162 if (sc->sc_callfn)
163 (*sc->sc_callfn)(sc->sc_system_reset, 0, 0, 0);
164 }
165
166 void
psci_powerdown(void)167 psci_powerdown(void)
168 {
169 struct psci_softc *sc = psci_sc;
170
171 if (sc->sc_callfn)
172 (*sc->sc_callfn)(sc->sc_system_off, 0, 0, 0);
173 }
174
175 /*
176 * Firmware-based workaround for CVE-2017-5715. We determine whether
177 * the workaround is actually implemented and needed the first time we
178 * are invoked such that we only make the firmware call when appropriate.
179 */
180
181 void
psci_flush_bp_none(void)182 psci_flush_bp_none(void)
183 {
184 }
185
186 void
psci_flush_bp_smccc_arch_workaround_1(void)187 psci_flush_bp_smccc_arch_workaround_1(void)
188 {
189 struct psci_softc *sc = psci_sc;
190
191 (*sc->sc_callfn)(SMCCC_ARCH_WORKAROUND_1, 0, 0, 0);
192 }
193
194 void
psci_flush_bp(void)195 psci_flush_bp(void)
196 {
197 struct psci_softc *sc = psci_sc;
198 struct cpu_info *ci = curcpu();
199
200 /*
201 * SMCCC 1.1 allows us to detect if the workaround is
202 * implemented and needed.
203 */
204 if (sc && sc->sc_smccc_version >= 0x10001 &&
205 smccc_arch_features(SMCCC_ARCH_WORKAROUND_1) == 0) {
206 /* Workaround implemented and needed. */
207 ci->ci_flush_bp = psci_flush_bp_smccc_arch_workaround_1;
208 ci->ci_flush_bp();
209 } else {
210 /* Workaround isn't implemented or isn't needed. */
211 ci->ci_flush_bp = psci_flush_bp_none;
212 }
213 }
214
215 void
smccc_enable_arch_workaround_2(void)216 smccc_enable_arch_workaround_2(void)
217 {
218 struct psci_softc *sc = psci_sc;
219
220 /*
221 * SMCCC 1.1 allows us to detect if the workaround is
222 * implemented and needed.
223 */
224 if (sc && sc->sc_smccc_version >= 0x10001 &&
225 smccc_arch_features(SMCCC_ARCH_WORKAROUND_2) == 0) {
226 /* Workaround implemented and needed. */
227 (*sc->sc_callfn)(SMCCC_ARCH_WORKAROUND_2, 1, 0, 0);
228 }
229 }
230
231 int
smccc_needs_arch_workaround_3(void)232 smccc_needs_arch_workaround_3(void)
233 {
234 struct psci_softc *sc = psci_sc;
235
236 /*
237 * SMCCC 1.1 allows us to detect if the workaround is
238 * implemented and needed.
239 */
240 if (sc && sc->sc_smccc_version >= 0x10001 &&
241 smccc_arch_features(SMCCC_ARCH_WORKAROUND_3) == 0) {
242 /* Workaround implemented and needed. */
243 return 1;
244 }
245
246 return 0;
247 }
248
249 int32_t
smccc_version(void)250 smccc_version(void)
251 {
252 struct psci_softc *sc = psci_sc;
253 int32_t version;
254
255 KASSERT(sc && sc->sc_callfn);
256 version = (*sc->sc_callfn)(SMCCC_VERSION, 0, 0, 0);
257 if (version != PSCI_NOT_SUPPORTED)
258 return version;
259
260 /* Treat NOT_SUPPORTED as 1.0 */
261 return 0x10000;
262 }
263
264 int32_t
smccc(uint32_t func_id,register_t arg0,register_t arg1,register_t arg2)265 smccc(uint32_t func_id, register_t arg0, register_t arg1, register_t arg2)
266 {
267 struct psci_softc *sc = psci_sc;
268
269 if (sc && sc->sc_callfn)
270 return (*sc->sc_callfn)(func_id, arg0, arg1, arg2);
271
272 return PSCI_NOT_SUPPORTED;
273 }
274
275 int32_t
smccc_arch_features(uint32_t arch_func_id)276 smccc_arch_features(uint32_t arch_func_id)
277 {
278 struct psci_softc *sc = psci_sc;
279
280 KASSERT(sc && sc->sc_callfn);
281 return (*sc->sc_callfn)(SMCCC_ARCH_FEATURES, arch_func_id, 0, 0);
282 }
283
284 uint32_t
psci_version(void)285 psci_version(void)
286 {
287 struct psci_softc *sc = psci_sc;
288
289 if (sc && sc->sc_callfn && sc->sc_psci_version != 0)
290 return (*sc->sc_callfn)(sc->sc_psci_version, 0, 0, 0);
291
292 /* No version support; return 0.0. */
293 return 0;
294 }
295
296 int32_t
psci_system_suspend(register_t entry_point_address,register_t context_id)297 psci_system_suspend(register_t entry_point_address, register_t context_id)
298 {
299 struct psci_softc *sc = psci_sc;
300
301 if (sc && sc->sc_callfn && sc->sc_system_suspend != 0)
302 return (*sc->sc_callfn)(sc->sc_system_suspend,
303 entry_point_address, context_id, 0);
304
305 return PSCI_NOT_SUPPORTED;
306 }
307
308 int32_t
psci_cpu_off(void)309 psci_cpu_off(void)
310 {
311 struct psci_softc *sc = psci_sc;
312
313 if (sc && sc->sc_callfn && sc->sc_cpu_off != 0)
314 return (*sc->sc_callfn)(sc->sc_cpu_off, 0, 0, 0);
315
316 return PSCI_NOT_SUPPORTED;
317 }
318
319 int32_t
psci_cpu_on(register_t target_cpu,register_t entry_point_address,register_t context_id)320 psci_cpu_on(register_t target_cpu, register_t entry_point_address,
321 register_t context_id)
322 {
323 struct psci_softc *sc = psci_sc;
324
325 if (sc && sc->sc_callfn && sc->sc_cpu_on != 0)
326 return (*sc->sc_callfn)(sc->sc_cpu_on, target_cpu,
327 entry_point_address, context_id);
328
329 return PSCI_NOT_SUPPORTED;
330 }
331
332 int32_t
psci_cpu_suspend(register_t power_state,register_t entry_point_address,register_t context_id)333 psci_cpu_suspend(register_t power_state, register_t entry_point_address,
334 register_t context_id)
335 {
336 struct psci_softc *sc = psci_sc;
337
338 if (sc && sc->sc_callfn && sc->sc_cpu_suspend != 0)
339 return (*sc->sc_callfn)(sc->sc_cpu_suspend, power_state,
340 entry_point_address, context_id);
341
342 return PSCI_NOT_SUPPORTED;
343 }
344
345 int32_t
psci_features(uint32_t psci_func_id)346 psci_features(uint32_t psci_func_id)
347 {
348 struct psci_softc *sc = psci_sc;
349
350 if (sc && sc->sc_callfn)
351 return (*sc->sc_callfn)(PSCI_FEATURES, psci_func_id, 0, 0);
352
353 return PSCI_NOT_SUPPORTED;
354 }
355
356 int
psci_can_suspend(void)357 psci_can_suspend(void)
358 {
359 struct psci_softc *sc = psci_sc;
360
361 return (sc && sc->sc_system_suspend != 0);
362 }
363
364 int
psci_method(void)365 psci_method(void)
366 {
367 struct psci_softc *sc = psci_sc;
368
369 return sc ? sc->sc_method : PSCI_METHOD_NONE;
370 }
371