arch/i386/stand/libsa/gidt.S

rtt

このサブルーチンでは、PCをリセットする処理を行っています。リセットが失敗するといくつかの方法でリセットを試みます。

warm bootの設定

BIOS Data Areaのアドレス0x472に0x1234を書き込むことにより,BIOSに対してwarm bootの設定を行います。このアドレスに書き込む値によって、下記のようにboot方法を設定を変更する事ができます。

Boot方法の設定
アドレス 書込み値 意味 詳細
0x4720x0000cold bootメモリチェックを行う。メモリの内容は書き変わる。
0x1234warm bootメモリチェックを行わない。メモリの内容は保証しない
0x4321warm bootメモリチェックを行わない。メモリの内容は保持される。

この処理は、リセット後のboot方法を設定しているだけで、アドレスの0x472に書き込むだけでリセットが起る訳ではありません。

keyboard controller経由でリセット

最初に、keyboard controller経由でリセットを試みます。port 0x64にアサインされている、キーボードのコマンドレジスタに0xfe(システムリセット)を書き込みます。これはCtrl+Alt+Delを行うのと同じ動作になります。

この後に、port 0x84から値を0x5000回も読み込んでいますが、これはwait処理では無いかと思われます(自信無し)。このループから抜けた場合(reset失敗した場合)、再度コマンドレジスタに0xfeを書き込みリトライしています。

除算エラーを使いリセット

まず、割込みディスクリプタにIdtr_resetのアドレスをセットします。この値がセットされると、除算エラーフォルト(割込み番号00H)発生した場合、アドレスの0H番地が実行されることになりシステムがリセットされます。そして、この値をセットした後、0を0で割る処理を行いリセットを試みています。

ダブルフォルトを発生させリセット

int 0x8を叩いてダブルフォルトを発生させ、システムをアボートさせます。

Segment Violationを発生させセット

スタック レジスタを0にセットし、リターンすると何処に戻るか不定になります。結果的にシャットダウン状態になりシステムがリセットされます。

pmm_init

このルーチンは、interrupts Descriptor Tableを操作しての設定を行っています。

割込みの設定

プロテクトモード用の割込みベクタの設定を行います。Idtrの内容を、IDTR(Interrupt Descriptor Table Register)にセットし、idtにあるInterrupt Descriptor Tableを有効ににします。

割り込みディスクリプタ・テーブル(idt)

このテーブルは、割り込みディスクリプタ・テーブルを定義しています。

予約済割り込みの設定

システムで予約されているベクタ番号IDT ディスクリプタのエントリーidteマクロを使用し作成します。このマクロは、下記のように定義されています。

#define idte(e) \ 
	.short  IPROC(e); .short (S32TEXT); \ 
	.short  ((0x80|SDT_SYS386TGT) << 8); .short (LINKADDR >> 16)

例えば、ベクター0番(除算エラー)のエントリーである "idte(de)" は次のように展開されます。

.short  Xde;               // 割り込みハンドラのオフセット(0-15)
.short (0x08);             // ハンドラの存在するセグメントのセレクタ値
.short ((0x80|15) << 8);   // トラップゲート
.short (0x40120 >> 16);    // 割り込みハンドラのオフセット(16-31)

これにより、除算エラー例外が発生したときは、ハンドラXdeルーチンがトラップゲート(IFフラグがクリアーされない)を介して呼び出されます。

予約済割込みハンドラの設定

上記では、ハンドラXdeを設定の例を示しましたが、ハンドラ自体は何処に存在するかというと、 IENTRYマクロIENTRY_ERRマクロによって定義されています。 ハンドラXdeはIENTRY_ERR(de,0,T_DIVIDE)として定義されていて、以下のように展開されます。

Xde:
    pushl   $0 ;  // err
    pushl   $8 ;  // trap no
    jmp     1f

またページフォルトのような例外はIENTRY(pf,T_PAGEFLT)として定義されていて、以下のように展開されます。

Xpf:
    pushl   $6 ;  // trap no
    jmp     1f

両方のハンドラは、最終的にlocore.Sのalltrapsを介してtraps関数が呼ばれます。この時、traps関数に引数として渡されるtrapframe構造体に、ハンドラ側でスタックに積まれたerrとtrip noが入ります。

ユーザ定義割込みの設定

ベクタ番号が32-79のユーザ定義割込みは、BIOS CALLのインターフェースとしてエントリーされ、idtbマクロにより作成されます。このマクロは下記のように定義され、idtbマクロからidteマクロが呼び出される形式になっています。

#define idtb(e) idte(emu##b)

そして、idtb(0)は次のように展開されます。

.short  Xemu0;             // 割り込みハンドラのオフセット(0-15)
.short (0x08);             // ハンドラの存在するセグメントのセレクタ値
.short ((0x80|15) << 8);   // トラップゲート
.short (0x40120 >> 16);    // 割り込みハンドラのオフセット(16-31)

DOINTマクロ

プロテクトモードの割り込み/例外が有効になると、BIOS CALLで使用していたベクター番号が変ってしまいます。
例えばディスク読み出しで使用される "INT 13H" は、プロテクトモードでは"INT 33H"となり使い難くなってしまいます、これを吸収するためDOINTマクロが用意されています。これは下記のように定義されていて、ずれた番号を補正するマクロになっている。
#define DOINT(n)        int     $0x20+(n)

ユーザ定義割込みハンドラの設定

Xemu[0-47]のハンドラは、IEMUENTマクロによって定義されています。Xemu0の場合、IEMUENT(0)と定義されていて、以下のように展開されます。

Xemu0:
      pushl $0; // BIOS INT No.
      jmp 1f;

最終的にXemu[0-47]のハンドラは、BIOS Function Noを引数として、EMUhルーチンにジャンプしBIOS CALLが処理されることになります。

EMUh

EMUhルーチンはユーザ定義割込みハンドラの共通処理ルーチンであり、BIOSのリアルモード インターフェースの仲介処理を行います。

eaxレジスタの保存

eaxレジスタには、通常BIOSのファンクションNo.がセットされているので、これを保存します。保存すると言ってもデータセグメント上でなくコードセグメント上に保存を行っています。

        /* save %eax */
        mov     %eax, 3f
            ......
        # data32 movl $Leax, %eax
        .byte   0x66, 0xb8
3:      .long   0x90909090   # <- ここにeaxの内容が書き込まれる
        # つまり eax=0x11112222だとすると
        # movl 0x11112222 , $eaxと等価になる

データセグメント上に保存しないのは、この後プロテクトモードからリアルモードにスイッチし、データセグメントが変ってしまうため、保存したデータにアクセスできなくなるので、このようなことをしていると思われます。

BIOS INT ベクター No.の取りだし

ジャンプ元のハンドラがセットした、BIOS INT ベクター No.をスタックからeaxレジスタにセットします。

レジスタをスタックに保存

各レジスタをスタックに保存

BIOS INT ベクター No.の保存

eaxレジスタにセットしたBIOS INT ベクター No.をセーブします。ここも、コード上のベクターNo.を直接書き換えている。

        /* save BIOS int vector */
        mov     %al, intno
           ..........
        int     $0   # <- $0の部分がベクターNo.に書き換えられる
intno     = . - 1

プロテクトモードからリアルモードへ移行

プロテクトモードからリアルモードへの移行処理は、prot2realマクロにより定義されています。

es dsレジスタの変更

破壊されるdsレジスタをスタックにセーブして、es dsレジスタを書き換える。この処理ではセグメントを変更している訳では無く、BIOSのパラメータをes dsレジスタにセットしていると言うことに注意。

例えばSYSTEM MEMORY MAPを取得する int 15(ax=E820h)はパラメータとして、es レジスタが指定されている。このように、BIOS CALLの中にはをes dsレジスタにパラメータを指定するものが存在する。

この値は、割り込み前に設定しなければならないが、es dsレジスタにパラメータを設定して割り込み処理に飛んでくると、セグメントが意図して所を指していないということが起るので、BIOSのワークエリアであるBIOS_regsに保存して、BIOS CALL直前になってレジスタにセットすると言うことをしている。

BIOS CALL

一番最初にセーブした、eaxレジスタの値をeaxレジスタに戻し、BIOS CALLを実行する。

レジスタの復帰と保存

まず、スタックに避難しておいたDSの内容を復帰させます。

次に、ワークレジスタとして使用する ESとBXの内容をBIOS_regsにセットします。

ステータスレジスタの内容をセット

ステータスレジスタの下位バイトをBHレジスタにセットします。

リアルモードからプロテクトモードへ移行

リアルモードからプロテクトモードへの移行処理は、real2protマクロにより定義されています。

real2protでは、AXレジスタが破壊されてしまいますので、AXの内容をコード上に書き込み、移行処理後にAXの内容を復帰させています。

スタックの書き換え

現在のスタックフレームの内容は、下記の様になっています。

現時点でのスタックフレーム
現時点でのスタックフレーム

処理の最後の方でpopaやiretしたときに、現在あるレジスタの内容が破壊されないようにスタックを書き換えます。

EFLAGSは、全部書き換えてしまうとマズイので、BHレジスタに保存しておいた下位8bitのみを書き換えます。

NT(Nested Task) Flagをクリア

ステータスレジスタのNT(Nested Task) Flagをクリア。なんでクリアする必要があるのかは、今のところ不明。X-)

レジスタの内容を保存

BIOS Call時にBIOSが返してくるレジスタの内容を保存するエリア、BIOS_regsにレジスタの内容を保存します。

割り込み前のレジスタに復帰

割り込み処理の最初にセーブしておいたレジスタの内容を復帰します。

割り込み処理から抜ける

iretで割り込み処理から抜けます。

Last modified: Sun May 11 18:47:38 2008 JST