Suporte a interrupções de Passive-Level
A partir da versão 1.11 da estrutura, os drivers KMDF (Kernel-Mode Driver Framework) e UMDF (User-Mode Driver Framework) em execução em Windows 8 ou versões posteriores do sistema operacional podem criar objetos de interrupção que exigem tratamento de nível passivo. Se o driver configurar um objeto de interrupção para tratamento de interrupção de nível passivo, a estrutura chamará a ISR (rotina de serviço de interrupção) do driver e outras funções de retorno de chamada de evento de objeto de interrupção em IRQL = PASSIVE_LEVEL mantendo um bloqueio de interrupção de nível passivo.
Se você estiver desenvolvendo um driver baseado em estrutura para uma plataforma SoC (System on a Chip), poderá usar interrupções de modo passivo para se comunicar com um dispositivo fora do SoC em um barramento de baixa velocidade, como I²C, SPI ou UART.
Caso contrário, você deve usar interrupções que exigem tratamento no DIRQL (IRQL) do dispositivo . Se o driver der suporte a MSIs (interrupções sinalizadas por mensagem), você deverá usar o tratamento de interrupção DIRQL. Nas versões 1.9 e anteriores, a estrutura sempre processa interrupções em IRQL = DIRQL.
Este tópico descreve como criar, atender e sincronizar interrupções de nível passivo.
Criando uma interrupção de Passive-Level
Para criar um objeto de interrupção de nível passivo, um driver deve inicializar uma estrutura WDF_INTERRUPT_CONFIG e passá-la para o método WdfInterruptCreate . Na estrutura de configuração, o driver deve:
- Defina o membro PassiveHandling como TRUE.
- Forneça uma função de retorno de chamada EvtInterruptIsr , a ser chamada no nível passivo.
- Opcionalmente, defina a AutomaticSerialization como TRUE. Se o driver definir AutomaticSerialization como TRUE, a estrutura sincronizará a execução das funções de retorno de chamada EvtInterruptDpc ou EvtInterruptWorkItem do objeto de interrupção com funções de retorno de chamada de outros objetos que estão sob o objeto pai da interrupção.
- Opcionalmente, o driver pode fornecer uma função de retorno de chamada EvtInterruptWorkItem , a ser chamada em IRQL = PASSIVE_LEVEL ou uma função de retorno de chamada EvtInterruptDpc , a ser chamada em IRQL = DISPATCH_LEVEL.
Para obter informações adicionais sobre como definir os membros acima da estrutura de configuração, consulte WDF_INTERRUPT_CONFIG. Para obter informações sobre como habilitar e desabilitar interrupções de nível passivo, consulte Habilitando e desabilitando interrupções.
Manutenção de uma interrupção de Passive-Level
A função de retorno de chamada EvtInterruptIsr , que é executada em IRQL = PASSIVE_LEVEL com o bloqueio de interrupção de nível passivo mantido, normalmente agenda um item de trabalho de interrupção ou interrompe o DPC para processar informações relacionadas à interrupção posteriormente. Os drivers baseados em estrutura implementam rotinas de item de trabalho ou DPC como funções de retorno de chamada EvtInterruptWorkItem ou EvtInterruptDpc .
Para agendar a execução de uma função de retorno de chamada EvtInterruptWorkItem , um driver chama WdfInterruptQueueWorkItemForIsr de dentro da função de retorno de chamada EvtInterruptIsr .
Para agendar a execução de uma função de retorno de chamada EvtInterruptDpc , um driver chama WdfInterruptQueueDpcForIsr de dentro da função de retorno de chamada EvtInterruptIsr . (Lembre-se de que a função de retorno de chamada EvtInterruptIsr de um driver pode chamar WdfInterruptQueueWorkItemForIsr ou WdfInterruptQueueDpcForIsr, mas não ambos.)
A maioria dos drivers usa uma única função de retorno de chamada EvtInterruptWorkItem ou EvtInterruptDpc para cada tipo de interrupção. Se o driver criar vários objetos de interrupção de estrutura para cada dispositivo, considere usar um retorno de chamada EvtInterruptWorkItem ou EvtInterruptDpc separado para cada interrupção.
Normalmente, os drivers concluem solicitações de E/S em suas funções de retorno de chamada EvtInterruptWorkItem ou EvtInterruptDpc .
O exemplo de código a seguir demonstra como um driver que usa interrupções de nível passivo pode agendar um retorno de chamada EvtInterruptWorkItem de dentro de sua função 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);
}
}
Sincronizando uma interrupção de Passive-Level
Para evitar deadlock, siga estas diretrizes ao escrever um driver que implementa a manipulação de interrupção de nível passivo:
Se AutomaticSerialization estiver definido como TRUE, não envie solicitações síncronas de dentro de uma função de retorno de chamada EvtInterruptDpc ou EvtInterruptWorkItem .
Libere o bloqueio de interrupção de nível passivo antes de concluir solicitações de E/S.
Forneça EvtInterruptDisable, EvtInterruptEnable e EvtInterruptWorkItem conforme necessário.
Se o driver precisar executar o trabalho relacionado à interrupção em um contexto de thread arbitrário, como em um manipulador de solicitação, use WdfInterruptTryToAcquireLock e WdfInterruptReleaseLock. Não chame WdfInterruptAcquireLock, WdfInterruptSynchronize, WdfInterruptEnable ou WdfInterruptDisable de um contexto de thread arbitrário. Para obter um exemplo de um cenário de deadlock que pode ser causado chamando WdfInterruptAcquireLock de um contexto de thread arbitrário, consulte a seção Comentários de WdfInterruptAcquireLock.
Se a chamada para WdfInterruptTryToAcquireLock falhar, o driver poderá adiar seu trabalho relacionado à interrupção para um item de trabalho. Nesse item de trabalho, o driver pode adquirir com segurança o bloqueio de interrupção chamando WdfInterruptAcquireLock. Para obter mais informações, consulte WdfInterruptTryToAcquireLock.
Em um contexto de thread não arbitrário, como um item de trabalho, o driver pode chamar WdfInterruptAcquireLock ou WdfInterruptSynchronize.
Para obter mais informações sobre como usar bloqueios de interrupção, consulte Sincronizando código de interrupção.