Operating Systems Development Series | |
ようこそ
前回は、Video BIOSファームウェアのVGAを中心に、ビデオデバイスに関するいくつかの概念を紹介しました。今回は、VGA、SuperVGA、Video BIOSをサポートするビデオデバイスの異なるハードウェアとファームウェアのインタフェースを見て、これらのインタフェースをサポートする共通のビデオインタフェースをC言語で実装してみます。以下、取り上げるトピックを紹介します。
まず、パーソナルコンピュータで最も多くサポートされ、最も古い規格の1つであるVGA(Video Graphics Array)規格を紹介します。VGA規格は、標準的な方法でビデオハードウェアと対話する方法を提供しますが、低解像度の表示モードと、最新のディスプレイデバイスに存在するグラフィックアクセラレーションサポートの欠如に制限されています。ここでは、ハードウェアインターフェイスとVGA BIOSファームウェアインターフェイスの両方について見ていきます。VGA BIOSインターフェースは、VGAハードウェアインターフェースよりはるかにシンプルですが、リアルまたはv86プロセッサモードでのみ使用できます。ハードウェアインターフェイスは、どのプロセッサモードからでも使用できます。
VBEは Video Electronic Standards Association(VESA)が策定した規格で 、高解像度ディスプレイモードやモニター機能をサポートするためのBIOS拡張機能の標準セットを提供するものである。BIOSの拡張機能であるため、すべてのパソコンが対応しているわけではありません。また、リアルモードまたはv86モードからしか使用できない。
本章の目的は、ビデオモードの変更とディスプレイメモリへのアクセスをカバーすることです。この章の終わりには、VGAまたはSuperVGAを使用して、そのようなことを行うデモを作成する必要があります。この後の章では、グラフィックスに焦点を当てます(SuperVGAのハードウェアグラフィックサポートもあるかもしれません)。
この章のメイントピックに入る前に、少し回り道をして、BIOS を詳しく見ておく必要があります。前回の記事で、VGAのためにこれらのBIOSサービスのいくつかを使用したことを思い出してください。ソフトウェアがこれらのサービスを使用する場合、リアルモードまたはv8086モードで実行する必要があります。このため、プロテクトモードやロングモードのソフトウェアには問題があります。 このため、先に進む前に解決する方法を見つける必要があります。ただし、BIOSサービスを使用する予定がない場合は、このセクションは読み飛ばしてください。
プロテクトモード(ロングモードではない)からBIOSを呼び出すには、2つのアプローチがあります。
最初の方法はより単純ですが、より完全な設計のシステムには非常に不都合です。2番目の方法は最もよく使われる方法ですが、最も難しい方法でもあります。ユーザーモード、割り込みディスパッチ、タスクスイッチ、命令エミュレーションが必要です。
1つ目の方法は、ソフトウェアが必要なときに保護モードから実モードに切り替えられることが必要です。この方法は、ソフトウェアの設計に一定の制限(仮想メモリや上位カーネルをサポートしないなど)を設けないと、サポートが複雑化し、割に合わなくなる可能性があります。しかし、この方法は、実装が最も簡単で、ソフトウェアの追加サポートが最も少なくて済む方法です。これらの理由から、関連するデモではこの方法を選択しましたが、ソフトウェアシステムが十分に大規模で、その必要性が認められる場合には、方法2を使用することを強くお勧めします。
この方法を実装するためには、32ビットプロテクトモードと16ビットリアルモード間のインターフェースとして動作するルーチンまたはルーチンのセットが必要です。これらのルーチンは、ルーチンの入出力値を保持したまま、以下のことを行う必要があります。
このルーチンは、システムがサポートするものへの要求が高くなるにつれて、非常に複雑になる可能性があります。上記のリストは多くのように聞こえますが、システムがページングを使用せず、カーネルイメージが1MB未満であることを前提に、よりトリッキーでハードなものとなっています。言い換えれば、プロジェクトのベースアドレスは64Kで、ルーチンを比較的単純に保つためにページングを無効にしていると仮定しています。
デモでは、BIOSを呼び出すためにio_servicesというメソッドを使用しています。上記の手順でリアルモードに落とします。io_servicesは以下のような感じです。
extern void io_services (unsigned int num, INTR* in, INTR* out);
numは割り込み番号、 inは INTR 構造体 へのポインタ 、outは出力 INTR 構造体 へのポインタ です 。INTR はレジスタ値を格納する構造 体の集合体です。io_ servicesも INTRも かなり大きいので、本文中では省略します。デモの中ではbios.asmを 参照してください。
|
この例では、関数io_servicesとINTR 構造体を使用して、ビデオモードを設定するためにBIOSを呼び出します。Cコードはプロテクトモードで実行されていることに注意してください。 void vga_set_mode (int mode) { /* call BIOS */ INTR in, out; in.eax.val = mode; io_services (0x10, &in, &out); } |
ソフトウェアがロングモードの場合、デバイスを直接プログラムするか、エミュレータを書くしか選択肢がありません。
2つ目の方法は、v8086モードを使用する方法です。これは、BIOSファームウェアの呼び出しをサポートする方法として、長期的には圧倒的に優れていますが、最も要求の多い方法でもあります。v8086モードでは、最低限、オペレーティングシステムが以下をサポートする必要があります。
仮想8086モードはユーザーモードプロセスとしてのみ実行可能です。しかし、ユーザーモードプロセスはカーネルモード命令(int命令など)を実行できないため、BIOSを呼び出すことができず、ある種の目的を 失ってしまうという問題が生じます。つまり、v8086 プロセスがint(割り込み)命令を 実行すると、GPF(general protection fault)が発生します。 これを解決するにはどうしたらいいでしょうか?
ここで完全に解決策がないわけではありません。v8086プロセスはBIOSを呼び出すことができませんが、カーネルは 呼び出すことができます。 GPFが発生すると、カーネルが効果的に呼び出されます。カーネルのGPFハンドラは、何がGPFを引き起こしたかを検出し、次のように対処することができます。
v86_monitorは、すべてのv8086プロセスに対してカーネルGPFハンドラから呼び出される特別な関数 です。v8086プロセスがBIOSを呼び出そうとする(あるいはカーネルモード命令を使おうとする)たびに、カーネルGPFハンドラが呼び出され、 v86_monitorを呼び出してv8086タスクを「監視」するというものです。
v86_monitorは、v8086タスクが使用しようとした問題のある命令をエミュレートする役割を 持つv8086モニタを実装して います。例えば、v8086モニタは、割り込み呼び出しとして問題のある命令(CPUによってCS:EIPが 与えられる)を検出し 、IVT [n] (n = 呼び出すBIOS番号)を呼び出すことによってそれを エミュレートします(ただし、IVT命令ポインタが 線形ではなくセグメント:オフセットフォーマットであることを思い出して ください)。
読者の皆様は、SuperVGAだけを見たいのであれば、このセクションは読み飛ばしていただいて結構です。
VGA(Video Graphics Array )は、1987年にIBM PS/2コンピュータ用に初めて導入されたディスプレイハードウェアの設計であるが[1]、ディスプレイの標準として各機関に広く採用されている。サポートされているビデオモードの最高解像度は640x480x16色である。PCメーカーが広く採用したため、VGAは現在でも最新のPCでサポートされている最も古い規格の1つとなっている。
VGAに続いてIBMのExtended Graphics Array(XGA)規格が登場したが、各メーカーが実装した拡張機能により、最近のPCで一般的な SuperVGA アダプタが生み出された。ほとんどのSuperVGAカードはVGA規格との後方互換性がある。
VGAの複雑さのため、これがVGAの完全な説明にはならないことに注意してください。VGAハードウェアの詳細については、資料[2]と[3]を参照してください。また、VGAに関する多くの大型書籍のうちの1つを読むことをお勧めします。
VGAがサポートするビデオモードとモード番号の標準セットがあります。ビデオモードとは 、解像度、ビット深度 (画素あたりのビット数)、色数、 メモリモードなどの ディスプレイ 構成とそのプロパティを 指します。標準的なビデオモード番号は、0h、1h、2h、3h、4h、5h、7h、Dh、Eh、Fh、10h、11h、12h、13hです。 モード番号自体には特別な意味はなく、ビデオBIOSが特定のビデオモードを参照するために使用されるだけです。さらに多くのモードが存在する可能性がありますが、それらは非標準です。
Mode |
Resolution |
Color depth |
Mode |
Resolution |
Color depth |
0h |
40x25 Text |
16 Color |
Dh |
320x200 |
16 Color |
1h |
40x25 Text |
16 Color |
Eh |
640x200 |
16 Color |
2h |
80x25 Text |
16 Color |
Fh |
640x350 |
2 Color |
3h |
80x25 Text |
16 Color |
10h |
640x350 |
16 Color |
4h |
320x200 |
4 Color |
11h |
640x480 |
2 Color |
5h |
320x200 |
4 Gray |
12h |
640x480 |
16 Color |
7h |
80x25 Text |
2 Color |
13h |
320x200 |
256 Color |
標準のVGAがサポートする最高解像度は、640×480×16色の モード12hです(面白いことに、Windows XPのロゴ画面はモード12hで動作します)。 これ以上の解像度は、後述するSuperVGAでないと得ることができません。
まず、VGAのファームウェア・インターフェースと、ビデオBIOSが提供するファシリティについて見ていきます。前回は、ビデオモードを設定するための割り込み0x10ファンクション0を筆頭に、いくつかのファシリティを紹介しました。ビデオBIOSは、より抽象的なインタフェースを通じて、VGAハードウェアの設定、取得、および操作のためのサービスを提供します。おそらく、ソフトウェアがハードウェアを直接制御するよりも、ビデオBIOSの機能を使用する方がはるかに安全で、シンプルで、ポータブルです。
ここでは、ソフトウェアがビデオサービスに使用できる一般的なファシリティを紹介します。もちろん、これらはBIOS割り込みなので、リアルモードかv8086モードで、BIOSファームウェアを搭載したシステムでのみ使用できます。また、カーネルモードのソフトウェアでしか使用できません。
|
ビデオモードを設定する機能です。 void vga_set_mode (int mode) { /* call BIOS */ INTR in, out; in.eax.val = mode; io_services (0x10, &in, &out); } |
これで、上記の関数を使って、Mode 13hのような任意のVGA BIOSビデオモードを設定できるようになりました。結果は次のようになります。
Result after setting VGA mode.
上の画像からわかるように、ディスプレイにはたくさんのゴミがあります。この「ゴミ」は、実はモード切り替え前のVGAメモリにあったものです。これには、プレーン1のテクスチャ文字やプレーン2のVGAフォントなど、4つのプレーン(これについては後述します)のすべてが含まれています。ディスプレイをクリアすると、このすべてがクリアされます。ディスプレイをクリアするとVGAフォントもクリアされてしまうので、テキストモードに戻さなければならない場合に問題となります。これを解決するには、2つの方法があります。
上記のいずれかを実行することで、グラフィックモードに切り替わり、エラーなくテキストモードに戻ることができます。
|
次のコードは、VGAパレットを設定するものです。 void vga_set_palette (int id) { /* call BIOS */ INTR in, out; in.eax.val = 0xB; in.ebx.val = 0x0100 | id; io_services (0x10, &in, &out); } |
|
以下は、ピクセルの書き込みです。これはデモでは使用されないので省略されるかもしれません。 void vga_plot_pixel (int col, int x, int y) { /* call BIOS */ INTR in, out; in.eax.val = 0x0C00 | col; in.ebx.val = 0; in.ecx.val = x; in.edx.val = y; io_services (0x10, &in, &out); } |
Bochsエミュレータのバージョンによっては、この割り込みに対応していないものがありますので、ご注意ください。しかし、VirtualPCでは実装されています。 上記の割り込みを使って、ピクセルをプロットすることができます。
|
次のコードは、モード情報を返すものです。 void vga_get_mode (unsigned int* col, unsigned int* dispPage, unsigned int* actPage) { INTR in, out; /* sanity check */ if (!col || !dispPage || !actPage) return; in.eax.val = 0xf; io_services (0x10, &in, &out); *dispPage = out.ax.r.al; *actPage = out.bx.r.bh; } |
VGAのハードウェアインターフェイスは非常に複雑で、5つのコントローラと、ポートI/Oアドレス空間からアクセスできる100以上のハードウェアレジスタで構成されています。標準的なレジスタ構成は、現在でも見られる標準的なVGAビデオモードの定義に役立っている。ポートI/Oアドレス空間を使用するため、ソフトウェアはどのプロセッサモードでもVGAハードウェアとのインタフェースが可能です。 もちろん、ソフトウェアがハードウェアと実際に対話するためのインおよびアウト命令ファミリのスーパーバイザレベル(リング0)である必要があります。このように、VGAハードウェアにアクセスできるのはカーネルモードソフトウェアのみです。ユーザーモードのソフトウェアがこれを試みると、inまたはout命令の実行時に一般保護フォルトが生成されます。
ハードウェアを見る前に、警告があります。この警告は、無効なデータやデバイスがサポートする範囲外のデータ(例えば、デバイスが安全な方法で作業できない過剰な周波数設定)からの保護が欠けている可能性があるすべてのディスプレイモニターとビデオカードに適用されます。ほとんどの最新のモニターは、無効な設定に対してエラーメッセージを表示するか、出力を表示しません。実際のハードウェアでテストする前に、まずエミュレータや仮想環境でテストし、ドライバソフトウェアが正しく動作することを確認してから、実際のハードウェアでテストするようにしてください。
VGAのメモリレイアウトは、現在アクティブになっているビデオモードの種類によって異なります。VGAは以下のメモリレイアウトをサポートしています。
次に、これらのすべてのモードと、それらがどのような場合に使用されるかを見ていきます。ディスプレイのメモリを正しく読み書きするためには、さまざまなメモリモデルを知っておくことが重要です。リニアモードは最も複雑なので、最後に説明します。しかし、その前に、まずビデオメモリへのアクセス方法を知っておく必要があります。
システムアーキテクチャの章で見た メモリマップを思い出して ください。 ここにもう一度あります。
VGAメモリは4つの プレーンとして参照 されます。 それぞれ64kのメモリです。これは、4つのウィンドウが重なっているようなものです。これらのウィンドウがプレーンです。
16色モードでは、1色あたり4ビットです。この4ビットは各プレーンの同じ位置に格納されています。
|
plane0[0]、plane1[0]、plane2[0]、plane3[0]に4bitの画素を格納することができる。これは、ディスプレイメモリに1つのピクセルを表示します。4 ビットピクセルが 4 つのプレーンすべてに格納され、各プレーンはピクセルの 1 ビットだけを格納することに注意してください。また、ピクセルは4つのプレーンすべてで同じ位置(インデックスが0)にあることにも注目してください。 |
プレーナーメモリモードでの画素の書き込みの例は、レジスタやビデオモードの設定方法を確認した後に少しずつ紹介していきますが、残念ながら画素の書き込みには、書き込み先のプレーンを選択するためのハードウェアレジスタへの書き込みも必要なため、まだ例を挙げることはできません。
パレットメモリモードでは、各画素はカラーテーブルの 番号(インデックス)で表現され、カラーテーブルがパレットとなる。最も代表的なのは、1画素が8ビットの256色モードだろう。このほか、2色しかない16色(4ビットピクセル)、モノクロ(1ビットピクセル)モードもあります。
カラーテーブルは次のようなものです。これは16色モードで使用されるパレットで、実際、システム起動時のデフォルトはVGAテキストモードパレットです。
インデックス |
色名 |
インデックス |
色名 |
0 |
ブラック |
8 |
ダークグレー |
1 |
青 |
9 |
ライトブルー |
2 |
グリーン |
10 |
ライトグリーン |
3 |
シアン |
11 |
ライトシアン |
4 |
赤色 |
12 |
ライトレッド |
5 |
マゼンタ |
13 |
ライトマゼンタ |
6 |
ブラウン |
14 |
黄色 |
7 |
ライトグレー |
15 |
白色 |
テキストモードでのパレットの使用例を見てみましょう。
|
テキストモードでは、各文字は文字コード(通常はASCII文字)と属性バイトを含むことを思い出してください。アトリビュート・バイトは、上記のパレットへのインデックスです。言い換えれば、もしあなたがプロテクトモードでテキストモードを扱ったことがあるなら、すでにパレットメモリーモードを扱ったことがあるのです! |
256色モードは、DOSビデオゲームで広く使われていた。 これは、標準的なVideo BIOSのモード番号から Mode 13h として悪名高く知られるようになった 。Mode 13hは、非常に高速でリニアであり、一度に256色を画面に表示できるため、興味深いモードである。 つまり、このモードのビデオメモリは、 リニア 、各ピクセルが1バイトで、メモリ内に互いに直後に格納される。Mode 13hが面白いのは、VGAがリニアではなくプレーナディスプレイデバイスであることだ。
|
以下のCコードでは、モード13hのあるx、y位置に256色モードの画素をプロットする関数pixel_256を定義しています。モード13hは320x200なので、ピッチ(ここではピッチは幅)は320であることを想起してください。また、VGA BIOSの割り込みプロットピクセルサービスとは異なり、こちらはどんなPCエミュレータや仮想マシンでも動作します。 #define VGA_VRAM 0xA0000 #define PITCH 320 void pixel_256 (unsigned char color, unsigned int x, unsigned int y) { unsigned char* fb = (unsigned char*) VGA_VRAM; unsigned int offset = y * PITCH + x; fb [offset] = color; } |
上記の例は、Mode 13hを使った作業の簡便さを示すものでもあります。複数のピクセルに連続して書き込むだけで、複数のピクセルをレンダリングすることができます。256色モードでは、各ピクセルはバイトで表されるため、符号なし文字列を使用します。
パレットメモリーモードでは、一度に表示できる色数に上限があります。これは、そのモードで使用されているカラーテーブルのエントリ数と同じです。例えば、256色モードでは最大256色まで、16色モードでは16色までしか表示できない。ソフトウェアでカラーテーブルを変更することで、異なる色を表示することも可能です。
リニアメモリモード リニアフレームバッファ(LFB)をサポートするビデオモードのセット です。 リニアメモリは連続したバイトの配列で、C言語の配列と同じです。モード13hは、リニアメモリ モデルを持つモードの一例 です。また 、ModeX (360x480x256 color) やMode Q (chain-4 256x256x256 color) は、Mode 13h の改良版として悪名高い モードです。
VGAはリニアデバイスではなく、リニアメモリモードをサポートしないことを思い出してください。Mode 13h、Mode X、Mode Qといったモードはすべて、リニアメモリモデルのように見えるようにハードウェアを構成するプレーナモードである。
リニアメモリーモードで画素を書き込むには、画素位置のオフセットを計算してフレームバッファに書き込むだけでよい。
|
次のコードは、リニアメモリーモードで8ビットピクセルをプロットします。 この例は、上記と同じですが、より汎用的で、リニアメモリーモデルを持つどのモードにも適用可能です。 #define VGA_VRAM 0xA0000 #define PITCH 320 void pixel_256 (unsigned char color, unsigned int x, unsigned int y) { unsigned char* fb = (unsigned char*) VGA_VRAM; unsigned int offset = y * PITCH + x; fb [offset] = color; } |
VGAのハードウェア設計は、以下のコンポーネントで構成されています。この中には見覚えのあるものもあるかもしれません。
すべてのハードウェアレジスタはI/Oポート空間にマッピングされています。つまり、CPU命令の インとアウトの ファミリーを 使用してアクセスすることができます。ほとんどのコントローラは 、アドレスレジスタと データレジスタの2つのレジスタをマッピングして います 。アドレスレジスタには 、読み書きしたい特定のレジスタの インデックスが 格納 され、 データ レジスタには、そのレジスタのデータが格納されます。このことは、いくつかの例を見ていくと、より明確になるかもしれません。
これらの各コンポーネントを見る前に、VGAハードウェアの複雑さを強調しておきたいと思います。VGAを深くカバーする書籍が一冊まるごと書かれており、1つの記事ですべての詳細をカバーすることは非常に困難な作業です。そこで、ここでは、各コンポーネントが行うことと、そのコンポーネントが使用するレジスタのセットについてのみ、最小限の議論にとどめることにします。レジスタの数が多いので、ここではレジスタの説明は省略します。残念ながら、ハードウェアレジスタの背景がわからないと、ビデオモードの設定方法を説明することができません。これを是正するために、すべてのレジスタのリストを提示し、最後にビデオモードの設定の表を記載します。 これにより、読者は各レジスタの詳細や形式を気にすることなく、VGAビデオモードを設定することができます。しかし、VGAについてもっと知りたいと思う人には、[3]や[4]を参照して、各レジスタについてもっと読むことをお勧めします。
要するに、レジスタの詳細についてはあまり気にしないでください。どのレジスタにどの値を入れるかは、ビデオモードリストが教えてくれます。
|
CRTコントローラー(CRTC)の設定が不適切な場合、ビデオカードや接続されたモニターに損傷を与える可能性があります。まれにですが、不適切な設定によりCRTやLCDモニターが焼損・爆発する事例があることが知られています。 |
CRTコントローラ(CRTC )は 、ディスプレイモニタへのビデオデータの出力を制御 する役割を担っています。アドレス・レジスタとデータ・レジスタでアクセスします。 アドレス・レジスタは3D4hに、データ・レジスタは3D5hにあります(Miscellaneous Output Register I/O Address selectフィールドが設定されている場合は、それぞれ3B4h、3B5hになります。)。 アドレス・レジスタでアクセスしたいCRTCレジスタを選択し、データ・レジスタでそのCRTCレジスタからの読み出しまたは書き込みを行います。
レジスタの数が多いので、各レジスタの完全な技術的レビューは行いません。CRTレジスタの詳細については、FreeVGAプロジェクトの専用ページで解説していますので、そちらを参照してください。もし需要があれば、将来的にVGAに関するトピックを拡張する可能性があります。
0 |
水平方向の総レジスタ |
1 |
エンド ホリゾンタル ディスプレイ レジスタ |
2 |
水平ブランキングレジスタの開始 |
3 |
水平ブランキングレジスタの終了 |
4 |
水平リトレース開始レジスタ |
5 |
水平リトレース終了レジスタ |
6 |
垂直方向の総レジスタ |
7 |
オーバーフローレジスタ |
8 |
プリセット行走査線レジスタ |
9 |
最大走査線数レジスタ |
10 |
カーソル開始レジスタ |
11 |
カーソル終了レジスタ |
12 |
スタートアドレスハイレジスタ |
13 |
スタートアドレスLowレジスタ |
14 |
カーソル位置上位レジスタ |
15 |
カーソル位置ローレジ |
16 |
垂直リトレース開始レジスタ |
17 |
垂直リトレース終了レジスタ |
18 |
垂直方向表示終了レジスタ |
19 |
オフセット・レジスタ |
20 |
アンダーライン位置レジスタ |
21 |
垂直ブランキングレジスタの開始 |
22 |
垂直ブランキングレジスタの終了 |
23 |
CRTCモード制御レジスタ |
24 |
ラインコンペアレジスタ |
CRTCレジスタは、ディスプレイの水平および垂直リトレースとブランキング期間のピクセルクロックのタイミングを制御することができます。これらのタイミングは、ディスプレイの出力(ビデオ解像度)とリフレッシュ・レートを制御するのに役立ちます。また、CRTCのモード制御レジスタは、CRT本体と一部のアドレッシング・モードを制御するためのものです。
グラフィックス・コントローラは、CPUとビデオ・メモリ間のインターフェイスを管理する役割を担っています。アドレスレジスタは3CEhに、データレジスタは3CFhにマッピングされています。 グラフィックスコントローラのレジスタにアクセスするには、アドレスレジスタにレジスタのインデックスを書き込み、データレジスタからリードまたはライトしてください。
以下は標準的なレジスタの一覧ですが、各レジスタの詳細についてはFreeVGAプロジェクトを参照してください。
0 |
セット/リセット・レジスタ |
1 |
イネーブルセット/リセットレジスタ |
2 |
カラーコンペアレジスタ |
3 |
データローテートレジスター |
4 |
読み出しマップ選択レジスタ |
5 |
グラフィックモードレジスタ |
6 |
雑多なグラフィックス・レジスタ |
7 |
カラードントケアレジスター |
8 |
ビットマスクレジスタ |
シーケンサは、ビデオデータとRAMDACの間のインターフェイスを管理します。 アドレス・レジスタは3C4hに、データ・レジスタは3C5hにマッピングされています。シーケンサのレジスタにアクセスするには、アドレス・レジスタにレジスタのインデックスを書き込み、データ・レジスタからリードまたはライトしてください。
以下は標準的なレジスタの一覧ですが、各レジスタの詳細についてはFreeVGAプロジェクトを参照してください。
0 |
リセット・レジスタ |
1 |
クロッキングモードレジスタ |
2 |
マップマスクレジスタ |
3 |
キャラクタ・マップ・レジスタ |
4 |
シーケンサメモリモードレジスタ |
アトリビュート・コントローラは、VGA用の21個のレジスタで構成されています。このコントローラは 、3C0hと 3C1hの2つのレジスタをI/Oポート空間にマッピングして います。 他のコントローラと異なり 、アトリビュートコントローラは、3C0hのレジスタをデータ書き込みポートおよびアドレスポートとして使用します。3C1hのレジスタはデータ読み込みレジスタです。 アトリビュートコントローラと通信 するには、ソフトウェアはまずポート3C0hにレジスタアドレスを書き込み、その次にそのレジスタへデータを書き込む必要があります。データの読み込みにはポート3C0hへ書き、3C1hから読み込みを行うことが可能です。アトリビュート・アドレス・レジスタも特定のフォーマットを持っています。
Attribute Address Register |
|||||||
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
|
PAS |
Address |
アドレスとは、アクセスするレジスタのインデックスのことです。以下のレジスタの表を参照してください。
PAS (Palette Address Source) ホストまたはEGAディスプレイ・アダプタによるパレット・データへのアクセスを決定します。0の場合、ホストはパレットRAMにアクセスでき、アダプタはディスプレイメモリのパレットへのアクセスを禁止します。1の場合、ディスプレイ・メモリはパレットRAMにアクセスでき、ホストはパレットへのアクセスを禁止されます。
アトリビュートコントローラは、カラーインデックスと色を対応させる16個のパレットレジスタのセットを格納する。この小さなパレットは、EGAからの欠点である。VGAはまだこれらのレジスタを使用しますが、パレット情報を格納する代わりに、カラーインデックスレジスタの2番目のセットにアドレスを格納します。ここでは、その仕組みの詳細については説明しませんが、興味のある方は [4] ページの 394 を読んでみてください。
以下に標準的なレジスタの一覧を示しますが、各レジスタの詳細については、FreeVGAプロジェクトまたは[4]を参照してください。
0-15 |
パレット入力レジスタ |
16 |
モード制御レジスタ |
17 |
オーバースキャン・カラー・レジスタ |
18 |
カラープレーンイネーブルレジスタ |
19 |
水平画素パンニングレジスタ |
20 |
カラーセレクトレジスタ |
このようなレジスタのリストには終わりがありませんね。これまで、VGAの主要なコントローラのレジスタのリストを見てきましたが、ビデオモードで使用される汎用レジスタや雑多なデータ用のレジスタのセットもあり、見ておく必要があります。これらのレジスタは、一般レジスタまたは外部レジスタと呼ばれます。
以下に標準的なレジスタの一覧を示しますが、各レジスタの詳細については、FreeVGAプロジェクトまたは[4]を参照してください。なお、ここではカラーアダプタとVGA(EGAではない)を想定しています。 モノクロアダプタとEGAアダプタでは、異なるポートを使用する場合があります。
Write port: 3C2h Read port: 3CCh |
Miscellaneous output register |
Write port: 3BAh Read port: 3CAh |
Feature control register |
Read port: 3C2h |
Input Status #0 register |
Read port: 3BAh |
Input Status #1 register |
これらのレジスタは、ソフトウェアが256色パレットを操作し、管理するためのものです。256色モードで作業する場合、これらのレジスタは重要なセットであり、ビデオモードの設定後に使用されることがあるので、よく理解しておくとよいでしょう。また、このレジスタは、これから見ていく最後のレジスタセットであることも知っておくとよいでしょう。
以下に標準的なレジスタの一覧を示しますが、各レジスタの詳細については、FreeVGAプロジェクトまたは[4]を参照してください。
Write port: 3C8h Read port: 3C8h |
PEL address write mode register |
Write port: 3C7h |
PEL address read mode register |
Write port: 3C9h Read port: 3C9h |
PEL data register |
Read port: 3C7h |
DAC state register |
Write port: 3C6h Read port: 3C6h |
PEL mask register |
ビデオハードウェアを設定することで、異なるプロパティを持つ表示モードを定義することができます。しかし、ビデオハードウェアがサポートする多くのモードは、多くのモニタがサポートしていません。他の構成は、望ましくない効果を持つ可能性があります。いくつかのVGAモード設定は、ほとんどのモニターでサポートされているため、標準となっています。以下は、標準的なビデオモードとその設定の一覧です。 標準のビデオモード番号は、Mode 0h、1h、2h、3h、4h、5h、7h、Dh、Eh、Fh、10h、11h、12h、13hです。 Mode Xや Mode Qは よくサポートされていますが、標準モードではありません。
その他のリストについては[3]または[4]を参照してください。以下の表は、[4]の304-305ページから採用しました。表は、各モードが変更するレジスタの異なるセットで分割されています。一番上の行はモード番号、左の列は特定のレジスタにアクセスするためのインデックスを表しています。4]によると、提示された値は 、標準的なビデオBIOSが使用する初期モード状態を 表して います。
ビデオモードを設定するには、ソフトウェアがビデオ出力を無効にして、関連する値を変更するすべてのハードウェアレジスタに書き込む必要があります。 これにより、読み取り/書き込みモード、アドレス開始、水平/垂直ブランキング期間とタイミング、リフレッシュレート、解像度、グラフィックスモードタイプなど、すべてが設定されます。
以下の表は、正確を期して作成したものであるが、発見されていない誤りがある可能性がある。表は[4]で発表されたものと一致するように書かれたが、書き直しの際に誤りがある可能性がある。原著者に謝意を表する。
一般レジスタ
|
Mode |
||||||||||||||
Index |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
D |
E |
F |
10 |
11 |
12 |
13 |
0 |
63 |
63 |
63 |
63 |
63 |
63 |
63 |
A6 |
63 |
63 |
A2 |
A3 |
E3 |
E3 |
63 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
2 |
70 |
70 |
70 |
70 |
70 |
70 |
70 |
70 |
70 |
70 |
70 |
70 |
70 |
70 |
70 |
3 |
4 |
4 |
5 |
5 |
4 |
4 |
5 |
FF |
4 |
4 |
FF |
4 |
4 |
4 |
4 |
シーケンスレジスタ
|
Mode |
||||||||||||||
Index |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
D |
E |
F |
10 |
11 |
12 |
13 |
0 |
3 |
3 |
3 |
3 |
3 |
3 |
3 |
3 |
3 |
3 |
3 |
3 |
3 |
3 |
3 |
1 |
9 |
9 |
1 |
1 |
9 |
9 |
1 |
0 |
9 |
1 |
1 |
1 |
1 |
1 |
1 |
2 |
3 |
3 |
3 |
3 |
3 |
3 |
1 |
3 |
F |
F |
F |
F |
F |
F |
F |
3 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
4 |
2 |
2 |
2 |
2 |
2 |
2 |
6 |
2 |
6 |
6 |
6 |
6 |
6 |
6 |
E |
CRTCレジスター
|
Mode |
||||||||||||||
Index |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
D |
E |
F |
10 |
11 |
12 |
13 |
0 |
2D |
2D |
5F |
5F |
2D |
2D |
5F |
FF |
2D |
5F |
FF |
5F |
5F |
5F |
5F |
1 |
27 |
27 |
4F |
4F |
27 |
27 |
4F |
FF |
27 |
4F |
FF |
4F |
4F |
4F |
4F |
2 |
28 |
28 |
50 |
50 |
28 |
28 |
50 |
FF |
28 |
50 |
FF |
50 |
50 |
50 |
50 |
3 |
90 |
90 |
82 |
82 |
90 |
90 |
82 |
FF |
90 |
82 |
FF |
82 |
82 |
82 |
82 |
4 |
2B |
2B |
55 |
55 |
2B |
2B |
54 |
FF |
2B |
54 |
FF |
54 |
54 |
54 |
24 |
5 |
A0 |
A0 |
81 |
81 |
80 |
80 |
80 |
FF |
80 |
80 |
FF |
80 |
80 |
80 |
80 |
6 |
BF |
BF |
BF |
BF |
BF |
BF |
BF |
FF |
BF |
BF |
FF |
BF |
B |
B |
BF |
7 |
1F |
1F |
1F |
1F |
1F |
1F |
1F |
FF |
1F |
1F |
FF |
1F |
3E |
3E |
1F |
8 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
FF |
0 |
0 |
FF |
0 |
0 |
0 |
0 |
9 |
C7 |
C7 |
C7 |
C7 |
C1 |
C1 |
C1 |
FF |
C0 |
C0 |
FF |
40 |
40 |
40 |
41 |
A |
6 |
6 |
6 |
6 |
0 |
0 |
0 |
FF |
0 |
0 |
FF |
0 |
0 |
0 |
0 |
B |
7 |
7 |
7 |
7 |
0 |
0 |
0 |
FF |
0 |
0 |
FF |
0 |
0 |
0 |
0 |
C |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
FF |
0 |
0 |
FF |
0 |
0 |
0 |
0 |
D |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
FF |
0 |
0 |
FF |
0 |
0 |
0 |
0 |
E |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
FF |
0 |
0 |
FF |
0 |
0 |
0 |
0 |
F |
31 |
31 |
59 |
59 |
31 |
31 |
59 |
FF |
31 |
59 |
FF |
59 |
59 |
59 |
31 |
10 |
9C |
9C |
9C |
9C |
9C |
9C |
9C |
FF |
9C |
9C |
FF |
83 |
EA |
EA |
9C |
11 |
8E |
8E |
8E |
8E |
8E |
8E |
8E |
FF |
8E |
8E |
FF |
85 |
8C |
8C |
8E |
12 |
8F |
8F |
8F |
8F |
8F |
8F |
8F |
FF |
8F |
8F |
FF |
5D |
DF |
DF |
8F |
13 |
14 |
14 |
28 |
28 |
14 |
14 |
28 |
FF |
14 |
28 |
FF |
28 |
28 |
28 |
28 |
14 |
1F |
1F |
1F |
1F |
0 |
0 |
0 |
FF |
0 |
0 |
FF |
F |
0 |
0 |
40 |
15 |
96 |
96 |
96 |
96 |
96 |
96 |
96 |
FF |
96 |
96 |
FF |
63 |
E7 |
E7 |
96 |
16 |
B9 |
B9 |
B9 |
B9 |
B9 |
B9 |
B9 |
FF |
B9 |
B9 |
FF |
BA |
4 |
4 |
B9 |
17 |
A3 |
A3 |
A3 |
A3 |
A2 |
A2 |
C2 |
FF |
E3 |
E3 |
FF |
E3 |
C3 |
E3 |
A3 |
18 |
FF |
FF |
FF |
FF |
FF |
FF |
FF |
FF |
FF |
FF |
FF |
FF |
FF |
FF |
FF |
グラフィックコントローラ
|
Mode |
||||||||||||||
Index |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
D |
E |
F |
10 |
11 |
12 |
13 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
2 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
3 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
4 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
5 |
10 |
10 |
10 |
10 |
30 |
30 |
0 |
10 |
10 |
0 |
0 |
10 |
0 |
0 |
40 |
6 |
0E |
0E |
0E |
0E |
0F |
0F |
0D |
0A |
5 |
5 |
5 |
5 |
5 |
5 |
5 |
7 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
F |
5 |
0 |
5 |
F |
F |
8 |
FF |
FF |
FF |
FF |
FF |
FF |
FF |
FF |
FF |
FF |
FF |
FF |
FF |
FF |
FF |
アトリビュートコントローラ
|
Mode |
||||||||||||||
Index |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
D |
E |
F |
10 |
11 |
12 |
13 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
1 |
1 |
1 |
1 |
13 |
13 |
17 |
8 |
1 |
1 |
8 |
1 |
3F |
1 |
1 |
2 |
2 |
2 |
2 |
2 |
15 |
15 |
17 |
8 |
2 |
2 |
0 |
2 |
3F |
2 |
2 |
3 |
3 |
3 |
3 |
3 |
17 |
17 |
17 |
8 |
3 |
3 |
0 |
3 |
3F |
3 |
3 |
4 |
4 |
4 |
4 |
4 |
2 |
2 |
17 |
8 |
4 |
4 |
18 |
4 |
3F |
4 |
4 |
5 |
5 |
5 |
5 |
5 |
4 |
4 |
17 |
8 |
5 |
5 |
18 |
5 |
3F |
5 |
5 |
6 |
6 |
6 |
6 |
6 |
6 |
6 |
17 |
8 |
6 |
6 |
0 |
14 |
3F |
14 |
6 |
7 |
7 |
7 |
7 |
7 |
7 |
7 |
17 |
8 |
7 |
7 |
0 |
7 |
3F |
7 |
7 |
8 |
10 |
10 |
10 |
10 |
10 |
10 |
17 |
10 |
10 |
10 |
0 |
38 |
3F |
38 |
8 |
9 |
11 |
11 |
11 |
11 |
11 |
11 |
17 |
18 |
11 |
11 |
8 |
39 |
3F |
39 |
9 |
A |
12 |
12 |
12 |
12 |
12 |
12 |
17 |
18 |
12 |
12 |
0 |
3A |
3F |
3A |
0A |
B |
13 |
13 |
13 |
13 |
13 |
13 |
17 |
18 |
13 |
13 |
0 |
3B |
3F |
3B |
0B |
C |
14 |
14 |
14 |
14 |
14 |
14 |
17 |
18 |
14 |
14 |
0 |
3C |
3F |
3C |
0C |
D |
15 |
15 |
15 |
15 |
15 |
15 |
17 |
18 |
15 |
15 |
18 |
3D |
3F |
3D |
0D |
E |
16 |
16 |
16 |
16 |
16 |
16 |
17 |
18 |
16 |
16 |
0 |
3E |
3F |
3E |
0E |
F |
17 |
17 |
17 |
17 |
17 |
17 |
17 |
18 |
17 |
17 |
0 |
3F |
3F |
3F |
0F |
10 |
8 |
8 |
8 |
8 |
1 |
1 |
1 |
0E |
1 |
1 |
0B |
1 |
1 |
1 |
41 |
11 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
12 |
0F |
0F |
0F |
0F |
3 |
3 |
1 |
0F |
0F |
0F |
5 |
0F |
0F |
0F |
0F |
13 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
14 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
ビデオモードを設定するには、上記の表でモードが変更するすべてのレジスタを設定する必要があります。次の例は、モードの設定を説明するものです。
|
In order to set a video mode, we need to upload all of the values into the registers associated with that mode. For example, if we have a function, uploadRegisters, that does this, we can set Mode 13h by doing the following. unsigned char _mode13h = { /* general registers */ 0x63,0,0x70,0x4, /* sequencer */ 0x3,0x1,0xF,0,0xE, /* CRTC */ 0x5F,0x4F,0x50,0x82,0x24,0x80,0xBF,0x1F,0,0x41, 0,0,0,0,0,0x31,0x9C,0x8E,0x8F,0x28,0x40,0x96,0x89,0xA3,0xFF, /* graphics */ 0,0,0,0,0,0x40,0x5,0xF,0xFF, /* attribute */ 0,1,2,3,4,5,6,7,8,9,0xA,0xB,0xC,0xD,0xE,0xF,0x41,0,0xF,0,0 } /* set mode 13h */ uploadRegisters (&_mode13); |
レジスタ値の書き込み後、ビデオモードの変更に成功しました。
VGAモード13hで動作するデモとディスプレイメモリのクリアリング
先ほど、平面メモリモデルを見て、ハードウェアレジスタを見るまで、ピクセルのプロットの例を完成させることができなかったことを思い出してください。今、その例を紹介します。
|
次のコードは、8ビットピクセルを平面的にプロットするものです。 void pixel_p (unsigned char color, unsigned int x, unsigned int y) { unsigned int* fb = (unsigned int*) 0xa0000; unsigned int offset = y * pitch + (x/8); bankSwitch (offset >> 16); /* writes to bit mask register of graphics controller to select plane */ outportb(0x3CE,8); outportb(0x3CF,0x80 >> (x & 7)); fb [bankOffset] = color; } |
SuperVGAは、SuperVGA(SVGA)を含むディスプレイハードウェアのクラスを指し、通常、XGA、SXGA、SXGA+、UXGA、QXGA、QSXGA、および2560x2048以上の解像度をサポートする他の規格も含まれる。SuperVGAは、VGAのメーカーによる拡張機能として始まり、現在のような個別のSuperVGA規格に採用された。つまり、SuperVGAデバイスを包括する規格は存在しないのだ。すべてのSuperVGAカードは異なっており、異なるハードウェアとファームウェアのインターフェイスを提供します。そこで問題です。SuperVGAの標準的な使い方がないのに、どうやってサポートするのでしょうか。この問題は、ほとんどのメーカーがカードの技術仕様を公開していないという事実によって、さらに深刻になっています。その代わり、彼らはWindowsやLinuxなどの異なるオペレーティングシステム用のブラックボックスドライバを書いている。
したがって、SuperVGAのデバイスに対応するためには、2つの選択肢しかありません。
最初の選択肢は、仕様書を入手する必要があるため、困難です。仕様が分からないデバイスはリバースエンジニアリングに頼らざるを得ません。今回取り上げるSuperVGAの「カード」は、Bochsエミュレータで使用されているものです。Bochsを使う人なら誰でも使えるので、これを選びました。しかし、ドライバのコードはBochs固有のものになります。
VBEは、SuperVGAハードウェアのためのBIOS割り込み拡張の標準セットです 。VBEは非常にシンプルで、SuperVGAを扱うための単一の標準的なインタフェースを作成します。しかし、リアルモードまたはv8086モードが必要で、BIOS割り込みを使用し、拡張機能であるため、すべてのマシンがVBEをサポートしているわけではありません。
Vesa Bios Extensions (VBE)は、SuperVGAモードで動作するためのBIOS割り込みサービス一式を定義して います([5]を参照)。VBEは Vesa Bios Extensions (VBE) Core Standardで定義されています([5]を参照)。 カーネルモードのソフトウェアは、リアルモードまたはv8086モードでBIOSサービスを呼び出すことができます。
VBEは、ビデオBIOSと同様に モード番号のセットを定義しています。モード番号は次のような形式です。
VBE Mode Number |
|||||||
15 |
14 |
13 |
12 |
11 |
10 |
9 |
Bits 0...8 |
DM |
LFB |
Reserved, set to 0 |
Mode |
Mode number は実際のモード番号です。ビット8がセットされていれば、VESAで定義された標準的なモードです(詳細は後述します)。
LFBは、リニアフレームバッファ(LFB)モードと バンクスイッチングモードを選択 します。0の場合、バンクスイッチングを使用し、標準的なVGAフレームバッファを使用します。1の場合、リニアフレームバッファを使用します。
DMは、モード設定時に表示メモリをクリアするかどうかを選択します。0の場合、表示メモリはクリアされます。1の場合、表示メモリはクリアされません。
VESA定義モードは、BIOSベンダーがサポートを推奨しているビデオモード番号の標準セットです。次の表は、グラフィックモードの一覧です。
Mode |
Resolution |
Color depth |
Mode |
Resolution |
Color depth |
100h |
640x480 |
256 |
113h |
800x600 |
32K |
101h |
640x480 |
256 |
114h |
800x600 |
64K |
102h |
800x600 |
16 |
115h |
800x600 |
16.8M |
103h |
800x600 |
256 |
116h |
1024x768 |
32K |
10Dh |
320x200 |
32K |
117h |
1024x768 |
64K |
10Eh |
320x200 |
64K |
118h |
1024x768 |
16.8M |
10Fh |
320x200 |
16.8M |
119h |
1280x1024 |
32K |
110h |
640x480 |
32K |
11Ah |
1280x1024 |
64K |
111h |
640x480 |
64K |
11Bh |
1280x1024 |
16.8M |
112h |
640x480 |
16.8M |
|
|
|
11Bhを超えるモード番号も定義可能ですが、標準ではありません。したがって、さらに高解像度のモードをサポートすることも可能である。
|
VBEモード番号113hはバンクスイッチングを使用する800x600x32Kカラーモード、VBEモード番号8113hはリニアフレームバッファ(LFB)を使用する800x600x32カラーモードが選択されます。モード番号の形式を覚えておきましょう |
ここで、VBE BIOS サービスに注目します。これらのサービスはすべて[5]で参照できることを念頭に置いてください。ここでは、ビデオモードの設定とディスプレイメモリアクセスに必要な最も重要な3つのサービスのみを取り上げます。その他の割り込みについては、仕様書(読みやすい仕様書の一つです)を参照してください。
Structure. |
vbeInfoBlock 構造体は、以下のフォーマットを持つ。 |
||||||||||||
typedef struct _vbeInfoBlock { uint8_t signature[4]; // �VESA� uint16_t version; // Either 0x0200 (VBE 2.0) or 0x0300 (VBE 3.0) uint32_t oemString; // Far pointer to OEM name uint8_t capabilities[4]; // capabilities uint32_t videoModesPtr; // Far pointer to video mode list uint16_t totalMemory; // Memory size in 64K blocks uint16_t oemSoftwareRev; uint32_t oemVenderNamePtr; uint32_t oemProductNamePtr; uint32_t oemProductRevPtr; uint8_t reserved [222]; uint8_t oemData [256]; }vbeInfoBlock; |
|||||||||||||
The vbeInfoBlock.capabilities field has the following format.
D0: 0の場合、DACは6ビット/色に固定されます。1の場合、DACの幅を8ビット/色に切り替えることができます。 D1:0 の場合、コントローラは標準的な VGA モードをサポートします。1の場合、コントローラは標準的なVGAモードをサポートしていません。 D2: 0なら、RAMDACは通常動作です。1の場合、ファンクション9のブランク・ビットを使用するように仕様で指示されています。 |
次の例は、この割り込みを呼び出す例です。
|
ここで言うべきことは本当に多くはありません。割り込みを呼び出すには、この構造体を用いてrm_biosルーチンを呼び出します。SEGとOFFSETは、それぞれリアルモードのセグメントと オフセットを計算するマクロです。これは、BIOSに渡すときに、32ビットのリニアアドレスを16ビットのセグメント:オフセットアドレスに変換するために使用されます。 void vbe_get_descr (vbeInfoBlock* descr) { INTR in, out; /* sanity checks */ if (!descr) return; /* call BIOS */ in.eax.val =0x4F00; in.es = SEG((unsigned int) descr); in.edi.val = OFFSET((unsigned int) descr); io_services (0x10, &in, &out); } |
Structure. |
ModeInfoBlockは以下の構造体である。構造体のサイズが大きいので、すべてのメンバについて説明することはしません。後の章で説明する可能性のある重要なメンバにはコメントを付けています。 typedef struct _modeInfoBlock { uint16_t attributes; uint8_t windowA, windowB; uint16_t granularity; uint16_t windowSize; uint16_t segmentA, segmentB; uint32_t winFuncPtr; /* ptr to INT 0x10 Function 0x4F05 */ uint16_t pitch; /* bytes per scan line */ uint16_t resolutionX, resolutionY; /* resolution */ uint8_t wChar, yChar, planes, bpp, banks; /* number of banks */ uint8_t memoryModel, bankSize, imagePages; uint8_t reserved0; uint8_t readMask, redPosition; /* color masks */ uint8_t greenMask, greenPosition; uint8_t blueMask, bluePosition; uint8_t reservedMask, reservedPosition; uint8_t directColorAttributes; uint32_t physbase; /* pointer to LFB in LFB modes */ uint32_t offScreenMemOff; uint16_t offScreenMemSize; uint8_t reserved1 [206]; }modeInfoBlock; |
次の例では、割り込みの呼び出しを実演しています。
|
次の関数は、上記の割り込みを使用して、基本的なバンク切り替えを行います。SEGと OFFSETを使用して、outの32ビットリニアアドレスを16ビットセグメント:オフセットファーポインタに変換していることに注意してください。 void vbe_get_mode (int mode, modeInfoBlock* descr) { INTR in, out; /* sanity check */ if (!descr) return; /* call BIOS */ in.eax.val = 0x4F01; in.ecx.val = mode; in.es = SEG ((unsigned int)descr); in.edi.val = OFFSET((unsigned int)descr); io_services (0x10, &in, &out); } |
最後に見るのは、ディスプレイモードを設定するための割り込みです。これは、任意のVGAとVBEで定義されたSuperVGAモード、および標準以外の拡張モードを設定するために使用できます。
|
次の関数は、上記の割り込みを利用して、VBEモードを設定するものです。 void vbe_set_mode (int mode) { /* call BIOS */ INTR in, out; in.eax.val = 0x4F02; in.ebx.val = mode; io_services (0x10, &in, &out); } |
vbe_set_modeを呼び出すと、VBEで定義された任意のモードを設定することができ、次のような結果が得られます。
Demo running in SuperVGA Mode 118h (1024x768)
VBEは、SuperVGAで使用されている2つの標準的な表示用メモリのアプローチをサポートしています。LFB(Linear Frame Buffer )とバンクスイッチングです。この2つでVBEがサポートするあらゆるビデオモードの設定を試みることができることを思い出してください。これらは、ディスプレイメモリへのアクセス方法の違いに過ぎません。このセクションでは、両方のタイプのモードを操作する方法について見ていきます。
LFBモードでは、すべてのビデオメモリが 物理アドレス 空間にマップさ れ、通常は3GB〜4GBの範囲のハイアドレスになります(IA32アーキテクチャを想定)。ディスプレイメモリはC言語の配列のように線形で、ポインタから読み書きするだけですべてのビデオメモリに アクセスすることが可能です。VBEでは 、INT 0x10 Function 4F01hを 呼んで、現在のモードのmodeInfoBlock 構造体を取得することで、このポインタを 得ることができます。このとき表示するポインタは、ちょうどmodeInfoBlock.physbaseになります。
|
次のCコードは、16ビットの赤緑青(RGB)画素をプロットして表示するものです。 void pixel_16RGB (unsigned short color, unsigned short x, unsigned short y) { unsigned short* fb = (unsigned short*) _modeInfo.physBasePtr; unsigned short offset = x + y * (_modeInfo.bytesPerScanLine / 2); fb [offset] = color; } |
LFBモードの表示用メモリは、表示用メモリがマッピングされているため、プロテクトモードまたはロングモードからしかアクセスすることができません。リアルモードでは、高解像度モードの表示メモリにアクセスするには、バンク切り替えを使用するしかありません。
バンク切り替えを使用するモードでは、常に64KのディスプレイメモリがVGAメモリスペースの 0xa0000物理アドレス にマッピング されています。この64Kのディスプレイメモリのブロックを バンクと呼びます。 ソフトウェアは一度に1つのバンクにしかアクセスできないため、ディスプレイメモリのすべてにアクセス することはできません。そのため、ソフトウエアは必要に応じてバンクを切り替え、すべてのディスプレイメモリにアクセスしなければならない。
つまり、バンク0はディスプレイメモリの0〜64Kのバイト、バンク1はディスプレイメモリの64K〜128Kのバイト、といった具合に、バンクを切り替えることで、ソフトウエアはこの64Kの "窓 "からすべてのディスプレイメモリにアクセスできる。つまり、バンクを切り替えることで、この64Kの "窓 "から、どんなに高解像度であっても、すべてのディスプレイメモリにアクセスすることができるのだ。
|
次のコードは、8ビットRGBピクセルをバンク切り替えモードでプロットします。 void pixel_8RGB (unsigned char color, unsigned short x, unsigned short y) { unsigned char* fb = (unsigned char*) 0xa0000; unsigned int offset = x + (long)y * _modeInfo.bytesPerScanLine; vbe_bankSwitch (offset >> 16); fb [offset & 0xffff] = color; } |
バンク切り替えモードは、より多くの計算を必要とするため、一般的に速度が遅くなります。つまり、画面上のピクセルの位置だけでなく、バンク内のオフセットや切り替え先のバンクも計算する必要があるのです。この計算は、ディスプレイメモリから読み出す場合も同様です。ただし、バンク切り替えモードは、ディスプレイメモリの64Kをマッピングするだけなので、リアルモードやv86モードでも使用可能です。
VBEでは、INT 0x10 Function 0x4F05 - Display Window Controlを呼び出すことでバンクを切り替えることができました。 また 、INT 0x10 Function 0x4F01 - Get VBE Mode Informationを呼び出して VbeModeInfo.WinFuncPtrを呼び出すことで(VBE仕様が推奨するように)直接呼び出す ことが可能です。
ディスプレイバンクを設定または取得します。VbeModeInfo.WinFuncPtrから呼び出すこともできます。
|
次の関数は、上記の割り込みを利用して、基本的なバンク切り替えを行います。これは、VBEの実装によっては、読み出しと書き込みのウィンドウが別々になっている場合があるため、ウィンドウAとウィンドウBに対して2回割り込みを呼び出していることに注意してください。 void vbe_bankSwitch (int bank) { INTR in, out; bank <<= bankShift; in.eax.val=0x4F05; in.ebx.val = 0; /* BH=0 (Set memory window) BL=0 (Window A) */ in.edx.val = bank; io_services (0x10, &in, &out); /* call BIOS */ in.eax.val=0x4F05; in.ebx.val = 1; /* BH=0 (Set memory window) BL=1 (Window A) */ in.edx.val = bank; io_services (0x10, &in, &out); /* call BIOS */ } |
Bochsエミュレータは、ファームウェアを呼び出すことなく直接VBEモードを設定する別の方法を提供します。 サポートは限定的ですが、高解像度グラフィックスを扱うためのセミポータブルな方法(互換性のあるBochsエミュレータが必要なだけ)を提供します。
|
以下の機能により、Bochs社がサポートするあらゆるVBEモードを設定することが可能です。 #define VBE_DISPI_IOPORT_INDEX 0x01CE #define VBE_DISPI_IOPORT_DATA 0x01CF #define VBE_DISPI_INDEX_ID 0x0 #define VBE_DISPI_INDEX_XRES 0x1 #define VBE_DISPI_INDEX_YRES 0x2 #define VBE_DISPI_INDEX_BPP 0x3 #define VBE_DISPI_INDEX_ENABLE 0x4 #define VBE_DISPI_INDEX_BANK 0x5 #define VBE_DISPI_INDEX_VIRT_WIDTH 0x6 #define VBE_DISPI_INDEX_VIRT_HEIGHT 0x7 #define VBE_DISPI_INDEX_X_OFFSET 0x8 #define VBE_DISPI_INDEX_Y_OFFSET 0x9 #define VBE_DISPI_DISABLED 0x00 #define VBE_DISPI_ENABLED 0x01 #define VBE_DISPI_GETCAPS 0x02 #define VBE_DISPI_8BIT_DAC 0x20 #define VBE_DISPI_LFB_ENABLED 0x40 #define VBE_DISPI_NOCLEARMEM 0x80 void BlBochsVbeWrite (uint16_t index, uint16_t value) { WRITE_PORT_USHORT(VBE_DISPI_IOPORT_INDEX, index); WRITE_PORT_USHORT(VBE_DISPI_IOPORT_DATA, value); } void BlBochsSetMode (uint16_t xres, uint16_t yres, uint16_t bpp) { BlBochsVbeWrite (VBE_DISPI_INDEX_ENABLE, VBE_DISPI_DISABLED); BlBochsVbeWrite (VBE_DISPI_INDEX_XRES, xres); BlBochsVbeWrite (VBE_DISPI_INDEX_YRES, yres); BlBochsVbeWrite (VBE_DISPI_INDEX_BPP, bpp); BlBochsVbeWrite (VBE_DISPI_INDEX_ENABLE, VBE_DISPI_ENABLED | VBE_DISPI_LFB_ENABLED); } |
標準的なスーパーVGAのハードウェア・インターフェースは存在しないことを思い出してください。つまり、この方法はBochsに特化したものであり、他の環境やプラットフォームでは動作しない可能性があります。読者の多くはBochsを主なエミュレータとして使用すると思われますので、最高の互換性を維持するためにこの方法を使用します。また、この方法は最もシンプルです。
ただし、より高い互換性のために、仮想8086モードを使用してVBEサービスを直接利用することが推奨されます。
この章では、VGA BIOS、VGAハードウェア、SuperVGA、VESA BIOS Extensionsの紹介を含め、多くのことをカバーしました。これで、後の章で必要となる低解像度および高解像度グラフィックモードの切り替えに必要な資料をすべて網羅しました。次の数章では、実際のグラフィックスレンダリングとパイプラインに焦点を当て、場合によってはSuperVGAのビットを追加する予定です。次回は、基本的なプリミティブとグラフィックスレンダリング、そしてトランスフォームから始めるかもしれません。
後のデモで使用するのは、今回最後に紹介した「Bochs VBE Interface」という方法です。これにより、読者の多くがBochsを使うことを前提に、後の記事で高解像度のLFBモードを使うことができるようになりました。つまり、表示モードを設定するために好きな方法を使用することができ、よりグラフィックスに焦点を当てた次の数回の記事にも従うことができます。