xref: /openbsd-src/sys/dev/fdt/psci.c (revision 99fd087599a8791921855f21bd7e36130f39aadc)
1 /*	$OpenBSD: psci.c,v 1.8 2018/05/23 09:12:34 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_smccc_version;
58 };
59 
60 struct psci_softc *psci_sc;
61 
62 int	psci_match(struct device *, void *, void *);
63 void	psci_attach(struct device *, struct device *, void *);
64 void	psci_reset(void);
65 void	psci_powerdown(void);
66 
67 extern register_t hvc_call(register_t, register_t, register_t, register_t);
68 extern register_t smc_call(register_t, register_t, register_t, register_t);
69 
70 int32_t smccc_version(void);
71 int32_t smccc_arch_features(uint32_t);
72 
73 uint32_t psci_version(void);
74 int32_t psci_features(uint32_t);
75 
76 struct cfattach psci_ca = {
77 	sizeof(struct psci_softc), psci_match, psci_attach
78 };
79 
80 struct cfdriver psci_cd = {
81 	NULL, "psci", DV_DULL
82 };
83 
84 int
85 psci_match(struct device *parent, void *match, void *aux)
86 {
87 	struct fdt_attach_args *faa = aux;
88 
89 	return OF_is_compatible(faa->fa_node, "arm,psci") ||
90 	    OF_is_compatible(faa->fa_node, "arm,psci-0.2") ||
91 	    OF_is_compatible(faa->fa_node, "arm,psci-1.0");
92 }
93 
94 void
95 psci_attach(struct device *parent, struct device *self, void *aux)
96 {
97 	struct psci_softc *sc = (struct psci_softc *)self;
98 	struct fdt_attach_args *faa = aux;
99 	char method[128];
100 	uint32_t version;
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 	version = psci_version();
131 	printf(": PSCI %d.%d", version >> 16, version & 0xffff);
132 
133 	if (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 determine whether
169  * the workaround is actually implemented and needed the first time we
170  * are invoked such that we only make the firmware call when appropriate.
171  */
172 
173 void
174 psci_flush_bp_none(void)
175 {
176 }
177 
178 void
179 psci_flush_bp_smccc_arch_workaround_1(void)
180 {
181 	struct psci_softc *sc = psci_sc;
182 
183 	(*sc->sc_callfn)(SMCCC_ARCH_WORKAROUND_1, 0, 0, 0);
184 }
185 
186 void
187 psci_flush_bp(void)
188 {
189 	struct psci_softc *sc = psci_sc;
190 	struct cpu_info *ci = curcpu();
191 
192 	/*
193 	 * SMCCC 1.1 allows us to detect if the workaround is
194 	 * implemented and needed.
195 	 */
196 	if (sc && sc->sc_smccc_version >= 0x10001 &&
197 	    smccc_arch_features(SMCCC_ARCH_WORKAROUND_1) == 0) {
198 		/* Workaround implemented and needed. */
199 		ci->ci_flush_bp = psci_flush_bp_smccc_arch_workaround_1;
200 		ci->ci_flush_bp();
201 	} else {
202 		/* Workaround isn't implemented or isn't needed. */
203 		ci->ci_flush_bp = psci_flush_bp_none;
204 	}
205 }
206 
207 int32_t
208 smccc_version(void)
209 {
210 	struct psci_softc *sc = psci_sc;
211 
212 	if (sc && sc->sc_callfn)
213 		return (*sc->sc_callfn)(SMCCC_VERSION, 0, 0, 0);
214 
215 	return PSCI_NOT_SUPPORTED;
216 }
217 
218 int32_t
219 smccc_arch_features(uint32_t arch_func_id)
220 {
221 	struct psci_softc *sc = psci_sc;
222 
223 	if (sc && sc->sc_callfn)
224 		return (*sc->sc_callfn)(SMCCC_ARCH_FEATURES, arch_func_id, 0, 0);
225 
226 	return PSCI_NOT_SUPPORTED;
227 }
228 
229 uint32_t
230 psci_version(void)
231 {
232 	struct psci_softc *sc = psci_sc;
233 
234 	if (sc && sc->sc_callfn && sc->sc_psci_version != 0)
235 		return (*sc->sc_callfn)(sc->sc_psci_version, 0, 0, 0);
236 
237 	/* No version support; return 0.0. */
238 	return 0;
239 }
240 
241 int32_t
242 psci_cpu_on(register_t target_cpu, register_t entry_point_address,
243     register_t context_id)
244 {
245 	struct psci_softc *sc = psci_sc;
246 
247 	if (sc && sc->sc_callfn && sc->sc_cpu_on != 0)
248 		return (*sc->sc_callfn)(sc->sc_cpu_on, target_cpu,
249 		    entry_point_address, context_id);
250 
251 	return PSCI_NOT_SUPPORTED;
252 }
253 
254 int32_t
255 psci_features(uint32_t psci_func_id)
256 {
257 	struct psci_softc *sc = psci_sc;
258 
259 	if (sc && sc->sc_callfn)
260 		return (*sc->sc_callfn)(PSCI_FEATURES, psci_func_id, 0, 0);
261 
262 	return PSCI_NOT_SUPPORTED;
263 }
264