xref: /openbsd-src/sys/dev/fdt/psci.c (revision 07eca60229dc516417bb980ffa9ffd5220e0c9f5)
1*07eca602Skettenis /*	$OpenBSD: psci.c,v 1.17 2024/07/10 11:01:24 kettenis Exp $	*/
24406bb04Sjsg 
34406bb04Sjsg /*
44406bb04Sjsg  * Copyright (c) 2016 Jonathan Gray <jsg@openbsd.org>
54406bb04Sjsg  *
64406bb04Sjsg  * Permission to use, copy, modify, and distribute this software for any
74406bb04Sjsg  * purpose with or without fee is hereby granted, provided that the above
84406bb04Sjsg  * copyright notice and this permission notice appear in all copies.
94406bb04Sjsg  *
104406bb04Sjsg  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
114406bb04Sjsg  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
124406bb04Sjsg  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
134406bb04Sjsg  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
144406bb04Sjsg  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
154406bb04Sjsg  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
164406bb04Sjsg  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
174406bb04Sjsg  */
184406bb04Sjsg 
194406bb04Sjsg #include <sys/param.h>
204406bb04Sjsg #include <sys/device.h>
214406bb04Sjsg #include <sys/systm.h>
224406bb04Sjsg 
234406bb04Sjsg #include <machine/bus.h>
244406bb04Sjsg #include <machine/fdt.h>
254406bb04Sjsg 
264406bb04Sjsg #include <dev/ofw/openfirm.h>
274406bb04Sjsg #include <dev/ofw/fdt.h>
284406bb04Sjsg 
2988672784Skettenis #include <dev/fdt/pscivar.h>
3088672784Skettenis 
314406bb04Sjsg extern void (*cpuresetfn)(void);
324406bb04Sjsg extern void (*powerdownfn)(void);
334406bb04Sjsg 
34cdcef956Skettenis #define SMCCC_VERSION		0x80000000
35cdcef956Skettenis #define SMCCC_ARCH_FEATURES	0x80000001
36cdcef956Skettenis #define SMCCC_ARCH_WORKAROUND_1	0x80008000
378291c3f1Skettenis #define SMCCC_ARCH_WORKAROUND_2	0x80007fff
3816569a75Spatrick #define SMCCC_ARCH_WORKAROUND_3	0x80003fff
39cdcef956Skettenis 
404406bb04Sjsg struct psci_softc {
414406bb04Sjsg 	struct device	 sc_dev;
428bcbf207Skettenis 	register_t	 (*sc_callfn)(register_t, register_t, register_t,
438bcbf207Skettenis 			     register_t);
445f9962a9Sjsg 	uint32_t	 sc_psci_version;
455f9962a9Sjsg 	uint32_t	 sc_system_off;
465f9962a9Sjsg 	uint32_t	 sc_system_reset;
47149f7d98Skettenis 	uint32_t	 sc_system_suspend;
485f9962a9Sjsg 	uint32_t	 sc_cpu_on;
49149f7d98Skettenis 	uint32_t	 sc_cpu_off;
505f4ad52dSkettenis 	uint32_t	 sc_cpu_suspend;
51cdcef956Skettenis 
52cdcef956Skettenis 	uint32_t	 sc_smccc_version;
5316569a75Spatrick 	uint32_t	 sc_method;
544406bb04Sjsg };
554406bb04Sjsg 
564406bb04Sjsg struct psci_softc *psci_sc;
574406bb04Sjsg 
584406bb04Sjsg int	psci_match(struct device *, void *, void *);
594406bb04Sjsg void	psci_attach(struct device *, struct device *, void *);
604406bb04Sjsg void	psci_reset(void);
614406bb04Sjsg void	psci_powerdown(void);
624406bb04Sjsg 
638bcbf207Skettenis extern register_t hvc_call(register_t, register_t, register_t, register_t);
648bcbf207Skettenis extern register_t smc_call(register_t, register_t, register_t, register_t);
654406bb04Sjsg 
66cdcef956Skettenis int32_t smccc_version(void);
67cdcef956Skettenis int32_t smccc_arch_features(uint32_t);
68cdcef956Skettenis 
69cdcef956Skettenis uint32_t psci_version(void);
70cdcef956Skettenis int32_t psci_features(uint32_t);
71cdcef956Skettenis 
729fdf0c62Smpi const struct cfattach psci_ca = {
734406bb04Sjsg 	sizeof(struct psci_softc), psci_match, psci_attach
744406bb04Sjsg };
754406bb04Sjsg 
764406bb04Sjsg struct cfdriver psci_cd = {
774406bb04Sjsg 	NULL, "psci", DV_DULL
784406bb04Sjsg };
794406bb04Sjsg 
804406bb04Sjsg int
psci_match(struct device * parent,void * match,void * aux)814406bb04Sjsg psci_match(struct device *parent, void *match, void *aux)
824406bb04Sjsg {
834406bb04Sjsg 	struct fdt_attach_args *faa = aux;
844406bb04Sjsg 
85a60283c7Spatrick 	return OF_is_compatible(faa->fa_node, "arm,psci") ||
86a60283c7Spatrick 	    OF_is_compatible(faa->fa_node, "arm,psci-0.2") ||
87a60283c7Spatrick 	    OF_is_compatible(faa->fa_node, "arm,psci-1.0");
884406bb04Sjsg }
894406bb04Sjsg 
904406bb04Sjsg void
psci_attach(struct device * parent,struct device * self,void * aux)914406bb04Sjsg psci_attach(struct device *parent, struct device *self, void *aux)
924406bb04Sjsg {
934406bb04Sjsg 	struct psci_softc *sc = (struct psci_softc *)self;
944406bb04Sjsg 	struct fdt_attach_args *faa = aux;
954406bb04Sjsg 	char method[128];
962d5eadd6Skettenis 	uint32_t version;
974406bb04Sjsg 
984406bb04Sjsg 	if (OF_getprop(faa->fa_node, "method", method, sizeof(method))) {
9916569a75Spatrick 		if (strcmp(method, "hvc") == 0) {
1008bcbf207Skettenis 			sc->sc_callfn = hvc_call;
10116569a75Spatrick 			sc->sc_method = PSCI_METHOD_HVC;
10216569a75Spatrick 		} else if (strcmp(method, "smc") == 0) {
1038bcbf207Skettenis 			sc->sc_callfn = smc_call;
10416569a75Spatrick 			sc->sc_method = PSCI_METHOD_SMC;
10516569a75Spatrick 		}
1064406bb04Sjsg 	}
1074406bb04Sjsg 
108a60283c7Spatrick 	/*
109a60283c7Spatrick 	 * The function IDs are only to be parsed for the old specification
110a60283c7Spatrick 	 * (as in version 0.1).  All newer implementations are supposed to
111a60283c7Spatrick 	 * use the specified values.
112a60283c7Spatrick 	 */
113a60283c7Spatrick 	if (OF_is_compatible(faa->fa_node, "arm,psci-0.2") ||
114a60283c7Spatrick 	    OF_is_compatible(faa->fa_node, "arm,psci-1.0")) {
11588672784Skettenis 		sc->sc_psci_version = PSCI_VERSION;
116a60283c7Spatrick 		sc->sc_system_off = SYSTEM_OFF;
117a60283c7Spatrick 		sc->sc_system_reset = SYSTEM_RESET;
1188bcbf207Skettenis 		sc->sc_cpu_on = CPU_ON;
119149f7d98Skettenis 		sc->sc_cpu_off = CPU_OFF;
1205f4ad52dSkettenis 		sc->sc_cpu_suspend = CPU_SUSPEND;
121a60283c7Spatrick 	} else if (OF_is_compatible(faa->fa_node, "arm,psci")) {
122a60283c7Spatrick 		sc->sc_system_off = OF_getpropint(faa->fa_node,
123a60283c7Spatrick 		    "system_off", 0);
124a60283c7Spatrick 		sc->sc_system_reset = OF_getpropint(faa->fa_node,
125a60283c7Spatrick 		    "system_reset", 0);
1268bcbf207Skettenis 		sc->sc_cpu_on = OF_getpropint(faa->fa_node, "cpu_on", 0);
127149f7d98Skettenis 		sc->sc_cpu_off = OF_getpropint(faa->fa_node, "cpu_off", 0);
1285f4ad52dSkettenis 		sc->sc_cpu_suspend = OF_getpropint(faa->fa_node,
1295f4ad52dSkettenis 		    "cpu_suspend", 0);
130a60283c7Spatrick 	}
131a60283c7Spatrick 
1324406bb04Sjsg 	psci_sc = sc;
13388672784Skettenis 
1342d5eadd6Skettenis 	version = psci_version();
1352d5eadd6Skettenis 	printf(": PSCI %d.%d", version >> 16, version & 0xffff);
136cdcef956Skettenis 
1372d5eadd6Skettenis 	if (version >= 0x10000) {
138cdcef956Skettenis 		if (psci_features(SMCCC_VERSION) == PSCI_SUCCESS) {
139cdcef956Skettenis 			sc->sc_smccc_version = smccc_version();
140cdcef956Skettenis 			printf(", SMCCC %d.%d", sc->sc_smccc_version >> 16,
141cdcef956Skettenis 			    sc->sc_smccc_version & 0xffff);
142cdcef956Skettenis 		}
143149f7d98Skettenis 		if (psci_features(SYSTEM_SUSPEND) == PSCI_SUCCESS) {
144149f7d98Skettenis 			sc->sc_system_suspend = SYSTEM_SUSPEND;
145149f7d98Skettenis 			printf(", SYSTEM_SUSPEND");
146149f7d98Skettenis 		}
147cdcef956Skettenis 	}
148cdcef956Skettenis 
149cdcef956Skettenis 	printf("\n");
15088672784Skettenis 
151a60283c7Spatrick 	if (sc->sc_system_off != 0)
1524406bb04Sjsg 		powerdownfn = psci_powerdown;
153a60283c7Spatrick 	if (sc->sc_system_reset != 0)
154a60283c7Spatrick 		cpuresetfn = psci_reset;
1554406bb04Sjsg }
1564406bb04Sjsg 
157cdcef956Skettenis void
psci_reset(void)158cdcef956Skettenis psci_reset(void)
159cdcef956Skettenis {
160cdcef956Skettenis 	struct psci_softc *sc = psci_sc;
161cdcef956Skettenis 
162cdcef956Skettenis 	if (sc->sc_callfn)
163cdcef956Skettenis 		(*sc->sc_callfn)(sc->sc_system_reset, 0, 0, 0);
164cdcef956Skettenis }
165cdcef956Skettenis 
166cdcef956Skettenis void
psci_powerdown(void)167cdcef956Skettenis psci_powerdown(void)
168cdcef956Skettenis {
169cdcef956Skettenis 	struct psci_softc *sc = psci_sc;
170cdcef956Skettenis 
171cdcef956Skettenis 	if (sc->sc_callfn)
172cdcef956Skettenis 		(*sc->sc_callfn)(sc->sc_system_off, 0, 0, 0);
173cdcef956Skettenis }
174cdcef956Skettenis 
175cdcef956Skettenis /*
1762d5eadd6Skettenis  * Firmware-based workaround for CVE-2017-5715.  We determine whether
1772d5eadd6Skettenis  * the workaround is actually implemented and needed the first time we
1782d5eadd6Skettenis  * are invoked such that we only make the firmware call when appropriate.
179cdcef956Skettenis  */
180cdcef956Skettenis 
181cdcef956Skettenis void
psci_flush_bp_none(void)182cdcef956Skettenis psci_flush_bp_none(void)
183cdcef956Skettenis {
184cdcef956Skettenis }
185cdcef956Skettenis 
186cdcef956Skettenis void
psci_flush_bp_smccc_arch_workaround_1(void)187cdcef956Skettenis psci_flush_bp_smccc_arch_workaround_1(void)
188cdcef956Skettenis {
189cdcef956Skettenis 	struct psci_softc *sc = psci_sc;
190cdcef956Skettenis 
191cdcef956Skettenis 	(*sc->sc_callfn)(SMCCC_ARCH_WORKAROUND_1, 0, 0, 0);
192cdcef956Skettenis }
193cdcef956Skettenis 
194cdcef956Skettenis void
psci_flush_bp(void)195cdcef956Skettenis psci_flush_bp(void)
196cdcef956Skettenis {
197cdcef956Skettenis 	struct psci_softc *sc = psci_sc;
198cdcef956Skettenis 	struct cpu_info *ci = curcpu();
199cdcef956Skettenis 
200cdcef956Skettenis 	/*
2012d5eadd6Skettenis 	 * SMCCC 1.1 allows us to detect if the workaround is
2022d5eadd6Skettenis 	 * implemented and needed.
203cdcef956Skettenis 	 */
2042d5eadd6Skettenis 	if (sc && sc->sc_smccc_version >= 0x10001 &&
2052d5eadd6Skettenis 	    smccc_arch_features(SMCCC_ARCH_WORKAROUND_1) == 0) {
206cdcef956Skettenis 		/* Workaround implemented and needed. */
207cdcef956Skettenis 		ci->ci_flush_bp = psci_flush_bp_smccc_arch_workaround_1;
208cdcef956Skettenis 		ci->ci_flush_bp();
209cdcef956Skettenis 	} else {
2102d5eadd6Skettenis 		/* Workaround isn't implemented or isn't needed. */
211cdcef956Skettenis 		ci->ci_flush_bp = psci_flush_bp_none;
212cdcef956Skettenis 	}
213cdcef956Skettenis }
214cdcef956Skettenis 
2158291c3f1Skettenis void
smccc_enable_arch_workaround_2(void)2168291c3f1Skettenis smccc_enable_arch_workaround_2(void)
2178291c3f1Skettenis {
2188291c3f1Skettenis 	struct psci_softc *sc = psci_sc;
2198291c3f1Skettenis 
2208291c3f1Skettenis 	/*
2218291c3f1Skettenis 	 * SMCCC 1.1 allows us to detect if the workaround is
2228291c3f1Skettenis 	 * implemented and needed.
2238291c3f1Skettenis 	 */
2248291c3f1Skettenis 	if (sc && sc->sc_smccc_version >= 0x10001 &&
2258291c3f1Skettenis 	    smccc_arch_features(SMCCC_ARCH_WORKAROUND_2) == 0) {
2268291c3f1Skettenis 		/* Workaround implemented and needed. */
2278291c3f1Skettenis 		(*sc->sc_callfn)(SMCCC_ARCH_WORKAROUND_2, 1, 0, 0);
2288291c3f1Skettenis 	}
2298291c3f1Skettenis }
2308291c3f1Skettenis 
23116569a75Spatrick int
smccc_needs_arch_workaround_3(void)2328291c3f1Skettenis smccc_needs_arch_workaround_3(void)
23316569a75Spatrick {
23416569a75Spatrick 	struct psci_softc *sc = psci_sc;
23516569a75Spatrick 
23616569a75Spatrick 	/*
23716569a75Spatrick 	 * SMCCC 1.1 allows us to detect if the workaround is
23816569a75Spatrick 	 * implemented and needed.
23916569a75Spatrick 	 */
24016569a75Spatrick 	if (sc && sc->sc_smccc_version >= 0x10001 &&
24116569a75Spatrick 	    smccc_arch_features(SMCCC_ARCH_WORKAROUND_3) == 0) {
24216569a75Spatrick 		/* Workaround implemented and needed. */
24316569a75Spatrick 		return 1;
24416569a75Spatrick 	}
24516569a75Spatrick 
24616569a75Spatrick 	return 0;
24716569a75Spatrick }
24816569a75Spatrick 
249cdcef956Skettenis int32_t
smccc_version(void)250cdcef956Skettenis smccc_version(void)
251cdcef956Skettenis {
252cdcef956Skettenis 	struct psci_softc *sc = psci_sc;
253fb4391c8Skettenis 	int32_t version;
254cdcef956Skettenis 
255fb4391c8Skettenis 	KASSERT(sc && sc->sc_callfn);
256fb4391c8Skettenis 	version = (*sc->sc_callfn)(SMCCC_VERSION, 0, 0, 0);
257fb4391c8Skettenis 	if (version != PSCI_NOT_SUPPORTED)
258fb4391c8Skettenis 		return version;
259cdcef956Skettenis 
260fb4391c8Skettenis 	/* Treat NOT_SUPPORTED as 1.0 */
261fb4391c8Skettenis 	return 0x10000;
262cdcef956Skettenis }
263cdcef956Skettenis 
264cdcef956Skettenis int32_t
smccc(uint32_t func_id,register_t arg0,register_t arg1,register_t arg2)2652fb1186fSkettenis smccc(uint32_t func_id, register_t arg0, register_t arg1, register_t arg2)
2662fb1186fSkettenis {
2672fb1186fSkettenis 	struct psci_softc *sc = psci_sc;
2682fb1186fSkettenis 
2692fb1186fSkettenis 	if (sc && sc->sc_callfn)
2702fb1186fSkettenis 		return (*sc->sc_callfn)(func_id, arg0, arg1, arg2);
2712fb1186fSkettenis 
2722fb1186fSkettenis 	return PSCI_NOT_SUPPORTED;
2732fb1186fSkettenis }
2742fb1186fSkettenis 
2752fb1186fSkettenis int32_t
smccc_arch_features(uint32_t arch_func_id)276cdcef956Skettenis smccc_arch_features(uint32_t arch_func_id)
277cdcef956Skettenis {
278cdcef956Skettenis 	struct psci_softc *sc = psci_sc;
279cdcef956Skettenis 
280fb4391c8Skettenis 	KASSERT(sc && sc->sc_callfn);
281cdcef956Skettenis 	return (*sc->sc_callfn)(SMCCC_ARCH_FEATURES, arch_func_id, 0, 0);
282cdcef956Skettenis }
283cdcef956Skettenis 
28488672784Skettenis uint32_t
psci_version(void)28588672784Skettenis psci_version(void)
28688672784Skettenis {
28788672784Skettenis 	struct psci_softc *sc = psci_sc;
28888672784Skettenis 
28988672784Skettenis 	if (sc && sc->sc_callfn && sc->sc_psci_version != 0)
29088672784Skettenis 		return (*sc->sc_callfn)(sc->sc_psci_version, 0, 0, 0);
29188672784Skettenis 
29288672784Skettenis 	/* No version support; return 0.0. */
29388672784Skettenis 	return 0;
29488672784Skettenis }
29588672784Skettenis 
29645584b64Skettenis int32_t
psci_system_suspend(register_t entry_point_address,register_t context_id)297149f7d98Skettenis psci_system_suspend(register_t entry_point_address, register_t context_id)
298149f7d98Skettenis {
299149f7d98Skettenis 	struct psci_softc *sc = psci_sc;
300149f7d98Skettenis 
301149f7d98Skettenis 	if (sc && sc->sc_callfn && sc->sc_system_suspend != 0)
302149f7d98Skettenis 		return (*sc->sc_callfn)(sc->sc_system_suspend,
303149f7d98Skettenis 		    entry_point_address, context_id, 0);
304149f7d98Skettenis 
305149f7d98Skettenis 	return PSCI_NOT_SUPPORTED;
306149f7d98Skettenis }
307149f7d98Skettenis 
308149f7d98Skettenis int32_t
psci_cpu_off(void)309149f7d98Skettenis psci_cpu_off(void)
310149f7d98Skettenis {
311149f7d98Skettenis 	struct psci_softc *sc = psci_sc;
312149f7d98Skettenis 
313149f7d98Skettenis 	if (sc && sc->sc_callfn && sc->sc_cpu_off != 0)
314149f7d98Skettenis 		return (*sc->sc_callfn)(sc->sc_cpu_off, 0, 0, 0);
315149f7d98Skettenis 
316149f7d98Skettenis 	return PSCI_NOT_SUPPORTED;
317149f7d98Skettenis }
318149f7d98Skettenis 
319149f7d98Skettenis int32_t
psci_cpu_on(register_t target_cpu,register_t entry_point_address,register_t context_id)32045584b64Skettenis psci_cpu_on(register_t target_cpu, register_t entry_point_address,
32145584b64Skettenis     register_t context_id)
3228bcbf207Skettenis {
3238bcbf207Skettenis 	struct psci_softc *sc = psci_sc;
32445584b64Skettenis 
32545584b64Skettenis 	if (sc && sc->sc_callfn && sc->sc_cpu_on != 0)
32645584b64Skettenis 		return (*sc->sc_callfn)(sc->sc_cpu_on, target_cpu,
32745584b64Skettenis 		    entry_point_address, context_id);
32845584b64Skettenis 
32945584b64Skettenis 	return PSCI_NOT_SUPPORTED;
3304406bb04Sjsg }
331cdcef956Skettenis 
332cdcef956Skettenis int32_t
psci_cpu_suspend(register_t power_state,register_t entry_point_address,register_t context_id)3335f4ad52dSkettenis psci_cpu_suspend(register_t power_state, register_t entry_point_address,
3345f4ad52dSkettenis     register_t context_id)
3355f4ad52dSkettenis {
3365f4ad52dSkettenis 	struct psci_softc *sc = psci_sc;
3375f4ad52dSkettenis 
3385f4ad52dSkettenis 	if (sc && sc->sc_callfn && sc->sc_cpu_suspend != 0)
3395f4ad52dSkettenis 		return (*sc->sc_callfn)(sc->sc_cpu_suspend, power_state,
3405f4ad52dSkettenis 		    entry_point_address, context_id);
3415f4ad52dSkettenis 
3425f4ad52dSkettenis 	return PSCI_NOT_SUPPORTED;
3435f4ad52dSkettenis }
3445f4ad52dSkettenis 
3455f4ad52dSkettenis int32_t
psci_features(uint32_t psci_func_id)346cdcef956Skettenis psci_features(uint32_t psci_func_id)
347cdcef956Skettenis {
348cdcef956Skettenis 	struct psci_softc *sc = psci_sc;
349cdcef956Skettenis 
350cdcef956Skettenis 	if (sc && sc->sc_callfn)
351cdcef956Skettenis 		return (*sc->sc_callfn)(PSCI_FEATURES, psci_func_id, 0, 0);
352cdcef956Skettenis 
353cdcef956Skettenis 	return PSCI_NOT_SUPPORTED;
354cdcef956Skettenis }
355149f7d98Skettenis 
356149f7d98Skettenis int
psci_can_suspend(void)357149f7d98Skettenis psci_can_suspend(void)
358149f7d98Skettenis {
359149f7d98Skettenis 	struct psci_softc *sc = psci_sc;
360149f7d98Skettenis 
361149f7d98Skettenis 	return (sc && sc->sc_system_suspend != 0);
362149f7d98Skettenis }
36316569a75Spatrick 
36416569a75Spatrick int
psci_method(void)36516569a75Spatrick psci_method(void)
36616569a75Spatrick {
36716569a75Spatrick 	struct psci_softc *sc = psci_sc;
36816569a75Spatrick 
36916569a75Spatrick 	return sc ? sc->sc_method : PSCI_METHOD_NONE;
37016569a75Spatrick }
371