MSX Datapack wiki化計画

15章 マッパーサポートルーチン

※
※ このページは書きかけです。以下の可能性があります。
※ ・誤字、脱字等のチェックをおこなっていません。
※ ・テキスト、表などフォーマットがされていない。
※ ・画像が抜けている。
※

 MSX-DOS2にはメモリマッパーのサポートを提供するルーチンが含まれています。これによってMSXのアプリケーションプログラムやMSX-DOSの外部プログラムは、RAMディスクや他のすべてのシステムソフトウェアと衝突することなしに、基本の64Kのメモリよりも多くのメモリを使用することができるようになります。

 15.1 マッパーの初期化

 MSXマシンでは、DOSカーネルは初期化される際、システムにメモリマッパーが存在することをチェックし、メモリマッパー上に最低128KのRAMが存在していることを確認します。最低128KのマッパーRAMが存在するスロットが1つでも見つかるとカーネルはそれらのうち最大のRAM容量を持つスロット(同じ容量のマッパースロットがある場合、最もスロット番号の小さいもの)を、以降システムRAMとして使用できるようにします。このスロットをプライマリマッパースロットといいます。メモリマッパー上に十分なメモリがないと、MSX-DOS2は立ち上がりません。
 MSX turbo Rマシンでは、常にスロット3-0がプライマリマッパースロットになります。
 次にカーネルは、プライマリマッパースロットで利用できるすべての16KのRAMセグメントのテーブルを作成します。ユーザー用の64Kを構成する最初の4つと最も大きい番号のつけられた2つのセグメントはシステム用に割り当てられ、その一つはDOSのカーネルコードに、もう一つはDOSのカーネルの作業領域として割り当てられます。他の(最低2つ)セグメントは初期状態では未使用として登録されます。さらにカーネルは、(もしあれば)その他のマッパーRAMスロットについて、同様のテーブルを作成します。これらのセグメントは初期状態ではすべて未使用として登録されます。

 15.2 マッパー変数とルーチン

 マッパーサポートルーチンはMSX-DOSのシステムエリアにあるいくつかのテーブルを更新します。これらのテーブルはユーザープログラムから参照して種々の目的に利用することはできますが、絶対に変更してはなりません。テーブルの内容を以下に示します。

表2.18 マッパーサポートルーチンが変更するテーブル

アドレス機能
+0 マッパースロットのスロットアドレス。
+1 16KRAMセグメントの総数。1〜255(プライマリでは8〜255)。
+2 未使用の16KRAMセグメントの数。
+3 システムに割り当てられた16KRAMセグメントの数(プライマリでは最低6)。
+4 ユーザーに割り当てられた16KRAMセグメントの数。
+5〜+7 システム予約。常にゼロ。
+8〜 他のマッパースロット(複数)のエントリ。ない場合+8はゼロ。

 プログラムは種々のサブルーチンを呼ぶことによってマッパーサポートルーチンを使用します。これらのルーチンは、MSX-DOSのシステムエリアにあるジャンプテーブルからアクセスすることができます。ジャンプテーブルの内容は以下のとおりです。

表2.19 ジャンプテーブルの内容

アドレスエントリ名機能
+0H ALL_SEG 16Kのセグメントを割り当てる。
+3H FRE_SEG 16Kのセグメントを解放する。
+6H RD_SEG アドレスA:HLからAにバイトを読む。
+9H WR_SEG EからアドレスA:HLへバイトを書く。
+CH CAL_SEG セグメント間コール。IY:IXのアドレス。
+FH CALLS セグメント間コール。コール命令の後の行のアドレス。
+12H PUT_PH セグメントをページ(HL)に置く。
+15H GET_PH ページ(HL)の現在のセグメントを得る。
+18H PUT_P0 セグメントをページ0に置く。
+1BH GET_P0 ページ0の現在のセグメントを得る。
+1EH PUT_P1 セグメントをページ1に置く。
+21H GET_P1 ページ1の現在のセグメントを得る。
+24H PUT_P2 セグメントをページ2に置く。
+27H GET_P2 ページ2の現在のセグメントを得る。
+2AH PUT_P3 ページ3は絶対に変更してはならないため、サポートされていない。コールされると「NOP」のように動作する。
+2DH GET_P3 ページ3の現在のセグメントを得る。

 15.3 マッパールーチンの使用法

 プログラムは「ALL_SEG」ルーチンをコールすることによっていつでも16K単位でRAMセグメントを要求できます。このルーチンはプログラムが使用できる新しいセグメントのセグメント番号を返し、未使用セグメントが存在しない場合にはエラーを返します。プログラムで、基本の64KRAM以外のセグメントを使用する場合、必ず明示的に割り当てられたセグメントを使用して下さい。
 セグメントの割り当てにはユーザーセグメントとシステムセグメントがあります。ユーザーセグメントはプログラムが終了した場合には自動的に解放されますが、システムセグメントはプログラムが明示的に解放しない限り解放されません。プログラムがセグメントを割り当てる場合には、必要の無い限りユーザーセグメントとして割り当てて下さい。
 RAMセグメントは指定のセグメントのバイト単位の読み書きをする「RD_SEG」および「WR_SEG」によってアクセスできます。「CAL_SEG」と「CALLS」は、既存のMSXシステムのインタースロットコールと同じような方法でセグメント間コールを実行します。
 明示的にセグメントをページングしたり、特定のページ中にはどのセグメントがあるかを見つけるためのルーチンが提供されています。例えば、HL中のアドレスの最上位の2ビットによってページ(0〜3)を指定するルーチン(「GET_Pn」および「SET_Pn」)などです。これるのルーチンは非常に高速ですので、プログラムの性能が犠牲になることはありません。
 ページ3はマッパーサポートルーチンとシステム変数を持っているため、セグメントの切り替えは絶対にできません。また、ページ0は割り込みおよびスロット切り替えのエントリポイントを持っているため、切り換えに際しては細心の注意が必要です。ページ1とページ2はどのようにも変更できます。
 マッパーサポートルーチンはどれもスロット選択メカニズムに全く影響を与えません。例えば「PUT_P1」がコールされると、指定のRAMセグメントは、マッパースロットがページ1に選択されている場合にのみアドレス4000H〜7FFFHに現れます。「RD_REG」および「WR_REG」ルーチンは指定のページ中の現在のスロットの選択に関わらず、常にRAMセグメントをアクセスできますが、ページ2はマッパーRAMスロットになっていなければなりません。

 15.4 セグメントの割り付けと解放

 次の2つのルーチンはセグメントの割り付けと解放を行うために用意されています。AF、BC以外のすべてのレジスタは保存されます。エラーはリターンの際にキャリーフラグがセットされることによって示されます。これらのルーチンがコールされる時にはスロットの選択とRAMのページングはどのような状態にあっても構わず、どちらも保存されます。スタックはこれらのルーチンのどちらがコールされる場合でも、ページ0あるいはページ2にあってはいけません。
 プログラムはこれらのルーチンにより明示的にセグメントを割り付けない限り、どのセグメントも(基本の64Kを構成する4つのセグメントを除いて)使用してはならず、解放した後はそのセグメントを使用し続けてはなりません。
 セグメントはユーザーセグメントあるいはシステムセグメントのどちらかとして割り当てることができます。両者の相違はプログラムが終了するとユーザーセグメントは自動的に解放されますが、システムセグメントは解放されないという点です。プログラム自体が終了した後でもセグメント中にデータを必要とする場合を除き、ユーザーセグメントとして割り付けて下さい。ユーザーセグメントは常に最小の番号の未使用セグメントから割り付けられ、システムセグメントは最大の番号から割り付けられます。
 「セグメントの割り付け」からのエラーは、通常未使用セグメントがないことを示しますが、AおよびBレジスタに不正なパラメータが渡されてことを示す場合もあります。「セグメントの解放」からのエラーは指定のセグメント番号が存在しないか、あるいは既に解放されていることを示します。

ALL_SEG (ALLocate Segment)

パラメータ A=0 ユーザーセグメントの割り付け
A=1 システムセグメントの割り付け
B=0 プライマリマッパーの割り付け
B≠0 複数マッパーサポートによる割り付け
FxxxSSPPスロットアドレス(0の場合プライマリマッパー)
xxx=000指定のスロットのみ割り付け
xxx=001指定のスロット以外の割り付け
xxx=010指定のスロットで割り付けを試み、失敗の場合他のスロット(あれば)を試みる
xxx=011指定のスロット以外で割り付けを試み、失敗の場合他のスロット(あれば)を試みる
結果 キャリーセット=未使用セグメントがない
キャリークリア=セグメントが割り付けられた
A=割り付けられたセグメント番号
B=マッパースロットのスロットアドレス(B=0でコールされた場合は0)

FRE_SEG (FREe SEGment)

パラメータ A=解放するセグメント番号
B=0プライマリマッパー
B≠0プライマリ以外のマッパー
結果 キャリーセット=エラー
キャリークリア=セグメントの解放に成功

 15.5 インターセグメントリード・ライト

 次の2つのルーチンは任意のマッパーRAMセグメントの1バイトのデータをリード・ライトするために用意されています。コール手順はMSXシステムROMによって提供されるインタースロットリード・ライトルーチンと非常に似ています。AF以外のすべてのレジスタは保存され、セグメント番号が有効かどうかを確認するためのチェックはされません。
 セグメント番号により任意の16Kのセグメントを4つのページのどれにも指定できるため、アドレスの上位2ビットは無視され、データは常にページ2を通してリード・ライトされます。データは現在のページングあるいはページ0および1におけるスロットの選択に関わらず、指定されたセグメントとのリード・ライトが行われますが、これらのルーチンがコールされる時には、マッパーRAMスロットがページ2に選択されていなければなりません。これは、アクセスを高速にするため、スロット切り替えを行わないからです。また、スタックはページ2にあってはなりません。これらのルーチンは割り込みを禁止して戻ります。

RD_REG (ReaD SEGment)

パラメータ A =読み出すセグメント番号
HL=セグメント内のアドレス
結果 A =そのアドレスのバイトの値
その他のすべてのレジスタは保存される

WR_SEG (WRite SEGment)

パラメータ A =書き込むセグメント番号
HL=セグメント内のアドレス
E =書き込む値
結果 A =破棄される
その他のすべてのレジスタは保存される

 15.6 インターセグメントコール

 インターセグメントコールをサポートするため、2つのルーチンが用意されています。これらはMSXのシステムROMで提供されている2つのインタースロットコールをモデルとしており、その使用法も非常に似ています。
 指定されたセグメントが実際に存在するかどうかについてのチェックは行われないため、これを確認するのはユーザーの責任となります。コールされたセグメントは指定されたアドレスのページにページングされますが、これらのルーチンはいずれもスロットの選択を変更しないため、マッパースロットがこのページでイネーブルされていることを確認するのはユーザーの責任になります。これによって、ルーチンは高速になります。
 ページ3へのインターセグメントコールは実行することができません。これを実行しようとしても、ページングは行われず、指定されたアドレスが単にコールされるだけです。また、ページ0をコールする場合は、ページ0には割り込みやその他のエントリポイントがあるため、慎重に行って下さい。これらのコールで、スタックが切り換えられるページと重ならないように注意して下さい。
 これらのルーチンはインタースロットコールと違ってコールされたルーチンへ制御を渡す前に割り込みを禁止すると言うことはありません。従ってコールされたルーチンで割り込みフラグを変更しない限り、呼び出し側へは同じ状態で戻ります。
 インターセグメントコールルーチンで内部的に使用されるレジスタIX、IY、AF'、BC'、DE'、HL'にはパラメータを渡すことはできません。これらのレジスタはインターセグメントコール、またはコールされたルーチンで破壊されます。他のレジスタ(AF、BC、DEおよびHL)はコールされたルーチンへそのまま渡され、そこから呼び出し側へ返されます。

CAL_REG (CALl SEGment)

パラメータ IY=コールされるセグメント番号
IX=コールするアドレス
AF、BC、DE、HLがコールされたルーチンに渡される
その他のレジスタは破壊される
結果 AF、BC、DE、HLおよびIYがコールされたルーチンから返される。
この他のすべては破壊される。

CALLS (CALL Segment)

パラメータ AF、BC、DE、HLがコールされたルーチンに渡される
その他のレジスタは破壊される
コール手順 CALL CALLS
DB SEGMENT
DW ADDRESS
結果 AF、BC、DE、HL、IXおよびIYがコールされたルーチンから返される。
この他のすべては破壊される。

 15.7 ダイレクトページングルーチン

 以下のルーチンはプログラムがハードウェアをアクセスする必要なしに、直接現在のページング状態を操作するために用意されています。このルーチンを使えば、ハードウェアの細かい部分に違いがあっても互換性が確保できます。これらのルーチンは非常に高速なため、これを使用することでプログラムの効率に影響することはありません。
 ルーチンは任意の4つのマッパーレジスタに対する直接リード・ライトと同様の機能を提供するために用意されます。セグメント番号の有効性についてのチェックはされないため、ユーザーが行わなければなりません。ここで重要なことは、レジスタに書き込まれた値は、同時にメモリに記憶され、レジスタの値を要求されたときには、この記憶されているメモリの値を返し、決してメモリマッパーのレジスタを直接読み出すことはしないということです。
 従って、例えば「PUT_P1」によってセグメントをイネーブルし、「GET_P1」をコールすると実際に書き込んだ値が返されます。マッパーレジスタを直接読み込んだ婆には、セグメント番号のうち必要でない上位ビットは通常記録されないため、書き込んだ値とは異なる値が返される可能性があります。
 また、システムに複数のマッパーレジスタが存在しているときに、ハードウェアの競合により誤動作する可能性があります。ですから、ユーザーは必ずこれらのルーチンを使用してメモリマッパーを操作しなければなりません。
 「PUT_P3」ルーチンは提供されていますが、実際にはダミールーチンで、ページ3のレジスタは変更されません。これは、ページ3のレジスタの内容を絶対に変更してはならないためです。ただし、「GET_P3」ルーチンでページ3にどのセグメントがあるかを判断することはできます。
 もう1組のルーチン(「GET_PH」と「PUT_PH」)機能的には同じですが、ページをHレジスタの上位2ビットで指定します。これは、HLレジスタがアドレスを持っているときに有用です。これらのルーチンはHLレジスタを破壊しません。「PUT_PH」はページ3のレジスタを変更しません。

PUT_Pn n=0、1、2、3ページの選択
パラメータ A=セグメント番号
結果 なし
すべてのレジスタが保存される
GET_Pn n=0、1、2、3ページの選択
パラメータ なし
結果 A=セグメント番号
すべての他のレジスタが保存される
PUT_PH
パラメータ H=アドレスの上位バイト
結果 A=セグメント番号
結果 なし
すべてのレジスタが保存される
GET_PH
パラメータ H=アドレスの上位バイト
結果 A=セグメント番号
すべての他のレジスタが保存される

 これらのダイレクトページングルーチンを使用してページングの状態を変更する前に、プログラムでは、まず「GET_Pn」を使用して、セグメントの初期状態を保存しておいて下さい(あとでそのセグメントを元に戻すため)。セグメントの初期状態はシステムの将来のバージョンでは変更される可能性があるので、プログラムでは、これらに固定した値を仮定してはなりません。