xref: /openbsd-src/sys/dev/fdt/psci.c (revision 8feacbfab8bc311c7e89273407c37e3fe2b0c6eb)
1 /*	$OpenBSD: psci.c,v 1.16 2024/04/13 14:20:48 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 #define PSCI_VERSION		0x84000000
41 #ifdef __LP64__
42 #define CPU_SUSPEND		0xc4000001
43 #else
44 #define CPU_SUSPEND		0x84000001
45 #endif
46 #define CPU_OFF			0x84000002
47 #ifdef __LP64__
48 #define CPU_ON			0xc4000003
49 #else
50 #define CPU_ON			0x84000003
51 #endif
52 #define SYSTEM_OFF		0x84000008
53 #define SYSTEM_RESET		0x84000009
54 #define PSCI_FEATURES		0x8400000a
55 #ifdef __LP64__
56 #define SYSTEM_SUSPEND		0xc400000e
57 #else
58 #define SYSTEM_SUSPEND		0x8400000e
59 #endif
60 
61 struct psci_softc {
62 	struct device	 sc_dev;
63 	register_t	 (*sc_callfn)(register_t, register_t, register_t,
64 			     register_t);
65 	uint32_t	 sc_psci_version;
66 	uint32_t	 sc_system_off;
67 	uint32_t	 sc_system_reset;
68 	uint32_t	 sc_system_suspend;
69 	uint32_t	 sc_cpu_on;
70 	uint32_t	 sc_cpu_off;
71 	uint32_t	 sc_cpu_suspend;
72 
73 	uint32_t	 sc_smccc_version;
74 	uint32_t	 sc_method;
75 };
76 
77 struct psci_softc *psci_sc;
78 
79 int	psci_match(struct device *, void *, void *);
80 void	psci_attach(struct device *, struct device *, void *);
81 void	psci_reset(void);
82 void	psci_powerdown(void);
83 
84 extern register_t hvc_call(register_t, register_t, register_t, register_t);
85 extern register_t smc_call(register_t, register_t, register_t, register_t);
86 
87 int32_t smccc_version(void);
88 int32_t smccc_arch_features(uint32_t);
89 
90 uint32_t psci_version(void);
91 int32_t psci_features(uint32_t);
92 
93 const struct cfattach psci_ca = {
94 	sizeof(struct psci_softc), psci_match, psci_attach
95 };
96 
97 struct cfdriver psci_cd = {
98 	NULL, "psci", DV_DULL
99 };
100 
101 int
102 psci_match(struct device *parent, void *match, void *aux)
103 {
104 	struct fdt_attach_args *faa = aux;
105 
106 	return OF_is_compatible(faa->fa_node, "arm,psci") ||
107 	    OF_is_compatible(faa->fa_node, "arm,psci-0.2") ||
108 	    OF_is_compatible(faa->fa_node, "arm,psci-1.0");
109 }
110 
111 void
112 psci_attach(struct device *parent, struct device *self, void *aux)
113 {
114 	struct psci_softc *sc = (struct psci_softc *)self;
115 	struct fdt_attach_args *faa = aux;
116 	char method[128];
117 	uint32_t version;
118 
119 	if (OF_getprop(faa->fa_node, "method", method, sizeof(method))) {
120 		if (strcmp(method, "hvc") == 0) {
121 			sc->sc_callfn = hvc_call;
122 			sc->sc_method = PSCI_METHOD_HVC;
123 		} else if (strcmp(method, "smc") == 0) {
124 			sc->sc_callfn = smc_call;
125 			sc->sc_method = PSCI_METHOD_SMC;
126 		}
127 	}
128 
129 	/*
130 	 * The function IDs are only to be parsed for the old specification
131 	 * (as in version 0.1).  All newer implementations are supposed to
132 	 * use the specified values.
133 	 */
134 	if (OF_is_compatible(faa->fa_node, "arm,psci-0.2") ||
135 	    OF_is_compatible(faa->fa_node, "arm,psci-1.0")) {
136 		sc->sc_psci_version = PSCI_VERSION;
137 		sc->sc_system_off = SYSTEM_OFF;
138 		sc->sc_system_reset = SYSTEM_RESET;
139 		sc->sc_cpu_on = CPU_ON;
140 		sc->sc_cpu_off = CPU_OFF;
141 		sc->sc_cpu_suspend = CPU_SUSPEND;
142 	} else if (OF_is_compatible(faa->fa_node, "arm,psci")) {
143 		sc->sc_system_off = OF_getpropint(faa->fa_node,
144 		    "system_off", 0);
145 		sc->sc_system_reset = OF_getpropint(faa->fa_node,
146 		    "system_reset", 0);
147 		sc->sc_cpu_on = OF_getpropint(faa->fa_node, "cpu_on", 0);
148 		sc->sc_cpu_off = OF_getpropint(faa->fa_node, "cpu_off", 0);
149 		sc->sc_cpu_suspend = OF_getpropint(faa->fa_node,
150 		    "cpu_suspend", 0);
151 	}
152 
153 	psci_sc = sc;
154 
155 	version = psci_version();
156 	printf(": PSCI %d.%d", version >> 16, version & 0xffff);
157 
158 	if (version >= 0x10000) {
159 		if (psci_features(SMCCC_VERSION) == PSCI_SUCCESS) {
160 			sc->sc_smccc_version = smccc_version();
161 			printf(", SMCCC %d.%d", sc->sc_smccc_version >> 16,
162 			    sc->sc_smccc_version & 0xffff);
163 		}
164 		if (psci_features(SYSTEM_SUSPEND) == PSCI_SUCCESS) {
165 			sc->sc_system_suspend = SYSTEM_SUSPEND;
166 			printf(", SYSTEM_SUSPEND");
167 		}
168 	}
169 
170 	printf("\n");
171 
172 	if (sc->sc_system_off != 0)
173 		powerdownfn = psci_powerdown;
174 	if (sc->sc_system_reset != 0)
175 		cpuresetfn = psci_reset;
176 }
177 
178 void
179 psci_reset(void)
180 {
181 	struct psci_softc *sc = psci_sc;
182 
183 	if (sc->sc_callfn)
184 		(*sc->sc_callfn)(sc->sc_system_reset, 0, 0, 0);
185 }
186 
187 void
188 psci_powerdown(void)
189 {
190 	struct psci_softc *sc = psci_sc;
191 
192 	if (sc->sc_callfn)
193 		(*sc->sc_callfn)(sc->sc_system_off, 0, 0, 0);
194 }
195 
196 /*
197  * Firmware-based workaround for CVE-2017-5715.  We determine whether
198  * the workaround is actually implemented and needed the first time we
199  * are invoked such that we only make the firmware call when appropriate.
200  */
201 
202 void
203 psci_flush_bp_none(void)
204 {
205 }
206 
207 void
208 psci_flush_bp_smccc_arch_workaround_1(void)
209 {
210 	struct psci_softc *sc = psci_sc;
211 
212 	(*sc->sc_callfn)(SMCCC_ARCH_WORKAROUND_1, 0, 0, 0);
213 }
214 
215 void
216 psci_flush_bp(void)
217 {
218 	struct psci_softc *sc = psci_sc;
219 	struct cpu_info *ci = curcpu();
220 
221 	/*
222 	 * SMCCC 1.1 allows us to detect if the workaround is
223 	 * implemented and needed.
224 	 */
225 	if (sc && sc->sc_smccc_version >= 0x10001 &&
226 	    smccc_arch_features(SMCCC_ARCH_WORKAROUND_1) == 0) {
227 		/* Workaround implemented and needed. */
228 		ci->ci_flush_bp = psci_flush_bp_smccc_arch_workaround_1;
229 		ci->ci_flush_bp();
230 	} else {
231 		/* Workaround isn't implemented or isn't needed. */
232 		ci->ci_flush_bp = psci_flush_bp_none;
233 	}
234 }
235 
236 void
237 smccc_enable_arch_workaround_2(void)
238 {
239 	struct psci_softc *sc = psci_sc;
240 
241 	/*
242 	 * SMCCC 1.1 allows us to detect if the workaround is
243 	 * implemented and needed.
244 	 */
245 	if (sc && sc->sc_smccc_version >= 0x10001 &&
246 	    smccc_arch_features(SMCCC_ARCH_WORKAROUND_2) == 0) {
247 		/* Workaround implemented and needed. */
248 		(*sc->sc_callfn)(SMCCC_ARCH_WORKAROUND_2, 1, 0, 0);
249 	}
250 }
251 
252 int
253 smccc_needs_arch_workaround_3(void)
254 {
255 	struct psci_softc *sc = psci_sc;
256 
257 	/*
258 	 * SMCCC 1.1 allows us to detect if the workaround is
259 	 * implemented and needed.
260 	 */
261 	if (sc && sc->sc_smccc_version >= 0x10001 &&
262 	    smccc_arch_features(SMCCC_ARCH_WORKAROUND_3) == 0) {
263 		/* Workaround implemented and needed. */
264 		return 1;
265 	}
266 
267 	return 0;
268 }
269 
270 int32_t
271 smccc_version(void)
272 {
273 	struct psci_softc *sc = psci_sc;
274 	int32_t version;
275 
276 	KASSERT(sc && sc->sc_callfn);
277 	version = (*sc->sc_callfn)(SMCCC_VERSION, 0, 0, 0);
278 	if (version != PSCI_NOT_SUPPORTED)
279 		return version;
280 
281 	/* Treat NOT_SUPPORTED as 1.0 */
282 	return 0x10000;
283 }
284 
285 int32_t
286 smccc(uint32_t func_id, register_t arg0, register_t arg1, register_t arg2)
287 {
288 	struct psci_softc *sc = psci_sc;
289 
290 	if (sc && sc->sc_callfn)
291 		return (*sc->sc_callfn)(func_id, arg0, arg1, arg2);
292 
293 	return PSCI_NOT_SUPPORTED;
294 }
295 
296 int32_t
297 smccc_arch_features(uint32_t arch_func_id)
298 {
299 	struct psci_softc *sc = psci_sc;
300 
301 	KASSERT(sc && sc->sc_callfn);
302 	return (*sc->sc_callfn)(SMCCC_ARCH_FEATURES, arch_func_id, 0, 0);
303 }
304 
305 uint32_t
306 psci_version(void)
307 {
308 	struct psci_softc *sc = psci_sc;
309 
310 	if (sc && sc->sc_callfn && sc->sc_psci_version != 0)
311 		return (*sc->sc_callfn)(sc->sc_psci_version, 0, 0, 0);
312 
313 	/* No version support; return 0.0. */
314 	return 0;
315 }
316 
317 int32_t
318 psci_system_suspend(register_t entry_point_address, register_t context_id)
319 {
320 	struct psci_softc *sc = psci_sc;
321 
322 	if (sc && sc->sc_callfn && sc->sc_system_suspend != 0)
323 		return (*sc->sc_callfn)(sc->sc_system_suspend,
324 		    entry_point_address, context_id, 0);
325 
326 	return PSCI_NOT_SUPPORTED;
327 }
328 
329 int32_t
330 psci_cpu_off(void)
331 {
332 	struct psci_softc *sc = psci_sc;
333 
334 	if (sc && sc->sc_callfn && sc->sc_cpu_off != 0)
335 		return (*sc->sc_callfn)(sc->sc_cpu_off, 0, 0, 0);
336 
337 	return PSCI_NOT_SUPPORTED;
338 }
339 
340 int32_t
341 psci_cpu_on(register_t target_cpu, register_t entry_point_address,
342     register_t context_id)
343 {
344 	struct psci_softc *sc = psci_sc;
345 
346 	if (sc && sc->sc_callfn && sc->sc_cpu_on != 0)
347 		return (*sc->sc_callfn)(sc->sc_cpu_on, target_cpu,
348 		    entry_point_address, context_id);
349 
350 	return PSCI_NOT_SUPPORTED;
351 }
352 
353 int32_t
354 psci_cpu_suspend(register_t power_state, register_t entry_point_address,
355     register_t context_id)
356 {
357 	struct psci_softc *sc = psci_sc;
358 
359 	if (sc && sc->sc_callfn && sc->sc_cpu_suspend != 0)
360 		return (*sc->sc_callfn)(sc->sc_cpu_suspend, power_state,
361 		    entry_point_address, context_id);
362 
363 	return PSCI_NOT_SUPPORTED;
364 }
365 
366 int32_t
367 psci_features(uint32_t psci_func_id)
368 {
369 	struct psci_softc *sc = psci_sc;
370 
371 	if (sc && sc->sc_callfn)
372 		return (*sc->sc_callfn)(PSCI_FEATURES, psci_func_id, 0, 0);
373 
374 	return PSCI_NOT_SUPPORTED;
375 }
376 
377 int
378 psci_can_suspend(void)
379 {
380 	struct psci_softc *sc = psci_sc;
381 
382 	return (sc && sc->sc_system_suspend != 0);
383 }
384 
385 int
386 psci_method(void)
387 {
388 	struct psci_softc *sc = psci_sc;
389 
390 	return sc ? sc->sc_method : PSCI_METHOD_NONE;
391 }
392