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