platform_chrp.c revision 266020
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: stable/10/sys/powerpc/pseries/platform_chrp.c 266020 2014-05-14 14:17:51Z ian $");
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
57extern void *ap_pcpu;
58#endif
59
60#ifdef __powerpc64__
61static uint8_t splpar_vpa[640] __aligned(64);
62#endif
63
64static vm_offset_t realmaxaddr = VM_MAX_ADDRESS;
65
66static int chrp_probe(platform_t);
67static int chrp_attach(platform_t);
68void chrp_mem_regions(platform_t, struct mem_region *phys, int *physsz,
69    struct mem_region *avail, int *availsz);
70static vm_offset_t chrp_real_maxaddr(platform_t);
71static u_long chrp_timebase_freq(platform_t, struct cpuref *cpuref);
72static int chrp_smp_first_cpu(platform_t, struct cpuref *cpuref);
73static int chrp_smp_next_cpu(platform_t, struct cpuref *cpuref);
74static int chrp_smp_get_bsp(platform_t, struct cpuref *cpuref);
75static void chrp_smp_ap_init(platform_t);
76#ifdef SMP
77static int chrp_smp_start_cpu(platform_t, struct pcpu *cpu);
78static struct cpu_group *chrp_smp_topo(platform_t plat);
79#endif
80static void chrp_reset(platform_t);
81#ifdef __powerpc64__
82#include "phyp-hvcall.h"
83static void phyp_cpu_idle(sbintime_t sbt);
84#endif
85
86static 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
107static platform_def_t chrp_platform = {
108	"chrp",
109	chrp_methods,
110	0
111};
112
113PLATFORM_DEF(chrp_platform);
114
115static int
116chrp_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
124static int
125chrp_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
160static int
161parse_drconf_memory(int *msz, int *asz, struct mem_region *ofmem,
162		    struct mem_region *ofavail)
163{
164	phandle_t phandle;
165	vm_offset_t base;
166	int i, idx, len, lasz, lmsz, res;
167	uint32_t lmb_size[2];
168	unsigned long *dmem, flags;
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. */
212		dmem = (void*)&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
237void
238chrp_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(physsz, availsz, phys, avail);
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
263static vm_offset_t
264chrp_real_maxaddr(platform_t plat)
265{
266	return (realmaxaddr);
267}
268
269static u_long
270chrp_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
285static int
286chrp_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
334static int
335chrp_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
378static int
379chrp_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
409static int
410chrp_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
446static struct cpu_group *
447chrp_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
478static void
479chrp_reset(platform_t platform)
480{
481	OF_reboot();
482}
483
484#ifdef __powerpc64__
485static void
486phyp_cpu_idle(sbintime_t sbt)
487{
488	phyp_hcall(H_CEDE);
489}
490
491static void
492chrp_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
503static void
504chrp_smp_ap_init(platform_t platform)
505{
506}
507#endif
508
509