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