SCIENCE PARK

デバドラ講座

【 デバドラ講座29 : ドライバと割り込みの接続 】

割り込み駆動型のデバイスにおいて、ドライバは割り込みの処理を行うためにドライバとデバイスからの割り込みを接続する必要があります。この処理はPnPディスパッチルーチンのIRP_MN_START_DEVICEで行われます。

通常ディスパッチルーチンはドライバエントリが完了してから呼び出しが可能になります。しかし、割り込みはドライバエントリルーチンが完了する前に呼び出される可能性があります。そこで、ドライバエントリルーチンにおいて、デバイスレジスタの操作により割り込みを一旦禁止します。そして、StartDeviceで接続した後に割り込みの許可を行います。

割り込みとドライバを接続させる際、割り込みレベルと割り込みベクタを関数に引き渡す必要があります。そこで、レジストリ又は他の関数によって識別された割り込みレベルと割り込みベクタをシステム全体で識別できる形に変換する必要があります。この変換はHalGetInterruptVector関数を呼んで行います。HalGetInterruptVectorの呼び出し形式は次のようになっています。

ULONG
HalGetInterruptVector (
IN INTERFACE_TYPE InterfaceType, /バスの種類を指定します。
IN ULONG BusNumber, //バス番号を指定します。
IN ULONG BusInterruptLevel, //デバイスの割り込みレベルです(変換前)。
IN ULONG BusInterruptVector, //デバイスの割り込みベクタです(変換前)。
OUT PKIRQL Irql, //システムIRQLを返します。
OUT PKAFFINITY Affinity //デバイス割り込みマスクを返します。
);

Affinity引数のビット数は、マルチプロセッサにおいてどのCPUで割り込みを処理するかを示します。Affinity値は32ビット長整数で取扱われ、ビット0はプロセッサ0、ビット31はプロセッサ31に相当します。例えば、4つのCPUを持つシステムの場合、ビット0からビット3までが割り当てられます。

割り込みレベルと割り込みベクタの変換は、PnPマネージャが行うので、ドライバでこの処理は省略できます。変換後、ISRと割り込みを接続させるために、IoConnectInterrupt関数を使用します。この関数を呼び出すとすぐに割り込みが発生する可能性があります。従って、ドライバは割り込みを受け取る準備をする必要があります。IoConnectInterruptのプロトコルは次のようになっています。

NTSTATUS  IoConnectInterrupt(
  OUT PKINTERRUPT *InterruptObject,
  IN PKSERVICE_ROUTINE ServiceRoutine,
  IN PVOID ServiceContext,
  IN PKSPIN_LOCK SpinLock OPTIONAL,
  IN ULONG Vector,
  IN KIRQL Irql,
  IN KIRQL SynchronizedIrql,
  IN KINTERRUPT_MODE InterruptMode,
  IN BOOLEAN ShareVector,
  IN KAFFINITY ProcessorEnableMask,
  IN BOOLEAN FloatingSave
);
InterruptObject:
割り込みオブジェクトへのポインタです。この関数が成功すると、割り込みオブジェクトへのポインタが返されます。
ServiceRoutine:
割り込みサービスルーチン(ISR)へのポインタです。
ServiceContext:
ISRに渡されるユーザー定義の引数です。通常、割り込みを扱うデバイスオブジェクトのポインタを指定します。
SpinLock:
外部で確保、初期化されたスピンロックへの任意のポインタです。スピンロックについて後で説明します。
Vector:
変換後の割り込みベクタを設定します。
Irql:
変換後の割り込みレベルを指定します。
SynchronizedIrql:
ISRを実行する際のIRQLを指定します。通常、上のIrqlと同じ値を設定します。
InterruptMode:
割り込みのモードを指定します。PCIバスの場合、LevelSensitiveを指定し、ISAバスの場合Latchedを指定します。
ShareVector:
ドライバが他のデバイスと割り込みベクタを共有するかを指定します。
ProcessorEnableMask:
この割り込みを処理するプロセッサを示します。PnPマネージャよりハードウェアリソースで割り当てた構造体のAffinityを指定します。
FloatingSave:
ISRを終了する際、浮動小数点レジスタを保存するかを指定します。x86アーキテクチャプロセッサではFALSEに指定します。

スピンロックとはISRの同期です。マルチプロセッサシステムでは、1つのCPUにおいてISRを処理している際に同じIRQLの割り込みが入ることはありません。しかし、他のCPUには同じIRQLの割り込みが入る可能性があります。複数のプロセッサで同じデバイスの同じIRQLの割り込みを同時に実行するのは大変危険です。スピンロックにより、システムをISRの異常な動作から守ります。また、スピンロックはドライバのデバイスレジスタへのアクセスの同期も行います。

ShareVector値によってドライバはベクタの共有の可否を設定することができます。共有可能な割り込みは、1つの割り込みラインを複数のデバイスが共有することができます。PCIバスは割り込みを共有することができます。割り込みの共有が可能な場合、自分が処理しているデバイス以外のデバイスからもISRが呼ばれる可能性があります。異なるデバイスからの割り込みの場合、それを識別して適切な処理を行わなければなりません。共有不可能の割り込みは1つのデバイスが1つの割り込みラインを独占します。他のデバイスはこのラインを使用することはできません。通常ISAバスは割り込みラインを共有できません。

割り込みオブジェクトはI/Oマネージャによって生成され、システム内部で構造体(KINTERRUPT)で管理されています。その構造体は完全に隠されており、開発者はその中身を見ることはできません。また、アクセスも許されていません。生成された割り込みオブジェクトは、割り込みが発生した際ISRへ渡されます。ISRのプロトコルは次のようになっています。

BOOLEAN(*PKSERVICE_ROUTINE)(
IN struct _KINTERRUPT *Interrupt,             
IN PVOID ServiceContext
);
Interrupt:
IoConnectInterruptにより作成された割り込みオブジェクトを指定します。
ServiceContext:
IoConnectInterruptで指定したServiceContextを指定します。

割り込みを接続した後にドライバをアンロードする際、ドライバと割り込みの接続を解除する必要があります。割り込みの接続を解除する前に、デバイスレジスタの操作により割り込みを禁止します。そして、IoDisconnectInterrupt関数を使用して割り込みの接続を解除します。ドライバのアンロード後に、割り込みが接続されたままの場合、ブルースクリンの発生、オペレーティングシステムの破壊等の障害が発生してしまいます。また、割り込みがドライバと接続されていない際に、IoDisconnectInterrupt関数を呼び出すと、システムがクラッシュしてしまいます。IoDisconnectInterrupt関数のプロトコルを次に示します。

VOID  IoDisconnectInterrupt (
              IN PKINTERRUPT InterruptObject
);
InterruptObject:
割り込みオブジェクトのポインタを指定します

以上の処理は、PnPメジャーディスパッチルーチンのマイナー機能(IRP_MN_SURPRISE_REMOVAL、IRP_MN_REMOVE_DEVICE)で実行されます。

参考資料

「NTドライバプログラミング」
Peter G. Viscarola、W. Anthony Mason著 久保雅俊、三浦秀朗訳 サイエンスパーク株式会社 監修
「WDMデバイスドライバプログラミング完全ガイド」上
Edword N. Dekker, Jpseph M. Newcomer著 株式会社クイック訳
page up