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