はじめに
以下の記事の続きで、引き続き MSX2+(FS-A1WSX) の BIOS のブートシーケンスをリバースエンジニアリングしていきます。
システムワークエリアの初期化
(1) 処理の流れ
以下、MSX Datapackの「1.1.3 システムワークエリアの初期化」からの抜粋です。
ワークエリアの初期化はまず0F380Hから0FFF7Hまでを0でクリアすることから始まります。その後、「1.1.2 RAMのサーチ」で得られた拡張スロットに関する情報を【EXPTBL(0FCC1H~0FCC4H)】と【SLTTBL(0FCC5H~0FCC8H)】に書き込みます。その他の初期値はテーブルからブロック転送するかプログラムにより設定します。
次に、ページ2と3で連続して実装されているRAM容量のチェックを行い、【BOTTOM(0FC48H)】という変数を設定します。RAMチェックの順番は以下の通りです。このときも元の内容は破壊されません。0FE00Hから0FEFFHまで1バイトずつ上位アドレス方向に 0FD00Hから0FDFFHまで1バイトずつ上位アドレス方向に : : 08000Hから080FFHまで1バイトずつ上位アドレス方向に
箇条書きにするとこんな感じでしょうか。
- 0xF380 〜 0xFFF7 を 0 クリア
- EXPTBL(0xFCC1 ~ 0xFCC4) 更新
- SLTTBL(0xFCC5 ~ 0xFCC8) 更新
- その他の初期値を設定(BIOS内のテーブルからLDIR or プログラムにより設定)
- BOTTOM(0FC48H) 更新
う〜ん...これは仕様書としては良くない書き方(曖昧)ですね...
(仕様書じゃないから仕方ないのかもしれませんが)
調査のしがいがあります。
0. 前処理
# ページ2,3をRAMスロットにする
ld hl,$0000 ;[7b61] 21 00 00
add hl,sp ;[7b64] 39
ld a,h ;[7b65] 7c
out ($a8),a ;[7b66] d3 a8
ld a,l ;[7b68] 7d
ld ($ffff),a ;[7b69] 32 ff ff
# レジスタCの値()をレジスタAに退避
ld a,c ;[7b6c] 79
RAMサーチでは、SP(スタックポインタ)にRAMが格納されているスロット番号を記憶していて、上位8bitがプライマリスロット、下位8bitがセカンダリスロットという形式になっています。
RAMサーチの段階ではRAMが使用できません。
つまりスタックもまだ使えないので、暇なスタックポインタ(SP)を一時的な記憶領域として利用しているようです。
1. 0xF380 〜 0xFFF7 を 0 クリア
ld bc,$0c77 ;[7b6d] 01 77 0c
ld de,$f381 ;[7b70] 11 81 f3
ld hl,$f380 ;[7b73] 21 80 f3
ld (hl),$00 ;[7b76] 36 00
ldir ;[7b78] ed b0
- RAM[0xF380] に 0 を書き込んでおく
- RAM[DE(0xF381)++] = RAM[HL(0xF380)++] を 0x0C77-1 回繰り返す
- RAM[0xF380〜0xFFF7] が全て 0クリア される
2. EXPTBL(0xFCC1 ~ 0xFCC4) 更新
ld c,a ;[7b7a] 4f
ld b,$04 ;[7b7b] 06 04
ld hl,$fcc4 ;[7b7d] 21 c4 fc
SetExptblLoop:
rr c ;[7b80] cb 19
sbc a ;[7b82] 9f
and $80 ;[7b83] e6 80
ld (hl),a ;[7b85] 77
dec hl ;[7b86] 2b
djnz SetExptblLoop ;[7b87] 10 f7
RAMサーチ結果のレジスタCの内容を元に 0xFCC4, 0xFCC3, 0xFCC2, 0xFCC1 の領域(EXPTBL)に値を設定しています。
コードを見ると、
- RR: C を 1 ビット右ローテション (下位1bitがcarry)
- SBC: A -= A + carry (A = 0 - carry)
- carry 0: A = 0x00
- carry 1: A = 0xFF (-1)
- AND: A &= 0x80
- A を [HL] にストア
- HLをデクリメント
という処理を4回繰り返しているようですね。
つまり、レジスタCへの格納値(RAMサーチ実行結果)とEXPTBLの設定値を凡例的に表記すると次のようになる筈です。
Reg.C:
bit7 | bit6 | bit5 | bit4 | bit3 | bit2 | bit1 | bit0 |
---|---|---|---|---|---|---|---|
* | * | * | * | D3 | D2 | D1 | D0 |
EXPTBL:
EXPTBL+3[0xFCC4] (Slot3) = D0 が 0 なら 0x00, 1 なら 0x80
EXPTBL+2[0xFCC3] (Slot2) = D1 が 0 なら 0x00, 1 なら 0x80
EXPTBL+1[0xFCC2] (Slot1) = D2 が 0 なら 0x00, 1 なら 0x80
EXPTBL+0[0xFCC1] (Slot0) = D3 が 0 なら 0x00, 1 なら 0x80
従って、 EXPTBL(0xFCC1〜0xFCC4)には、Slot0〜3毎に拡張スロットが有る場合は 0x080
、無い場合は 0x00
が設定される 仕様のようです。(仕様書にはこの点を明記しなければNGです)
それでは、何故 0x01 (LSB) じゃなくて、わざわざ 0x80 (MSB) をセットしているのか?
単純に拡張スロット有無を判断したいだけなら LSB で十分ですし、その方がブートシーケンスの処理も(少しだけ)簡単になりますが、わざわざ MSB でセットする形にしているのには、それなりの理由がある筈です。
この疑問に対する回答は、インタースロットコール の仕様に書かれていました。
例えば、RDSLT(特定スロットの特定領域の1バイトを読み込むインタースロットコール)では、
- レジスタ A に読み込み対象のスロット番号
- レジスタ HL に読み込むアドレス
を指定した上で CALL 0x000C
することで、レジスタ A に値が返ってくる仕様ですが、呼び出し時に指定するレジスタ A の形式は次のように規定されています。
つまり、あるスロットに対する RDSLT を行う時、
LD A, ($FCCx) # x=基本スロット番号+1
OR A, %0000yyxx # x=基本スロット番号(0~3), y=拡張スロット番号(0~3)
LD HL, $aaaa # a=読み込み先アドレス
CALL $000C
という形でコーディングすれば良いと考えられます。
なお、EXPTBL+0(0xFCC1) を レジスタAに設定すればBIOSに対するインタースロットコールができるという補足的な記述ならMSX Datapackにありました。(このことから類推せよ...と。スパルタだなぁw)
FS-A1WSXの場合、拡張スロットが有効なのは Slot0,3 (Slot1,2は無効) なので、
Slot0: [EXPTB+0($FFC1)] == 0x80 (enabled)
Slot1: [EXPTB+1($FFC2)] == 0x00 (disabled)
Slot2: [EXPTB+2($FFC3)] == 0x00 (disabled)
Slot3: [EXPTB+3($FFC4)] == 0x80 (enabled)
という形式になっている筈です。
この点について、BASICでPEEK命令を使って実機確認をしたかったのですが、CocoaMSXだとキーボード入力が何故かできなかった(Preferencesを見る限りできそうだが反応しなかった)ので、WebMSXで確認してみました。
Slot構成 (TINY SLOT CHECKER) | BASICでPEEKしてみた結果 |
---|---|
![]() |
![]() |
3. SLTTBL(0xFCC5 ~ 0xFCC8) 更新
# 基本スロットの設定内容を Reg.C に退避(後で復帰する時に使う)
in a,($a8) ;[7b89] db a8
ld c,a ;[7b8b] 4f
# 基本スロットをつぎのように変更
# Page0 = 0
# Page1 = 0
# Page2 = 0
# Page3 = 0
xor a ;[7b8c] af
out ($a8),a ;[7b8d] d3 a8
# 拡張スロットの設定内容を Reg.L に格納
ld a,($ffff) ;[7b8f] 3a ff ff
cpl ;[7b92] 2f
ld l,a ;[7b93] 6f
# 基本スロットを次のように変更
# Page0 = 0
# Page1 = 0
# Page2 = 0
# Page3 = 1
ld a,%01000000 ;[7b94] 3e 40
out ($a8),a ;[7b96] d3 a8
# 拡張スロットの設定内容を Reg.H に格納
ld a,($ffff) ;[7b98] 3a ff ff
cpl ;[7b9b] 2f
ld h,a ;[7b9c] 67
# 基本スロットを次のように変更
# Page0 = 0
# Page1 = 0
# Page2 = 0
# Page3 = 2
ld a,%10000000 ;[7b9d] 3e 80
out ($a8),a ;[7b9f] d3 a8
# 拡張スロットの設定内容を Reg.E に格納
ld a,($ffff) ;[7ba1] 3a ff ff
cpl ;[7ba4] 2f
ld e,a ;[7ba5] 5f
# 基本スロットを次のように変更
# Page0 = 0
# Page1 = 0
# Page2 = 0
# Page3 = 3
ld a,%11000000 ;[7ba6] 3e c0
out ($a8),a ;[7ba8] d3 a8
# 拡張スロットの設定内容を Reg.D に格納
ld a,($ffff) ;[7baa] 3a ff ff
cpl ;[7bad] 2f
ld d,a ;[7bae] 57
# 基本スロットの設定内容を Reg.C へ退避している内容(RAMが使える構成)に復帰
ld a,c ;[7baf] 79
out ($a8),a ;[7bb0] d3 a8
# SLTTBLを更新:
# [SLTTBL+0] = Reg.L
# [SLTTBL+1] = Reg.H
# [SLTTBL+2] = Reg.E
# [SLTTBL+3] = Reg.D
ld ($fcc5),hl ;[7bb2] 22 c5 fc
ex de,hl ;[7bb5] eb
ld ($fcc7),hl ;[7bb6] 22 c7 fc
つまり、
- SLTTBL+0(0xFCC5) = Page0~3 の基本スロットを 0 にした時の拡張スロット
- SLTTBL+1(0xFCC6) = Page3 の基本スロットを 1 にした時の拡張スロット
- SLTTBL+2(0xFCC7) = Page3 の基本スロットを 2 にした時の拡張スロット
- SLTTBL+3(0xFCC8) = Page3 の基本スロットを 3 にした時の拡張スロット
を設定しているようです。
なお、拡張スロットレジスタは、拡張スロットが選択されている場合はビット反転する旨が MSX Datapack に注記されています。
この注記の仕様ですが、何を言わんとしているのかがかなり分かり難いと思います。基本スロット選択レジスタと拡張スロット選択レジスタの操作はインタースロットコールを介して行うべきだから仕様の記述なんて曖昧でOK...みたいな心の声が聴こえてきます。この辺が今回の調査動機で、Z80のマシン語コードならほぼほぼ「強制的なオープンソース」みたいなもの(WebのJavaScriptに近いイメージ)で簡単にディスアセンブルで解析できる点が気に入っています。
例によって、WebMSX(MSX2+)で STTBL の内容を PEEK してみます。
ん...想定と少し違いますね...
BIOSのディスアセンブルからSLTTBL(0xFCC5)の更新箇所を調べてみたところ、複数箇所での更新処理が見受けられたので、BASIC起動までのフェーズで何度か書き換わっているのかもしれません。
MSX Datapackの「Appendix A.2 ワークエリア」を確認したところ、
アドレス | ラベル | 長さ | 内容 |
---|---|---|---|
FCC1 | EXPTBL | 4 | 各スロットの拡張の有無を示すフラグテーブル |
FCC5 | SLTTBL | 4 | 各拡張スロットレジスタ用の現在のスロット選択状況 |
とのことで、EXPTBL は constant テーブルのようですが、SLTTBL は variable テーブルのように見受けられます。
つまり、BASIC起動までの間に色々と書き換わっているようです。
4. その他の初期値を設定
# 割り込みを初期化して 0x7c76 へジャンプ
im 1 ;[7bb9] ed 56
jp $7c76 ;[7bbb] c3 76 7c
# スタックポインタ初期化(いよいよここからスタックも使い始める?)
ld sp,$f376 ;[7c76] 31 76 f3
# FD9A〜FFC8 を 0xC9 に設定
# - FD9A〜FDEA+4: 割り込み処理、他のコンソール入出力装置使用、文字セットやキー配列の変更
# - FDEF〜FEAD+4: ディスク装置接続
# - FEB2〜FEC6+4: 論理装置名の拡張
# - FECB〜FFC5+4: BASIC内部で使用 (最後の1バイトだけ 0x00 のまま)
ld bc,$022f ;[7c79] 01 2f 02
ld de,$fd9b ;[7c7c] 11 9b fd
ld hl,$fd9a ;[7c7f] 21 9a fd
ld (hl),$c9 ;[7c82] 36 c9
ldir ;[7c84] ed b0
# FC4A(HIMEM = 利用可能なメモリーの最上位番地) = 0xF380
ld hl,$f380 ;[7c86] 21 80 f3
ld ($fc4a),hl ;[7c89] 22 4a fc
以前書いた、MSXのSP初期値が 0xF380 という件は正確には「SP = (HIMEM)」とすべきだったようですね。
5. BOTTOM(0FC48H) 更新
これについては仕様が明確なので分かりやすいです。
ページ2と3で連続して実装されているRAM容量のチェックを行い、【BOTTOM(0FC48H)】という変数を設定します。RAMチェックの順番は以下の通りです。このときも元の内容は破壊されません。
0FE00Hから0FEFFHまで1バイトずつ上位アドレス方向に 0FD00Hから0FDFFHまで1バイトずつ上位アドレス方向に : : 08000Hから080FFHまで1バイトずつ上位アドレス方向に
念の為、コードも見ておきます。
call $7d5d ;[7c8c] cd 5d 7d
ld ($fc48),hl ;[7c8f] 22 48 fc
ld hl,$ef00 ;[7d5d] 21 00 ef
NEXT:
ld a,(hl) ;[7d60] 7e
cpl ;[7d61] 2f
ld (hl),a ;[7d62] 77
cp (hl) ;[7d63] be
cpl ;[7d64] 2f
ld (hl),a ;[7d65] 77
jr nz,END ;[7d66] 20 09
inc l ;[7d68] 2c
jr nz,NEXT ;[7d69] 20 f5
ld a,h ;[7d6b] 7c
dec a ;[7d6c] 3d
ret p ;[7d6d] f0
ld h,a ;[7d6e] 67
jr NEXT ;[7d6f] 18 ef
END:
ld l,$00 ;[7d71] 2e 00
inc h ;[7d73] 24
ret ;[7d74] c9
何やら、仕様と実装が違ってますねw
実装通りに修正するとこんな感じです。(修正部分は **
で囲んでいます)
ページ2と3で連続して実装されているRAM容量のチェックを行い、【BOTTOM(0FC48H)】という変数を設定します。RAMチェックの順番は以下の通りです。このときも元の内容は破壊されません。
**0EF00Hから0EFFFH** まで1バイトずつ上位アドレス方向に **0EE00Hから0EEFFH** まで1バイトずつ上位アドレス方向に : : 08000Hから080FFHまで1バイトずつ上位アドレス方向に
F000 〜 FFFF までの 4096バイトはノーチェックというのが正確な仕様のようです。
6. プログラムや変数初期値をストア
ld bc,$0090 ;[7c92] 01 90 00
ld de,$f380 ;[7c95] 11 80 f3
ld hl,$7f27 ;[7c98] 21 27 7f
ldir ;[7c9b] ed b0
0x7F27〜0x7FB6 の内容を RAM の 0xF380〜0xF40F にコピーしています。
- 0xF380〜 インタースロットコール用サブルーチン
- 0xF39A〜 USR関数のマシン語プログラムの開始番地、テキスト画面
- 0xF3B3〜 VRAMの各テーブルの番地、その他の画面設定
- 0xF3DF〜 VDPレジスタのセーブエリアなど
- 0xF3FC〜0xF40F カセット用パラメータなど
0xF39A以降の領域はブートシーケンスの解析にあまり関係しませんが、「インタースロットコール用サブルーチン」についてはBIOS内でもインタースロットコール(主にRDSLT)を実行しているので内容を詳しく見ていきます。
コピー元アドレス | コピー先アドレス | ラベル | 長さ | 内容 |
---|---|---|---|---|
7F27 | F380 | RDPRIM | 5 | 基本スロットから読み込み |
7F2C | F385 | WRPRIM | 7 | 基本スロットへ書き込み |
7F33 | F38C | CLPRIM | 14 | 基本スロットコール |
# 基本スロットから読み込み
out ($a8),a ;[7f27] d3 a8
ld e,(hl) ;[7f29] 5e
jr $7f2f ;[7f2a] 18 03
## ※relative jump なので $f380 へコピーされても正常に動く
# 基本スロットへ書き込み
out ($a8),a ;[7f2c] d3 a8
ld (hl),e ;[7f2e] 73
ld a,d ;[7f2f] 7a
out ($a8),a ;[7f30] d3 a8
ret ;[7f32] c9
# 基本スロットコール
out ($a8),a ;[7f33] d3 a8
ex af,af' ;[7f35] 08
call $f398 ;[7f36] cd 98 f3
ex af,af' ;[7f39] 08
pop af ;[7f3a] f1
out ($a8),a ;[7f3b] d3 a8
ex af,af' ;[7f3d] 08
ret ;[7f3e] c9
例えば、プログラムカウンタがページ0(0x0000〜0x3FFF)にある状態で、別スロットのページ0をRDSLTで読み込みたい場合、そのままページ切り替えをすると暴走してしまいます。
そこで、
- 現在のスロットをD、読み込み先の基本スロットをA、読み込みたいアドレスをHLに指定して0xF380をコール
- 基本スロットをAに切り替え(ページ0が切り替わる筈)
- HLの値をEに読み込む
- 基本スロットをDに切り替え(元のページ構成に復帰する筈)
という手続きを実施することで暴走を回避しています。
7. ファンクションキー文字列の初期化
call $003e ;[7c9d] cd 3e 00
:
jp $13a0 ;[003e] c3 a0 13
:
# FNKSTR (ファンクションキーの文字列保存エリア) を0クリア
ld hl,$f87f ;[13a0] 21 7f f8
ld b,$9f ;[13a3] 06 9f
push hl ;[13a5] e5
xor a ;[13a6] af
Reset_FNKSTR_loop:
ld (hl),a ;[13a7] 77
inc hl ;[13a8] 23
djnz Reset_FNKSTR_loop ;[13a9] 10 fc
pop hl ;[13ab] e1
ld b,$0a ;[13ac] 06 0a
ld de,$13c3 ;[13ae] 11 c3 13
Set_FNKSTR_loop1:
ld c,$10 ;[13b1] 0e 10
Set_FNKSTR_loop2:
ld a,(de) ;[13b3] 1a
inc de ;[13b4] 13
ld (hl),a ;[13b5] 77
inc hl ;[13b6] 23
dec c ;[13b7] 0d
or a ;[13b8] b7
jr nz,Set_FNKSTR_loop2 ;[13b9] 20 f8
push bc ;[13bb] c5
ld b,$00 ;[13bc] 06 00
add hl,bc ;[13be] 09
pop bc ;[13bf] c1
djnz Set_FNKSTR_loop1 ;[13c0] 10 ef
ret ;[13c2] c9
8. その他
xor a ;[7ca0] af
ld ($f660),a ;[7ca1] 32 60 f6
ld ($f87c),a ;[7ca4] 32 7c f8
ld a,$2c ;[7ca7] 3e 2c
ld ($f55d),a ;[7ca9] 32 5d f5
ld a,$3a ;[7cac] 3e 3a
ld ($f41e),a ;[7cae] 32 1e f4
ld hl,($0004) ;[7cb1] 2a 04 00
ld ($f920),hl ;[7cb4] 22 20 f9
ld hl,$f6e4 ;[7cb7] 21 e4 f6
ld ($f74c),hl ;[7cba] 22 4c f7
ld ($f674),hl ;[7cbd] 22 74 f6
ld bc,$00c8 ;[7cc0] 01 c8 00
add hl,bc ;[7cc3] 09
ld ($f672),hl ;[7cc4] 22 72 f6
ld a,$01 ;[7cc7] 3e 01
ld ($f6c3),a ;[7cc9] 32 c3 f6
call $7e6b ;[7ccc] cd 6b 7e
call $62e5 ;[7ccf] cd e5 62
ld hl,($fc48) ;[7cd2] 2a 48 fc
xor a ;[7cd5] af
ld (hl),a ;[7cd6] 77
inc hl ;[7cd7] 23
ld ($f676),hl ;[7cd8] 22 76 f6
call $6287 ;[7cdb] cd 87 62
call $003b ;[7cde] cd 3b 00
jr $7d14 ;[7ce1] 18 31
call $0162 ;[7d14] cd 62 01
ld hl,($fc48) ;[7d17] 2a 48 fc
xor a ;[7d1a] af
ld (hl),a ;[7d1b] 77
inc hl ;[7d1c] 23
ld ($f676),hl ;[7d1d] 22 76 f6
call $6287 ;[7d20] cd 87 62
call $7d29 ;[7d23] cd 29 7d
あまり収穫はなさそうなので調査省略してます。
9. 次の処理へのジャンプ
jp $411f ;[7d26] c3 1f 41
次回
書いたらリンクを貼る