1 /* $OpenBSD: psci.c,v 1.11 2022/07/09 19:27:56 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 #define CPU_OFF 0x84000002 40 #ifdef __LP64__ 41 #define CPU_ON 0xc4000003 42 #else 43 #define CPU_ON 0x84000003 44 #endif 45 #define SYSTEM_OFF 0x84000008 46 #define SYSTEM_RESET 0x84000009 47 #define PSCI_FEATURES 0x8400000a 48 #ifdef __LP64__ 49 #define SYSTEM_SUSPEND 0xc400000e 50 #else 51 #define SYSTEM_SUSPEND 0x8400000e 52 #endif 53 54 struct psci_softc { 55 struct device sc_dev; 56 register_t (*sc_callfn)(register_t, register_t, register_t, 57 register_t); 58 uint32_t sc_psci_version; 59 uint32_t sc_system_off; 60 uint32_t sc_system_reset; 61 uint32_t sc_system_suspend; 62 uint32_t sc_cpu_on; 63 uint32_t sc_cpu_off; 64 65 uint32_t sc_smccc_version; 66 }; 67 68 struct psci_softc *psci_sc; 69 70 int psci_match(struct device *, void *, void *); 71 void psci_attach(struct device *, struct device *, void *); 72 void psci_reset(void); 73 void psci_powerdown(void); 74 75 extern register_t hvc_call(register_t, register_t, register_t, register_t); 76 extern register_t smc_call(register_t, register_t, register_t, register_t); 77 78 int32_t smccc_version(void); 79 int32_t smccc_arch_features(uint32_t); 80 81 uint32_t psci_version(void); 82 int32_t psci_features(uint32_t); 83 84 const struct cfattach psci_ca = { 85 sizeof(struct psci_softc), psci_match, psci_attach 86 }; 87 88 struct cfdriver psci_cd = { 89 NULL, "psci", DV_DULL 90 }; 91 92 int 93 psci_match(struct device *parent, void *match, void *aux) 94 { 95 struct fdt_attach_args *faa = aux; 96 97 return OF_is_compatible(faa->fa_node, "arm,psci") || 98 OF_is_compatible(faa->fa_node, "arm,psci-0.2") || 99 OF_is_compatible(faa->fa_node, "arm,psci-1.0"); 100 } 101 102 void 103 psci_attach(struct device *parent, struct device *self, void *aux) 104 { 105 struct psci_softc *sc = (struct psci_softc *)self; 106 struct fdt_attach_args *faa = aux; 107 char method[128]; 108 uint32_t version; 109 110 if (OF_getprop(faa->fa_node, "method", method, sizeof(method))) { 111 if (strcmp(method, "hvc") == 0) 112 sc->sc_callfn = hvc_call; 113 else if (strcmp(method, "smc") == 0) 114 sc->sc_callfn = smc_call; 115 } 116 117 /* 118 * The function IDs are only to be parsed for the old specification 119 * (as in version 0.1). All newer implementations are supposed to 120 * use the specified values. 121 */ 122 if (OF_is_compatible(faa->fa_node, "arm,psci-0.2") || 123 OF_is_compatible(faa->fa_node, "arm,psci-1.0")) { 124 sc->sc_psci_version = PSCI_VERSION; 125 sc->sc_system_off = SYSTEM_OFF; 126 sc->sc_system_reset = SYSTEM_RESET; 127 sc->sc_cpu_on = CPU_ON; 128 sc->sc_cpu_off = CPU_OFF; 129 } else if (OF_is_compatible(faa->fa_node, "arm,psci")) { 130 sc->sc_system_off = OF_getpropint(faa->fa_node, 131 "system_off", 0); 132 sc->sc_system_reset = OF_getpropint(faa->fa_node, 133 "system_reset", 0); 134 sc->sc_cpu_on = OF_getpropint(faa->fa_node, "cpu_on", 0); 135 sc->sc_cpu_off = OF_getpropint(faa->fa_node, "cpu_off", 0); 136 } 137 138 psci_sc = sc; 139 140 version = psci_version(); 141 printf(": PSCI %d.%d", version >> 16, version & 0xffff); 142 143 if (version >= 0x10000) { 144 if (psci_features(SMCCC_VERSION) == PSCI_SUCCESS) { 145 sc->sc_smccc_version = smccc_version(); 146 printf(", SMCCC %d.%d", sc->sc_smccc_version >> 16, 147 sc->sc_smccc_version & 0xffff); 148 } 149 if (psci_features(SYSTEM_SUSPEND) == PSCI_SUCCESS) { 150 sc->sc_system_suspend = SYSTEM_SUSPEND; 151 printf(", SYSTEM_SUSPEND"); 152 } 153 } 154 155 printf("\n"); 156 157 if (sc->sc_system_off != 0) 158 powerdownfn = psci_powerdown; 159 if (sc->sc_system_reset != 0) 160 cpuresetfn = psci_reset; 161 } 162 163 void 164 psci_reset(void) 165 { 166 struct psci_softc *sc = psci_sc; 167 168 if (sc->sc_callfn) 169 (*sc->sc_callfn)(sc->sc_system_reset, 0, 0, 0); 170 } 171 172 void 173 psci_powerdown(void) 174 { 175 struct psci_softc *sc = psci_sc; 176 177 if (sc->sc_callfn) 178 (*sc->sc_callfn)(sc->sc_system_off, 0, 0, 0); 179 } 180 181 /* 182 * Firmware-based workaround for CVE-2017-5715. We determine whether 183 * the workaround is actually implemented and needed the first time we 184 * are invoked such that we only make the firmware call when appropriate. 185 */ 186 187 void 188 psci_flush_bp_none(void) 189 { 190 } 191 192 void 193 psci_flush_bp_smccc_arch_workaround_1(void) 194 { 195 struct psci_softc *sc = psci_sc; 196 197 (*sc->sc_callfn)(SMCCC_ARCH_WORKAROUND_1, 0, 0, 0); 198 } 199 200 void 201 psci_flush_bp(void) 202 { 203 struct psci_softc *sc = psci_sc; 204 struct cpu_info *ci = curcpu(); 205 206 /* 207 * SMCCC 1.1 allows us to detect if the workaround is 208 * implemented and needed. 209 */ 210 if (sc && sc->sc_smccc_version >= 0x10001 && 211 smccc_arch_features(SMCCC_ARCH_WORKAROUND_1) == 0) { 212 /* Workaround implemented and needed. */ 213 ci->ci_flush_bp = psci_flush_bp_smccc_arch_workaround_1; 214 ci->ci_flush_bp(); 215 } else { 216 /* Workaround isn't implemented or isn't needed. */ 217 ci->ci_flush_bp = psci_flush_bp_none; 218 } 219 } 220 221 int32_t 222 smccc_version(void) 223 { 224 struct psci_softc *sc = psci_sc; 225 int32_t version; 226 227 KASSERT(sc && sc->sc_callfn); 228 version = (*sc->sc_callfn)(SMCCC_VERSION, 0, 0, 0); 229 if (version != PSCI_NOT_SUPPORTED) 230 return version; 231 232 /* Treat NOT_SUPPORTED as 1.0 */ 233 return 0x10000; 234 } 235 236 int32_t 237 smccc_arch_features(uint32_t arch_func_id) 238 { 239 struct psci_softc *sc = psci_sc; 240 241 KASSERT(sc && sc->sc_callfn); 242 return (*sc->sc_callfn)(SMCCC_ARCH_FEATURES, arch_func_id, 0, 0); 243 } 244 245 uint32_t 246 psci_version(void) 247 { 248 struct psci_softc *sc = psci_sc; 249 250 if (sc && sc->sc_callfn && sc->sc_psci_version != 0) 251 return (*sc->sc_callfn)(sc->sc_psci_version, 0, 0, 0); 252 253 /* No version support; return 0.0. */ 254 return 0; 255 } 256 257 int32_t 258 psci_system_suspend(register_t entry_point_address, register_t context_id) 259 { 260 struct psci_softc *sc = psci_sc; 261 262 if (sc && sc->sc_callfn && sc->sc_system_suspend != 0) 263 return (*sc->sc_callfn)(sc->sc_system_suspend, 264 entry_point_address, context_id, 0); 265 266 return PSCI_NOT_SUPPORTED; 267 } 268 269 int32_t 270 psci_cpu_off(void) 271 { 272 struct psci_softc *sc = psci_sc; 273 274 if (sc && sc->sc_callfn && sc->sc_cpu_off != 0) 275 return (*sc->sc_callfn)(sc->sc_cpu_off, 0, 0, 0); 276 277 return PSCI_NOT_SUPPORTED; 278 } 279 280 int32_t 281 psci_cpu_on(register_t target_cpu, register_t entry_point_address, 282 register_t context_id) 283 { 284 struct psci_softc *sc = psci_sc; 285 286 if (sc && sc->sc_callfn && sc->sc_cpu_on != 0) 287 return (*sc->sc_callfn)(sc->sc_cpu_on, target_cpu, 288 entry_point_address, context_id); 289 290 return PSCI_NOT_SUPPORTED; 291 } 292 293 int32_t 294 psci_features(uint32_t psci_func_id) 295 { 296 struct psci_softc *sc = psci_sc; 297 298 if (sc && sc->sc_callfn) 299 return (*sc->sc_callfn)(PSCI_FEATURES, psci_func_id, 0, 0); 300 301 return PSCI_NOT_SUPPORTED; 302 } 303 304 int 305 psci_can_suspend(void) 306 { 307 struct psci_softc *sc = psci_sc; 308 309 return (sc && sc->sc_system_suspend != 0); 310 } 311