APIC (その3)

初期化手続きをamazon:Linuxカーネル解析入門を参照しつつ追いかけてみる。
とりあえず以下 (init_bsp_APIC()#arch/i386/kernel/apic.c) のあたりを。

    value = apic_read(APIC_LVR);
    ver = GET_APIC_VERSION(value);

    /*
     * Do not trust the local APIC being empty at bootup.
     */
    clear_local_APIC();

APIC_LVR は include/asm-i386/apicdef.h にて以下の通りマクロ定義されている。

#define        APIC_LVR    0x30

ソフトウェア・デベロッパーズ・マニュアル下巻の表 8-1 とかamazon:Linuxカーネル解析入門 p.150 や p.133 にある通り、Local APIC バージョンレジスタのアドレスと合致 (とは言え、ベースアドレスが 0xFEE00000 が前提なんですが) するのが分かる。
又、GET_APIC_VERSION も同一ソースにて定義されている。

#define            GET_APIC_VERSION(x)    ((x)&0xFF)

これもソフトウェア・デベロッパーズ・マニュアル下巻の 8.4.8. 節やamazon:Linuxカーネル解析入門 p.135 にある通り、0 から 7 ビットがバージョンのフィールドになっているので、0xFF と & 演算すりゃバージョンの取得が可能、という事が分かる。又、バージョンに関する記述も同様。
で、次の clear_local_APIC() は同一ソースに記述あり。amazon:Linuxカーネル解析入門によれば

「LVT」(Local Vector Table) の初期化を行なっており、割り込みが上がってこないようにマスクしています。
amazon:Linuxカーネル解析入門より引用

とある。具体的に何をしているのか。この関数 (clear_local_APIC()) 結構行数多いな。
まず最初に

    maxlvt = get_maxlvt();

と、している。名前的に LVT の最大値 (ってか数だな) を取得ですか。ちなみに get_maxlvt() は直上にて定義されている。

int get_maxlvt(void)
{
    unsigned int v, ver, maxlvt;

    v = apic_read(APIC_LVR);
    ver = GET_APIC_VERSION(v);
    /* 82489DXs do not report # of LVT entries. */
    maxlvt = APIC_INTEGRATED(ver) ? GET_APIC_MAXLVT(v) : 2;
    return maxlvt;
}

何度も apic_read とか GET_APIC_VERSION なんてやってて行儀悪いな、と一目見て思ってしまうんですが、インラインだしメモリのアクセスだし引数で値渡しなんかよりもよっぽど効率良いのでしょうね。
で、何をしているのか、というと

#define            APIC_INTEGRATED(x)    ((x)&0xF0)
(include/asm-i386/apicdef.h より一部引用)

という定義やamazon:Linuxカーネル解析入門 p.151 やマニュアル下巻の 8.4.8 節の記述等から P4 又は Xeon 向けな Local APIC かそれ以外かを切り分けている。それ以外の場合は LVT の数は 2 である、という事ですな。
で、P4 又は Xeon 向けな Local APIC であればレジスタの 16 から 23 ビットまでで「最大 LVT エントリ」用のフィールドとなっているとの事がamazon:Linuxカーネル解析入門 p.135 やらマニュアル下巻の 8.4.8 節の記述から分かる。GET_APIC_MAXLVT マクロの定義も include/asm-i386/apicdef.h にある。

#define            GET_APIC_MAXLVT(x)    (((x)>>16)&0xFF)

16 ビット右にずらして 8 ビット分を取り出しています。この後の処理的に APIC のバージョンとゆーよりは最大 LVT の数が処理の分岐のよりどころになっていくんだろうな、と勝手に類推。
てか、次でスデに maxlvt 使ってるな。

    /*
     * Masking an LVT entry on a P6 can trigger a local APIC error
     * if the vector is zero. Mask LVTERR first to prevent this.
     */
    if (maxlvt >= 3) {
        v = ERROR_APIC_VECTOR; /* any non-zero vector will do */
        apic_write_around(APIC_LVTERR, v | APIC_LVT_MASKED);
    }

P4 又は Xeon なら、な処理という意味ですな。ちなみにコメントによると P6 限定に読める。ベクタが 0 の場合、P6 な LVT エントリのマスクは local APIC error を起こせる、という意味?? で、まず LVTERR レジスタのマスクが必要、と。
ERROR_APIC_VECTOR の値は include/asm-i386/mach-default/irq_vectors.h にて定義。

#define ERROR_APIC_VECTOR    0xfe

又、APIC_LVT_MASKED の値は include/asm-i386/apicdef.h にて定義。

#define            APIC_LVT_MASKED            (1<<16)

仕様書 (マニュアル下巻) によると、LVT の 17 ビットがマスクビットになっているので、LVT 全般でこのマクロが使用可能になる。
で、ERROR_APIC_VECTORベクタフィールドに設定されてるんですが、今のトコ根拠はamazon:Linuxカーネル解析入門 p.152 の特別な割込みについての表のみ。
とりあえず、LVT エラーレジスタ (0xFEE00370) に 0x01fe が設定される、と。状態としては割込みベクタは 0xfe で割り込みはマスクされている、という事になるのか。
で、次の処理もひたすらマスク処理。

    /*
     * Careful: we have to set masks only first to deassert
     * any level-triggered sources.
     */
    v = apic_read(APIC_LVTT);
    apic_write_around(APIC_LVTT, v | APIC_LVT_MASKED);
    v = apic_read(APIC_LVT0);
    apic_write_around(APIC_LVT0, v | APIC_LVT_MASKED);
    v = apic_read(APIC_LVT1);
    apic_write_around(APIC_LVT1, v | APIC_LVT_MASKED);
    if (maxlvt >= 4) {
        v = apic_read(APIC_LVTPC);
        apic_write_around(APIC_LVTPC, v | APIC_LVT_MASKED);
    }

ソース見てて気がついたんですが、一旦レジスタの値を読みこんでマスクのみを更新した後にマスクビットのみを立ててそれ以外をクリアした状態でレジスタを更新する処理をしています。上記コメントにあるように、とても丁寧に初期化の処理を行なっているのが分かる。てか根拠が知りたいな。(安全策??)
とりあえず上記コードの処理をみてみる。ちなみにレジスタですが、

#define        APIC_LVTT    0x320
#define        APIC_LVTPC    0x340
#define        APIC_LVT0    0x350
#define     APIC_LVT1    0x360

(include/asm-i386/apicdef.h より一部抜粋)
となっておりそれぞれ

  • タイマレジスタ (0xFEE00320)
  • 性能モニタカウンタ (0xFEE00340)
  • LINT0 (0xFEE00350)
  • LINT1 (0xFEE00360)

に合致する。それぞれのレジスタを一旦読み込んでマスクビットを立てて出力。maxlvt が 4 以上の場合には性能カウンタレジスタについても同様の処理を行なっているのが分かる。
次は以下。

#ifdef CONFIG_X86_MCE_P4THERMAL
    if (maxlvt >= 5) {
        v = apic_read(APIC_LVTTHMR);
        apic_write_around(APIC_LVTTHMR, v | APIC_LVT_MASKED);
    }
#endif

ifdef で囲んであって、CONFIG_X86_MCE_P4THERMAL がナニしてあればコンパイルされる訳ですが、これは何処か、と探してみた。有効にするならば Processor type and features → Machine Check Exception → check for thermal throttling interrupt. (NEW) を選択すりゃ良いのな。P4 ってウチには無いのでナニですが、P4 に付属した thermal throttling という機能との事。有効になってりゃ、以下のようなメセジが dmesg に出てくるらしい。(完全に類推)

CPU0: Thermal monitoring enabled

プロセッサが温度監視してクロック下げたらロギングな割り込みを入れるみたい。上記処理であれば、CONFIG_X86_MCE_P4THERMAL が y かつ maxlvt が 5 以上の場合、LVT 温度センサレジスタを一旦読み込んで、マスク部分を更新してからレジスタに出力している。で、次からの処理でベクタ部分とかのマスク以外の部分をクリアする処理となっている。

    /*
     * Clean APIC state for other OSs:
     */
    apic_write_around(APIC_LVTT, APIC_LVT_MASKED);
    apic_write_around(APIC_LVT0, APIC_LVT_MASKED);
    apic_write_around(APIC_LVT1, APIC_LVT_MASKED);
    if (maxlvt >= 3)
        apic_write_around(APIC_LVTERR, APIC_LVT_MASKED);
    if (maxlvt >= 4)
        apic_write_around(APIC_LVTPC, APIC_LVT_MASKED);

#ifdef CONFIG_X86_MCE_P4THERMAL
    if (maxlvt >= 5)
        apic_write_around(APIC_LVTTHMR, APIC_LVT_MASKED);
#endif

基本的にマスクビット以外を 0 にしている処理となっている。
P4 以前のナニについては、タイマおよび LINT0、LINT1 のレジスタのみをマスクし、あとは maxlvt の値に沿って処理を行なっている。
最後の手続きを以下に。

    v = GET_APIC_VERSION(apic_read(APIC_LVR));
    if (APIC_INTEGRATED(v)) {    /* !82489DX */
        if (maxlvt > 3)        /* Due to Pentium errata 3AP and 11AP. */
            apic_write(APIC_ESR, 0);
        apic_read(APIC_ESR);
    }

何してんのか意味不明。P4 又は Xeon 以降のプロセッサでは一旦、エラー・ステータス・レジスタを読みこまないとイカンらしい。しかも maxlvt が 4 以上なら一旦エラー・ステータス・レジスタに 0 を出力してから読み込んでいる。
マニュアルではエラー・ステータス・レジスタは読み取り専用となってるんですが、コメントにあるように eratta な文字が見えますので、ハードなバグ対応なんではないかと。

結局、8259 かどうかは微妙ですが、割り込みマスクな処理だったな、と。