NetBSD-5.0BETA/macppcのSMP実装を駆け足で

i386のSMP実装がどうなっているのかではなく、一般的なSMP実装がどうなっているのかについて調べたいので、macppcも眺めてみる。

void
cpuattach(parent, self, aux)
	struct device *parent, *self;
	void *aux;
{
	struct cpu_info *ci;
	struct confargs *ca = aux;
	int id = ca->ca_reg[0];

	ci = cpu_attach_common(self, id);
	if (ci == NULL)
		return;

cpu_attach_common()を呼び出している。

struct cpu_info *
cpu_attach_common(struct device *self, int id)
{
	struct cpu_info *ci;
	u_int pvr, vers;

	ci = &cpu_info[id];
#ifndef MULTIPROCESSOR
	/*
	 * If this isn't the primary CPU, print an error message
	 * and just bail out.
	 */
	if (id != 0) {
		aprint_normal(": ID %d\n", id);
		aprint_normal("%s: processor off-line; multiprocessor support "
		    "not present in kernel\n", self->dv_xname);
		return (NULL);
	}
#endif

	ci->ci_cpuid = id;
	ci->ci_intrdepth = -1;
	ci->ci_dev = self;
	ci->ci_idlespin = cpu_idlespin;

	pvr = mfpvr();
	vers = (pvr >> 16) & 0xffff;

	switch (id) {
	case 0:
		/* load my cpu_number to PIR */
		switch (vers) {
		case MPC601:
		case MPC604:
		case MPC604e:
		case MPC604ev:
		case MPC7400:
		case MPC7410:
		case MPC7447A:
		case MPC7448:
		case MPC7450:
		case MPC7455:
		case MPC7457:
			mtspr(SPR_PIR, id);
		}
		cpu_setup(self, ci);
		break;
	default:
		if (id >= CPU_MAXNUM) {
			aprint_normal(": more than %d cpus?\n", CPU_MAXNUM);
			panic("cpuattach");
		}
#ifndef MULTIPROCESSOR
		aprint_normal(" not configured\n");
		return NULL;
#else
		mi_cpu_attach(ci);
		break;
#endif
	}
	return (ci);
}

cpu_attach_common()では、i386の時と同様にmi_cpu_attach()を呼び出している。
これによってスケジューラにCPUが認識される(はず)。
MULTIPROCESSORがコンフィグで無効になっていれば、mi_cpu_attach()されないのでユニプロセッサ構成になるという事らしい(多分)。
また、その手前で呼ばれているcpu_setup()はCPUの種類毎に固有のパラメータ設定/取得を行っている模様(あまりSMPとは関係なさそう)。

	if (ci->ci_khz == 0) {
		cpu_OFgetspeed(self, ci);
	}

	if (id > 0) {
#ifdef MULTIPROCESSOR
		cpu_spinup(self, ci);
#endif
		return;
	}

再びcpuattach()に戻る。
idが0以降ならcpu_spinup()を実行。多分これでCPUを起こしている。

#ifdef MULTIPROCESSOR
extern volatile u_int cpu_spinstart_ack;

int
cpu_spinup(struct device *self, struct cpu_info *ci)
{
	volatile struct cpu_hatch_data hatch_data, *h = &hatch_data;
	struct pglist mlist;
	int i, error, pvr, vers;
	char *cp, *hp;

	pvr = mfpvr();
	vers = pvr >> 16;
	KASSERT(ci != curcpu());

	/*
	 * Allocate some contiguous pages for the intteup PCB and stack
	 * from the lowest 256MB (because bat0 always maps it va == pa).
	 * Must be 16 byte aligned.
	 */
	error = uvm_pglistalloc(INTSTK, 0x10000, 0x10000000, 16, 0,
	    &mlist, 1, 1);
	if (error) {
		aprint_error(": unable to allocate idle stack\n");
		return -1;
	}

	KASSERT(ci != &cpu_info[0]);

	cp = (void *)VM_PAGE_TO_PHYS(TAILQ_FIRST(&mlist));
	memset(cp, 0, INTSTK);

	ci->ci_intstk = cp;

	/* Now allocate a hatch stack */
	error = uvm_pglistalloc(0x1000, 0x10000, 0x10000000, 16, 0,
	    &mlist, 1, 1);
	if (error) {
		aprint_error(": unable to allocate hatch stack\n");
		return -1;
	}

	hp = (void *)VM_PAGE_TO_PHYS(TAILQ_FIRST(&mlist));
	memset(hp, 0, 0x1000);

	/* Initialize secondary cpu's initial lwp to its idlelwp. */
	ci->ci_curlwp = ci->ci_data.cpu_idlelwp;
	ci->ci_curpcb = &ci->ci_curlwp->l_addr->u_pcb;
	ci->ci_curpm = ci->ci_curpcb->pcb_pm;

	cpu_hatch_data = h;
	h->running = 0;
	h->self = self;
	h->ci = ci;
	h->pir = ci->ci_cpuid;

	cpu_hatch_stack = (uint32_t)hp;
	ci->ci_lasttb = cpu_info[0].ci_lasttb;

	/* copy special registers */

	h->hid0 = mfspr(SPR_HID0);
	
	__asm volatile ("mfsdr1 %0" : "=r"(h->sdr1));
	for (i = 0; i < 16; i++) {
		__asm ("mfsrin %0,%1" : "=r"(h->sr[i]) :
		       "r"(i << ADDR_SR_SHFT));
	}
	if (oeacpufeat & OEACPU_64)
		h->asr = mfspr(SPR_ASR);
	else
		h->asr = 0;

	/* copy the bat regs */
	__asm volatile ("mfibatu %0,0" : "=r"(h->batu[0]));
	__asm volatile ("mfibatl %0,0" : "=r"(h->batl[0]));
	__asm volatile ("mfibatu %0,1" : "=r"(h->batu[1]));
	__asm volatile ("mfibatl %0,1" : "=r"(h->batl[1]));
	__asm volatile ("mfibatu %0,2" : "=r"(h->batu[2]));
	__asm volatile ("mfibatl %0,2" : "=r"(h->batl[2]));
	__asm volatile ("mfibatu %0,3" : "=r"(h->batu[3]));
	__asm volatile ("mfibatl %0,3" : "=r"(h->batl[3]));
	__asm volatile ("sync; isync");

	if (md_setup_trampoline(h, ci) == -1)
		return -1;
	md_presync_timebase(h);
	md_start_timebase(h);

	/* wait for secondary printf */

	delay(200000);

	if (h->running < 1) {
		aprint_error("%d:CPU %d didn't start %d\n", cpu_spinstart_ack,
		    ci->ci_cpuid, cpu_spinstart_ack);
		Debugger();
		return -1;
	}

	/* Register IPI Interrupt */
	if (ipiops.ppc_establish_ipi)
		ipiops.ppc_establish_ipi(IST_LEVEL, IPL_HIGH, NULL);

	return 0;
}

cpuinfoやlwp、cpu_hatch_dataなどを準備し、md_setup_trampoline()を実行している。
その後のmd_*_timebase()はi386におけるtscのセットアップと同じだと思う(多分)。
最後にIPI Interrupt(謎)を登録して終わり。

#ifdef MULTIPROCESSOR

int
md_setup_trampoline(volatile struct cpu_hatch_data *h, struct cpu_info *ci)
{
#ifdef OPENPIC
	if (openpic_base) {
		uint32_t kl_base = 0x80000000;	/* XXX */
		uint32_t gpio = kl_base + 0x5c;	/* XXX */
		u_int node, off;
		char cpupath[32];

		/* construct an absolute branch instruction */
		*(u_int *)EXC_RST =		/* ba cpu_spinup_trampoline */
		    0x48000002 | (u_int)cpu_spinup_trampoline;
		__syncicache((void *)EXC_RST, 0x100);
		h->running = -1;

		/* see if there's an OF property for the reset register */
		sprintf(cpupath, "/cpus/@%x", ci->ci_cpuid);
		node = OF_finddevice(cpupath);
		if (node == -1) {
			printf(": no OF node for CPU %d?\n", ci->ci_cpuid);
			return -1;
		}
		if (OF_getprop(node, "soft-reset", &off, 4) == 4) {
			gpio = kl_base + off;
		}

		/* Start secondary CPU. */
#if 1
		out8(gpio, 4);
		out8(gpio, 0);
#else
		openpic_write(OPENPIC_PROC_INIT, (1 << 1));
#endif
	} else {
#endif /* OPENPIC */
		/* Start secondary CPU and stop timebase. */
		out32(0xf2800000, (int)cpu_spinup_trampoline);
		ppc_send_ipi(1, PPC_IPI_NOMESG);
#ifdef OPENPIC
	}
#endif
	return 1;
}

md_setup_trampoline()では、単純にセカンダリCPUを起こしているのだと思われる。
その際、エントリポイントもしっかり指定している(cpu_spinup_trampoline())。

NetBSD-5.0BETA/macppcのSMP実装を駆け足で#2 - かーねる・う゛いえむにっきで書いたが、macppcの場合はopenfirmwareを使って先に起こしているようだ。
ここでは#ifdef OPENPICに該当しないので、何もしてないんだと思われる。
とはいえ、やっている事自体はほぼ同じなのでこの先の説明はほぼ当てはまるはずだと思う。
ここからは関数名がi386と共通になっているように思う。

#if defined(MULTIPROCESSOR) && !defined(PPC_OEA64) && !defined (PPC_IBM4XX)
ENTRY(cpu_spinup_trampoline)
	li	%r0,0
	mtmsr	%r0
	isync

	lis	%r4,_C_LABEL(cpu_hatch_stack)@ha
	lwz	%r1,_C_LABEL(cpu_hatch_stack)@l(%r4)

	bl	_C_LABEL(cpu_hatch)
	mr	%r1,%r3
	b	_C_LABEL(idle_loop)

cpu_spinup_trampoline()では、スタックポインタをセットしてcpu_hatch()、idle_loop()の順に呼ぶだけで特にi386版と変わらない。

register_t
cpu_hatch(void)
{
	volatile struct cpu_hatch_data *h = cpu_hatch_data;
	struct cpu_info * const ci = h->ci;
	u_int msr;
	int i;

	/* Initialize timebase. */
	__asm ("mttbl %0; mttbu %0; mttbl %0" :: "r"(0));

	/*
	 * Set PIR (Processor Identification Register).  i.e. whoami
	 * Note that PIR is read-only on some CPU versions, so we write to it
	 * only if it has a different value than we need.
	 */

	msr = mfspr(SPR_PIR);
	if (msr != h->pir)
		mtspr(SPR_PIR, h->pir);
	
	__asm volatile ("mtsprg 0,%0" :: "r"(ci));
	cpu_spinstart_ack = 0;

	/* Initialize MMU. */
	__asm ("mtibatu 0,%0" :: "r"(h->batu[0]));
	__asm ("mtibatl 0,%0" :: "r"(h->batl[0]));
	__asm ("mtibatu 1,%0" :: "r"(h->batu[1]));
	__asm ("mtibatl 1,%0" :: "r"(h->batl[1]));
	__asm ("mtibatu 2,%0" :: "r"(h->batu[2]));
	__asm ("mtibatl 2,%0" :: "r"(h->batl[2]));
	__asm ("mtibatu 3,%0" :: "r"(h->batu[3]));
	__asm ("mtibatl 3,%0" :: "r"(h->batl[3]));

	mtspr(SPR_HID0, h->hid0);

	__asm ("mtibatl 0,%0; mtibatu 0,%1; mtdbatl 0,%0; mtdbatu 0,%1;"
	    :: "r"(battable[0].batl), "r"(battable[0].batu));

	__asm volatile ("sync");
	for (i = 0; i < 16; i++)
		__asm ("mtsrin %0,%1" :: "r"(h->sr[i]), "r"(i << ADDR_SR_SHFT));
	__asm volatile ("sync; isync");

	if (oeacpufeat & OEACPU_64)
		mtspr(SPR_ASR, h->asr);

	cpu_spinstart_ack = 1;
	__asm ("ptesync");
	__asm ("mtsdr1 %0" :: "r"(h->sdr1));
	__asm volatile ("sync; isync");

	cpu_spinstart_ack = 5;
	for (i = 0; i < 16; i++)
		__asm ("mfsrin %0,%1" : "=r"(h->sr[i]) :
		       "r"(i << ADDR_SR_SHFT));

	/* Enable I/D address translations. */
	msr = mfmsr();
	msr |= PSL_IR|PSL_DR|PSL_ME|PSL_RI;
	mtmsr(msr);
	__asm volatile ("sync; isync");
	cpu_spinstart_ack = 2;

	md_sync_timebase(h);

	cpu_setup(h->self, ci);

	h->running = 1;
	__asm volatile ("sync; isync");

	while (start_secondary_cpu == 0)
		;

	__asm volatile ("sync; isync");

	aprint_normal("cpu%d started\n", curcpu()->ci_index);
	__asm volatile ("mtdec %0" :: "r"(ticks_per_intr));

	md_setup_interrupts();

	ci->ci_ipending = 0;
	ci->ci_cpl = 0;

	mtmsr(mfmsr() | PSL_EE);
	return ci->ci_data.cpu_idlelwp->l_addr->u_pcb.pcb_sp;
}

cpu_hatch()の方は、インラインアセンブリやハードウェアレジスタ弄りの連続で何をやっているのかさっぱり分からないが、要するにC側のコード内でセカンダリCPUの初期化をやっているという事だろう(ついでにcpuinfoも設定したりしているが)。

なんだか、powerpcの方がコード量的に少なくて追いやすかった。
やはりi386はハードの仕様が複雑だからコードもこんがらがるんだろうか。
それとも単に私の理解力が低いだけか。