Unterstützung von Passive-Level Interrupts
Ab Frameworkversion 1.11 können Kernel-Mode Driver Framework (KMDF) und User-Mode Driver Framework (UMDF)-Treiber, die unter Windows 8 oder höheren Versionen des Betriebssystems ausgeführt werden, Interruptobjekte erstellen, die eine Behandlung auf passiver Ebene erfordern. Wenn der Treiber ein Interruptobjekt für die Behandlung von Interrupt auf passiver Ebene konfiguriert, ruft das Framework die Interruptdienstroutine (ISR) des Treibers und andere Rückruffunktionen des Interruptobjektereignisses am IRQL = PASSIVE_LEVEL auf, während eine Interruptsperre auf passiver Ebene beibehalten wird.
Wenn Sie einen frameworkbasierten Treiber für eine SoC-Plattform (System on a Chip) entwickeln, können Sie Interrupts im passiven Modus verwenden, um mit einem off-SoC-Gerät über einen Low-Speed-Bus wie I²C, SPI oder UART zu kommunizieren.
Andernfalls sollten Sie Interrupts verwenden, die am IRQL (DIRQL) des Geräts verarbeitet werden müssen . Wenn Ihr Treiber MSIs (Message-Signaled Interrupts) unterstützt, müssen Sie die DIRQL-Interruptbehandlung verwenden. In Versionen 1.9 und früher verarbeitet das Framework Interrupts immer unter IRQL = DIRQL.
In diesem Thema wird beschrieben, wie Unterbrechungen auf passiver Ebene erstellt, gewartet und synchronisiert werden .
Erstellen eines Passive-Level Interrupts
Um ein Interruptobjekt auf passiver Ebene zu erstellen, muss ein Treiber eine WDF_INTERRUPT_CONFIG-Struktur initialisieren und an die WdfInterruptCreate-Methode übergeben. In der Konfigurationsstruktur sollte der Treiber Folgendes ausführen:
- Legen Sie das PassiveHandling-Element auf TRUE fest.
- Stellen Sie eine EvtInterruptIsr-Rückruffunktion bereit, die auf passiver Ebene aufgerufen werden soll.
- Legen Sie optional die AutomaticSerialization auf TRUE fest. Wenn der Treiber AutomaticSerialization auf TRUE festlegt, synchronisiert das Framework die Ausführung der Rückruffunktionen EvtInterruptDpc oder EvtInterruptWorkItem des Interruptobjekts mit Rückruffunktionen von anderen Objekten, die sich unter dem übergeordneten Objekt des Interrupts befinden.
- Optional kann der Treiber entweder eine EvtInterruptWorkItem-Rückruffunktion bereitstellen, die unter IRQL = PASSIVE_LEVEL aufgerufen werden soll, oder eine EvtInterruptDpc-Rückruffunktion , die unter IRQL = DISPATCH_LEVEL aufgerufen werden soll.
Weitere Informationen zum Festlegen der obigen Elemente der Konfigurationsstruktur finden Sie unter WDF_INTERRUPT_CONFIG. Informationen zum Aktivieren und Deaktivieren von Interrupts auf passiver Ebene finden Sie unter Aktivieren und Deaktivieren von Interrupts.
Warten eines Passive-Level Interrupts
Die Rückruffunktion EvtInterruptIsr , die bei IRQL = PASSIVE_LEVEL ausgeführt wird, während die Unterbrechungssperre auf passiver Ebene gehalten wird, plant in der Regel ein Interruptarbeitselement oder interrupt DPC, um interruptbezogene Informationen zu einem späteren Zeitpunkt zu verarbeiten. Frameworkbasierte Treiber implementieren Arbeitselement- oder DPC-Routinen als EvtInterruptWorkItem - oder EvtInterruptDpc-Rückruffunktionen .
Um die Ausführung einer EvtInterruptWorkItem-Rückruffunktion zu planen, ruft ein Treiber WdfInterruptQueueWorkItemForIsr aus der Rückruffunktion EvtInterruptIsr auf.
Um die Ausführung einer EvtInterruptDpc-Rückruffunktion zu planen, ruft ein Treiber WdfInterruptQueueDpcForIsr aus der Rückruffunktion EvtInterruptIsr auf. (Denken Sie daran, dass die EvtInterruptIsr-Rückruffunktion eines Treibers WdfInterruptQueueWorkItemForIsr oder WdfInterruptQueueDpcForIsr aufrufen kann, aber nicht beide.)
Die meisten Treiber verwenden eine einzelne EvtInterruptWorkItem- oder EvtInterruptDpc-Rückruffunktion für jeden Interrupttyp. Wenn Ihr Treiber mehrere Framework-Interruptobjekte für jedes Gerät erstellt, sollten Sie einen separaten EvtInterruptWorkItem - oder EvtInterruptDpc-Rückruf für jeden Interrupt verwenden.
Treiber führen in der Regel E/A-Anforderungen in ihren Rückruffunktionen EvtInterruptWorkItem oder EvtInterruptDpc aus.
Im folgenden Codebeispiel wird veranschaulicht, wie ein Treiber, der Interrupts auf passiver Ebene verwendet, einen EvtInterruptWorkItem-Rückruf innerhalb seiner EvtInterruptIsr-Funktion planen kann.
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);
}
}
Synchronisieren eines Passive-Level Interrupts
Um Deadlocks zu verhindern, befolgen Sie die folgenden Richtlinien, wenn Sie einen Treiber schreiben, der die Behandlung von Interrupts auf passiver Ebene implementiert:
Wenn AutomaticSerialization auf TRUE festgelegt ist, senden Sie keine synchronen Anforderungen aus einer EvtInterruptDpc - oder EvtInterruptWorkItem-Rückruffunktion .
Lösen Sie die Unterbrechungssperre auf passiver Ebene, bevor Sie E/A-Anforderungen abschließen.
Stellen Sie evtInterruptDisable, EvtInterruptEnable und EvtInterruptWorkItem nach Bedarf bereit.
Wenn Ihr Treiber interruptbezogene Arbeiten in einem beliebigen Threadkontext ausführen muss, z. B. in einem Anforderungshandler, verwenden Sie WdfInterruptTryToAcquireLock und WdfInterruptReleaseLock. Rufen Sie WdfInterruptAcquireLock, WdfInterruptSynchronize, WdfInterruptEnable oder WdfInterruptDisable nicht aus einem beliebigen Threadkontext auf. Ein Beispiel für ein Deadlockszenario, das durch aufrufen von WdfInterruptAcquireLock aus einem beliebigen Threadkontext verursacht werden kann, finden Sie im Abschnitt Hinweise von WdfInterruptAcquireLock.
Wenn der Aufruf von WdfInterruptTryToAcquireLock fehlschlägt , kann der Treiber seine unterbrechungsbezogene Arbeit auf ein Arbeitselement verschieben. In diesem Arbeitselement kann der Treiber die Interruptsperre sicher abrufen, indem er WdfInterruptAcquireLock aufruft. Weitere Informationen finden Sie unter WdfInterruptTryToAcquireLock.
In einem nicht beliebigen Threadkontext, z. B. einem Arbeitselement, kann der Treiber WdfInterruptAcquireLock oder WdfInterruptSynchronize aufrufen.
Weitere Informationen zur Verwendung von Interruptsperren finden Sie unter Synchronisieren von Interruptcode.