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