FreeBSD-current/mipsのSMP実装#初期化編
AsiaBSDConのニュース記事などを読んでいて気がついたが、どうもFreeBSD-current/mipsにはSMP対応コードが既に入っているらしい。
が、MP対応なチップ向けのコードが入っておらず、しかもgenericなコードの中にしばしば#if defined(TARGET_OCTEON)という文字が現れたりする。
mlでOCTEONはきちんと動いてるけどCaviumのライセンスに問題があってコードを公開出来ない、などと言っている人が居たが、もしかしたらその公開できないFreeBSD-current/OCTEONはSMPで動いているのかもしれない。
まずはentrypointから。
/* * The following needs to be done differently for each platform and * there needs to be a good way to plug this in. */ #if defined(SMP) && defined(CPU_XLR) /* * Block all the slave CPUs */ /* * Read the cpu id from the cp0 config register * cpuid[9:4], thrid[3: 0] */ mfc0 a0, COP_0_CONFIG, 7 srl a1, a0, 4 andi a1, a1, 0x3f andi a0, a0, 0xf /* calculate linear cpuid */ sll t0, a1, 2 addu a2, t0, a0 /* Initially, disable all hardware threads on each core except thread0 */ li t1, VCPU_ID_0 li t2, XLR_THREAD_ENABLE_IND mtcr t1, t2 #endif #if defined(TARGET_OCTEON) /* Maybe this is mips32/64 generic? */ .set push .set mips32r2 rdhwr t0, $0 .set pop #else move t0, zero #endif /* Stage the secondary cpu start until later */ bne t0, zero, start_secondary nop #ifdef SMP la t0, _C_LABEL(__pcpu) SET_CPU_PCPU(t0) /* If not master cpu, jump... */ /*XXX this assumes the above #if 0'd code runs */ bne a2, zero, start_secondary nop #endif
色々グダグダと書いてあって一部分からない所もあるが、結局の所セカンダリCPUはstart_secondaryへ飛ばしている。
#ifdef SMP start_secondary: move a0, a1 2: addiu t0, PCPU_SIZE subu a1, 1 bne a1, zero, 2b nop SET_CPU_PCPU(t0) smp_wait: lw sp, PC_BOOT_STACK(t0) beqz sp, smp_wait nop jal _C_LABEL(smp_init_secondary) nop #else start_secondary: b start_secondary nop #endif
entrypointから飛んできたセカンダリCPUがPC_BOOT_STACK(t0)が0の間ビジーループしてメインCPUが上がってくるのを待ち、値が取れてからsmp_init_secondary()をコールしている。
void smp_init_secondary(u_int32_t cpuid) { if (cpuid >= MAXCPU) panic ("cpu id exceeds MAXCPU\n"); /* tlb init */ R4K_SetWIRED(0); R4K_TLBFlush(num_tlbentries); R4K_SetWIRED(VMWIRED_ENTRIES); MachSetPID(0); Mips_SyncCache(); mips_cp0_status_write(0); while (!aps_ready) ; mips_sync(); mips_sync(); /* Initialize curthread. */ KASSERT(PCPU_GET(idlethread) != NULL, ("no idle thread")); PCPU_SET(curthread, PCPU_GET(idlethread)); mtx_lock_spin(&ap_boot_mtx); smp_cpus++; CTR1(KTR_SMP, "SMP: AP CPU #%d Launched", PCPU_GET(cpuid)); /* Build our map of 'other' CPUs. */ PCPU_SET(other_cpus, all_cpus & ~PCPU_GET(cpumask)); printf("SMP: AP CPU #%d Launched!\n", PCPU_GET(cpuid)); if (smp_cpus == mp_ncpus) { smp_started = 1; smp_active = 1; } mtx_unlock_spin(&ap_boot_mtx); while (smp_started == 0) ; /* nothing */ /* Enable Interrupt */ mips_cp0_status_write(SR_INT_ENAB); /* ok, now grab sched_lock and enter the scheduler */ mtx_lock_spin(&sched_lock); /* * Correct spinlock nesting. The idle thread context that we are * borrowing was created so that it would start out with a single * spin lock (sched_lock) held in fork_trampoline(). Since we've * explicitly acquired locks in this function, the nesting count * is now 2 rather than 1. Since we are nested, calling * spinlock_exit() will simply adjust the counts without allowing * spin lock using code to interrupt us. */ spinlock_exit(); KASSERT(curthread->td_md.md_spinlock_count == 1, ("invalid count")); binuptime(PCPU_PTR(switchtime)); PCPU_SET(switchticks, ticks); /* kick off the clock on this cpu */ mips_start_timer(); cpu_throw(NULL, choosethread()); /* doesn't return */ panic("scheduler returned us to %s", __func__); }
TLBを設定、cpumaskを設定し割り込みを有効化、タイマを開始してスケジューラにCPUを明け渡している。
メインCPU側からの初期化もみていこう。
#ifdef SMP la t0, _C_LABEL(__pcpu) SET_CPU_PCPU(t0) /* If not master cpu, jump... */ /*XXX this assumes the above #if 0'd code runs */ bne a2, zero, start_secondary nop #endif /* Call the platform-specific startup code. */ jal _C_LABEL(platform_start) sw zero, START_FRAME - 8(sp) # Zero out old fp for debugger la sp, _C_LABEL(thread0) lw a0, TD_PCB(sp) li t0, ~7 and a0, a0, t0 subu sp, a0, START_FRAME jal _C_LABEL(mi_startup) # mi_startup(frame) sw zero, START_FRAME - 8(sp) # Zero out old fp for debugger PANIC("Startup failed!")
セカンダリCPUではなかった場合は、start_secondaryへ飛ばずにplatform_start()を呼び、それが終わったらmi_startup()を呼ぶ。
platform_start()を一通り読んでみたが、ここでは未だメインCPUのみの初期化に止まっているようだ。
mi_startup()へ進むと、SYSINIT()マクロによってカーネルに登録されている初期化関数群が指定された順序で実行されていく。
このうち、kern/subr_smp.cのcpu_mpがSMPの初期化に関係あるようだ(まぁ、名前のまんまだし)。
/* * Call the MD SMP initialization code. */ static void mp_start(void *dummy) { /* Probe for MP hardware. */ if (smp_disabled != 0 || cpu_mp_probe() == 0) { mp_ncpus = 1; all_cpus = PCPU_GET(cpumask); return; } mtx_init(&smp_ipi_mtx, "smp rendezvous", NULL, MTX_SPIN); cpu_mp_start(); printf("FreeBSD/SMP: Multiprocessor System Detected: %d CPUs\n", mp_ncpus); cpu_mp_announce(); } SYSINIT(cpu_mp, SI_SUB_CPU, SI_ORDER_THIRD, mp_start, NULL);
SYSINIT()マクロによってmi_startup()からmp_start()が実行され、cpu_mp_start()とcpu_mp_announce()が実行される。
void cpu_mp_start(void) { int i, cpuid; mtx_init(&ap_boot_mtx, "ap boot", NULL, MTX_SPIN); cpuid = 1; for (i = 0; i < MAXCPU; i++) { if (i == boot_cpu_id) continue; if (smp_start_secondary(i)) { all_cpus |= (1 << cpuid); mp_ncpus++; cpuid++; } } idle_mask |= CR_INT_IPI; PCPU_SET(other_cpus, all_cpus & ~PCPU_GET(cpumask)); }
cpu_mp_start()はsmp_start_secondary()をCPUの数だけ実行する。
static int smp_start_secondary(int cpuid) { struct pcpu *pcpu; int i; if (bootverbose) printf("smp_start_secondary: starting cpu %d\n", cpuid); pcpu_init(&__pcpu[cpuid], cpuid, sizeof(struct pcpu)); if (bootverbose) printf("smp_start_secondary: cpu %d started\n", cpuid); return 1; }
smp_start_secondary()はpcpu_init()を実行する。
/* * Initialize the MI portions of a struct pcpu. */ void pcpu_init(struct pcpu *pcpu, int cpuid, size_t size) { bzero(pcpu, size); KASSERT(cpuid >= 0 && cpuid < MAXCPU, ("pcpu_init: invalid cpuid %d", cpuid)); pcpu->pc_cpuid = cpuid; pcpu->pc_cpumask = 1 << cpuid; cpuid_to_pcpu[cpuid] = pcpu; SLIST_INSERT_HEAD(&cpuhead, pcpu, pc_allcpu); cpu_pcpu_init(pcpu, cpuid, size); pcpu->pc_rm_queue.rmq_next = &pcpu->pc_rm_queue; pcpu->pc_rm_queue.rmq_prev = &pcpu->pc_rm_queue; }
pcpu_init()によりstruct pcpuが初期化され、cpu_pcpu_init()が呼び出される。
/* * Initialise a struct pcpu. */ void cpu_pcpu_init(struct pcpu *pcpu, int cpuid, size_t size) { #ifdef SMP if (cpuid != 0) pcpu->pc_boot_stack = (void *)(pcpu_boot_stack + cpuid * (KSTACK_PAGES * PAGE_SIZE)); #endif pcpu->pc_next_asid = 1; pcpu->pc_asid_generation = 1; }
ここでセカンダリCPUがstart_secondaryで待っているboot_stackのアドレスが設定され、smp_init_secondary()に進むようになる。