Поддержка прерываний Passive-Level
Начиная с версии 1.11, драйверы Kernel-Mode Driver Framework (KMDF) и User-Mode Driver Framework (UMDF), работающие в Windows 8 или более поздних версиях операционной системы, могут создавать объекты прерываний, которые требуют обработки на пассивном уровне. Если драйвер настраивает объект прерывания для обработки прерываний пассивного уровня, фреймворк вызывает подпрограмму службы прерываний драйвера (ISR) и другие функции обратного вызова событий объекта прерывания при IRQL = PASSIVE_LEVEL, удерживая блокировку прерываний на пассивном уровне.
Если вы разрабатываете драйвер на основе платформы System on a Chip (SoC), вы можете использовать пассивный режим прерываний для взаимодействия с устройством вне SoC через низкоскоростную шину, например I²C, SPI или UART.
В противном случае следует использовать прерывания , которые требуют обработки на уровне IRQL устройства (DIRQL). Если драйвер поддерживает прерывания, сигнализированные сообщением (MSIs), необходимо использовать обработку прерываний DIRQL. В версиях 1.9 и более ранних фреймворк всегда обрабатывает прерывания на уровне IRQL = DIRQL.
В этом разделе описывается, как создать, обслуживатьи синхронизировать прерывания пассивного уровня.
Создание прерывания Passive-Level
Чтобы создать объект прерывания пассивного уровня, драйвер должен инициализировать структуру WDF_INTERRUPT_CONFIG и передать ее в метод WdfInterruptCreate. В структуре конфигурации драйвер должен:
- Задайте для элемента PassiveHandling значение TRUE.
- Предоставьте функцию обратного вызова EvtInterruptIsr для вызова на пассивном уровне.
- При необходимости присвойте параметру AutomaticSerialization значение 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, драйвер вызывает WdfInterruptQueueWorkItemForIsr из функции обратного вызоваEvtInterruptIsr.
Чтобы запланировать выполнение функции обратного вызова EvtInterruptDpc, драйвер вызывает WdfInterruptQueueDpcForIsr из функции обратного вызова EvtInterruptIsr. (Помните, что функция обратного вызова драйвера EvtInterruptIsr может вызывать WdfInterruptQueueWorkItemForIsr или WdfInterruptQueueDpcForIsr, но не обе.)
Большинство драйверов используют одну функцию EvtInterruptWorkItem или EvtInterruptDpc обратного вызова для каждого типа прерывания. Если драйвер создает несколько объектов прерываний фреймворка для каждого устройства, рассмотрите возможность использования отдельного обратного вызова EvtInterruptWorkItem или EvtInterruptDpc для каждого прерывания.
Драйверы обычно выполняют запросы ввода-вывода в своих функциях обратного вызова EvtInterruptWorkItem или EvtInterruptDpc.
В следующем примере кода показано, как драйвер, использующий прерывания пассивного уровня, может запланировать callback-функцию EvtInterruptWorkItem из своей функции EvtInterruptIsr.
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.
Дополнительные сведения об использовании блокировок прерываний см. в разделе "Синхронизация кода прерываний".