xref: /freebsd/sys/powerpc/pseries/platform_chrp.c (revision 5b9c547c)
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/smp.h>
38 #include <vm/vm.h>
39 #include <vm/pmap.h>
40 
41 #include <machine/bus.h>
42 #include <machine/cpu.h>
43 #include <machine/hid.h>
44 #include <machine/platformvar.h>
45 #include <machine/pmap.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 	uint64_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_getprop(phandle, "ibm,lmb-size", lmb_size, sizeof(lmb_size));
186 	if (res == -1)
187 		return (0);
188 	printf("Logical Memory Block size: %d MB\n", lmb_size[1] >> 20);
189 
190 	/* Parse the /ibm,dynamic-memory.
191 	   The first position gives the # of entries. The next two words
192  	   reflect the address of the memory block. The next four words are
193 	   the DRC index, reserved, list index and flags.
194 	   (see PAPR C.6.6.2 ibm,dynamic-reconfiguration-memory)
195 
196 	    #el  Addr   DRC-idx  res   list-idx  flags
197 	   -------------------------------------------------
198 	   | 4 |   8   |   4   |   4   |   4   |   4   |....
199 	   -------------------------------------------------
200 	*/
201 
202 	len = OF_getproplen(phandle, "ibm,dynamic-memory");
203 	if (len > 0) {
204 
205 		/* We have to use a variable length array on the stack
206 		   since we have very limited stack space.
207 		*/
208 		cell_t arr[len/sizeof(cell_t)];
209 
210 		res = OF_getprop(phandle, "ibm,dynamic-memory", &arr,
211 				 sizeof(arr));
212 		if (res == -1)
213 			return (0);
214 
215 		/* Number of elements */
216 		idx = arr[0];
217 
218 		/* First address, in arr[1], arr[2]*/
219 		dmem = (uint64_t*)&arr[1];
220 
221 		for (i = 0; i < idx; i++) {
222 			base = *dmem;
223 			dmem += 2;
224 			flags = *dmem;
225 			/* Use region only if available and not reserved. */
226 			if ((flags & 0x8) && !(flags & 0x80)) {
227 				ofmem[lmsz].mr_start = base;
228 				ofmem[lmsz].mr_size = (vm_size_t)lmb_size[1];
229 				ofavail[lasz].mr_start = base;
230 				ofavail[lasz].mr_size = (vm_size_t)lmb_size[1];
231 				lmsz++;
232 				lasz++;
233 			}
234 			dmem++;
235 		}
236 	}
237 
238 	*msz = lmsz;
239 	*asz = lasz;
240 
241 	return (1);
242 }
243 
244 void
245 chrp_mem_regions(platform_t plat, struct mem_region *phys, int *physsz,
246     struct mem_region *avail, int *availsz)
247 {
248 	vm_offset_t maxphysaddr;
249 	int i;
250 
251 	ofw_mem_regions(phys, physsz, avail, availsz);
252 	parse_drconf_memory(phys, physsz, avail, availsz);
253 
254 	/*
255 	 * On some firmwares (SLOF), some memory may be marked available that
256 	 * doesn't actually exist. This manifests as an extension of the last
257 	 * available segment past the end of physical memory, so truncate that
258 	 * one.
259 	 */
260 	maxphysaddr = 0;
261 	for (i = 0; i < *physsz; i++)
262 		if (phys[i].mr_start + phys[i].mr_size > maxphysaddr)
263 			maxphysaddr = phys[i].mr_start + phys[i].mr_size;
264 
265 	for (i = 0; i < *availsz; i++)
266 		if (avail[i].mr_start + avail[i].mr_size > maxphysaddr)
267 			avail[i].mr_size = maxphysaddr - avail[i].mr_start;
268 }
269 
270 static vm_offset_t
271 chrp_real_maxaddr(platform_t plat)
272 {
273 	return (realmaxaddr);
274 }
275 
276 static u_long
277 chrp_timebase_freq(platform_t plat, struct cpuref *cpuref)
278 {
279 	phandle_t phandle;
280 	int32_t ticks = -1;
281 
282 	phandle = cpuref->cr_hwref;
283 
284 	OF_getprop(phandle, "timebase-frequency", &ticks, sizeof(ticks));
285 
286 	if (ticks <= 0)
287 		panic("Unable to determine timebase frequency!");
288 
289 	return (ticks);
290 }
291 
292 static int
293 chrp_smp_first_cpu(platform_t plat, struct cpuref *cpuref)
294 {
295 	char buf[8];
296 	phandle_t cpu, dev, root;
297 	int res, cpuid;
298 
299 	root = OF_peer(0);
300 
301 	dev = OF_child(root);
302 	while (dev != 0) {
303 		res = OF_getprop(dev, "name", buf, sizeof(buf));
304 		if (res > 0 && strcmp(buf, "cpus") == 0)
305 			break;
306 		dev = OF_peer(dev);
307 	}
308 	if (dev == 0) {
309 		/*
310 		 * psim doesn't have a name property on the /cpus node,
311 		 * but it can be found directly
312 		 */
313 		dev = OF_finddevice("/cpus");
314 		if (dev == 0)
315 			return (ENOENT);
316 	}
317 
318 	cpu = OF_child(dev);
319 
320 	while (cpu != 0) {
321 		res = OF_getprop(cpu, "device_type", buf, sizeof(buf));
322 		if (res > 0 && strcmp(buf, "cpu") == 0)
323 			break;
324 		cpu = OF_peer(cpu);
325 	}
326 	if (cpu == 0)
327 		return (ENOENT);
328 
329 	cpuref->cr_hwref = cpu;
330 	res = OF_getprop(cpu, "ibm,ppc-interrupt-server#s", &cpuid,
331 	    sizeof(cpuid));
332 	if (res <= 0)
333 		res = OF_getprop(cpu, "reg", &cpuid, sizeof(cpuid));
334 	if (res <= 0)
335 		cpuid = 0;
336 	cpuref->cr_cpuid = cpuid;
337 
338 	return (0);
339 }
340 
341 static int
342 chrp_smp_next_cpu(platform_t plat, struct cpuref *cpuref)
343 {
344 	char buf[8];
345 	phandle_t cpu;
346 	int i, res, cpuid;
347 
348 	/* Check for whether it should be the next thread */
349 	res = OF_getproplen(cpuref->cr_hwref, "ibm,ppc-interrupt-server#s");
350 	if (res > 0) {
351 		cell_t interrupt_servers[res/sizeof(cell_t)];
352 		OF_getprop(cpuref->cr_hwref, "ibm,ppc-interrupt-server#s",
353 		    interrupt_servers, res);
354 		for (i = 0; i < res/sizeof(cell_t) - 1; i++) {
355 			if (interrupt_servers[i] == cpuref->cr_cpuid) {
356 				cpuref->cr_cpuid = interrupt_servers[i+1];
357 				return (0);
358 			}
359 		}
360 	}
361 
362 	/* Next CPU core/package */
363 	cpu = OF_peer(cpuref->cr_hwref);
364 	while (cpu != 0) {
365 		res = OF_getprop(cpu, "device_type", buf, sizeof(buf));
366 		if (res > 0 && strcmp(buf, "cpu") == 0)
367 			break;
368 		cpu = OF_peer(cpu);
369 	}
370 	if (cpu == 0)
371 		return (ENOENT);
372 
373 	cpuref->cr_hwref = cpu;
374 	res = OF_getprop(cpu, "ibm,ppc-interrupt-server#s", &cpuid,
375 	    sizeof(cpuid));
376 	if (res <= 0)
377 		res = OF_getprop(cpu, "reg", &cpuid, sizeof(cpuid));
378 	if (res <= 0)
379 		cpuid = 0;
380 	cpuref->cr_cpuid = cpuid;
381 
382 	return (0);
383 }
384 
385 static int
386 chrp_smp_get_bsp(platform_t plat, struct cpuref *cpuref)
387 {
388 	ihandle_t inst;
389 	phandle_t bsp, chosen;
390 	int res, cpuid;
391 
392 	chosen = OF_finddevice("/chosen");
393 	if (chosen == 0)
394 		return (ENXIO);
395 
396 	res = OF_getprop(chosen, "cpu", &inst, sizeof(inst));
397 	if (res < 0)
398 		return (ENXIO);
399 
400 	bsp = OF_instance_to_package(inst);
401 
402 	/* Pick the primary thread. Can it be any other? */
403 	cpuref->cr_hwref = bsp;
404 	res = OF_getprop(bsp, "ibm,ppc-interrupt-server#s", &cpuid,
405 	    sizeof(cpuid));
406 	if (res <= 0)
407 		res = OF_getprop(bsp, "reg", &cpuid, sizeof(cpuid));
408 	if (res <= 0)
409 		cpuid = 0;
410 	cpuref->cr_cpuid = cpuid;
411 
412 	return (0);
413 }
414 
415 #ifdef SMP
416 static int
417 chrp_smp_start_cpu(platform_t plat, struct pcpu *pc)
418 {
419 	cell_t start_cpu;
420 	int result, err, timeout;
421 
422 	if (!rtas_exists()) {
423 		printf("RTAS uninitialized: unable to start AP %d\n",
424 		    pc->pc_cpuid);
425 		return (ENXIO);
426 	}
427 
428 	start_cpu = rtas_token_lookup("start-cpu");
429 	if (start_cpu == -1) {
430 		printf("RTAS unknown method: unable to start AP %d\n",
431 		    pc->pc_cpuid);
432 		return (ENXIO);
433 	}
434 
435 	ap_pcpu = pc;
436 	powerpc_sync();
437 
438 	result = rtas_call_method(start_cpu, 3, 1, pc->pc_cpuid, EXC_RST, pc,
439 	    &err);
440 	if (result < 0 || err != 0) {
441 		printf("RTAS error (%d/%d): unable to start AP %d\n",
442 		    result, err, pc->pc_cpuid);
443 		return (ENXIO);
444 	}
445 
446 	timeout = 10000;
447 	while (!pc->pc_awake && timeout--)
448 		DELAY(100);
449 
450 	return ((pc->pc_awake) ? 0 : EBUSY);
451 }
452 
453 static struct cpu_group *
454 chrp_smp_topo(platform_t plat)
455 {
456 	struct pcpu *pc, *last_pc;
457 	int i, ncores, ncpus;
458 
459 	ncores = ncpus = 0;
460 	last_pc = NULL;
461 	for (i = 0; i <= mp_maxid; i++) {
462 		pc = pcpu_find(i);
463 		if (pc == NULL)
464 			continue;
465 		if (last_pc == NULL || pc->pc_hwref != last_pc->pc_hwref)
466 			ncores++;
467 		last_pc = pc;
468 		ncpus++;
469 	}
470 
471 	if (ncpus % ncores != 0) {
472 		printf("WARNING: Irregular SMP topology. Performance may be "
473 		     "suboptimal (%d CPUS, %d cores)\n", ncpus, ncores);
474 		return (smp_topo_none());
475 	}
476 
477 	/* Don't do anything fancier for non-threaded SMP */
478 	if (ncpus == ncores)
479 		return (smp_topo_none());
480 
481 	return (smp_topo_1level(CG_SHARE_L1, ncpus / ncores, CG_FLAG_SMT));
482 }
483 #endif
484 
485 static void
486 chrp_reset(platform_t platform)
487 {
488 	OF_reboot();
489 }
490 
491 #ifdef __powerpc64__
492 static void
493 phyp_cpu_idle(sbintime_t sbt)
494 {
495 	phyp_hcall(H_CEDE);
496 }
497 
498 static void
499 chrp_smp_ap_init(platform_t platform)
500 {
501 	if (!(mfmsr() & PSL_HV)) {
502 		/* Register VPA */
503 		phyp_hcall(H_REGISTER_VPA, 1UL, PCPU_GET(cpuid),
504 		    splpar_vpa[PCPU_GET(cpuid)]);
505 
506 		/* Set interrupt priority */
507 		phyp_hcall(H_CPPR, 0xff);
508 	}
509 }
510 #else
511 static void
512 chrp_smp_ap_init(platform_t platform)
513 {
514 }
515 #endif
516 
517