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

どこから2コア目以降が起動するのか分かった(と思う)。
NetBSD-5.0BETA/i386のSMP実装#1 - かーねる・う゛いえむにっきでちょっと触れたmpbios_scan()の中で、lapic_boot_init()を呼んでいる箇所がある:

	/*
	 * looks like we've got a MP system.  start setting up
	 * infrastructure..
	 * XXX is this the right place??
	 */

#if NACPI > 0
	if (mpacpi_ncpu == 0) {
#endif
		lapic_base = LAPIC_BASE;
		if (mp_cth != NULL)
			lapic_base = (paddr_t)mp_cth->apic_address;

#if NLAPIC > 0
		lapic_boot_init(lapic_base);
#endif
#if NACPI > 0
	}
#endif

lapic_boot_init()はlapic_map()を呼ぶ:

/*
 * Initialize fixed idt vectors for use by local apic.
 */
void
lapic_boot_init(paddr_t lapic_base)
{
	lapic_map(lapic_base);

lapic_map()の最後で、MULTIPROCESSORならcpu_init_first()を呼んでいる:

#ifdef MULTIPROCESSOR
	cpu_init_first();	/* catch up to changed cpu_number() */
#endif

長ったらしいがもう少し続く。cpu_init_first()の頭でcpu_copy_trampoline()が呼ばれている:

/*
 * Runs once per boot once multiprocessor goo has been detected and
 * the local APIC on the boot processor has been mapped.
 *
 * Called from lapic_boot_init() (from mpbios_scan()).
 */
void
cpu_init_first(void)
{

	cpu_info_primary.ci_cpuid = lapic_cpu_number();
	cpu_copy_trampoline();

このcpu_copy_trampoline()は2コア目以降が有効になった時のエントリアドレスに起動用コードを配置(コピー)している。

static void
cpu_copy_trampoline(void)
{
	/*
	 * Copy boot code.
	 */
	extern u_char cpu_spinup_trampoline[];
	extern u_char cpu_spinup_trampoline_end[];
	
	vaddr_t mp_trampoline_vaddr;

	mp_trampoline_vaddr = uvm_km_alloc(kernel_map, PAGE_SIZE, 0,
	    UVM_KMF_VAONLY);

	pmap_kenter_pa(mp_trampoline_vaddr, mp_trampoline_paddr,
	    VM_PROT_READ | VM_PROT_WRITE);
	pmap_update(pmap_kernel());
	memcpy((void *)mp_trampoline_vaddr,
	    cpu_spinup_trampoline,
	    cpu_spinup_trampoline_end - cpu_spinup_trampoline);

	pmap_kremove(mp_trampoline_vaddr, PAGE_SIZE);
	pmap_update(pmap_kernel());
	uvm_km_free(kernel_map, mp_trampoline_vaddr, PAGE_SIZE, UVM_KMF_VAONLY);
}

コピーの作業が終わった後でSMPが有効化され、各CPUはcpu_spinup_trampolineから起動する。
この関数の実体はarch/i386/i386/mptramp.Sにある:

	.text
	.align 4,0x0
	.code16
_C_LABEL(cpu_spinup_trampoline):
	cli
	xorw    %ax,%ax
	movw    %ax, %ds
	movw    %ax, %es
	movw    %ax, %ss
	data32 addr32 lgdt    (gdt_desc)      # load flat descriptor table
	movl    %cr0, %eax       # get cr0
	orl     $0x1, %eax      # enable protected mode
	movl    %eax, %cr0      # doit
	ljmpl    $0x8, $mp_startup

起動用gdtをロード、cr0をクリアし32bitプロテクトモードへ切り替え、ljmplでコードセグメントをロード。

_TRMP_LABEL(mp_startup)
	.code32

	movl    $0x10, %eax     # data segment
	movw    %ax, %ds
	movw    %ax, %ss
	movw    %ax, %es
	movw    %ax, %fs
	movw    %ax, %gs
	
	movl    $ (MP_TRAMPOLINE+PAGE_SIZE-16),%esp  # bootstrap stack end,
						     # with scratch space..
	
#ifdef MPDEBUG
	leal    RELOC(cpu_trace),%edi       
#endif

	HALT(0x1)
	/* First, reset the PSL. */
	pushl   $PSL_MBO
	popfl
	
	movl	RELOC(pmap_largepages),%eax
	orl	%eax,%eax
	jz	1f
	movl	%cr4,%eax
	orl	$CR4_PSE,%eax
	movl	%eax,%cr4
1:

	movl	RELOC(mp_pdirpa),%ecx
	HALTT(0x5,%ecx)
	
        /* Load base of page directory and enable mapping. */
        movl    %ecx,%cr3               # load ptd addr into mmu
        movl    %cr0,%eax               # get control word
                                        # enable paging & NPX emulation
        orl     $(CR0_PE|CR0_PG|CR0_NE|CR0_TS|CR0_EM|CR0_MP|CR0_WP),%eax
        movl    %eax,%cr0               # and page NOW!

#ifdef MPDEBUG
	leal    _C_LABEL(cpu_trace),%edi
#endif
	HALT(0x6)

/*
 * ok, we are now running with paging enabled and sharing page tables
 * with cpu0.  figure out which processor we really are, what stack we
 * should be on, etc.
 */

	/* Don't touch lapic until BP has done init sequence. */
1:
	movl	_C_LABEL(cpu_starting),%ecx
	pause
	testl	%ecx, %ecx
	jz	1b
	
	HALTT(0x7, %ecx)

	/* %ecx points at our cpu_info structure. */

	movw    $((MAXGDTSIZ*8) - 1), 6(%esp)	# prepare segment descriptor
	movl    CPU_INFO_GDT(%ecx), %eax	# for our real gdt
	movl    %eax, 8(%esp)
	lgdt	6(%esp)
	HALT(0x8)	
	jmp	1f
	nop
1:	
	HALT(0x12)
	movl    $GSEL(GDATA_SEL, SEL_KPL),%eax 	#switch to new segment
	movl    %eax,%ds
	movl    %eax,%es
	movl    %eax,%ss
	HALT(0x13)
	pushl   $GSEL(GCODE_SEL, SEL_KPL)
	pushl	$mp_cont
	HALT(0x14)
	lret

データセグメントをロード、どこかからスタックへのアドレスを算出してスタックポインタに設定、ページングを有効化、メインCPUとの待ち合わせを行ってこのCPUに用意されたGDTをロード、再度セグメントを切り替えmp_cont()のアドレスをスタックに積み、mp_cont()に向かってロングリターン。

mp_cont:
	HALT(0x15)

	movl	CPU_INFO_IDLELWP(%ecx),%esi
	movl	L_ADDR(%esi),%esi
	
	/* %esi now points at our PCB. */
		
	HALTT(0x19, %esi)
	
	movl	PCB_ESP(%esi),%esp
	movl	PCB_EBP(%esi),%ebp
	
	HALT(0x20)	
	/* Switch address space. */
	movl	PCB_CR3(%esi),%eax
	HALTT(0x22, %eax)		
	movl	%eax,%cr3
	HALT(0x25)
	/* load segment registers. */
	movl	$GSEL(GCPU_SEL, SEL_KPL),%eax
	HALTT(0x26,%eax)	
	movl	%eax,%fs		
	xorl	%eax,%eax
	HALTT(0x27,%eax)		
	movl	%eax,%gs
	movl    PCB_CR0(%esi),%eax
	HALTT(0x28,%eax)		
	movl    %eax,%cr0
	HALTT(0x30,%ecx)	
	pushl	%ecx
	call	_C_LABEL(cpu_hatch)
	HALT(0x33)
	jmp	_C_LABEL(idle_loop)
	
	.data
_C_LABEL(mp_pdirpa):
	.long	0
#ifdef MPDEBUG
	.global _C_LABEL(cpu_trace)
_C_LABEL(cpu_trace):
	.long  0x40
	.long  0xff
	.long  0xff		
#endif

CPU_INFO_IDLELWP(%ecx)がどうなってるのかよく分からないが、ともかくidle状態のlwpを取得して、その中にあるPCBからレジスタを復帰(初期化という方が正確か)、最後はreturnするのではなくcpu_hatchを呼びidle_loopへジャンプしている。

/*
 * The CPU ends up here when its ready to run
 * This is called from code in mptramp.s; at this point, we are running
 * in the idle pcb/idle stack of the new CPU.  When this function returns,
 * this processor will enter the idle loop and start looking for work.
 */
void
cpu_hatch(void *v)
{
	struct cpu_info *ci = (struct cpu_info *)v;
	int s, i;

#ifdef __x86_64__
	cpu_init_msrs(ci, true);
#endif
	cpu_probe(ci);

	ci->ci_data.cpu_cc_freq = cpu_info_primary.ci_data.cpu_cc_freq;
	/* cpu_get_tsc_freq(ci); */ 

	KDASSERT((ci->ci_flags & CPUF_PRESENT) == 0);

	/*
	 * Synchronize time stamp counters.  Invalidate cache and do twice
	 * to try and minimize possible cache effects.  Note that interrupts
	 * are off at this point.
	 */
	wbinvd();
	atomic_or_32(&ci->ci_flags, CPUF_PRESENT);
	tsc_sync_ap(ci);
	tsc_sync_ap(ci);

	/*
	 * Wait to be brought online.  Use 'monitor/mwait' if available,
	 * in order to make the TSC drift as much as possible. so that
	 * we can detect it later.  If not available, try 'pause'. 
	 * We'd like to use 'hlt', but we have interrupts off.
	 */
	while ((ci->ci_flags & CPUF_GO) == 0) {
		if ((ci->ci_feature2_flags & CPUID2_MONITOR) != 0) {
			x86_monitor(&ci->ci_flags, 0, 0);
			if ((ci->ci_flags & CPUF_GO) != 0) {
				continue;
			}
			x86_mwait(0, 0);
		} else {
			for (i = 10000; i != 0; i--) {
				x86_pause();
			}
		}
	}

	/* Because the text may have been patched in x86_patch(). */
	wbinvd();
	x86_flush();

	KASSERT((ci->ci_flags & CPUF_RUNNING) == 0);

	lcr3(pmap_kernel()->pm_pdirpa);
	curlwp->l_addr->u_pcb.pcb_cr3 = pmap_kernel()->pm_pdirpa;
	lcr0(ci->ci_data.cpu_idlelwp->l_addr->u_pcb.pcb_cr0);
	cpu_init_idt();
	gdt_init_cpu(ci);
	lapic_enable();
	lapic_set_lvt();
	lapic_initclocks();

#ifdef i386
	npxinit(ci);
#else
	fpuinit(ci);
#endif
	lldt(GSYSSEL(GLDT_SEL, SEL_KPL));
	ltr(ci->ci_tss_sel);

	cpu_init(ci);
	cpu_get_tsc_freq(ci);

	s = splhigh();
#ifdef i386
	lapic_tpr = 0;
#else
	lcr8(0);
#endif
	x86_enable_intr();
	splx(s);
	x86_errata();

	aprint_debug_dev(ci->ci_dev, "running\n");
}

cpu_hatch()では、CPUF_PRESENTフラグをci->ci_flagsに設定してtscを同期するなどcpu_boot_secondary()との待ち合わせ作業を行っている。
また、詳細はよく分からないがやりのこしの初期化作業を色々と行っている。

おそらくサブCPUがここを通過してメインCPUがcpu_boot_secondary_processors()を通過するとプロセスがサブCPUに割り当て可能になるのではないか?