このサブルーチンでは、PCをリセットする処理を行っています。リセットが失敗するといくつかの方法でリセットを試みます。
このサブルーチンでは、PCをリセットする処理を行っています。リセットが失敗するといくつかの方法でリセットを試みます。
BIOS Data Areaのアドレス0x472に0x1234を書き込むことにより,BIOSに対してwarm bootの設定を行います。このアドレスに書き込む値によって、下記のようにboot方法を設定を変更する事ができます。
アドレス | 書込み値 | 意味 | 詳細 |
---|---|---|---|
0x472 | 0x0000 | cold boot | メモリチェックを行う。メモリの内容は書き変わる。 |
0x1234 | warm boot | メモリチェックを行わない。メモリの内容は保証しない | |
0x4321 | warm boot | メモリチェックを行わない。メモリの内容は保持される。 |
この処理は、リセット後のboot方法を設定しているだけで、アドレスの0x472に書き込むだけでリセットが起る訳ではありません。
最初に、keyboard controller経由でリセットを試みます。port 0x64にアサインされている、キーボードのコマンドレジスタに0xfe(システムリセット)を書き込みます。これはCtrl+Alt+Delを行うのと同じ動作になります。
この後に、port 0x84から値を0x5000回も読み込んでいますが、これはwait処理では無いかと思われます(自信無し)。このループから抜けた場合(reset失敗した場合)、再度コマンドレジスタに0xfeを書き込みリトライしています。
まず、割込みディスクリプタにIdtr_resetのアドレスをセットします。この値がセットされると、除算エラーフォルト(割込み番号00H)発生した場合、アドレスの0H番地が実行されることになりシステムがリセットされます。そして、この値をセットした後、0を0で割る処理を行いリセットを試みています。
int 0x8を叩いてダブルフォルトを発生させ、システムをアボートさせます。
スタック レジスタを0にセットし、リターンすると何処に戻るか不定になります。結果的にシャットダウン状態になりシステムがリセットされます。
このルーチンは、interrupts Descriptor Tableを操作しての設定を行っています。
プロテクトモード用の割込みベクタの設定を行います。Idtrの内容を、IDTR(Interrupt Descriptor Table Register)にセットし、idtにあるInterrupt Descriptor Tableを有効ににします。
このテーブルは、割り込みディスクリプタ・テーブルを定義しています。
システムで予約されているベクタ番号の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)
#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ルーチンはユーザ定義割込みハンドラの共通処理ルーチンであり、BIOSのリアルモード インターフェースの仲介処理を行います。
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.をスタックからeaxレジスタにセットします。
各レジスタをスタックに保存
eaxレジスタにセットしたBIOS INT ベクター No.をセーブします。ここも、コード上のベクターNo.を直接書き換えている。
/* save BIOS int vector */ mov %al, intno .......... int $0 # <- $0の部分がベクターNo.に書き換えられる intno = . - 1
プロテクトモードからリアルモードへの移行処理は、prot2realマクロにより定義されています。
破壊される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直前になってレジスタにセットすると言うことをしている。
一番最初にセーブした、eaxレジスタの値をeaxレジスタに戻し、BIOS CALLを実行する。
まず、スタックに避難しておいたDSの内容を復帰させます。
次に、ワークレジスタとして使用する ESとBXの内容をBIOS_regsにセットします。
ステータスレジスタの下位バイトをBHレジスタにセットします。
リアルモードからプロテクトモードへの移行処理は、real2protマクロにより定義されています。
real2protでは、AXレジスタが破壊されてしまいますので、AXの内容をコード上に書き込み、移行処理後にAXの内容を復帰させています。
現在のスタックフレームの内容は、下記の様になっています。
処理の最後の方でpopaやiretしたときに、現在あるレジスタの内容が破壊されないようにスタックを書き換えます。
EFLAGSは、全部書き換えてしまうとマズイので、BHレジスタに保存しておいた下位8bitのみを書き換えます。
ステータスレジスタのNT(Nested Task) Flagをクリア。なんでクリアする必要があるのかは、今のところ不明。X-)
BIOS Call時にBIOSが返してくるレジスタの内容を保存するエリア、BIOS_regsにレジスタの内容を保存します。
割り込み処理の最初にセーブしておいたレジスタの内容を復帰します。
iretで割り込み処理から抜けます。