xref: /openbsd/sys/dev/fdt/psci.c (revision 07eca602)
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