Поддержка прерываний 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 .

В следующем примере кода показано, как драйвер, использующий прерывания пассивного уровня, может запланировать обратный вызов 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

Чтобы предотвратить взаимоблокировку, следуйте приведенным ниже рекомендациям при написании драйвера, который реализует обработку прерываний пассивного уровня:

Дополнительные сведения об использовании блокировок прерываний см. в статье Синхронизация кода прерываний.