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