読者です 読者をやめる 読者になる 読者になる

send_init_ipi()は名前からしてIPI(Interpurocessor Interrupts)を初期化する関数。

minix

調べているのはここにあるソース。
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が起動するようになる。