Mendukung Gangguan Passive-Level

Dimulai dengan kerangka kerja versi 1.11, driver Kernel-Mode Driver Framework (KMDF) dan User-Mode Driver Framework (UMDF) yang berjalan pada versi Windows 8 atau yang lebih baru dari sistem operasi dapat membuat objek interupsi yang memerlukan penanganan tingkat pasif. Jika driver mengonfigurasi objek interupsi untuk penanganan gangguan tingkat pasif, kerangka kerja memanggil rutinitas layanan interupsi driver (ISR) dan fungsi panggilan balik peristiwa objek interupsi lainnya di IRQL = PASSIVE_LEVEL sambil memegang kunci interupsi tingkat pasif.

Jika Anda mengembangkan driver berbasis kerangka kerja untuk platform System on a Chip (SoC), Anda dapat menggunakan interupsi mode pasif untuk berkomunikasi dengan perangkat off-SoC melalui bus berkecepatan rendah, seperti I²C, SPI, atau UART.

Jika tidak, Anda harus menggunakan gangguan yang memerlukan penanganan di IRQL perangkat (DIRQL). Jika driver Anda mendukung gangguan sinyal pesan (MSI), Anda harus menggunakan penanganan gangguan DIRQL. Dalam versi 1.9 dan yang lebih lama, kerangka kerja selalu memproses gangguan di IRQL = DIRQL.

Topik ini menjelaskan cara membuat, melayani, dan menyinkronkan gangguan tingkat pasif.

Membuat Gangguan Passive-Level

Untuk membuat objek interupsi tingkat pasif, driver harus menginisialisasi struktur WDF_INTERRUPT_CONFIG dan meneruskannya ke metode WdfInterruptCreate . Dalam struktur konfigurasi, driver harus:

  • Atur anggota PassiveHandling ke TRUE.
  • Berikan fungsi panggilan balik EvtInterruptIsr , untuk dipanggil pada tingkat pasif.
  • Secara opsional atur AutomaticSerialization ke TRUE. Jika driver mengatur AutomaticSerialization ke TRUE, maka kerangka kerja menyinkronkan eksekusi fungsi panggilan balik objek interupsi EvtInterruptDpc atau EvtInterruptWorkItem fungsi panggilan balik dari objek lain yang berada di bawah objek induk interupsi.
  • Secara opsional, driver dapat menyediakan fungsi panggilan balik EvtInterruptWorkItem , untuk dipanggil di IRQL = PASSIVE_LEVEL, atau fungsi panggilan balik EvtInterruptDpc , untuk dipanggil di IRQL = DISPATCH_LEVEL.

Untuk informasi tambahan tentang pengaturan anggota struktur konfigurasi di atas, lihat WDF_INTERRUPT_CONFIG. Untuk informasi tentang mengaktifkan dan menonaktifkan gangguan tingkat pasif, lihat Mengaktifkan dan Menonaktifkan Gangguan.

Melayani Gangguan Passive-Level

Fungsi panggilan balik EvtInterruptIsr , yang berjalan di IRQL = PASSIVE_LEVEL dengan kunci interupsi tingkat pasif yang ditahan, biasanya menjadwalkan item kerja yang mengganggu atau mengganggu DPC untuk memproses informasi terkait interupsi di lain waktu. Driver berbasis kerangka kerja menerapkan item kerja atau rutinitas DPC sebagai fungsi panggilan balik EvtInterruptWorkItem atau EvtInterruptDpc .

Untuk menjadwalkan eksekusi fungsi panggilan balik EvtInterruptWorkItem , driver memanggil WdfInterruptQueueWorkItemForIsr dari dalam fungsi panggilan balik EvtInterruptIsr .

Untuk menjadwalkan eksekusi fungsi panggilan balik EvtInterruptDpc , driver memanggil WdfInterruptQueueDpcForIsr dari dalam fungsi panggilan balik EvtInterruptIsr . (Ingat bahwa fungsi panggilan balik EvtInterruptIsr driver dapat memanggil WdfInterruptQueueWorkItemForIsr atau WdfInterruptQueueDpcForIsr, tetapi tidak keduanya.)

Sebagian besar driver menggunakan satu fungsi panggilan balik EvtInterruptWorkItem atau EvtInterruptDpc untuk setiap jenis gangguan. Jika driver Anda membuat beberapa objek interupsi kerangka kerja untuk setiap perangkat, pertimbangkan untuk menggunakan panggilan balik EvtInterruptWorkItem atau EvtInterruptDpc terpisah untuk setiap gangguan.

Driver biasanya menyelesaikan permintaan I/O dalam fungsi panggilan balik EvtInterruptWorkItem atau EvtInterruptDpc mereka.

Contoh kode berikut menunjukkan bagaimana driver yang menggunakan gangguan tingkat pasif mungkin menjadwalkan panggilan balik EvtInterruptWorkItem dari dalam fungsi 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);
    }
}

Menyinkronkan Gangguan Passive-Level

Untuk mencegah kebuntuan, ikuti panduan ini saat menulis driver yang menerapkan penanganan gangguan tingkat pasif:

Untuk informasi selengkapnya tentang menggunakan kunci interupsi, lihat Menyinkronkan Kode Interupsi.