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レジスタのベースアドレスを取得できているか確認する
- Local APICを有効化する
- Local APICタイマーからの割り込み設定を行う
- ローカル割り込みピンからの割り込みを無効果する
- Performance Counterからの割り込みを無効化する
- APICのエラー検知に関わる割り込み設定を行う
- EOIレジスタを初期化する
- IPIを送信する
- 割り込みの優先度の設定を行う
Local APICレジスタのベースアドレスを取得できているか確認する
if(!lapic) return
LocalAPICレジスタのベースアドレスの値(0xFEE0 0000)がlapicに格納されていることを確認します(LocalAPICレジスタのベースアドレスはmpinit関数で取得します)。!lapic が真となる場合→lapicが0の場合→Local APICレジスタのベースアドレスが格納されていない場合は、処理を終了します。
Local APIC レジスタについて
Local APICレジスタのようなメモリマップドされたレジスタ のアドレスを格納するための変数には、volatileをつけてコンパイラの最適化を抑制します(コンパイラの最適化を抑制しなかった場合、ソースコードの記述で意図したとおりの命令にコンパイルされない可能性があるためです)。
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タイマーの割り込みが発生するまでのカウントダウンを繰り返し行うモードです。
ローカル割り込みピンからの割り込みを無効果する
ローカル割り込みピンLINT0からの割り込みを無効化する
lapicw(LINT0, MASKED);
lapicw関数を呼び出して、LVT LINT0 レジスタに値を設定し、ローカル割り込みピンLINT0からの割り込みを無効化します。
第一引数LINT0→0x0350/4は、LVT LINT0 レジスタを指定するインデックスです。
第二引数MASKED→0x0001 0000は、LVT LINT0 レジスタのMaskフラグに設定する値で、ローカル割り込みピンLINT0からの割り込みを無効化します。
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からの割り込みを無効化します。
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までとなります。
EOIレジスタを初期化する
lapicw(EOI, 0);
lapicw関数を呼び出して、EOIレジスタを0の値で初期化しています。
第一引数EOI→0x00B0/4は、EOIレジスタを指定するインデックスです。
第二引数0は、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 4 と Intel 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の送信が完了した場合は、ループを脱出して次の処理に進みます。