OS起動編④ lapicinit() (Xv6を読む~OSコードリーディング~)

前回
jupiteroak.hatenablog.com
トップページ
jupiteroak.hatenablog.com




main.c(一部抜粋)
https://github.com/mit-pdos/xv6-public/blob/master/main.c#L23

int
main(void)
{
  ...
  lapicinit();     // interrupt controller
  ...

lapic.c
https://github.com/mit-pdos/xv6-public/blob/master/lapic.c#L54

void
lapicinit(void)
{
  if(!lapic)
    return;

  // Enable local APIC; set spurious interrupt vector.
  lapicw(SVR, ENABLE | (T_IRQ0 + IRQ_SPURIOUS));

  // The timer repeatedly counts down at bus frequency
  // from lapic[TICR] and then issues an interrupt.
  // If xv6 cared more about precise timekeeping,
  // TICR would be calibrated using an external time source.
  lapicw(TDCR, X1);
  lapicw(TIMER, PERIODIC | (T_IRQ0 + IRQ_TIMER));
  lapicw(TICR, 10000000);

  // Disable logical interrupt lines.
  lapicw(LINT0, MASKED);
  lapicw(LINT1, MASKED);

  // Disable performance counter overflow interrupts
  // on machines that provide that interrupt entry.
  if(((lapic[VER]>>16) & 0xFF) >= 4)
    lapicw(PCINT, MASKED);

  // Map error interrupt to IRQ_ERROR.
  lapicw(ERROR, T_IRQ0 + IRQ_ERROR);

  // Clear error status register (requires back-to-back writes).
  lapicw(ESR, 0);
  lapicw(ESR, 0);

  // Ack any outstanding interrupts.
  lapicw(EOI, 0);

  // Send an Init Level De-Assert to synchronise arbitration ID's.
  lapicw(ICRHI, 0);
  lapicw(ICRLO, BCAST | INIT | LEVEL);
  while(lapic[ICRLO] & DELIVS)
    ;

  // Enable interrupts on the APIC (but not on the processor).
  lapicw(TPR, 0);
}

lapicinit関数では、各種のLocal APICレジスタに値を設定して、Local APICの初期設定を行なっています。


処理の内容

Local APICレジスタのベースアドレスを取得できているか確認する

if(!lapic)
  return

LocalAPICレジスタのベースアドレスの値(0xFEE0 0000)がlapicに格納されていることを確認します(LocalAPICレジスタのベースアドレスはmpinit関数で取得します)。!lapic が真となる場合→lapicが0の場合→Local APICレジスタのベースアドレスが格納されていない場合は、処理を終了します。

Local APIC レジスタについて

Local APICレジスタのようなメモリマップドされたレジスタ のアドレスを格納するための変数には、volatileをつけてコンパイラの最適化を抑制します(コンパイラの最適化を抑制しなかった場合、ソースコードの記述で意図したとおりの命令にコンパイルされない可能性があるためです)。

lapic.c

volatile uint *lapic;  // Initialized in mp.c

各種のLocal APICレジスタは16バイト(128ビット)境界でアラインメントされているので、uint型(符号なし4バイト(32bit)の整数型)のポインタlapicでLocal APICレジスタにアクセスする場合、lapic[0]、lapic[4]、lapic[8]、、、が各種のLoval APICレジスタの先頭アドレス(ベースアドレス+オフセットアドレス)となります。

レジスタ アドレス lapic
予約済み 0xFEE0 0000H lapic[0]
0xFEE0 0004H lapic[1]
0xFEE0 0008H lapic[2]
0xFEE0 000CH lapic[3]
予約済み 0xFEE0 0010H lapic[4]
... ...
Local APIC ID Register 0xFEE0 0020H lapic[8]
... ...
Local APIC Version Register 0xFEE0 0030H lapic[12]
ここで使用されているレジスタを挙げる ... ...


各種のLocal APICレジスタのオフセットアドレスを4で除算する(2bit右へシフトする)ことで、各種のLocal APICレジスタのオフセットアドレスに対応するインデックスを得ることができます。

レジスタ オフセットアドレス lapicのインデックス
予約済み 0x0000 0x0000/4 = 0x0000 = 0
予約済み 0x0010 0x0010/4 = 0x0004 = 4
Local APIC ID Register 0x0020 0x0020/4 = 0x0008 = 8
Local APIC Version Register 0x0030 0x0030/4 = 0x000c =12
ここで使用されているレジスタを挙げる ... ...

Local APICを有効化する

lapicw(SVR, ENABLE | (T_IRQ0 + IRQ_SPURIOUS));

lapicw関数を呼び出して、Spurious Interrupt Vector レジスタに値を設定し、Local APICを有効化しています。
第一引数SVR→0x00F0/4は、Spurious Interrupt Vector レジスタを指定するインデックスです。
第二引数 ENABLE | (T_IRQ0 + IRQ_SPURIOUS)→0x0000 013Fは、Spurious Interrupt Vector レジスタに設定する値で、Local APICを有効化させる値ENABLEと割り込みベクタ番号T_IRQ0 + IRQ_SPURIOUSの論理和です。

Spurious Interrupt Vector レジスタについて

Spurious Interrupt Vector レジスタは、Local APICの有効化・無効化に関与している32bit幅のレジスタです(その他の機能もありますがここでは割愛します)。


bit 7-0 Spurious Vector フィールド
Spurious Vector フィールド(bit 7-0)は割り込みベクタ番号を設定するフィールドです。
設定値 ENABLE | (T_IRQ0 + IRQ_SPURIOUS) は0x0000 013Fなので、このフィールド(bit 7-0)には 割り込みベクタ番号0x3F→(0b 0011 1111)→63が設定されます。Intel 64 IA-32architectureでは0から31までの割り込みベクタ番号は予約済みなので、ユーザーが設定できる割り込みベクタ番号は32から255までとなります。


bit 8 APIC Software Enable/Disable フラグ
APIC Software Enable/Disable フラグ(bit 8)は、ローカルAPICを有効化・無効化させるフラグです。
設定値 ENABLE | (T_IRQ0 + IRQ_SPURIOUS) は 0x0000 013Fなので(bit 11-8の値は1h→0b 0001なので)、このフラグ(bit 8)には1が設定され、ローカルAPICが有効化されます。

Local APICタイマーからの割り込み設定を行う

Local APICタイマーの周波数を設定する

lapicw(TDCR, X1);

lapicw関数を呼び出して、Divide Configuration レジスタに値を設定し、LocalAPICタイマーの周波数を設定しています。
第一引数TDCR→0x03E0/4は、Divide Configuration レジスタを指定するインデックスです。
第二引数X1→0x0000 000Bは、Divide Configuration レジスタのDivide Value フィールドに設定する値です。

Divide Configuration レジスタについて

Divide Configuration レジスタは、LocalAPICタイマーの周波数設定に関与している32bit幅のレジスタです。


bits 3,1,0 Divide Value フィールド
プロセッサのバスクロックの周波数に対する除数を指定するフィールドです。
プロセッサのバスクロックの周波数をこのフィールドが指定する値で除算したものが、LocalAPICタイマーの周波数となります。
設定値が0x0000 000Bなので(bit3-0の値はBh→0b 1011なので)、Divide Value フィールドの値は111となります。
Divide Value フィールドの値が111の時、プロセッサのバスクロックの周波数に対する除数は1となるので(111: Divide by 1)、プロセッサのバスクロックを1で割った値がAPICタイマーの周波数となります。

Local APICタイマーのタイマーモードを設定する

lapicw(TIMER, PERIODIC | (T_IRQ0 + IRQ_TIMER));

lapicw関数を呼び出して、LVT Timer レジスタに値を設定し、LocalAPICタイマーのタイマーモードを設定しています。
第一引数TIMER→0x0320/4は、LVT Timer レジスタを指定するインデックスです。
第二引数PERIODIC | (T_IRQ0 + IRQ_TIMER)→0x0002 0020は、 LVT Timer レジスタに設定する値で、タイマーモードを指定する値PERIODICと割り込みベクタ番号T_IRQ0 + IRQ_TIMERの論理和です。

LVT Timer レジスタについて

LVT Timer レジスタは、APICタイマーの設定に関与している32bit幅のレジスタです。


bit 7-0: Vector フィールド
Vector フィールドには、割り込みベクタ番号を設定します。
設定値PERIODIC | (T_IRQ0 + IRQ_TIMER)は0x0002 0020なので、このフィールド(bit7-0)には割り込みベクタ番号0x20→0b 0010 0000→32が設定されています。
Intel 64 IA-32architectureでは0から31までの割り込みベクタ番号は予約済みなので、ユーザーが設定できる割り込みベクタ番号は32から255までとなります。


bit 18-17 Timer Mode フィールド
タイマーモードを設定するフィールドです。
設定値 PERIODIC | (T_IRQ0 + IRQ_TIMER)は0x0002 0020なので(bit19-16の値は2h→0b 0010なので)、bit18-17 Timer Mode フィールドの値は01(periodic モード)となります。periodicモードは、APICタイマーの割り込みが発生するまでのカウントダウンを繰り返し行うモードです。

Local APICタイマーのカウントダウン値を設定する

lapicw(TICR, 1000 0000);

lapicw関数を呼び出して、Initial-Count レジスタに値を設定し、Local APICタイマーのカウントダウン値を設定します。
第一引数TICR→0x0380/4は、 Initial-Count レジスタを指定するインデックスです。
第二引数1000 0000は、Initial-Countレジスタに設定する値です

Initial-Count レジスタについて

Initial-Count レジスタは、APICタイマーの割り込みが発生するまでのカウントダウンの値を設定する32bit幅のレジスタです。

ローカル割り込みピンからの割り込みを無効果する

ローカル割り込みピンLINT0からの割り込みを無効化する

lapicw(LINT0, MASKED);

lapicw関数を呼び出して、LVT LINT0 レジスタに値を設定し、ローカル割り込みピンLINT0からの割り込みを無効化します。
第一引数LINT0→0x0350/4は、LVT LINT0 レジスタを指定するインデックスです。
第二引数MASKED→0x0001 0000は、LVT LINT0 レジスタのMaskフラグに設定する値で、ローカル割り込みピンLINT0からの割り込みを無効化します。

LVT LINT0 レジスタについて

LVT LINT0 レジスタは、プロセッサのローカル割り込みピンLINT0(に直接接続されたI/Oデバイス)からの割り込みの伝達方法を指定する32bit幅のレジスタです。


bit 16 : Mask フラグ
Maskフラグは、割り込みの有効化・無効化を指定するフラグです。
設定値MASKEDは0x0001 0000なので、このフラグ(bit16)には割り込みの受け入れ抑制を指定する1が設定されています。

ローカル割り込みピンLINT1からの割り込みを無効化する

lapicw(LINT1, MASKED);

lapicw関数を呼び出して、LVT LINT1 レジスタに値を設定し、ローカル割り込みピンLINT1からの割り込みを無効化します。
第一引数LINT1→0x0360/4は、LVT LINT1 レジスタを指定するインデックスです。
第二引数MASKED→0x0001 0000は、LVT LINT1 レジスタのMaskフラグにに設定する値で、ローカル割り込みピンLINT1からの割り込みを無効化します。

LVT LINT1 レジスタについて

LVT LINT1 レジスタは、プロセッサのローカル割り込みピンLINT1(に直接接続されたI/Oデバイス)からの割り込みの伝達方法を指定する32bit幅のレジスタです。


bit 16 : Mask フラグ
Maskフラグ(bit16)は、割り込みの有効化・無効化を指定するフラグです。
設定値MASKEDは0x0001 0000なので、このフラグ(bit16)には割り込みの受け入れ抑制を指定する1が設定されています。

Performance Counterからの割り込みを無効化する

 if(((lapic[VER]>>16) & 0xFF) >= 4)
    lapicw(PCINT, MASKED);

Local APIC レジスタのベースアドレスlapic(0xFEE0 0000H))とインデックスVER→0x0030/4→0x00C→12を用いて、Local APIC Version レジスタ(32bit幅)の値を取得しています(VERは、Local APIC Version レジスタを指定するインデックスです)。
さらに、取得した値を16bit右にシフト演算し、0x00FFでマスク処理することにより、Local APIC Version Registerのbit23-16の値を取り出しています。
Local APIC Version Registerのbit23-16の値(Max LVT Entryフィールドの値)は、LVTのエントリ数-1の値を示しています。
((lapic[VER]>>16) & 0xFF) >= 4 が真となる場合→Max LVT Entryフィールドの値が4以上(LVTエントリ数が5以上)の場合に、LVT Performance Counter レジスタの設定を行なっています。
if文内では、lapicw関数を呼びだして、LVT Performance Counter レジスタに値を設定し、Performance Counterからの割り込みを無効化しています。
第一引数PCINT→0x0340/4は、LVT Performance Counter レジスタを指定するインデックスです。
第二引数MASKED→0x0001 0000は、LVT Performance Counter レジスタのMaskフラグに設定する値で、Performance Counterからの割り込みを無効化します。

LVT Performance Counter レジスタについて

LVT Performance Counter レジスタは、Performance monitoring counterからプロセッサへの割り込み伝達方法を指定する32bit幅のレジスタです。


bit 16 : Mask フラグ
Maskフラグ(bit16)は、割り込みの有効化・無効化を指定するフラグです。
設定値MASKEDは0x0001 0000なので、このフラグ(bit16)には割り込みの受け入れ抑制を指定する1が設定されています。

APICのエラー検知に関わる割り込み設定を行う

APICが内部のエラーを検知した時に発生させる割り込みの伝達方法を指定する

 lapicw(ERROR, T_IRQ0 + IRQ_ERROR);

lapicw関数を呼び出して、LVT Error レジスタに値を設定し、APICが内部のエラーを検知した時に発生する割り込みの伝達方法を指定します。
第一引数ERROR→0x0370/4は、LVT Error レジスタを指定するインデックスです。
第二引数T_IRQ0 + IRQ_ERRORは、LVT Error レジスタVectorフィールドに設定する割り込みベクタ番号の値です。

LVT Error レジスタについて

LVT Error レジスタは、APICが内部のエラーを検知した時に発生する、プロセッサへの割り込みの伝達方法を指定するレジスタです。


bit 7-0 Vectorフィールド
Vector フィールドには、割り込みベクタ番号を設定します。設定値T_IRQ0 + IRQ_ERRORは0x0000 0033なので、このフィールド(bit7-0)には、0x33→0b 0011 0011→51が割り込みベクタ番号として設定されます。
Intel 64 IA-32architectureでは0から31までの割り込みベクタ番号は予約済みなので、ユーザーが設定できる割り込みベクタ番号は32から255までとなります。

Error Status レジスタを初期化する

 // Clear error status register (requires back-to-back writes).
lapicw(ESR, 0)
lapicw(ESR, 0)

lapicw関数を呼び出して、Error Status レジスタを0の値で初期化しています。
第一引数ESR→0x0280/4は、Error Status レジスタを指定するインデックスです。
第二引数0は、Error Status レジスタの初期化に使用する値です。
理由はわかりませんが、back-to-back write が必要なようです。

Error Status レジスタについて

Error Status レジスタは、APICで割り込み処理中に検知されたエラーを記録するレジスタです。

EOIレジスタを初期化する

lapicw(EOI, 0);

lapicw関数を呼び出して、EOIレジスタを0の値で初期化しています。
第一引数EOI→0x00B0/4は、EOIレジスタを指定するインデックスです。
第二引数0は、EOIレジスタの初期化に使用する値です。

EOIレジスタについて

EOIレジスタは、割り込みサービスルーチンが完了したことを示すために使用される32bit幅のレジスタです。
NMI、SMI、INIT、ExtINT、Start-Up、INIT-Deassert、それぞれのdelivery modeで伝達される割り込みを除いた、全ての割り込みにおいて、割り込みハンドラの処理が終了するまでに(iret命令が実行される前の時点で)、EOIレジスタへの書き込みを行わなければなりません。

IPIを送信する

INIT Level De-assert のIPIを送信する

lapicw(ICRHI, 0);
lapicw(ICRLO, BCAST | INIT | LEVEL);

lapicw関数を呼び出して、Interrupt Command レジスタに書き込みを行い、プロセッサへIPI(プロセッサ間割り込み)を送信しています。
1つ目のlapicw関数の第一引数ICRHI→0x0310/4はInterrupt Command レジスタの上位32bitを指定するインデックス、第二引数の0がInterrupt Command レジスタの上位32bitに書き込まれる値です。
2つ目のlapicw関数の第一引数ICRLO→0x0300/4はInterrupt Command レジスタの下位32bitを指定するインデックス、第二引数のBCAST | INIT | LEVEL→0x0008 8500 がInterrupt Command レジスタの下位32bitに書き込まれる値で、INIT Level De-assert のIPIを送信するための値を書き込んでいます。
 

Interrupt Command レジスタについて

Interrupt Command レジスタは、IPI(プロセッサ間割り込み)を送信するために使用されるレジスタです。
Interrupt Command レジスタのの下位32ビット(bit31-0)へ書き込みを行った時にIPIが送信されるので、Interrupt Command レジスタの上位32bit(bit63-32)、Interrupt Command レジスタのの下位32ビット(bit31-0)の順で書き込みを行なっています。
今回送信するIPIの種類(delivery mode)は、INIT Level De-assertです。INIT Level De-assertは、Local APIC が持つArb ID レジスタにそのLocal APICが持つAPIC ID レジスタの値を設定するために、システム上の全てのLocal APICに同期メッセージとして送信されます。


bit 63-56 Destination
IPIの標的となるプロセッサやプロセッサ群を指定するフィールドですが、今回のIPI(INIT Level De-assert)ではDestinationフィールドの値は無視されます。


bit19-18 : Destination Shorthand
割り込みの標的を表す簡略表現を示すフィールドです。
Interrupt Command レジスタのの下位32ビット(bit31-0)の設定値BCAST | INIT | LEVELは0x0008 8500なので(bit23-16の値は8h→0b 0000 1000なので)、このフィールド(bit19-18)には、10の値が設定されます。
今回送信するIPI(INIT Level De-assert)は、destination shorthand フィールドの値を無視しますが、All including self shorthandを指定すべきということから、All Including Selfを示す10が指定されています。All Including Self(10)は、IPIの送信元であるプロセッサを含む、システム上の全てのプロセッサにIPIを送信します。


bit15: Trigger Mode
Trigger Modeフラグは、INIT level de-assertのDelivery Modeにおいて、割り込み信号を検出する方式を選択するフラグです。
Interrupt Command レジスタのの下位32ビット(bit31-0)の設定値BCAST | INIT | LEVELは0x0008 8500なので(bit15-12の値は8h→0b 1000なので)、Trigger Modeフラグには1の値が設定されます。
INIT level de-assert のDelivery Modeでは、このフラグは1(level)にセットされなければなりません。


bit14: Level
IInterrupt Command レジスタのの下位32ビット(bit31-0)の設定値BCAST | INIT | LEVELは0x0008 8500なので(bit 15-12の値は8h→0b 1000なので)、Levelフラグ(bit14)には0が設定されます。
INIT level de-assert のDelivery Modeでは、このフラグは0にセットされなければなりません。


bit 11:Destination Mode
Destination Modeフラグは、Destination フィールドが標的となるプロセッサを指定する方式を選択フィールドです。
Interrupt Command レジスタの下位32ビット(bit31-0)の設定値BCAST | INIT | LEVELは0x0008 8500なので(bit11-8の値は5h→0b 0101なので)、このフラグ(bit11)には、0(physical desitination mode)が設定されます。physical destination modeでは、標的となるプロセッサはLocal APIC ID で指定されます。


bit 10-8: Delivery Mode
Delivery Modeフィールドは、プロセッサに送信されるIPIの種類を指定するフィールドです。
Interrupt Command レジスタの下位32ビット(bit31-0)の設定値BCAST | INIT | LEVELは0x0008 8500なので(bit 11-8の値が 5h→0b 0101なので)、このフィールド(bit 10-8)には、当然、101 (INIT Level De-assert)が指定されています。
このデリバリーモードは、Pentium 4Intel Xeon プロセッサではサポートされていません。


bit 7-0: Vector
送信される割り込みのベクタ番号を設定するフィールドですが、今回のIPIでは将来の互換性のために0x00を設定しているようです。

INIT Level De-assert のIPIが送信されたことを確認する

while(lapic[ICRLO] & DELIVS)
;

Local APIC レジスタのベースアドレスlapic(0xFEE0 0000H))とインデックスICRLOを用いて、Interrupt Command レジスタの下位32ビット(bit31-0)の値を取得しています(ICRLOは、LInterrupt Command レジスタの下位32ビット(bit31-0)を指定するインデックスです)。
さらに、取得した値をDELIVS→0x0000 1000でマスク処理することで、bit12 Delivery Statusフラグの値を取り出しています。
Delivery Statusフラグは、IPIの伝達状態を示すフラグです。
lapic[ICRLO] & DELIVSが真の場合→Delivery Statusフラグが1の場合は→IPIが送信を完了していない場合は、ループを継続します。
lapic[ICRLO] & DELIVSが偽の場合→Delivery Statusフラグが0の場合は→IPIが送信を完了した場合はIPIの送信が完了した場合は、ループを脱出して次の処理に進みます。

割り込みの優先度の設定を行う

#define TPR (0x0080/4) // Task Priority

lapicw(TPR, 0);

lapicw関数を呼び出して、Task Priority レジスタに0の値を設定しています。
第一引数 TPR→0x0080/4は、Task Priority レジスタを指定するインデックスです。
第二引数0は、Task Priority レジスタに設定する値です。

Task Priority レジスタについて

task priority レジスタは、プロセッサで実行されるタスク(プロセスやスレッド)の優先度を指定するために使用される32bit幅のレジスタです(プロセッサに割り込む優先度の閾値の設定にも関与しています)。
今回は、どの割り込みの伝達も抑制しないように(Processor Priority Registerのbit7-4が0の値になるように)、0を設定しています。




次回
jupiteroak.hatenablog.com