調べているのはここにあるソース。
http://gsd.unex.es/projects/minixsmp/
そんでは、関数を見ていくと、引数の1番目(trampoline_addr)はfind_trampoline()で見つけたトランポリンコードのアドレス。
まず分かるのはcmosのデータを読んだり、書いたりしていることがわかる。
void send_init_ipi(u32_t trampoline_addr, int which_cpu) { /* Send INIT IPI to a processor in order to start execution in it. First, send an INIT assert, and then, an INIT deassert */ u32_t icr_h,icr_l; u16_t value; u8_t old_cmos; u32_t old_vector; /* Reprogram warm reset: write code 0A at CMOS addr 0F */ old_cmos=cmos_read(0x0F); cmos_write(0x0F, 0x0A); /* save old reset vector at 40:67 (dw) */ phys_copy(0x467,vir2phys(&old_vector), sizeof(u32_t)); /* program reset vector to point at trampoline */ value=(trampoline_addr >> 4); /* trampoline segment */ phys_copy(vir2phys(&value), 0x469, sizeof(u16_t)); value=(trampoline_addr & 0xF); /* trampoline offset */ phys_copy(vir2phys(&value), 0x467, sizeof(u16_t)); /* prepare to send INIT IPI */ icr_h = LOCAL_APIC_READ(LOCAL_APIC_ICR_HIGH); icr_l = LOCAL_APIC_READ(LOCAL_APIC_ICR_LOW); icr_l &= 0xFFF00000; /* clear */ icr_h &= 0xF0FFFFFF; /* clear */ icr_l |= (DELIVERY_INIT<<DELIVERY_SHIFT); /* stablish INIT */ icr_l |= (1 << LEVEL_SHIFT); /* level = assert */ icr_l |= (1 << TRIGGER_SHIFT); /* trigger = level */ icr_l |= (PHYSICAL_DEST << DEST_MODE_SHIFT); /* destination = physical */ icr_l |= (DEST_FIELD << DEST_SHORT_SHIFT); /* destination by field */ icr_h |= (which_cpu << DEST_FIELD_SHIFT); /* cpu to interrupt */ /* send the IPI (assert) */ apic_error_status(); /* first, clear previous errors */ LOCAL_APIC_WRITE(LOCAL_APIC_ICR_HIGH, icr_h); LOCAL_APIC_WRITE(LOCAL_APIC_ICR_LOW, icr_l); wait_for_ipi_completion(); /* send the IPI (deassert) */ apic_error_status(); /* first, clear previous errors */ icr_l ^= (1<<LEVEL_SHIFT); /* switch to deassert */ LOCAL_APIC_WRITE(LOCAL_APIC_ICR_HIGH, icr_h); LOCAL_APIC_WRITE(LOCAL_APIC_ICR_LOW, icr_l); wait_for_ipi_completion(); /* restore old reset vector and cmos shutdown code */ phys_copy(vir2phys(&old_vector), 0x467, sizeof(u32_t)); cmos_write(0x0F, old_cmos); }
ここでやってる処理の概要はMPの仕様書の「Appendix A System BIOS Programming Guidelines」と「Appendix B Operating System Programming Guidelines」あたりが詳しい。
特に0x467ってなんぞやというのを調べてみると、「warm-reset vector, which is a doubleword pointer in system RAM location 40:67h」となっている。
CMOS関連の処理はAPを起動させるために必要。
cmos_write(0x0F, 0x0A); /* save old reset vector at 40:67 (dw) */ phys_copy(0x467,vir2phys(&old_vector), sizeof(u32_t));
CMOSに対して書き込んでいる0x0fの意味は、Shutdown codeのある場所らしい。0fに対してa0を書き込むのはwarm resetを意味しているようだ。
そして、トランポリコードをアドレス0x467に書き込む。
/* program reset vector to point at trampoline */ value=(trampoline_addr >> 4); /* trampoline segment */ phys_copy(vir2phys(&value), 0x469, sizeof(u16_t)); value=(trampoline_addr & 0xF); /* trampoline offset */ phys_copy(vir2phys(&value), 0x467, sizeof(u16_t));
その後、しばらくは変数に値を設定して、IPIを初期化する準備を行う。
/* prepare to send INIT IPI */ icr_h = LOCAL_APIC_READ(LOCAL_APIC_ICR_HIGH); icr_l = LOCAL_APIC_READ(LOCAL_APIC_ICR_LOW); icr_l &= 0xFFF00000; /* clear */ icr_h &= 0xF0FFFFFF; /* clear */ icr_l |= (DELIVERY_INIT<<DELIVERY_SHIFT); /* stablish INIT */ icr_l |= (1 << LEVEL_SHIFT); /* level = assert */ icr_l |= (1 << TRIGGER_SHIFT); /* trigger = level */ icr_l |= (PHYSICAL_DEST << DEST_MODE_SHIFT); /* destination = physical */ icr_l |= (DEST_FIELD << DEST_SHORT_SHIFT); /* destination by field */ icr_h |= (which_cpu << DEST_FIELD_SHIFT); /* cpu to interrupt */
変数に値を設定し終わったらデータを書き込む。
/* send the IPI (assert) */ apic_error_status(); /* first, clear previous errors */ LOCAL_APIC_WRITE(LOCAL_APIC_ICR_HIGH, icr_h); LOCAL_APIC_WRITE(LOCAL_APIC_ICR_LOW, icr_l); wait_for_ipi_completion();
最初のapic_error_status()はコメントそのままな処理で、次にデータを書き込む。
データを書き込んだら、それが反映されるまで待つ処理が行われる(wait_for_ipi_completion)。
wait_for_ipi_completion()はこんな感じの処理。
void wait_for_ipi_completion(void) { /* Wait until current IPI is delivered */ u32_t status = (DELIVERY_STATUS>>DELIVERY_STATUS_SHIFT); int count=0,err; if (status!=DELIVERY_PENDING) return; err=0; while ( (DELIVERY_STATUS == (DELIVERY_PENDING<<DELIVERY_STATUS_SHIFT)) && (err==0) ){ err=apic_error_status(); count++; if (count>2000) { ADDS_MP_STATUS("ERROR: IPI timed out after 2000 loops\n"); break; } } if (err) { ADDS_MP_STATUS("APIC IPI ERROR: STATUS "); ADDN_MP_STATUS(err, 2, 8); ADDS_MP_STATUS("\n"); } }
while文の比較条件につかっている値の定義はこちら。
ようはデータを読み出してみて(DELIVERY_STATUS)、状況はどうか調べてる。
#define DELIVERY_STATUS_SHIFT 12 /* status of ipi */ #define DELIVERY_PENDING 1 /* ipi is being sent */ #define DELIVERY_IDLE 0 /* sent: no work to be done */ #define DELIVERY_STATUS (LOCAL_APIC_READ(LOCAL_APIC_ICR_LOW) \ & (1<<DELIVERY_STATUS_SHIFT))
そして、最後に
/* restore old reset vector and cmos shutdown code */ phys_copy(vir2phys(&old_vector), 0x467, sizeof(u32_t)); cmos_write(0x0F, old_cmos);
一旦セーブしたresetベクターのとcmos内容を書き戻している。
これでIPIの初期化が終わったので、mp_start()に戻って次の関数send_startup_ipi()をコールしてIPIが有効になってAPが起動するようになる。