PC-98x1/disk/bootstrap

PC-98x1/disk/bootstrap

まあ要するに、起動時にディスクの先頭からちょっとだけ読み込んで実行するヨって感じのアレ。

DOS 領域の内部情報(BPB など)に関してはブートセクタの項を参照。

起動可能なデバイスを見つけたとき、システムはシリンダ 0、ヘッド 0、セクタ 0(FD の場合はセクタ 1)から数セクタ分をロードし、その先頭アドレスに FAR CALL する。

ロード位置とサイズ(読み込むセクタ数)は起動デバイスによって微妙に異なる。 これについては PC-98 シリーズのテクニカルデータブック(ハードウェア編)に説明がある。 (しかし、個人的に見たやつは最新版じゃなかったので 1.44M に関する記述がなかった。もっとも最新版に書いてあるのかどうかは知らないが)

ブートストラップ呼び出し時のレジスタ設定

とりあえずブートストラップ呼び出し時のレジスタがどうなってるか知りたかったので、てきとーに表示するプログラム(dbreg98.nas)を書いてブートセクタにつっこんでみた。 結果はこんな感じ(PC-9821Xc13 にて確認)。

1.44M 2HD (512bytes/sct)

CS=1FE0 DS=0000 ES=1FE0 SS=0020
DI=055C SI=1FE0 BP=0000 SP=01DC
BX=0200 DX=0001 CX=0200 AX=0030
BOOT=30
RETF F700:017B

1.25M 2HD (1024bytes/sct)

CS=1FC0 DS=0000 ES=1FC0 SS=0020
DI=055C SI=1FC0 BP=0000 SP=01DE
BX=0400 DX=0001 CX=0300 AX=0090
BOOT=90
RETF F700:017B

1.2M 2HC (512bytes/sct)

CS=1FC0 DS=0000 ES=1FC0 SS=0020
DI=055C SI=1FC0 BP=0000 SP=01DE
BX=0400 DX=0001 CX=0200 AX=0090
BOOT=90
RETF F700:017B

640K/720K 2DD (512bytes/sct)

CS=1FC0 DS=0000 ES=1FC0 SS=0020
DI=055C SI=1FC0 BP=0000 SP=01DE
BX=0400 DX=0001 CX=0200 AX=00FD
BOOT=10
RETF F700:017B

以上の結果から「ほぼ確か」だと言えそうななこと:

  • ロードアドレスは 1FC0:0000h もしくは 1FE0:0000h。
    (テクニカルデータブックによると、FM FD と ハードディスクは 1FE0:0000h、MFM FD は 1FC0:0000h に読み込まれる。1.44M FD はやや例外的らしい)
  • スタックポインタは 0020:0000〜0020:0200h 付近。 つまり割り込みベクタ 80h〜FFh の領域。
  • DS は 0(システム変数領域の参照には便利?)

ある程度「類推」できそうなこと:

  • 基本的に、読み込み時に DISK BIOS に渡した値がそのまま入ってる? …と思ったら 2DD の場合だけ AL が違いますね。 これはこれで気になる。
  • 1.44M の FD だけ IPL サイズが 512 バイト止まりってことですか?

テクニカルデータブックによると、ハードディスクの IPL(IBM でいうところの MBR)は 1FE0:0000h から2セクタ分読み込まれるそうです(セクタサイズが 256bytes/sct なら 512bytes、512bytes/sct なら 1024bytes)

ブートストラップ呼び出し時のレジスタ設定(エミュレータ編)

とりあえず各種 PC-98 エミュでもやってみた。
(1024bytes/sct, 1MB FD イメージ。実機 BIOS ROM は未使用)

Anex86 V2.78

CS=1FC0 DS=0000 ES=0000 SS=0030
DI=0000 SI=0000 BP=0000 SP=00FE
BX=0000 DX=0000 CX=0000 AX=0000
BOOT=90
RETF 0800:0000

NP2 0.81a (PC-9801VX)

CS=1FC0 DS=0000 ES=DF00 SS=0030
DI=8B00 SI=09FB BP=167B SP=00FA
BX=04E0 DX=043D CX=0000 AX=8309
BOOT=90
RETF FD80:012C

T98-Next next_rb131

CS=1FC0 DS=0000 ES=0000 SS=0030
DI=0000 SI=0000 BP=0000 SP=00FE
BX=0000 DX=0400 CX=0000 AX=0000
BOOT=90
RETF 0800:0000

渡されるレジスタの値を利用してブートセクタのコードサイズをうまいこと切り詰められないかなー、とか思っていたけどやっぱダメか。

ブートストラップ呼び出し時のレジスタ設定(固定ディスク起動メニュー)

ハードディスクから起動した場合、先頭シリンダにある起動メニューで領域を選択すると、その領域の IPL セクタを読み込んでその先頭に処理を移す。

各領域の IPL に渡されるレジスタ内容はどうなるのか…というより、どの領域の IPL が読み込まれたのかというのを IPL 内部で動的に判定する方法はあるのか? という話。 BIOS のワークエリアのどっかにそれらしい情報をメモっておいてくれないのかと思ったが甘かったようだ(undoc2 の memsys.txt にも載ってない)。 ひとつの HDD に複数の DOS 領域がある場合、MS-DOS はどうやって起動ドライブを判定しているんだろう。

実機で調査するのが面倒なので np2 を使った。 40M の SASI ハードディスクイメージに DOS 領域を3つ(10M,10M,20M)作って SASI #2 (DA/UA=0x81) にマウントし、NEC MS-DOS 3.3D の固定ディスク起動メニュープログラム(V2.20)から各領域の IPL を起動してみた。

領域情報部分(シリンダ 0、ヘッド 0、セクタ 1)のダンプ:

0000 a1 81 00 00 00 00 01 00 00 00 01 00 00 00 96 00  。・...........・
0010 54 45 53 54 30 31 20 20 20 20 20 20 20 20 20 20  TEST01          
0020 a2 81 00 00 00 00 97 00 00 00 97 00 00 00 2c 01  「・...・..・..,.
0030 54 45 53 54 30 32 20 20 20 20 20 20 20 20 20 20  TEST02          
0040 a3 91 00 00 00 00 2d 01 00 00 2d 01 00 00 58 02  」・...-...-...X.
0050 54 45 53 54 30 33 20 20 20 20 20 20 20 20 20 20  TEST03          

TEST01 領域起動:

CS=1F80 DS=0000 ES=1F80 SS=0030
DI=0000 SI=0200 BP=0000 SP=00F2
BX=0400 DX=0000 CX=0001 AX=0081
BOOT=81
RETF 0181:0100

TEST02 領域起動:

CS=1F80 DS=0000 ES=1F80 SS=0030
DI=0000 SI=0220 BP=0000 SP=00F2
BX=0400 DX=0000 CX=0097 AX=0081
BOOT=81
RETF 0181:0101

TEST03 領域起動:

CS=1F80 DS=0000 ES=1F80 SS=0030
DI=0000 SI=0240 BP=0000 SP=00F2
BX=0400 DX=0000 CX=012D AX=0081
BOOT=81
RETF 0181:0102

イメージを SASI #1 (DA/UA=0x80) にマウントしなおして再起動すると RETF の上位ワードと AX が 0080 になる。 その他の値は同じ。

以上の結果から、ある程度「類推」できそうなこと:

  • IPL コードは 1F80:0000 から 1024 バイト(2〜4 セクタ)分読み込まれる
  • 起動ユニット、領域番号に相当する情報がスタックに積んである?  (それらしい情報がわざとスタックに積んであるということは…この情報を使って起動領域を判定してかまわないということか? EPSON の起動メニューとかで互換性あんのこれ?)
  • AX, BX, CX, DX にはディスク BIOS を呼び出した後の値がほぼそのまま入っている、と考えてもよさそう。
  • SI には該当する領域情報のポインタっぽいのが入ってる? (利用するのはやや危険かも)

…と思ったが、自動起動設定にしておくとスタックにそれらしい値が積まれないようである。

SASI #1 の各領域を自動起動にした場合。

TEST01 領域自動起動:

CS=1F80 DS=0000 ES=1F80 SS=0030
DI=037C SI=0200 BP=0000 SP=00F6
BX=0400 DX=0000 CX=0001 AX=0080
BOOT=80
RETF 1FC0:0096

TEST02 領域自動起動:

CS=1F80 DS=0000 ES=1F80 SS=0030
DI=041C SI=0220 BP=0000 SP=00F6
BX=0400 DX=0000 CX=0097 AX=0080
BOOT=80
RETF 1FC0:0096

TEST03 領域自動起動:

CS=1F80 DS=0000 ES=1F80 SS=0030
DI=04BC SI=0240 BP=0000 SP=00F6
BX=0400 DX=0000 CX=012D AX=0080
BOOT=80
RETF 1FC0:0096

んー、結局

  • 領域情報を読み込んで IPL のシリンダ値と CX を照合
  • SI から適当に逆算
    • 512バイトセクタの場合は (SI and 0x01ff) shr 5
    • 256バイトセクタの場合は (SI and 0x00ff) shr 5

とかそんな感じになっちゃうんスかねえ。

各領域内の IPL を読み込んで起動するだけの簡単ブートローダー (boot98mbr_test.zip) を作って調べてみた。 とりあえず NEC 版の MS-DOS 3.3D に関する限り、DOS の起動ドライブ文字は領域内の IPL に渡される si レジスタの値によって決定されているように思われる。

(20150702 追記)
256バイト/セクタの HD 上にある領域から起動した場合、SI レジスタの上位バイトは 01xxh になるようだ(512バイトの場合は 02xxh もしくは 03xxh)。

資料

  • GRUB98:stage1/stage1-98.S
  • FreeBSD(98):start.S
  • disktut には PC-98 用 MS-DOS などの IPL 部の逆アセンブルリストが載っている。