1 /* $OpenBSD: opal.c,v 1.11 2020/12/30 06:06:30 gkoehler Exp $ */ 2 /* 3 * Copyright (c) 2020 Mark Kettenis <kettenis@openbsd.org> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18 #include <sys/param.h> 19 #include <sys/device.h> 20 #include <sys/malloc.h> 21 #include <sys/sysctl.h> 22 #include <sys/systm.h> 23 24 #include <machine/bus.h> 25 #include <machine/cpu.h> 26 #include <machine/fdt.h> 27 #include <machine/opal.h> 28 29 #include <dev/clock_subr.h> 30 31 #include <dev/ofw/openfirm.h> 32 #include <dev/ofw/fdt.h> 33 34 #define OPAL_NUM_HANDLERS 4 35 36 struct opal_intr { 37 struct opal_softc *oi_sc; 38 uint32_t oi_isn; 39 }; 40 41 struct intrhand { 42 uint64_t ih_events; 43 int (*ih_func)(void *); 44 void *ih_arg; 45 }; 46 47 struct opal_softc { 48 struct device sc_dev; 49 50 struct opal_intr *sc_intr; 51 int sc_nintr; 52 53 struct intrhand *sc_handler[OPAL_NUM_HANDLERS]; 54 55 struct todr_chip_handle sc_todr; 56 57 int *sc_pstate; 58 int *sc_freq; 59 int sc_npstate; 60 }; 61 62 struct opal_softc *opal_sc; 63 64 int opal_match(struct device *, void *, void *); 65 void opal_attach(struct device *, struct device *, void *); 66 67 struct cfattach opal_ca = { 68 sizeof (struct opal_softc), opal_match, opal_attach 69 }; 70 71 struct cfdriver opal_cd = { 72 NULL, "opal", DV_DULL 73 }; 74 75 void opal_attach_deferred(struct device *); 76 void opal_attach_node(struct opal_softc *, int); 77 int opal_gettime(struct todr_chip_handle *, struct timeval *); 78 int opal_settime(struct todr_chip_handle *, struct timeval *); 79 void opal_configure_idle_states(struct opal_softc *, int); 80 void opal_found_stop_state(struct opal_softc *, uint64_t); 81 82 extern int perflevel; 83 84 void opalpm_init(struct opal_softc *, int); 85 int opalpm_find_index(struct opal_softc *); 86 int opalpm_cpuspeed(int *); 87 void opalpm_setperf(int); 88 89 int 90 opal_match(struct device *parent, void *match, void *aux) 91 { 92 struct fdt_attach_args *faa = aux; 93 94 return OF_is_compatible(faa->fa_node, "ibm,opal-v3"); 95 } 96 97 void 98 opal_attach(struct device *parent, struct device *self, void *aux) 99 { 100 struct opal_softc *sc = (struct opal_softc *)self; 101 struct fdt_attach_args *faa = aux; 102 uint32_t *interrupts; 103 int len, i; 104 int node; 105 106 node = OF_getnodebyname(faa->fa_node, "firmware"); 107 if (node) { 108 char version[64]; 109 110 version[0] = 0; 111 OF_getprop(node, "version", version, sizeof(version)); 112 version[sizeof(version) - 1] = 0; 113 printf(": %s", version); 114 } 115 116 len = OF_getproplen(faa->fa_node, "opal-interrupts"); 117 if (len > 0 && (len % sizeof(uint32_t)) != 0) { 118 printf(": can't parse interrupts\n"); 119 return; 120 } 121 122 printf("\n"); 123 124 /* There can be only one. */ 125 KASSERT(opal_sc == NULL); 126 opal_sc = sc; 127 128 if (len > 0) { 129 interrupts = malloc(len, M_TEMP, M_WAITOK); 130 OF_getpropintarray(faa->fa_node, "opal-interrupts", 131 interrupts, len); 132 sc->sc_nintr = len / sizeof(uint32_t); 133 134 sc->sc_intr = mallocarray(sc->sc_nintr, 135 sizeof(struct opal_intr), M_DEVBUF, M_WAITOK); 136 137 for (i = 0; i < sc->sc_nintr; i++) { 138 sc->sc_intr[i].oi_sc = sc; 139 sc->sc_intr[i].oi_isn = interrupts[i]; 140 } 141 142 free(interrupts, M_TEMP, len); 143 144 config_defer(self, opal_attach_deferred); 145 } 146 147 sc->sc_todr.todr_gettime = opal_gettime; 148 sc->sc_todr.todr_settime = opal_settime; 149 todr_attach(&sc->sc_todr); 150 151 node = OF_getnodebyname(faa->fa_node, "power-mgt"); 152 if (node) { 153 opal_configure_idle_states(sc, node); 154 opalpm_init(sc, node); 155 } 156 157 node = OF_getnodebyname(faa->fa_node, "consoles"); 158 if (node) { 159 for (node = OF_child(node); node; node = OF_peer(node)) 160 opal_attach_node(sc, node); 161 } 162 163 node = OF_getnodebyname(faa->fa_node, "sensors"); 164 if (node) { 165 for (node = OF_child(node); node; node = OF_peer(node)) 166 opal_attach_node(sc, node); 167 } 168 } 169 170 int 171 opal_intr(void *arg) 172 { 173 struct opal_intr *oi = arg; 174 struct opal_softc *sc = oi->oi_sc; 175 uint64_t events = 0; 176 int i; 177 178 opal_handle_interrupt(oi->oi_isn, opal_phys(&events)); 179 180 /* Handle registered events. */ 181 for (i = 0; i < OPAL_NUM_HANDLERS; i++) { 182 struct intrhand *ih = sc->sc_handler[i]; 183 184 if (ih == NULL) 185 continue; 186 if ((events & ih->ih_events) == 0) 187 continue; 188 189 ih->ih_func(ih->ih_arg); 190 } 191 192 return 1; 193 } 194 195 void * 196 opal_intr_establish(uint64_t events, int level, int (*func)(void *), void *arg) 197 { 198 struct opal_softc *sc = opal_sc; 199 struct intrhand *ih; 200 int i; 201 202 for (i = 0; i < OPAL_NUM_HANDLERS; i++) { 203 if (sc->sc_handler[i] == NULL) 204 break; 205 } 206 if (i == OPAL_NUM_HANDLERS) 207 return NULL; 208 209 ih = malloc(sizeof(*ih), M_DEVBUF, M_WAITOK); 210 ih->ih_events = events; 211 ih->ih_func = func; 212 ih->ih_arg = arg; 213 sc->sc_handler[i] = ih; 214 215 return ih; 216 } 217 218 void 219 opal_attach_deferred(struct device *self) 220 { 221 struct opal_softc *sc = (struct opal_softc *)self; 222 int i; 223 224 for (i = 0; i < sc->sc_nintr; i++) { 225 intr_establish(sc->sc_intr[i].oi_isn, IST_LEVEL, IPL_TTY, 226 NULL, opal_intr, &sc->sc_intr[i], sc->sc_dev.dv_xname); 227 } 228 } 229 230 int 231 opal_print(void *aux, const char *pnp) 232 { 233 struct fdt_attach_args *faa = aux; 234 char name[32]; 235 236 if (!pnp) 237 return (QUIET); 238 239 if (OF_getprop(faa->fa_node, "name", name, sizeof(name)) > 0) { 240 name[sizeof(name) - 1] = 0; 241 printf("\"%s\"", name); 242 } else 243 printf("node %u", faa->fa_node); 244 245 printf(" at %s", pnp); 246 247 return (UNCONF); 248 } 249 250 void 251 opal_attach_node(struct opal_softc *sc, int node) 252 { 253 struct fdt_attach_args faa; 254 char buf[32]; 255 256 if (OF_getproplen(node, "compatible") <= 0) 257 return; 258 259 if (OF_getprop(node, "status", buf, sizeof(buf)) > 0 && 260 strcmp(buf, "disabled") == 0) 261 return; 262 263 memset(&faa, 0, sizeof(faa)); 264 faa.fa_name = ""; 265 faa.fa_node = node; 266 267 config_found(&sc->sc_dev, &faa, opal_print); 268 } 269 270 int 271 opal_gettime(struct todr_chip_handle *ch, struct timeval *tv) 272 { 273 struct clock_ymdhms dt; 274 uint64_t time; 275 uint32_t date; 276 int64_t error; 277 278 do { 279 error = opal_rtc_read(opal_phys(&date), opal_phys(&time)); 280 if (error == OPAL_BUSY_EVENT) 281 opal_poll_events(NULL); 282 } while (error == OPAL_BUSY_EVENT); 283 284 if (error != OPAL_SUCCESS) 285 return EIO; 286 287 dt.dt_sec = FROMBCD((time >> 40) & 0xff); 288 dt.dt_min = FROMBCD((time >> 48) & 0xff); 289 dt.dt_hour = FROMBCD((time >> 56) & 0xff); 290 dt.dt_day = FROMBCD((date >> 0) & 0xff); 291 dt.dt_mon = FROMBCD((date >> 8) & 0xff); 292 dt.dt_year = FROMBCD((date >> 16) & 0xff); 293 dt.dt_year += 100 * FROMBCD((date >> 24) & 0xff); 294 295 tv->tv_sec = clock_ymdhms_to_secs(&dt); 296 tv->tv_usec = 0; 297 298 return 0; 299 } 300 301 int 302 opal_settime(struct todr_chip_handle *ch, struct timeval *tv) 303 { 304 struct clock_ymdhms dt; 305 uint64_t time = 0; 306 uint32_t date = 0; 307 int64_t error; 308 309 clock_secs_to_ymdhms(tv->tv_sec, &dt); 310 311 time |= (uint64_t)TOBCD(dt.dt_sec) << 40; 312 time |= (uint64_t)TOBCD(dt.dt_min) << 48; 313 time |= (uint64_t)TOBCD(dt.dt_hour) << 56; 314 date |= (uint32_t)TOBCD(dt.dt_day); 315 date |= (uint32_t)TOBCD(dt.dt_mon) << 8; 316 date |= (uint32_t)TOBCD(dt.dt_year) << 16; 317 date |= (uint32_t)TOBCD(dt.dt_year / 100) << 24; 318 319 do { 320 error = opal_rtc_write(date, time); 321 if (error == OPAL_BUSY_EVENT) 322 opal_poll_events(NULL); 323 } while (error == OPAL_BUSY_EVENT); 324 325 if (error != OPAL_SUCCESS) 326 return EIO; 327 328 return 0; 329 } 330 331 #define OPAL_PM_LOSE_USER_CONTEXT 0x00001000 332 #define OPAL_PM_STOP_INST_FAST 0x00100000 333 334 void 335 opal_configure_idle_states(struct opal_softc *sc, int node) 336 { 337 uint64_t *states; 338 uint32_t accept, *flags; 339 int count, flen, i, slen; 340 char *prop; 341 342 prop = "ibm,cpu-idle-state-flags"; 343 flen = OF_getproplen(node, prop); 344 if (flen <= 0 || flen % sizeof(flags[0]) != 0) 345 return; 346 count = flen / sizeof(flags[0]); 347 slen = count * sizeof(states[0]); 348 349 flags = malloc(flen, M_DEVBUF, M_WAITOK); 350 states = malloc(slen, M_DEVBUF, M_WAITOK); 351 OF_getpropintarray(node, prop, flags, flen); 352 353 /* Power ISA v3 uses the psscr with the stop instruction. */ 354 prop = "ibm,cpu-idle-state-psscr"; 355 if (OF_getpropint64array(node, prop, states, slen) == slen) { 356 /* 357 * Find the deepest idle state that doesn't lose too 358 * much context. 359 */ 360 accept = OPAL_PM_LOSE_USER_CONTEXT | OPAL_PM_STOP_INST_FAST; 361 for (i = count - 1; i >= 0; i--) { 362 if ((flags[i] & ~accept) == 0) { 363 opal_found_stop_state(sc, states[i]); 364 break; 365 } 366 } 367 } 368 369 free(flags, M_DEVBUF, flen); 370 free(states, M_DEVBUF, slen); 371 } 372 373 void cpu_idle_stop(void); 374 #ifdef MULTIPROCESSOR 375 void cpu_hatch_and_stop(void); 376 #endif 377 378 void 379 opal_found_stop_state(struct opal_softc *sc, uint64_t state) 380 { 381 #ifdef MULTIPROCESSOR 382 uint32_t pirs[8]; 383 int i, len, node; 384 char buf[32]; 385 #endif 386 387 cpu_idle_state_psscr = state; 388 cpu_idle_cycle_fcn = &cpu_idle_stop; 389 printf("%s: idle psscr %llx\n", sc->sc_dev.dv_xname, 390 (unsigned long long)state); 391 392 #ifdef MULTIPROCESSOR 393 /* 394 * Idle the other hardware threads. We use only one thread of 395 * each cpu core. The other threads are idle in OPAL. If we 396 * move them to a deeper idle state, then the core might 397 * switch to single-thread mode, increase performance. 398 */ 399 node = OF_parent(curcpu()->ci_node); 400 for (node = OF_child(node); node != 0; node = OF_peer(node)) { 401 if (OF_getprop(node, "device_type", buf, sizeof(buf)) <= 0 || 402 strcmp(buf, "cpu") != 0) 403 continue; 404 len = OF_getpropintarray(node, "ibm,ppc-interrupt-server#s", 405 pirs, sizeof(pirs)); 406 if (len > 0 && len % 4 == 0) { 407 /* Skip i = 0, the first hardware thread. */ 408 for (i = 1; i < len / 4; i++) 409 opal_start_cpu(pirs[i], 410 (vaddr_t)cpu_hatch_and_stop); 411 } 412 } 413 #endif 414 } 415 416 void 417 opalpm_init(struct opal_softc *sc, int node) 418 { 419 int i, len; 420 421 len = OF_getproplen(node, "ibm,pstate-ids"); 422 if (len <= 0 || len % sizeof(int) != 0 || 423 len != OF_getproplen(node, "ibm,pstate-frequencies-mhz")) { 424 printf("%s: can't parse pstates\n", sc->sc_dev.dv_xname); 425 return; 426 } 427 sc->sc_pstate = malloc(len, M_DEVBUF, M_WAITOK); 428 sc->sc_freq = malloc(len, M_DEVBUF, M_WAITOK); 429 sc->sc_npstate = len / sizeof(int); 430 OF_getprop(node, "ibm,pstate-ids", sc->sc_pstate, len); 431 OF_getprop(node, "ibm,pstate-frequencies-mhz", sc->sc_freq, len); 432 433 if ((i = opalpm_find_index(sc)) != -1) 434 perflevel = (sc->sc_npstate - 1 - i) * 100 / sc->sc_npstate; 435 cpu_cpuspeed = opalpm_cpuspeed; 436 #ifndef MULTIPROCESSOR 437 cpu_setperf = opalpm_setperf; 438 #else 439 ul_setperf = opalpm_setperf; 440 cpu_setperf = mp_setperf; 441 #endif 442 } 443 444 int 445 opalpm_find_index(struct opal_softc *sc) 446 { 447 int i, pstate; 448 449 /* 450 * POWER9 23.5.8.3 Power Management Status Register (PMSR) 451 * 8:15 Local Actual Pstate 452 */ 453 pstate = (mfpmsr() >> 48) & 0xff; 454 for (i = 0; i < sc->sc_npstate; i++) { 455 if (sc->sc_pstate[i] == pstate) 456 return i; 457 } 458 return -1; 459 } 460 461 int 462 opalpm_cpuspeed(int *freq) 463 { 464 struct opal_softc *sc = opal_sc; 465 int i; 466 467 if ((i = opalpm_find_index(sc)) == -1) 468 return 1; 469 *freq = sc->sc_freq[i]; 470 return 0; 471 } 472 473 void 474 opalpm_setperf(int level) 475 { 476 struct opal_softc *sc = opal_sc; 477 uint64_t pstate; 478 int i; 479 480 /* 481 * Assume that "ibm,pstate-frequencies-mhz" is sorted from 482 * fastest to slowest. 483 */ 484 i = (100 - level) * (sc->sc_npstate - 1) / 100; 485 pstate = sc->sc_pstate[i]; 486 487 /* 488 * POWER9 23.5.8.1 Power Management Control Register (PMCR) 489 * 0:7 Upper Pstate request 490 * 8:15 Lower Pstate request 491 * 60:63 Version 492 */ 493 mtpmcr((pstate << 56) | (pstate << 48) | 0); 494 } 495