xref: /openbsd/sys/dev/fdt/psci.c (revision d89ec533)
1 /*	$OpenBSD: psci.c,v 1.10 2021/10/24 17:52:26 mpi 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 const 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 	int32_t version;
212 
213 	KASSERT(sc && sc->sc_callfn);
214 	version = (*sc->sc_callfn)(SMCCC_VERSION, 0, 0, 0);
215 	if (version != PSCI_NOT_SUPPORTED)
216 		return version;
217 
218 	/* Treat NOT_SUPPORTED as 1.0 */
219 	return 0x10000;
220 }
221 
222 int32_t
223 smccc_arch_features(uint32_t arch_func_id)
224 {
225 	struct psci_softc *sc = psci_sc;
226 
227 	KASSERT(sc && sc->sc_callfn);
228 	return (*sc->sc_callfn)(SMCCC_ARCH_FEATURES, arch_func_id, 0, 0);
229 }
230 
231 uint32_t
232 psci_version(void)
233 {
234 	struct psci_softc *sc = psci_sc;
235 
236 	if (sc && sc->sc_callfn && sc->sc_psci_version != 0)
237 		return (*sc->sc_callfn)(sc->sc_psci_version, 0, 0, 0);
238 
239 	/* No version support; return 0.0. */
240 	return 0;
241 }
242 
243 int32_t
244 psci_cpu_on(register_t target_cpu, register_t entry_point_address,
245     register_t context_id)
246 {
247 	struct psci_softc *sc = psci_sc;
248 
249 	if (sc && sc->sc_callfn && sc->sc_cpu_on != 0)
250 		return (*sc->sc_callfn)(sc->sc_cpu_on, target_cpu,
251 		    entry_point_address, context_id);
252 
253 	return PSCI_NOT_SUPPORTED;
254 }
255 
256 int32_t
257 psci_features(uint32_t psci_func_id)
258 {
259 	struct psci_softc *sc = psci_sc;
260 
261 	if (sc && sc->sc_callfn)
262 		return (*sc->sc_callfn)(PSCI_FEATURES, psci_func_id, 0, 0);
263 
264 	return PSCI_NOT_SUPPORTED;
265 }
266