xref: /openbsd-src/sys/arch/powerpc64/dev/opal.c (revision 46035553bfdd96e63c94e32da0210227ec2e3cf1)
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