xref: /freebsd/sys/powerpc/pseries/platform_chrp.c (revision 6e778a7efdc0e804471750157f6bacd1ef7d1580)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2008 Marcel Moolenaar
5  * Copyright (c) 2009 Nathan Whitehorn
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 #include <sys/cdefs.h>
31 __FBSDID("$FreeBSD$");
32 
33 #include <sys/param.h>
34 #include <sys/systm.h>
35 #include <sys/kernel.h>
36 #include <sys/bus.h>
37 #include <sys/pcpu.h>
38 #include <sys/proc.h>
39 #include <sys/sched.h>
40 #include <sys/smp.h>
41 #include <vm/vm.h>
42 #include <vm/pmap.h>
43 
44 #include <machine/bus.h>
45 #include <machine/cpu.h>
46 #include <machine/hid.h>
47 #include <machine/platformvar.h>
48 #include <machine/rtas.h>
49 #include <machine/smp.h>
50 #include <machine/spr.h>
51 #include <machine/trap.h>
52 
53 #include <dev/ofw/openfirm.h>
54 #include <machine/ofw_machdep.h>
55 
56 #include "platform_if.h"
57 
58 #ifdef SMP
59 extern void *ap_pcpu;
60 #endif
61 
62 #ifdef __powerpc64__
63 static uint8_t splpar_vpa[MAXCPU][640] __aligned(128); /* XXX: dpcpu */
64 #endif
65 
66 static vm_offset_t realmaxaddr = VM_MAX_ADDRESS;
67 
68 static int chrp_probe(platform_t);
69 static int chrp_attach(platform_t);
70 void chrp_mem_regions(platform_t, struct mem_region *phys, int *physsz,
71     struct mem_region *avail, int *availsz);
72 static vm_offset_t chrp_real_maxaddr(platform_t);
73 static u_long chrp_timebase_freq(platform_t, struct cpuref *cpuref);
74 static int chrp_smp_first_cpu(platform_t, struct cpuref *cpuref);
75 static int chrp_smp_next_cpu(platform_t, struct cpuref *cpuref);
76 static int chrp_smp_get_bsp(platform_t, struct cpuref *cpuref);
77 static void chrp_smp_ap_init(platform_t);
78 #ifdef SMP
79 static int chrp_smp_start_cpu(platform_t, struct pcpu *cpu);
80 static struct cpu_group *chrp_smp_topo(platform_t plat);
81 #endif
82 static void chrp_reset(platform_t);
83 #ifdef __powerpc64__
84 #include "phyp-hvcall.h"
85 static void phyp_cpu_idle(sbintime_t sbt);
86 #endif
87 
88 static platform_method_t chrp_methods[] = {
89 	PLATFORMMETHOD(platform_probe, 		chrp_probe),
90 	PLATFORMMETHOD(platform_attach,		chrp_attach),
91 	PLATFORMMETHOD(platform_mem_regions,	chrp_mem_regions),
92 	PLATFORMMETHOD(platform_real_maxaddr,	chrp_real_maxaddr),
93 	PLATFORMMETHOD(platform_timebase_freq,	chrp_timebase_freq),
94 
95 	PLATFORMMETHOD(platform_smp_ap_init,	chrp_smp_ap_init),
96 	PLATFORMMETHOD(platform_smp_first_cpu,	chrp_smp_first_cpu),
97 	PLATFORMMETHOD(platform_smp_next_cpu,	chrp_smp_next_cpu),
98 	PLATFORMMETHOD(platform_smp_get_bsp,	chrp_smp_get_bsp),
99 #ifdef SMP
100 	PLATFORMMETHOD(platform_smp_start_cpu,	chrp_smp_start_cpu),
101 	PLATFORMMETHOD(platform_smp_topo,	chrp_smp_topo),
102 #endif
103 
104 	PLATFORMMETHOD(platform_reset,		chrp_reset),
105 
106 	{ 0, 0 }
107 };
108 
109 static platform_def_t chrp_platform = {
110 	"chrp",
111 	chrp_methods,
112 	0
113 };
114 
115 PLATFORM_DEF(chrp_platform);
116 
117 static int
118 chrp_probe(platform_t plat)
119 {
120 	if (OF_finddevice("/memory") != -1 || OF_finddevice("/memory@0") != -1)
121 		return (BUS_PROBE_GENERIC);
122 
123 	return (ENXIO);
124 }
125 
126 static int
127 chrp_attach(platform_t plat)
128 {
129 #ifdef __powerpc64__
130 	int i;
131 
132 	/* XXX: check for /rtas/ibm,hypertas-functions? */
133 	if (!(mfmsr() & PSL_HV)) {
134 		struct mem_region *phys, *avail;
135 		int nphys, navail;
136 		mem_regions(&phys, &nphys, &avail, &navail);
137 		realmaxaddr = phys[0].mr_size;
138 
139 		pmap_mmu_install("mmu_phyp", BUS_PROBE_SPECIFIC);
140 		cpu_idle_hook = phyp_cpu_idle;
141 
142 		/* Set up important VPA fields */
143 		for (i = 0; i < MAXCPU; i++) {
144 			bzero(splpar_vpa[i], sizeof(splpar_vpa));
145 			/* First two: VPA size */
146 			splpar_vpa[i][4] =
147 			    (uint8_t)((sizeof(splpar_vpa[i]) >> 8) & 0xff);
148 			splpar_vpa[i][5] =
149 			    (uint8_t)(sizeof(splpar_vpa[i]) & 0xff);
150 			splpar_vpa[i][0xba] = 1;	/* Maintain FPRs */
151 			splpar_vpa[i][0xbb] = 1;	/* Maintain PMCs */
152 			splpar_vpa[i][0xfc] = 0xff;	/* Maintain full SLB */
153 			splpar_vpa[i][0xfd] = 0xff;
154 			splpar_vpa[i][0xff] = 1;	/* Maintain Altivec */
155 		}
156 		mb();
157 
158 		/* Set up hypervisor CPU stuff */
159 		chrp_smp_ap_init(plat);
160 	}
161 #endif
162 
163 	/* Some systems (e.g. QEMU) need Open Firmware to stand down */
164 	ofw_quiesce();
165 
166 	return (0);
167 }
168 
169 static int
170 parse_drconf_memory(struct mem_region *ofmem, int *msz,
171 		    struct mem_region *ofavail, int *asz)
172 {
173 	phandle_t phandle;
174 	vm_offset_t base;
175 	int i, idx, len, lasz, lmsz, res;
176 	uint32_t flags, lmb_size[2];
177 	uint32_t *dmem;
178 
179 	lmsz = *msz;
180 	lasz = *asz;
181 
182 	phandle = OF_finddevice("/ibm,dynamic-reconfiguration-memory");
183 	if (phandle == -1)
184 		/* No drconf node, return. */
185 		return (0);
186 
187 	res = OF_getencprop(phandle, "ibm,lmb-size", lmb_size,
188 	    sizeof(lmb_size));
189 	if (res == -1)
190 		return (0);
191 	printf("Logical Memory Block size: %d MB\n", lmb_size[1] >> 20);
192 
193 	/* Parse the /ibm,dynamic-memory.
194 	   The first position gives the # of entries. The next two words
195  	   reflect the address of the memory block. The next four words are
196 	   the DRC index, reserved, list index and flags.
197 	   (see PAPR C.6.6.2 ibm,dynamic-reconfiguration-memory)
198 
199 	    #el  Addr   DRC-idx  res   list-idx  flags
200 	   -------------------------------------------------
201 	   | 4 |   8   |   4   |   4   |   4   |   4   |....
202 	   -------------------------------------------------
203 	*/
204 
205 	len = OF_getproplen(phandle, "ibm,dynamic-memory");
206 	if (len > 0) {
207 
208 		/* We have to use a variable length array on the stack
209 		   since we have very limited stack space.
210 		*/
211 		cell_t arr[len/sizeof(cell_t)];
212 
213 		res = OF_getencprop(phandle, "ibm,dynamic-memory", arr,
214 		    sizeof(arr));
215 		if (res == -1)
216 			return (0);
217 
218 		/* Number of elements */
219 		idx = arr[0];
220 
221 		/* First address, in arr[1], arr[2]*/
222 		dmem = &arr[1];
223 
224 		for (i = 0; i < idx; i++) {
225 			base = ((uint64_t)dmem[0] << 32) + dmem[1];
226 			dmem += 4;
227 			flags = dmem[1];
228 			/* Use region only if available and not reserved. */
229 			if ((flags & 0x8) && !(flags & 0x80)) {
230 				ofmem[lmsz].mr_start = base;
231 				ofmem[lmsz].mr_size = (vm_size_t)lmb_size[1];
232 				ofavail[lasz].mr_start = base;
233 				ofavail[lasz].mr_size = (vm_size_t)lmb_size[1];
234 				lmsz++;
235 				lasz++;
236 			}
237 			dmem += 2;
238 		}
239 	}
240 
241 	*msz = lmsz;
242 	*asz = lasz;
243 
244 	return (1);
245 }
246 
247 void
248 chrp_mem_regions(platform_t plat, struct mem_region *phys, int *physsz,
249     struct mem_region *avail, int *availsz)
250 {
251 	vm_offset_t maxphysaddr;
252 	int i;
253 
254 	ofw_mem_regions(phys, physsz, avail, availsz);
255 	parse_drconf_memory(phys, physsz, avail, availsz);
256 
257 	/*
258 	 * On some firmwares (SLOF), some memory may be marked available that
259 	 * doesn't actually exist. This manifests as an extension of the last
260 	 * available segment past the end of physical memory, so truncate that
261 	 * one.
262 	 */
263 	maxphysaddr = 0;
264 	for (i = 0; i < *physsz; i++)
265 		if (phys[i].mr_start + phys[i].mr_size > maxphysaddr)
266 			maxphysaddr = phys[i].mr_start + phys[i].mr_size;
267 
268 	for (i = 0; i < *availsz; i++)
269 		if (avail[i].mr_start + avail[i].mr_size > maxphysaddr)
270 			avail[i].mr_size = maxphysaddr - avail[i].mr_start;
271 }
272 
273 static vm_offset_t
274 chrp_real_maxaddr(platform_t plat)
275 {
276 	return (realmaxaddr);
277 }
278 
279 static u_long
280 chrp_timebase_freq(platform_t plat, struct cpuref *cpuref)
281 {
282 	phandle_t phandle;
283 	int32_t ticks = -1;
284 
285 	phandle = cpuref->cr_hwref;
286 
287 	OF_getencprop(phandle, "timebase-frequency", &ticks, sizeof(ticks));
288 
289 	if (ticks <= 0)
290 		panic("Unable to determine timebase frequency!");
291 
292 	return (ticks);
293 }
294 
295 static int
296 chrp_smp_first_cpu(platform_t plat, struct cpuref *cpuref)
297 {
298 	char buf[8];
299 	phandle_t cpu, dev, root;
300 	int res, cpuid;
301 
302 	root = OF_peer(0);
303 
304 	dev = OF_child(root);
305 	while (dev != 0) {
306 		res = OF_getprop(dev, "name", buf, sizeof(buf));
307 		if (res > 0 && strcmp(buf, "cpus") == 0)
308 			break;
309 		dev = OF_peer(dev);
310 	}
311 	if (dev == 0) {
312 		/*
313 		 * psim doesn't have a name property on the /cpus node,
314 		 * but it can be found directly
315 		 */
316 		dev = OF_finddevice("/cpus");
317 		if (dev == 0)
318 			return (ENOENT);
319 	}
320 
321 	cpu = OF_child(dev);
322 
323 	while (cpu != 0) {
324 		res = OF_getprop(cpu, "device_type", buf, sizeof(buf));
325 		if (res > 0 && strcmp(buf, "cpu") == 0)
326 			break;
327 		cpu = OF_peer(cpu);
328 	}
329 	if (cpu == 0)
330 		return (ENOENT);
331 
332 	cpuref->cr_hwref = cpu;
333 	res = OF_getencprop(cpu, "ibm,ppc-interrupt-server#s", &cpuid,
334 	    sizeof(cpuid));
335 	if (res <= 0)
336 		res = OF_getencprop(cpu, "reg", &cpuid, sizeof(cpuid));
337 	if (res <= 0)
338 		cpuid = 0;
339 	cpuref->cr_cpuid = cpuid;
340 
341 	return (0);
342 }
343 
344 static int
345 chrp_smp_next_cpu(platform_t plat, struct cpuref *cpuref)
346 {
347 	char buf[8];
348 	phandle_t cpu;
349 	int i, res, cpuid;
350 
351 	/* Check for whether it should be the next thread */
352 	res = OF_getproplen(cpuref->cr_hwref, "ibm,ppc-interrupt-server#s");
353 	if (res > 0) {
354 		cell_t interrupt_servers[res/sizeof(cell_t)];
355 		OF_getencprop(cpuref->cr_hwref, "ibm,ppc-interrupt-server#s",
356 		    interrupt_servers, res);
357 		for (i = 0; i < res/sizeof(cell_t) - 1; i++) {
358 			if (interrupt_servers[i] == cpuref->cr_cpuid) {
359 				cpuref->cr_cpuid = interrupt_servers[i+1];
360 				return (0);
361 			}
362 		}
363 	}
364 
365 	/* Next CPU core/package */
366 	cpu = OF_peer(cpuref->cr_hwref);
367 	while (cpu != 0) {
368 		res = OF_getprop(cpu, "device_type", buf, sizeof(buf));
369 		if (res > 0 && strcmp(buf, "cpu") == 0)
370 			break;
371 		cpu = OF_peer(cpu);
372 	}
373 	if (cpu == 0)
374 		return (ENOENT);
375 
376 	cpuref->cr_hwref = cpu;
377 	res = OF_getencprop(cpu, "ibm,ppc-interrupt-server#s", &cpuid,
378 	    sizeof(cpuid));
379 	if (res <= 0)
380 		res = OF_getencprop(cpu, "reg", &cpuid, sizeof(cpuid));
381 	if (res <= 0)
382 		cpuid = 0;
383 	cpuref->cr_cpuid = cpuid;
384 
385 	return (0);
386 }
387 
388 static int
389 chrp_smp_get_bsp(platform_t plat, struct cpuref *cpuref)
390 {
391 	ihandle_t inst;
392 	phandle_t bsp, chosen;
393 	int res, cpuid;
394 
395 	chosen = OF_finddevice("/chosen");
396 	if (chosen == 0)
397 		return (ENXIO);
398 
399 	res = OF_getencprop(chosen, "cpu", &inst, sizeof(inst));
400 	if (res < 0)
401 		return (ENXIO);
402 
403 	bsp = OF_instance_to_package(inst);
404 
405 	/* Pick the primary thread. Can it be any other? */
406 	cpuref->cr_hwref = bsp;
407 	res = OF_getencprop(bsp, "ibm,ppc-interrupt-server#s", &cpuid,
408 	    sizeof(cpuid));
409 	if (res <= 0)
410 		res = OF_getencprop(bsp, "reg", &cpuid, sizeof(cpuid));
411 	if (res <= 0)
412 		cpuid = 0;
413 	cpuref->cr_cpuid = cpuid;
414 
415 	return (0);
416 }
417 
418 #ifdef SMP
419 static int
420 chrp_smp_start_cpu(platform_t plat, struct pcpu *pc)
421 {
422 	cell_t start_cpu;
423 	int result, err, timeout;
424 
425 	if (!rtas_exists()) {
426 		printf("RTAS uninitialized: unable to start AP %d\n",
427 		    pc->pc_cpuid);
428 		return (ENXIO);
429 	}
430 
431 	start_cpu = rtas_token_lookup("start-cpu");
432 	if (start_cpu == -1) {
433 		printf("RTAS unknown method: unable to start AP %d\n",
434 		    pc->pc_cpuid);
435 		return (ENXIO);
436 	}
437 
438 	ap_pcpu = pc;
439 	powerpc_sync();
440 
441 	result = rtas_call_method(start_cpu, 3, 1, pc->pc_cpuid, EXC_RST, pc,
442 	    &err);
443 	if (result < 0 || err != 0) {
444 		printf("RTAS error (%d/%d): unable to start AP %d\n",
445 		    result, err, pc->pc_cpuid);
446 		return (ENXIO);
447 	}
448 
449 	timeout = 10000;
450 	while (!pc->pc_awake && timeout--)
451 		DELAY(100);
452 
453 	return ((pc->pc_awake) ? 0 : EBUSY);
454 }
455 
456 static struct cpu_group *
457 chrp_smp_topo(platform_t plat)
458 {
459 	struct pcpu *pc, *last_pc;
460 	int i, ncores, ncpus;
461 
462 	ncores = ncpus = 0;
463 	last_pc = NULL;
464 	for (i = 0; i <= mp_maxid; i++) {
465 		pc = pcpu_find(i);
466 		if (pc == NULL)
467 			continue;
468 		if (last_pc == NULL || pc->pc_hwref != last_pc->pc_hwref)
469 			ncores++;
470 		last_pc = pc;
471 		ncpus++;
472 	}
473 
474 	if (ncpus % ncores != 0) {
475 		printf("WARNING: Irregular SMP topology. Performance may be "
476 		     "suboptimal (%d CPUS, %d cores)\n", ncpus, ncores);
477 		return (smp_topo_none());
478 	}
479 
480 	/* Don't do anything fancier for non-threaded SMP */
481 	if (ncpus == ncores)
482 		return (smp_topo_none());
483 
484 	return (smp_topo_1level(CG_SHARE_L1, ncpus / ncores, CG_FLAG_SMT));
485 }
486 #endif
487 
488 static void
489 chrp_reset(platform_t platform)
490 {
491 	OF_reboot();
492 }
493 
494 #ifdef __powerpc64__
495 static void
496 phyp_cpu_idle(sbintime_t sbt)
497 {
498 	register_t msr;
499 
500 	msr = mfmsr();
501 
502 	mtmsr(msr & ~PSL_EE);
503 	if (sched_runnable()) {
504 		mtmsr(msr);
505 		return;
506 	}
507 
508 	phyp_hcall(H_CEDE); /* Re-enables interrupts internally */
509 	mtmsr(msr);
510 }
511 
512 static void
513 chrp_smp_ap_init(platform_t platform)
514 {
515 	if (!(mfmsr() & PSL_HV)) {
516 		/* Register VPA */
517 		phyp_hcall(H_REGISTER_VPA, 1UL, PCPU_GET(cpuid),
518 		    splpar_vpa[PCPU_GET(cpuid)]);
519 
520 		/* Set interrupt priority */
521 		phyp_hcall(H_CPPR, 0xff);
522 	}
523 }
524 #else
525 static void
526 chrp_smp_ap_init(platform_t platform)
527 {
528 }
529 #endif
530 
531