1 /* $OpenBSD: psci.c,v 1.7 2018/05/03 09:45:57 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 #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_version; 58 uint32_t sc_smccc_version; 59 }; 60 61 struct psci_softc *psci_sc; 62 63 int psci_match(struct device *, void *, void *); 64 void psci_attach(struct device *, struct device *, void *); 65 void psci_reset(void); 66 void psci_powerdown(void); 67 68 extern register_t hvc_call(register_t, register_t, register_t, register_t); 69 extern register_t smc_call(register_t, register_t, register_t, register_t); 70 71 int32_t smccc_version(void); 72 int32_t smccc_arch_features(uint32_t); 73 74 uint32_t psci_version(void); 75 int32_t psci_features(uint32_t); 76 77 struct cfattach psci_ca = { 78 sizeof(struct psci_softc), psci_match, psci_attach 79 }; 80 81 struct cfdriver psci_cd = { 82 NULL, "psci", DV_DULL 83 }; 84 85 int 86 psci_match(struct device *parent, void *match, void *aux) 87 { 88 struct fdt_attach_args *faa = aux; 89 90 return OF_is_compatible(faa->fa_node, "arm,psci") || 91 OF_is_compatible(faa->fa_node, "arm,psci-0.2") || 92 OF_is_compatible(faa->fa_node, "arm,psci-1.0"); 93 } 94 95 void 96 psci_attach(struct device *parent, struct device *self, void *aux) 97 { 98 struct psci_softc *sc = (struct psci_softc *)self; 99 struct fdt_attach_args *faa = aux; 100 char method[128]; 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 sc->sc_version = psci_version(); 131 printf(": PSCI %d.%d", sc->sc_version >> 16, sc->sc_version & 0xffff); 132 133 if (sc->sc_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 pick the 169 * appropriate mechanism based on the PSCI and SMCCC versions. We 170 * determine whether the workaround is actually needed the first time 171 * we are invoked such that we only make the firmware call if we 172 * really need to. 173 */ 174 175 void 176 psci_flush_bp_none(void) 177 { 178 } 179 180 void 181 psci_flush_bp_psci_version(void) 182 { 183 struct psci_softc *sc = psci_sc; 184 185 (*sc->sc_callfn)(PSCI_VERSION, 0, 0, 0); 186 } 187 188 void 189 psci_flush_bp_smccc_arch_workaround_1(void) 190 { 191 struct psci_softc *sc = psci_sc; 192 193 (*sc->sc_callfn)(SMCCC_ARCH_WORKAROUND_1, 0, 0, 0); 194 } 195 196 void 197 psci_flush_bp(void) 198 { 199 struct psci_softc *sc = psci_sc; 200 struct cpu_info *ci = curcpu(); 201 202 /* No PSCI or an old version of PSCI; nothing we can do. */ 203 if (sc == NULL || sc->sc_version < 0x10000) { 204 ci->ci_flush_bp = psci_flush_bp_none; 205 return; 206 } 207 208 /* 209 * PSCI 1.0 or later with SMCCC 1.0; invoke PSCI_VERSION and 210 * hope for the best. 211 */ 212 if (sc->sc_smccc_version < 0x10001) { 213 ci->ci_flush_bp = psci_flush_bp_psci_version; 214 ci->ci_flush_bp(); 215 return; 216 } 217 218 /* 219 * SMCCC 1.1 or later; we can actually detect if the 220 * workaround is implemented and needed. 221 */ 222 if (smccc_arch_features(SMCCC_ARCH_WORKAROUND_1) == 0) { 223 /* Workaround implemented and needed. */ 224 ci->ci_flush_bp = psci_flush_bp_smccc_arch_workaround_1; 225 ci->ci_flush_bp(); 226 } else { 227 /* No workaround needed. */ 228 ci->ci_flush_bp = psci_flush_bp_none; 229 } 230 } 231 232 int32_t 233 smccc_version(void) 234 { 235 struct psci_softc *sc = psci_sc; 236 237 if (sc && sc->sc_callfn) 238 return (*sc->sc_callfn)(SMCCC_VERSION, 0, 0, 0); 239 240 return PSCI_NOT_SUPPORTED; 241 } 242 243 int32_t 244 smccc_arch_features(uint32_t arch_func_id) 245 { 246 struct psci_softc *sc = psci_sc; 247 248 if (sc && sc->sc_callfn) 249 return (*sc->sc_callfn)(SMCCC_ARCH_FEATURES, arch_func_id, 0, 0); 250 251 return PSCI_NOT_SUPPORTED; 252 } 253 254 uint32_t 255 psci_version(void) 256 { 257 struct psci_softc *sc = psci_sc; 258 259 if (sc && sc->sc_callfn && sc->sc_psci_version != 0) 260 return (*sc->sc_callfn)(sc->sc_psci_version, 0, 0, 0); 261 262 /* No version support; return 0.0. */ 263 return 0; 264 } 265 266 int32_t 267 psci_cpu_on(register_t target_cpu, register_t entry_point_address, 268 register_t context_id) 269 { 270 struct psci_softc *sc = psci_sc; 271 272 if (sc && sc->sc_callfn && sc->sc_cpu_on != 0) 273 return (*sc->sc_callfn)(sc->sc_cpu_on, target_cpu, 274 entry_point_address, context_id); 275 276 return PSCI_NOT_SUPPORTED; 277 } 278 279 int32_t 280 psci_features(uint32_t psci_func_id) 281 { 282 struct psci_softc *sc = psci_sc; 283 284 if (sc && sc->sc_callfn) 285 return (*sc->sc_callfn)(PSCI_FEATURES, psci_func_id, 0, 0); 286 287 return PSCI_NOT_SUPPORTED; 288 } 289