支援 Passive-Level 中斷
從框架版本 1.11 開始,在 Windows 8 或更高版本的操作系統上運行的 Kernel-Mode 驅動程序框架(KMDF)和 User-Mode 驅動程序框架(UMDF)允許驅動程序建立需要被動層級處理的中斷物件。 如果驅動程式設定了被動層級中斷處理的中斷對象,架構會呼叫驅動程式的插斷服務例程 (ISR) 和其他 插斷物件事件回呼函式, 在 IRQL = PASSIVE_LEVEL,同時保留被動層級中斷鎖定。
如果您要在系統單晶片(SoC)平台上開發基於框架的驅動程式,您可以使用被動模式中斷,透過低速匯流排與晶片外裝置通訊,例如 I²C、SPI 或 UART。
否則,您應該使用需要在裝置 IRQL (DIRQL)處理的 中斷。 如果您的驅動程式支援訊息訊號中斷 (MSIs),您必須使用 DIRQL 中斷處理。 在 1.9 版和更早版本中,架構一律會在 IRQL = DIRQL 處理中斷。
建立 Passive-Level 中斷
若要建立被動層級中斷對象,驅動程式必須初始化 WDF_INTERRUPT_CONFIG 結構,並將它傳遞給 WdfInterruptCreate 方法。 在組態結構中,驅動程序應該:
- 將 PassiveHandling 成員設定為 TRUE。
- 提供 EvtInterruptIsr 回呼函式,在被動層級上呼叫。
- 選擇性地將 自動串行化 設定為 TRUE。 如果驅動程式將 AutomaticSerialization 設為 TRUE,則架構會同步處理中斷物件的 EvtInterruptDpc 或 EvtInterruptWorkItem 回呼函式,以及中斷父物件下其他物件的回呼函式。
- 驅動程式可以選擇提供 EvtInterruptWorkItem 回呼函式,以便在 IRQL = PASSIVE_LEVEL 時呼叫,或者提供 EvtInterruptDpc 回呼函式,以便在 IRQL = DISPATCH_LEVEL 時呼叫。
如需設定上述組態結構成員的詳細資訊,請參閱 WDF_INTERRUPT_CONFIG。 如需啟用和停用被動層級中斷的相關資訊,請參閱 啟用和停用中斷。
處理 Passive-Level 中斷
EvtInterruptIsr 回呼函式,會在 IRQL = PASSIVE_LEVEL 且保持被動層級中斷鎖定時執行,通常會排程中斷工作項目或中斷 DPC,以便稍後處理中斷相關資訊。 架構型驅動程式會將工作專案或 DPC 例程實作為 EvtInterruptWorkItem 或 EvtInterruptDpc 回呼函式。
若要排程執行 EvtInterruptWorkItem 回呼函式,驅動程式會從 EvtInterruptIsr 回呼函式內呼叫 WdfInterruptQueueWorkItemForIsr。
若要排程執行 EvtInterruptDpc 回呼函式,驅動程式會從 EvtInterruptIsr 回呼函式內呼叫 WdfInterruptQueueDpcForIsr。 (回想一下,驅動程式的 EvtInterruptIsr 回呼函式可以呼叫 WdfInterruptQueueWorkItemForIsr 或 WdfInterruptQueueDpcForIsr,但不能同時呼叫兩者。
大部分的驅動程式都會針對每種中斷類型使用單一 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);
}
}
同步處理 Passive-Level 中斷
若要防止死結,請在撰寫實作被動層級中斷處理的驅動程式時,遵循下列指導方針:
如果 AutomaticSerialization 設為 TRUE,請勿 從 EvtInterruptDpc 或 EvtInterruptWorkItem 回呼函式內傳送同步 要求。
視需要提供 EvtInterruptDisable、EvtInterruptEnable和 EvtInterruptWorkItem。
如果您的驅動程式必須在任意線程內容中執行中斷相關工作,例如在 要求處理程式中,請使用 WdfInterruptTryToAcquireLock 和 WdfInterruptReleaseLock。 請勿從任意線程內容呼叫 WdfInterruptAcquireLock、WdfInterruptSynchronize、WdfInterruptEnable或 WdfInterruptDisable。 如需查看因從任意線程內容呼叫 WdfInterruptAcquireLock 而導致的死結情況,請參閱 在 WdfInterruptAcquireLock的「注意」一節。
如果呼叫 WdfInterruptTryToAcquireLock 失敗,驅動程式可以將中斷相關的工作延後至工作項目。 在該工作專案中,驅動程式可以藉由呼叫 WdfInterruptAcquireLock安全地取得中斷鎖定。 如需詳細資訊,請參閱 WdfInterruptTryToAcquireLock。
在非任意線程內容中,例如工作項目,驅動程式可以呼叫 WdfInterruptAcquireLock 或 WdfInterruptSynchronize。
如需使用中斷鎖定的詳細資訊,請參閱 同步中斷代碼。