さて本来の作業、つまりドライバの主要な機能を sdc_driver.c ファイル に書く作業に取りかかりましょう。まず、ドライバが提供しなければいけない 機能の概要は次のとおりです:
ドライバファイルの具体的な中身に踏み込んでいく前に知っておくべき、 重要な情報がここにあります:
#define
してある所で両者を区別することが出来ます。
16 色サーバをサポートする場合、このサーバーに固有のコードは
XF86VGA16 の #define
によって有効化されるように
してください。ほとんどの場合、次の指定を stub_driver.c ファイル
の先頭近くに置いておけば充分です。
#ifdef XF86VGA16 #define MONOVGA #endif
運がよければ、チップセットのベンダーがデータブックに様々な BIOS モード用の レジスタ設定表を掲載してくれているでしょう。この表をじっくり見てそれぞれの BIOS モードを調べることによって、どんな操作をしなければならないかについて 多くのことを学ぶことが出来ます。
同じベンダーから出荷している複数のチップセットを単一のドライバで サポートすることが可能であり、むしろ望ましいとされています。 複数のチップセットをサポートする場合、それらに対応する一連の #define と、ドライバコード中でチップセットの識別が必要な時に 使用する`SDCchipset' 変数を用意することになるでしょう。 具体的な例としては Trident と PVGA1/WD を参照してください (Tseng ET3000 と ET4000 はこれに沿っていない例です。 これらのコードは複数のチップセットをサポートするための ドライバインターフェースが開発される前に実装されたものであり、 現在ではこのような実装方法に従うべきではありません。) ドライバーがチップセットのバージョンを区別して認識するべきなのは、 それらが互いに異なる扱いを必要とする場合だけであることに注意して ください。例えば、SDC ドライバが SDC-1a,SDC-1b および SDC-2 と いうチップセットをサポートしているとしましょう。さらにこのとき、 チップセット -1a と-1b は実質的に同じものであり、-2 は異なるもの だとします。このような場合には、ドライバーコードは -1 と -2 の 2 種類のチップセットとしてサポートするべきであり、 -1a と -1b を 区別するべきではありません。これによってエンドユーサの設定作業も 単純になることでしょう。
ユーザにドライバの挙動を制御させたい場合、または何かを決定するために ユーザによる介入を必要とする場合には、``option'' フラッグを使いましょう。 例えば SDC チップセットを使うビデオボードのベンダーが、 8 クロックに 対応するボードと 16 クロックに対応するボードを選択できるようにしていた ならば、これらのボードをチップセットに対する探査の結果から識別する方法は ありませんので、 option フラッグを提供し、ユーザが XF86Config ファイルを 使ってドライバの挙動を選択できるようにするべきです。 option フラッグは ``xf86_option.h'' ファイルに定義します。 再利用可能な option が既に存在していないかどうか、最初に 確認しておきましょう。もし存在する場合はそれを使ってください。 再利用可能なものが無かった場合は、新たに #define を追加し、 同じファイルにある「文字列->シンボル」の対応を定義するテーブル中に 追加する option フラッグで使う文字列を定義します。 option フラッグの使い方を理解するには ET4000, PVGA1/WD と Trident の ドライバを参照してください。
上記の説明から何が必要なのか理解したら、ドライバのデータ構造を 記入する段階に進みましょう。 最初に `vgaSDCRec' の構造を作成します。このデータ構造は SVGA の 状態情報を保持するためにドライバ内部で使用するデータ構造です。 このデータ構造の最初の項目は「常に」 `vgaHWRec std' とします。 この項目は汎用 VGA に関する部分の情報を保持するためのものです。 これに続いて、追加するドライバによって操作されるレジスタのひとつ ひとつに対応する `unsigned char' の領域を用意してください。 これが最初のデータ構造の全てです。
次に `SDC' 構造 (`vgaVideoChipRec' 型) の初期化をしなければいけません。 これは追加するドライバをサーバに識別させるためのグローバルな構造体です。 この構造体の名称はすべて大文字で `SDC' としなければなりません。 これはつまり、追加するドライバコードの存在するディレクトリの名称と 一致させる必要がある、ということです。これはリンクキットの再構成時に 必要なディレクトリとグローバルなデータ構造をすべて認識させるための 条件です。
この構造体の最初の部分は単にドライバ関数へのポインタを保持しておく ために使用されます。
次に、チップセットがバンク切り替えをどう扱うかについての情報を 初期化しなければなりません。次に示すような内容の領域を用意する 必要があります:
Ident() 関数は大変単純な関数です。サーバは自分自身に 組み込まれているドライバの一覧を出力するときに、返り値が NULL に なるまでこの関数を繰り返し呼び出します。 Ident() 関数はサポートしているチップセットのそれぞれに ついてその名称を返さなければなりません。 この関数には繰り返して呼ばれるたびに 0 から順に 1 つずつ増加して いく数値が渡されます。
ClockSelect() 関数は、クロックを探査する(これはつまり、 XF86Config ファイルで `Clocks' 行を指定しなかった場合ですが) 際に、パラメータの一部として渡された数値により指示されたドットクロックを 選択するために使用されます。この関数は、渡された数値に従ってチップセット の clock-select ビットを設定しなければなりません。 これに加えて 2 種類のダミーとしての値 (CLK_REG_SAVE, CLK_SAVE_RESTORE) がこの関数に渡される場合もあります。 CLK_REG_SAVE を受け取った場合には、関数がクロックの選択中に内容を変更する 可能性のあるすべてのレジスタについて、その内容を保存しておくようにします。 CLK_REG_RESTORE を受け取った場合には、これらのレジスタについて、関数が 保存しておいた内容を回復するようにします。 これは、クロック探査によってレジスタの内容が破壊されないことを確実にする ためのものです。
渡されたインデックスが不正な値であるか、もしくは何らかの理由によって クロックを設定できなかった場合、この関数は FALSE を返すべきです。
Probe() 関数はドライバの中で多分最も重要でかつ
最も直感的でない関数でしょう。
Probe 関数は他のすべてのチップセットを誤認識することなく、対象とする
チップセットを識別しなければなりません。もし XF86Config ファイルに
`Chipset
' 行が指定されているのなら、単純な文字列比較を実行
させるだけです。それ以外の場合には、どんなチップセットがシステムに
インストールされているか、何か別の方法で判定しなければなりません。
運がよければ、チップセットに認識機構 (識別/バージョンレジスタなど) が
用意されていて、かつそのことがデータブックに説明されているでしょう。
さもなければ、次に述べるような基準を使い、何らかの手順を決めて認識
させなければなりません。
認識手順としてよく使われるのは、対象とするチップに固有のパターンが レジスタ中に存在するかどうかを調べる方法、またはそのチップに固有の 拡張レジスタが存在するかどうかを調べる方法です。あるいはボードや チップセットによっては、特定の署名文字列を BIOS から読み取ることに よって必要な情報を入手することの可能な場合もあります。 既存の探査関数を勉強すること、および参考資料をきちんと使うことが 最善のアドバイスです。またここで追加する探査関数のコードが非破壊的な ものであることを確認しておかなければなりません。 これは、もし探査中にレジスタの内容を変更するのであれば、 最初に元のレジスタ設定を保存しておかなければいかないこと、 そして最後に自分が保存した内容を復元しておかなければいけないことを 意味します。
うまくチップセットを認識できたら、Probe() 関数は さらに他の部分の初期化をも行う必要があります。
VideoRam
' パラメタが指定されて
いない場合に備えて、搭載メモリ量を判定する必要があります。Clocks
' パラメタが指定されて
いない場合に備えて、使用可能なドットクロックの値を判定する
必要があります。
これは vgaGetClocks() 関数を呼び出して、
使用できるクロックの数と ClockSelect() 関数への
ポインタを渡してやることで実現できます。
EnterLeave() 関数はサーバを起動した仮想コンソールに入ったり 出たりする度に呼ばれます(仮想コンソール機能をもたない OS では、 この関数はまずサーバーの開始時に呼ばれ、終了時にもう一度呼ばれます)。 この関数の目的は (そうすることが必要な OS の場合に) I/O の権限を 譲渡したり剥奪したりすること、およびドライバが操作しなければならない ``保護された''レジスタへのアクセスをロック解除したり再ロックしたり することです。 これは非常に簡単な関数であり、スタブドライバ中のコメントに書かれた 方法で実装できます。
Restore() 関数は保存しておいたビデオ状態を回復するために 使用されます。この `restore' という名称は少しばかり誤った名称であり、 実際には保存された状態を回復するためばかりでなく、サーバが新規に 作成した状態へと移行するためにこの関数を使用することもあります。 Restore() 関数は次の動作を完全に実行しなければなりません。
Save() 関数はサーバ起動時にビデオカードの初期状態に関する情報を 取り出すために使用されます。Save() 関数は次の動作を完全に実行 しなければなりません。
Init() 関数はドライバの中で (Probe() 関数に次いで) 二番目に重要な関数です。これを使って、サーバの中に定義された各表示モードに 対するデータ構造を初期化します。この関数は `vgaSDCRec' データ構造全体を 初期化して、SVGA チップセットを目的の状態に移行させるために必要となる 情報を設定しなければなりません。このデータ構造の汎用 VGA 部分は (これも vgaHW.c 内にある) vgaHWInit() を呼び出して初期化します。
いったん汎用の部分が初期化されたら、Init() 関数は必要に応じて 汎用レジスタの中身を好きなように変更できます。その他必要な領域のすべて に適正な初期化情報を設定します。初期化される特定のモードについての情報 は `DisplayModeRec' 構造体へのポインタである `mode' パラメタを経由して 渡されます。このポインタを辿ることで必要なパラメータを決定できます。
レジスタの特定ビットの初期化方法だけが分かっている場合には、ここで初期化 しましょう。特定ビットだけを操作する場合には Restore() 関数を 使って read/modify/write を実行するのだということをしっかり覚えておいて 下さい。繰り返しますが、例えばこの関数の中で何が実行されるのかについて、 既存のドライバーコードを参照して調べておいてください。
<!_- The Adjust() function is another fairly basic function. It is called whenever the server needs to adjust the start of the displayed part of the video memory, due to scrolling of the virtual screen or when changing the displayed resolution. All it does is set the starting address on the chipset to match the specified coordinate. Follow the comments in the stub driver for details on how to implement it. --> Adjust() 関数はまた別の、とても基本的な関数です。 仮想スクリーンのスクロールや表示解像度の変更によって、 サーバがビデオメモリの表示用領域の開始点を調整する必要のあるときは いつも呼ばれます。 この関数の担当は、指定された座標に合わせてチップセットの開始アドレスを 設定することだけです。 実装方法の詳細は、スタブドライバ中のコメントを参照してください。
ValidMode() 関数は、このドライバーで扱うチップセットに固有の 事情によって、特定のグラフィックモードを使用できない場合があるかどうかを 確認するために使用されるものであり、必ず用意しなければいけません。 この関数は Probe() 実行後により上位のプログラムから呼ばれます。 多くの場合、この関数の中で特別な調査を実行する必要は無く、常に TRUE を 返すような単純なものでも充分でしょう。
SaveScreen() 関数はほとんどのチップセットでは必要ありません。 この関数が必要になるのは、 SVGA チップセット上で同期リセットが実行された時に、 ドライバの動作に必要な拡張レジスタの内容も一緒に変更されてしまうような場合です。 (このような場合、データブックに説明が記載されているはずです。) この関数が必要 *無い* 場合には、単にこれを定義しないでおいて、 vgaVideoChipRec 構造体を初期化する際には、この関数を指定するための フィールドに `NoopDDA' を設定します。 (NoopDDA は汎用の中身の無い関数です)。
この関数が *必要* な場合には、やるべきことは至極単純です。 この関数は必ず二回 (一回目はリセット前、二回目はリセット後) 呼ばれます。 前者の場合には引数として SS_START が渡され、後者の場合は SS_FINISH が 渡されます。必要な作業は、 SS_START で呼び出された際にはリセットによって 影響を受けるレジスタの内容をすべて保存すること、またここで保存した内容を 次に SS_FINISH で呼び出された際に回復することです。
GetMode() 関数は XFree86 1.3 では使われていません。 vgaVideoChipRec 中に用意されているこの関数のためのフィールドは、 `NoopDDA' を指定して初期化してください。
将来的には、この関数を使用することによって、サーバーおよびサーバーの ドライバーライブラリを利用したスタンドアローンプログラム、またはその どちらかからビデオモードを対話的に調節することが可能となる予定です。 この関数は、 SVGA レジスタを読み込み、DisplayModeRec 構造体に現在の ビデオモードを書き出します。
[ 訳注:最新の公開版である XFree86 3.3.3.1 でも、 この GetMode()関数をまともに実装したドライバは vga256/drivers 以下に 30種ほどあるドライバのうち、わずかに ati ドライバーだけです。 他に 2 種類ほど、中身の無い定義だけの関数を用意した例がありますが、 それ以外のドライバーでは `NoopDDA' が指定されています。 現状ではこの関数はあまり必要とされていないということなのかもしれません。 ]
FbInit() 関数はアクセラレータグラフィックスをサポートする ドライバが必ず用意しなければならない関数です。 この関数は標準の cfb.banked 関数を、それぞれのチップに合わせた アクセラレータ対応版の関数に置き換えるために使用されます。 vga256LowlevFuncs は置き換え可能な関数の一覧を含む構造体です。 この構造体は vga256.h で定義されています。 FbInit() 関数の例は et4000, pvga1 および cirrus のドライバに あります。
[ 訳注: XFree86 3.3.3.1 では、 vga256/drivers 以下のドライバー の多くがアクセラレータ対応となっており、多くのドライバーでこの関数が 定義されています。例えば chips(C&T) や tvga8900(trident)、 それに neo(NeoMagic) などのドライバーにも用意されています。 ]
この関数が必要 *無い* 場合には、単にこれを定義しないでおいて、 vgaVideoChipRec 構造体を初期化する際には、この関数を指定するための フィールドに `NoopDDA' を設定します。