はじめに
PCIデバイス制御方法
内部構造
DLL部分
NT用ドライバー
95/98用ドライバー

 

デバイスドライバーの役割

 

アプリケーションからDLLのメモリー、I/O、コンフィグレーション空間へのアクセス関数が呼ばれるとDeviceIoControlを使ってそれぞれに対応したデバイスドライバー内の処理が実行されます。以下にデバイスドライバーの主な機能について説明します。

物理メモリーへのアクセス

 

3のように仮想アドレス空間にマップされていない領域にはアクセスできません。そこで任意の物理メモリーにアクセスするには、そのアドレスをマップしマップした仮想アドレスをリードライトする処理が必要になります。

この処理はカーネルモードでないと出来ない処理なのでデバイスドライバー内部で行います。

メモリーリードを行う時、DLLからリスト4のようにしてDeviceIoControlを呼び出します。

ULONG    WINAPI _MemReadBlockLong(ULONG address, UCHAR* data, ULONG count)
{
    return _MemReadBlock(address, data, count, 4);
}

ULONG    WINAPI _MemReadBlockShort(ULONG address, UCHAR* data, ULONG count)
{
    return _MemReadBlock(address, data, count, 2);
}

ULONG    WINAPI _MemReadBlockChar(ULONG address, UCHAR* data, ULONG count)
{
    return _MemReadBlock(address, data, count, 1);
}


ULONG    _MemReadBlock(ULONG address, UCHAR* data, ULONG count, ULONG unitsize)
{
    PCIDEBUG_MEMREAD_INPUT param;
    BOOL IoctlResult;
    ULONG    ReturnedLength;
    ULONG    size;
    param.address = address;
    param.unitsize = unitsize;
    param.count = count;
    size = param.unitsize * param.count;

    // IOCTLを使ってデバイスドライバーを呼び出しメモリーリードする
    IoctlResult = DeviceIoControl(
        handle,                             // Handle to device
        IOCTL_PCI_READ_MEM,                 // IO Control code
        &param,                             // Buffer to driver
        sizeof(PCIDEBUG_MEMREAD_INPUT),     // Length of buffer in bytes.
        data,                             // Buffer from driver.
        size,                             // Length of buffer in bytes.
        &ReturnedLength,                 // Bytes placed in outbuf.
        NULL                             // NULL means wait till I/O completes.
    );
    if(IoctlResult && ReturnedLength == size){
        return param.count;
    } else {
        return 0;
    }
}

リスト4 DLL内のメモリーアクセス関数部分(memfunc.c)

パラメータとして、第2引数にメモリーリード処理のエントリーであることを表す IOCTL_PCI_READ_MEMを、第3引数のメモリーバッファーにはリードするアドレスやアクセス方法を入れておきます。

DeviceIoControlが呼び出されるとデバイスドライバー内のディスパッチ関数PciDispatchがカーネルモードで実行されます(リスト5)pIrpStackの内容から要求されているIOCTLの種類を調べIOCTL_PCI_READ_MEMの処理ルーチンであるPciIoctlReadMemに移ります。この関数で実際のメモリーリードを行います。

ここではまずバッファーの内容からアクセスするアドレスを得ます。

そしてMmMapIoSpace( )により仮想アドレス空間にマップします。この関数が返した値が仮想メモリーアドレスとなり、アクセスが可能になります。

メモリーリードにはアクセスワード長によりREAD_REGISTER_BUFFER_UCHARREAD_REGISTER_BUFFER_USHORTREAD_REGISTER_BUFFER_ULONGのいずれかを使用します。

メモリーライトについても同様な流れ出行います。詳しくはソースファイルを参照下さい。

I/O空間へのアクセス

 

I/O空間はメモリーのように仮想アドレスはありませんが、ユーザーモードから実行することはできません。カーネルモードで実行する必要があります。方法はメモリーの場合と同様でDeviceIoControlを使ってドライバーを制御します。IOCTLの種類はIOCTL_PCI_WRITE_PORT_XXXXまたはIOCTL_PCI_READ_PORT_XXXXを用います。これに対応して、デバイスドライバーではHalの関数READ_PORT_XXXXまたはWRITE_PORT_XXXXを使用してI/O空間のリードまたはライトを行います。

コンフィグレーション空間へのアクセス

コンフィグレーションレジスタアクセスへのアクセスするにはHalHalGetBusDataByOffset()HalSetBusDataByOffset()を使用します。この関数はPCIバスに限らずバスの状態を取得するためのものですが、第一引数にPCIConfigurationを指定することによりPCIのコンフィグレーションレジスターへのアクセスとなります。

これもカーネルモードでないと出来ないので、メモリーアクセスの場合と同じようにDLLからDeviceIoControlを使ってデバイスドライバーを制御里Halの関数を実行します。

IOCTLの種類をIOCTL_PCI_READ_CONFまたはIOCTL_PCI_WRITE_CONFとした場合がコンフィグレーション空間のリードまたはライト動作です。

DLLとドライバー間のやりとりはメモリーアクセスの場合と同様です。

 

割り込みについて

DLLの内部についてで説明したように、監視スレットを作る事により割り込み発生時にユーザーモードの関数を呼び出す仕組みをDLL内部にもっています。

実際に割り込みが起きた時には、まずデバイスドライバー内の割り込み処理ルーチンが呼び出されます。このルーチンが呼び出されたことをDLLに知らせなければなりません。しかしカーネルモードの関数から、直接ユーザーモードで動作している割り込み監視スレットを直接呼び出して割り込みを知らせることはできません。

デバイスドライバーからユーザーモードのソフトに対して通信する手法としてNTには名前付きイベントを使う方法があります。名前付きイベントはWin32の関数としてCreateEventSetEventResetEvent、やシグナル状態になるまで停止するWaitForSingleObjectなどが用意されています。デバイスドライバーからも同じ名前付きイベントを参照でき同じはたらきの関数が用意されているため比較的簡単にデバイスドライバーとユーザプログラムで同期を取る事ができます。

レベルセンシティブな割り込みの問題

割り込み信号の検出方法にはエッジセンシティブとレベルセンシティブの2つが有ります。

エッジセンシティブでは割り込み線の変化、例えば0から1への変化で割り込みが起きた事を伝えます(図17a)。割り込みが起きたあと1から0に戻す時は問題にしません。この代表的なものがISAバスの割り込みです。分りやすい方法ですが、これでは1つの割り込み線を複数のデバイスで共有することは考慮されていません。ワイヤードORによって割り込み線を共有しても一つのデバイスが割り込み線を1にしている間は他のデバイスのが割り込みを出そうとしてもORした結果は1で割り込みラインにエッジ(0から1への変化)が起きず検出できません。

wpeB.jpg (22412 バイト)

図17(a) エッジセンシティブな割り込み

これを解決したのがレベルセンシティブな割り込みです(図17b)。割り込みが発生したら、0から1に変化させ割り込み処理が終了したら1から0に戻します。これにより割り込み線が1の間は割り込み処理を要求しているデバイスがあるということが明確に分り割り込みの共有が可能になります。

PCIではレベルセンシティブな割り込みであり、割り込み線の共有も可能です。(PCIの割り込み線は負論理のため図とは論理が逆になります)

 

wpeC.jpg (29718 バイト)

図17(b) レベルセンシティブな割り込み

このレベルセンシティブな割り込みはpcidebug.dllNT用ドライバーの割り込み処理を次のような事情から複雑で少々トリッキーなものにしています。理由を説明する前にNTの割り込み処理について簡単にふれておきます。

Windows NTでは割り込みが発生するとデバイスドライバー内のISR(Interrupt Service Routine)が呼び出されます。

ISRでは割り込みの共有を考慮して次の処理を行わなければなりません

  1. 自分のデバイスが割り込みを発生しているかどうかを判断する
  2. 自分のデバイスが割り込み状態だったら、割り込みを解除する

割り込みを共有している場合、ISRが呼ばれたからといって自分のデバイスが割り込み状態であるとは限りません。1の判定が必要になります。

レベルセンシティブな割り込みでは2が重要な処理です。ISRを終える前に割り込みを解除しなければISRから戻った直後にまた割り込みが発生したことみなされISRが連続して呼ばれてしまいます。

この1,2の処理はデバイス固有な処理でありデバイス毎に異なります。

つまり、NTの割り込み処理を原理通りおこなうにはISRをデバイスに依存して作らなければいけません。

これではデバイスドライバーをデバイスに合わせて作りなおさなければならないことになります。なんとかしてどんなデバイスに対しても使えるISRを作れないでしょうか。

ISRを汎用なものにするには

そこでpcidebug.dllでは、少々強引な手法によりデバイスドライバー内のISRをデバイスに依存しないものにすることにしました。

ISRでの割り込みの解除はIoDisconnectInterrupt()を呼び出すことで代用する
他に同じ割り込みを使用するデバイスが無い環境で使う

これは本来の手法ではないのですがISRの中でIoDisconnectInterrupt()を呼び出しISRを切り離すことでISRが連続して呼び出されることを回避します。しかし、この手法は、同じ割り込みを使用している他のデバイスドライバーが動作しているとうまく動きません(正しい使い方ではないので文句は言えませんが)。他に割り込みを共有するデバイスがなければ自分のデバイスが割り込み状態であったかどうかを判断する必要もなくなります。

割り込みの共有を許さないのは大きな制約ですが、デバック目的であるとあきらめることにしましょう。

割り込みを共有させない方法

デバック対象デバイスのコンフィグレーションレジスターの割り込みラインレジスターの値を調べてみます。これにはPciWatchが利用できます。その値がデバック対象デバイスに割り当てられている割り込み番号です。

次にこれが他のデバイスで使用されていないかどうかWindowsに付属の診断プログラムで確認してみます。これを使うとどのドライバーがどの割り込みを使用しているか調べることができます。

割り込みが共有されていた場合、それを回避する一つの方法は割り込みを使用するデバイスを取り外すことです。動作していなければ問題無いのでデバイスドライバーを停止させるだけでも大丈夫です。もう一つはBIOSの設定でPCIスロット毎に割り込み番号の割り当てを指定する方法です。これはBIOSの機能に依存しできるものと出来ないものがあります。AWARD BIOSの場合はBIOSメニューの中からPNP AND PCI SETUPを選ぶ事で設定できるものが多いようです。

戻る 上へ 進む