NetBSD-5.0BETA/i386のSMP実装#3

再びconfigure()を追ってみる。
cpu_configure()が行われた後、SMPに関係ありそうな部分を探していくと、以下のようなコードが見つかった:

	/* Boot the secondary processors. */
	for (CPU_INFO_FOREACH(cii, ci)) {
		uvm_cpu_attach(ci);
	}
	mp_online = true;
#if defined(MULTIPROCESSOR)
	cpu_boot_secondary_processors();
#endif

uvm_cpu_attach()はCPUごとに必要なデータを初期化しているらしい。
ページングに必要なものなんだと思われる。

/*
 * uvm_cpu_attach: initialize per-CPU data structures.
 */

void
uvm_cpu_attach(struct cpu_info *ci)
{
	struct pgflbucket *bucketarray;
	struct pgfreelist pgfl;
	struct uvm_cpu *ucpu;
	vsize_t bucketcount;
	int lcv;

	if (CPU_IS_PRIMARY(ci)) {
		/* Already done in uvm_page_init(). */
		return;
	}

	/* Add more reserve pages for this CPU. */
	uvmexp.reserve_kernel += vm_page_reserve_kernel;

	/* Configure this CPU's free lists. */
	bucketcount = uvmexp.ncolors * VM_NFREELIST;
	bucketarray = malloc(bucketcount * sizeof(struct pgflbucket),
	    M_VMPAGE, M_WAITOK);
	ucpu = &uvm.cpus[cpu_index(ci)];
	ci->ci_data.cpu_uvm = ucpu;
	for (lcv = 0; lcv < VM_NFREELIST; lcv++) {
		pgfl.pgfl_buckets = (bucketarray + (lcv * uvmexp.ncolors));
		uvm_page_init_buckets(&pgfl);
		ucpu->page_free[lcv].pgfl_buckets = pgfl.pgfl_buckets;
	}
}

なんか豪快なことが書いてある。text領域にパッチを当てるらしい。
うーん、何の為に?

void
cpu_boot_secondary_processors(void)
{
	struct cpu_info *ci;
	u_long i;

	/* Now that we know the number of CPUs, patch the text segment. */
	x86_patch();

	for (i=0; i < maxcpus; i++) {
		ci = cpu_lookup(i);
		if (ci == NULL)
			continue;
		if (ci->ci_data.cpu_idlelwp == NULL)
			continue;
		if ((ci->ci_flags & CPUF_PRESENT) == 0)
			continue;
		if (ci->ci_flags & (CPUF_BSP|CPUF_SP|CPUF_PRIMARY))
			continue;
		cpu_boot_secondary(ci);
	}

	x86_mp_online = true;

	/* Now that we know about the TSC, attach the timecounter. */
	tsc_tc_init();

	/* Enable zeroing of pages in the idle loop if we have SSE2. */
	vm_page_zero_enable = ((cpu_feature & CPUID_SSE2) != 0);
}

x86_patch()のコードを眺めてみると、どうもmutex関数などをハード構成に応じて最適な関数で上書きしているらしい。
この為にテキスト領域のライトプロテクトを外したりしている。
なぜ関数ポインタではいけないのかがちょっと分からない。

void
cpu_boot_secondary(struct cpu_info *ci)
{
	int64_t drift;
	u_long psl;
	int i;

	atomic_or_32(&ci->ci_flags, CPUF_GO);
	for (i = 100000; (!(ci->ci_flags & CPUF_RUNNING)) && i > 0; i--) {
		i8254_delay(10);
	}
	if ((ci->ci_flags & CPUF_RUNNING) == 0) {
		aprint_error_dev(ci->ci_dev, "failed to start\n");
#if defined(MPDEBUG) && defined(DDB)
		printf("dropping into debugger; continue from here to resume boot\n");
		Debugger();
#endif
	} else {
		/* Synchronize TSC again, check for drift. */
		drift = ci->ci_data.cpu_cc_skew;
		psl = x86_read_psl();
		x86_disable_intr();
		wbinvd();
		tsc_sync_bp(ci);
		tsc_sync_bp(ci);
		x86_write_psl(psl);
		drift -= ci->ci_data.cpu_cc_skew;
		aprint_debug_dev(ci->ci_dev, "TSC skew=%lld drift=%lld\n",
		    (long long)ci->ci_data.cpu_cc_skew, (long long)drift);
		tsc_sync_drift(drift);
	}
}

cpu_boot_secondary()では、CPU起床の待ち合わせとTSCの同期をやっているようだ。
しかし、一体どこが起床のトリガになり、2コア目以降のプロセッサのエントリアドレスがどこにあるのかがさっぱりわからない。