/* $OpenBSD: psci.c,v 1.16 2024/04/13 14:20:48 kettenis Exp $ */ /* * Copyright (c) 2016 Jonathan Gray * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include extern void (*cpuresetfn)(void); extern void (*powerdownfn)(void); #define SMCCC_VERSION 0x80000000 #define SMCCC_ARCH_FEATURES 0x80000001 #define SMCCC_ARCH_WORKAROUND_1 0x80008000 #define SMCCC_ARCH_WORKAROUND_2 0x80007fff #define SMCCC_ARCH_WORKAROUND_3 0x80003fff #define PSCI_VERSION 0x84000000 #ifdef __LP64__ #define CPU_SUSPEND 0xc4000001 #else #define CPU_SUSPEND 0x84000001 #endif #define CPU_OFF 0x84000002 #ifdef __LP64__ #define CPU_ON 0xc4000003 #else #define CPU_ON 0x84000003 #endif #define SYSTEM_OFF 0x84000008 #define SYSTEM_RESET 0x84000009 #define PSCI_FEATURES 0x8400000a #ifdef __LP64__ #define SYSTEM_SUSPEND 0xc400000e #else #define SYSTEM_SUSPEND 0x8400000e #endif struct psci_softc { struct device sc_dev; register_t (*sc_callfn)(register_t, register_t, register_t, register_t); uint32_t sc_psci_version; uint32_t sc_system_off; uint32_t sc_system_reset; uint32_t sc_system_suspend; uint32_t sc_cpu_on; uint32_t sc_cpu_off; uint32_t sc_cpu_suspend; uint32_t sc_smccc_version; uint32_t sc_method; }; struct psci_softc *psci_sc; int psci_match(struct device *, void *, void *); void psci_attach(struct device *, struct device *, void *); void psci_reset(void); void psci_powerdown(void); extern register_t hvc_call(register_t, register_t, register_t, register_t); extern register_t smc_call(register_t, register_t, register_t, register_t); int32_t smccc_version(void); int32_t smccc_arch_features(uint32_t); uint32_t psci_version(void); int32_t psci_features(uint32_t); const struct cfattach psci_ca = { sizeof(struct psci_softc), psci_match, psci_attach }; struct cfdriver psci_cd = { NULL, "psci", DV_DULL }; int psci_match(struct device *parent, void *match, void *aux) { struct fdt_attach_args *faa = aux; return OF_is_compatible(faa->fa_node, "arm,psci") || OF_is_compatible(faa->fa_node, "arm,psci-0.2") || OF_is_compatible(faa->fa_node, "arm,psci-1.0"); } void psci_attach(struct device *parent, struct device *self, void *aux) { struct psci_softc *sc = (struct psci_softc *)self; struct fdt_attach_args *faa = aux; char method[128]; uint32_t version; if (OF_getprop(faa->fa_node, "method", method, sizeof(method))) { if (strcmp(method, "hvc") == 0) { sc->sc_callfn = hvc_call; sc->sc_method = PSCI_METHOD_HVC; } else if (strcmp(method, "smc") == 0) { sc->sc_callfn = smc_call; sc->sc_method = PSCI_METHOD_SMC; } } /* * The function IDs are only to be parsed for the old specification * (as in version 0.1). All newer implementations are supposed to * use the specified values. */ if (OF_is_compatible(faa->fa_node, "arm,psci-0.2") || OF_is_compatible(faa->fa_node, "arm,psci-1.0")) { sc->sc_psci_version = PSCI_VERSION; sc->sc_system_off = SYSTEM_OFF; sc->sc_system_reset = SYSTEM_RESET; sc->sc_cpu_on = CPU_ON; sc->sc_cpu_off = CPU_OFF; sc->sc_cpu_suspend = CPU_SUSPEND; } else if (OF_is_compatible(faa->fa_node, "arm,psci")) { sc->sc_system_off = OF_getpropint(faa->fa_node, "system_off", 0); sc->sc_system_reset = OF_getpropint(faa->fa_node, "system_reset", 0); sc->sc_cpu_on = OF_getpropint(faa->fa_node, "cpu_on", 0); sc->sc_cpu_off = OF_getpropint(faa->fa_node, "cpu_off", 0); sc->sc_cpu_suspend = OF_getpropint(faa->fa_node, "cpu_suspend", 0); } psci_sc = sc; version = psci_version(); printf(": PSCI %d.%d", version >> 16, version & 0xffff); if (version >= 0x10000) { if (psci_features(SMCCC_VERSION) == PSCI_SUCCESS) { sc->sc_smccc_version = smccc_version(); printf(", SMCCC %d.%d", sc->sc_smccc_version >> 16, sc->sc_smccc_version & 0xffff); } if (psci_features(SYSTEM_SUSPEND) == PSCI_SUCCESS) { sc->sc_system_suspend = SYSTEM_SUSPEND; printf(", SYSTEM_SUSPEND"); } } printf("\n"); if (sc->sc_system_off != 0) powerdownfn = psci_powerdown; if (sc->sc_system_reset != 0) cpuresetfn = psci_reset; } void psci_reset(void) { struct psci_softc *sc = psci_sc; if (sc->sc_callfn) (*sc->sc_callfn)(sc->sc_system_reset, 0, 0, 0); } void psci_powerdown(void) { struct psci_softc *sc = psci_sc; if (sc->sc_callfn) (*sc->sc_callfn)(sc->sc_system_off, 0, 0, 0); } /* * Firmware-based workaround for CVE-2017-5715. We determine whether * the workaround is actually implemented and needed the first time we * are invoked such that we only make the firmware call when appropriate. */ void psci_flush_bp_none(void) { } void psci_flush_bp_smccc_arch_workaround_1(void) { struct psci_softc *sc = psci_sc; (*sc->sc_callfn)(SMCCC_ARCH_WORKAROUND_1, 0, 0, 0); } void psci_flush_bp(void) { struct psci_softc *sc = psci_sc; struct cpu_info *ci = curcpu(); /* * SMCCC 1.1 allows us to detect if the workaround is * implemented and needed. */ if (sc && sc->sc_smccc_version >= 0x10001 && smccc_arch_features(SMCCC_ARCH_WORKAROUND_1) == 0) { /* Workaround implemented and needed. */ ci->ci_flush_bp = psci_flush_bp_smccc_arch_workaround_1; ci->ci_flush_bp(); } else { /* Workaround isn't implemented or isn't needed. */ ci->ci_flush_bp = psci_flush_bp_none; } } void smccc_enable_arch_workaround_2(void) { struct psci_softc *sc = psci_sc; /* * SMCCC 1.1 allows us to detect if the workaround is * implemented and needed. */ if (sc && sc->sc_smccc_version >= 0x10001 && smccc_arch_features(SMCCC_ARCH_WORKAROUND_2) == 0) { /* Workaround implemented and needed. */ (*sc->sc_callfn)(SMCCC_ARCH_WORKAROUND_2, 1, 0, 0); } } int smccc_needs_arch_workaround_3(void) { struct psci_softc *sc = psci_sc; /* * SMCCC 1.1 allows us to detect if the workaround is * implemented and needed. */ if (sc && sc->sc_smccc_version >= 0x10001 && smccc_arch_features(SMCCC_ARCH_WORKAROUND_3) == 0) { /* Workaround implemented and needed. */ return 1; } return 0; } int32_t smccc_version(void) { struct psci_softc *sc = psci_sc; int32_t version; KASSERT(sc && sc->sc_callfn); version = (*sc->sc_callfn)(SMCCC_VERSION, 0, 0, 0); if (version != PSCI_NOT_SUPPORTED) return version; /* Treat NOT_SUPPORTED as 1.0 */ return 0x10000; } int32_t smccc(uint32_t func_id, register_t arg0, register_t arg1, register_t arg2) { struct psci_softc *sc = psci_sc; if (sc && sc->sc_callfn) return (*sc->sc_callfn)(func_id, arg0, arg1, arg2); return PSCI_NOT_SUPPORTED; } int32_t smccc_arch_features(uint32_t arch_func_id) { struct psci_softc *sc = psci_sc; KASSERT(sc && sc->sc_callfn); return (*sc->sc_callfn)(SMCCC_ARCH_FEATURES, arch_func_id, 0, 0); } uint32_t psci_version(void) { struct psci_softc *sc = psci_sc; if (sc && sc->sc_callfn && sc->sc_psci_version != 0) return (*sc->sc_callfn)(sc->sc_psci_version, 0, 0, 0); /* No version support; return 0.0. */ return 0; } int32_t psci_system_suspend(register_t entry_point_address, register_t context_id) { struct psci_softc *sc = psci_sc; if (sc && sc->sc_callfn && sc->sc_system_suspend != 0) return (*sc->sc_callfn)(sc->sc_system_suspend, entry_point_address, context_id, 0); return PSCI_NOT_SUPPORTED; } int32_t psci_cpu_off(void) { struct psci_softc *sc = psci_sc; if (sc && sc->sc_callfn && sc->sc_cpu_off != 0) return (*sc->sc_callfn)(sc->sc_cpu_off, 0, 0, 0); return PSCI_NOT_SUPPORTED; } int32_t psci_cpu_on(register_t target_cpu, register_t entry_point_address, register_t context_id) { struct psci_softc *sc = psci_sc; if (sc && sc->sc_callfn && sc->sc_cpu_on != 0) return (*sc->sc_callfn)(sc->sc_cpu_on, target_cpu, entry_point_address, context_id); return PSCI_NOT_SUPPORTED; } int32_t psci_cpu_suspend(register_t power_state, register_t entry_point_address, register_t context_id) { struct psci_softc *sc = psci_sc; if (sc && sc->sc_callfn && sc->sc_cpu_suspend != 0) return (*sc->sc_callfn)(sc->sc_cpu_suspend, power_state, entry_point_address, context_id); return PSCI_NOT_SUPPORTED; } int32_t psci_features(uint32_t psci_func_id) { struct psci_softc *sc = psci_sc; if (sc && sc->sc_callfn) return (*sc->sc_callfn)(PSCI_FEATURES, psci_func_id, 0, 0); return PSCI_NOT_SUPPORTED; } int psci_can_suspend(void) { struct psci_softc *sc = psci_sc; return (sc && sc->sc_system_suspend != 0); } int psci_method(void) { struct psci_softc *sc = psci_sc; return sc ? sc->sc_method : PSCI_METHOD_NONE; }