パッシブ レベルの割り込みのサポート
フレームワーク バージョン 1.11 以降、Windows 8 以降のバージョンのオペレーティング システムで実行されているカーネルモード ドライバー フレームワーク (KMDF) ドライバーとユーザーモード ドライバー フレームワーク (UMDF) ドライバーでは、パッシブレベルの処理を必要とする割り込みオブジェクトの作成が可能です。 ドライバーがパッシブレベル割り込み処理のために割り込みオブジェクトを構成する場合、フレームワークは、パッシブレベル割り込みロックを保持しながら、IRQL = PASSIVE_LEVELでドライバーの割り込みサービス ルーチン (ISR) およびその他の割り込みオブジェクト イベントコールバック関数を呼び出します。
System on a Chip (SoC) プラットフォーム用のフレームワーク ベースのドライバーを開発している場合は、パッシブモードの割り込みを使用して、I²C、SPI、UART などの低速バス経由で SoC 以外のデバイスと通信できます。
それ以外の場合は、デバイスの IRQL (DIRQL) での処理を必要とする割り込みを使用する必要があります。 ドライバーがメッセージシグナル割り込み (MSI) をサポートしている場合は、DIRQL 割り込み処理を使用する必要があります。 バージョン 1.9 以前では、フレームワークは常に IRQL = DIRQL での割り込みを処理します。
このトピックでは、パッシブレベル割り込みを作成、サービス、同期する方法について説明します。
パッシブレベル割り込みの作成
パッシブレベル割り込みオブジェクトを作成するには、ドライバーが WDF_INTERRUPT_CONFIG 構造体を初期化し、WdfInterruptCreate メソッドに渡す必要があります。 構成構造では、ドライバーは次の手順を実行する必要があります。
- PassiveHandling メンバーを TRUE に設定します。
- パッシブ レベルで呼び出される EvtInterruptIsr コールバック関数を提供します。
- 必要に応じて、AutomaticSerialization を TRUE に設定します。 ドライバーが AutomaticSerialization を TRUE に設定した場合、フレームワークは割り込みオブジェクトの EvtInterruptDpc または EvtInterruptWorkItem コールバック関数の実行を、割り込みの親オブジェクトの下にある他のオブジェクトからのコールバック関数と同期します。
- 必要に応じて、ドライバーは、IRQL = PASSIVE_LEVEL で呼び出される EvtInterruptWorkItem コールバック関数、または IRQL = DISPATCH_LEVEL で呼び出される EvtInterruptDpc コールバック関数をのいずれかを提供できます。
構成構造の上記のメンバーの設定の詳細については、「WDF_INTERRUPT_CONFIG」を参照してください。 パッシブレベル割り込みの有効化と無効化については、「割り込みの有効化と無効化」を参照してください。
パッシブレベル割り込みのサービス
パッシブレベル割り込みロックが保持された IRQL = PASSIVE_LEVELで 実行される EvtInterruptIsr コールバック関数は、通常、割り込み作業項目または割り込み DPC をスケジュールして、割り込みに関連する情報を後で処理します。 フレームワーク ベースのドライバーは、作業項目または DPC ルーチンを EvtInterruptWorkItem または EvtInterruptDpc コールバック関数として実装します。
EvtInterruptWorkItem コールバック関数の実行をスケジュールするために、ドライバーは EvtInterruptIsr コールバック関数内から WdfInterruptQueueWorkItemForIsr を呼び出します。
EvtInterruptDpc コールバック関数の実行をスケジュールするには、ドライバーが EvtInterruptIsr コールバック関数内から WdfInterruptQueueDpcForIsr を呼び出す必要があります。 (ドライバーの EvtInterruptIsr コールバック関数は、WdfInterruptQueueWorkItemForIsr または WdfInterruptQueueDpcForIsr を呼び出すことができますが、両方を呼び出すわけではありません)。
ほとんどのドライバーは、割り込みの種類ごとに 1 つの EvtInterruptWorkItem または EvtInterruptDpc コールバック関数を使用します。 ドライバーがデバイスごとに複数のフレームワーク割り込みオブジェクトを作成する場合は、割り込みごとに個別の EvtInterruptWorkItem または EvtInterruptDpc コールバックを使用することを検討してください。
ドライバーは通常、EvtInterruptWorkItem または EvtInterruptDpc コールバック関数で I/O 要求を完了します。
次のコード例は、パッシブレベル割り込みを使用するドライバーが、EvtInterruptIsr 関数内から EvtInterruptWorkItem コールバックをスケジュールする方法を示しています。
BOOLEAN
EvtInterruptIsr(
_In_ WDFINTERRUPT Interrupt,
_In_ ULONG MessageID
)
/*++
Routine Description:
This routine responds to interrupts generated by the hardware.
It stops the interrupt and schedules a work item for
additional processing.
This ISR is called at PASSIVE_LEVEL (passive-level interrupt handling).
Arguments:
Interrupt - a handle to a framework interrupt object
MessageID - message number identifying the device's
hardware interrupt message (if using MSI)
Return Value:
TRUE if interrupt recognized.
--*/
{
UNREFERENCED_PARAMETER(MessageID);
NTSTATUS status;
PDEV_CONTEXT devCtx;
WDFREQUEST request;
WDF_MEMORY_DESCRIPTOR memoryDescriptor;
INT_REPORT intReport = {0};
BOOLEAN intRecognized;
WDFIOTARGET ioTarget;
ULONG_PTR bytes;
WDFMEMORY reqMemory;
intRecognized = FALSE;
//
// Typically the pattern in most ISRs (DIRQL or otherwise) is to:
// a) Check if the interrupt belongs to this device (shared interrupts).
// b) Stop the interrupt if the interrupt belongs to this device.
// c) Acknowledge the interrupt if the interrupt belongs to this device.
//
//
// Retrieve device context so that we can access our queues later.
//
devCtx = GetDevContext(WdfInterruptGetDevice(Interrupt));
//
// Init memory descriptor.
//
WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(
&memoryDescriptor,
&intReport,
sizeof(intReport);
//
// Send read registers/data IOCTL.
// This call stops the interrupt and reads the data at the same time.
// The device will reinterrupt when a new read is sent.
//
bytes = 0;
status = WdfIoTargetSendIoctlSynchronously(
ioTarget,
NULL,
IOCTL_READ_REPORT,
&memoryDescriptor,
NULL,
NULL,
&bytes);
//
// Return from ISR if this is not our interrupt.
//
if (intReport->Interrupt == FALSE) {
goto exit;
}
intRecognized = TRUE;
//
// Validate the data received.
//
...
//
// Retrieve the next read request from the ReportQueue which
// stores all the incoming IOCTL_READ_REPORT requests
//
request = NULL;
status = WdfIoQueueRetrieveNextRequest(
devCtx->ReportQueue,
&request);
if (!NT_SUCCESS(status) || (request == NULL)) {
//
// No requests to process.
//
goto exit;
}
//
// Retrieve the request buffer.
//
status = WdfRequestRetrieveOutputMemory(request, &reqMemory);
//
// Copy the data read into the request buffer.
// The request will be completed in the work item.
//
bytes = intReport->Data->Length;
status = WdfMemoryCopyFromBuffer(
reqMemory,
0,
intReport->Data,
bytes);
//
// Report how many bytes were copied.
//
WdfRequestSetInformation(request, bytes);
//
// Forward the request to the completion queue.
//
status = WdfRequestForwardToIoQueue(request, devCtx->CompletionQueue);
//
// Queue a work-item to complete the request.
//
WdfInterruptQueueWorkItemForIsr(FxInterrupt);
exit:
return intRecognized;
}
VOID
EvtInterruptWorkItem(
_In_ WDFINTERRUPT Interrupt,
_In_ WDFOBJECT Device
)
/*++
Routine Description:
This work item handler is triggered by the interrupt ISR.
Arguments:
WorkItem - framework work item object
Return Value:
None
--*/
{
UNREFERENCED_PARAMETER(Device);
WDFREQUEST request;
NTSTATUS status;
PDEV_CONTEXT devCtx;
BOOLEAN run, rerun;
devCtx = GetDevContext(WdfInterruptGetDevice(Interrupt));
WdfSpinLockAcquire(devCtx->WorkItemSpinLock);
if (devCtx->WorkItemInProgress) {
devCtx->WorkItemRerun = TRUE;
run = FALSE;
}
else {
devCtx->WorkItemInProgress = TRUE;
devCtx->WorkItemRerun = FALSE;
run = TRUE;
}
WdfSpinLockRelease(devCtx->WorkItemSpinLock);
if (run == FALSE) {
return;
}
do {
for (;;) {
//
// Complete all report requests in the completion queue.
//
request = NULL;
status = WdfIoQueueRetrieveNextRequest(devCtx->CompletionQueue,
&request);
if (!NT_SUCCESS(status) || (request == NULL)) {
break;
}
WdfRequestComplete(request, STATUS_SUCCESS);
}
WdfSpinLockAcquire(devCtx->WorkItemSpinLock);
if (devCtx->WorkItemRerun) {
rerun = TRUE;
devCtx->WorkItemRerun = FALSE;
}
else {
devCtx->WorkItemInProgress = FALSE;
rerun = FALSE;
}
WdfSpinLockRelease(devCtx->WorkItemSpinLock);
}
while (rerun);
}
VOID
EvtIoInternalDeviceControl(
_In_ WDFQUEUE Queue,
_In_ WDFREQUEST Request,
_In_ size_t OutputBufferLength,
_In_ size_t InputBufferLength,
_In_ ULONG IoControlCode
)
{
NTSTATUS status;
DEVICE_CONTEXT devCtx;
devCtx = GetDeviceContext(WdfIoQueueGetDevice(Queue));
switch (IoControlCode)
{
...
case IOCTL_READ_REPORT:
//
// Forward the request to the manual ReportQueue to be completed
// later by the interrupt work item.
//
status = WdfRequestForwardToIoQueue(Request, devCtx->ReportQueue);
break;
...
}
if (!NT_SUCCESS(status)) {
WdfRequestComplete(Request, status);
}
}
パッシブレベル割り込みの同期
デッドロックを防ぐには、パッシブレベル割り込み処理を実装するドライバーを記述するときに、次のガイドラインに従います。
AutomaticSerialization が TRUE に設定されている場合は、EvtInterruptDpc または EvtInterruptWorkItem コールバック関数内から同期要求を送信しないでください。
I/O 要求を完了する前に、パッシブレベル割り込みロックを解除します。
必要に応じて、EvtInterruptDisable、EvtInterruptEnable、EvtInterruptWorkItem を指定します。
要求ハンドラーなど、任意のスレッド コンテキストでドライバーが割り込み関連の作業を実行する必要がある場合は、WdfInterruptTryToAcquireLock と WdfInterruptReleaseLock を使用します。 任意のスレッド コンテキストから WdfInterruptAcquireLock、WdfInterruptSynchronize、WdfInterruptEnable、または WdfInterruptDisable を呼び出さないでください。 任意のスレッド コンテキストから WdfInterruptAcquireLock を呼び出すことによって発生する可能性があるデッドロック シナリオの例については、WdfInterrcuptAcquireLock の「解説」セクションを参照してください。
WdfInterruptTryToAcquireLock の呼び出しが失敗した場合、ドライバーは割り込み関連の作業を作業項目に延期できます。 その作業項目では、ドライバーは WdfInterruptAcquireLock を呼び出すことによって割り込みロックを安全に取得できます。 詳細については、「WdfInterruptTryToAcquireLock」を参照してください。
作業項目などの任意でないスレッド コンテキストでは、ドライバーは WdfInterruptAcquireLock または WdfInterruptSynchronize を呼び出すことができます。
割り込みロックの使用の詳細については、「割り込みコードの同期」を参照してください。