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