Compatibilidad con interrupciones de Passive-Level
A partir de la versión 1.11 del marco de trabajo, Kernel-Mode Driver Framework (KMDF) y los controladores de User-Mode Driver Framework (UMDF) que se ejecutan en Windows 8 o versiones posteriores del sistema operativo pueden crear objetos de interrupción que requieran control de nivel pasivo. Si el controlador configura un objeto de interrupción para el control de interrupciones de nivel pasivo, el marco llama a la rutina de servicio de interrupción del controlador (ISR) y a otras funciones de devolución de llamada de eventos de objeto de interrupción en IRQL = PASSIVE_LEVEL mientras mantiene un bloqueo de interrupción de nivel pasivo.
Si está desarrollando un controlador basado en marcos para un sistema en una plataforma de chip (SoC), puede usar interrupciones en modo pasivo para comunicarse con un dispositivo fuera del soC a través de un bus de baja velocidad, como I²C, SPI o UART.
De lo contrario, debe usar interrupciones que requieran el control en el IRQL (DIRQL) del dispositivo . Si el controlador admite interrupciones señaladas por mensajes (MSA), debe usar el control de interrupciones DIRQL. En las versiones 1.9 y anteriores, el marco siempre procesa interrupciones en IRQL = DIRQL.
En este tema se describe cómo crear, atender y sincronizar interrupciones de nivel pasivo.
Creación de una interrupción de Passive-Level
Para crear un objeto de interrupción de nivel pasivo, un controlador debe inicializar una estructura de WDF_INTERRUPT_CONFIG y pasarla al método WdfInterruptCreate . En la estructura de configuración, el controlador debe:
- Establezca el miembro PassiveHandling en TRUE.
- Proporcione una función de devolución de llamada EvtInterruptIsr , a la que se llamará en el nivel pasivo.
- Opcionalmente, establezca AutomaticSerialization en TRUE. Si el controlador establece AutomaticSerialization en TRUE, el marco sincroniza la ejecución de las funciones de devolución de llamada EvtInterruptDpc o EvtInterruptWorkItem del objeto de interrupción con funciones de devolución de llamada de otros objetos que están debajo del objeto primario de la interrupción.
- Opcionalmente, el controlador puede proporcionar una función de devolución de llamada EvtInterruptWorkItem , que se llamará en IRQL = PASSIVE_LEVEL o una función de devolución de llamada EvtInterruptDpc , a la que se llamará en IRQL = DISPATCH_LEVEL.
Para obtener más información sobre cómo establecer los miembros anteriores de la estructura de configuración, consulte WDF_INTERRUPT_CONFIG. Para obtener información sobre cómo habilitar y deshabilitar interrupciones de nivel pasivo, consulte Habilitación y deshabilitación de interrupciones.
Mantenimiento de una interrupción de Passive-Level
La función de devolución de llamada EvtInterruptIsr , que se ejecuta en IRQL = PASSIVE_LEVEL con el bloqueo de interrupción de nivel pasivo mantenido, normalmente programa un elemento de trabajo de interrupción o interrumpe DPC para procesar información relacionada con interrupciones más adelante. Los controladores basados en marcos implementan rutinas de elemento de trabajo o DPC como funciones de devolución de llamada EvtInterruptWorkItem o EvtInterruptDpc .
Para programar la ejecución de una función de devolución de llamada EvtInterruptWorkItem , un controlador llama a WdfInterruptQueueWorkItemForIsr desde la función de devolución de llamada EvtInterruptIsr .
Para programar la ejecución de una función de devolución de llamada EvtInterruptDpc , un controlador llama a WdfInterruptQueueDpcForIsr desde la función de devolución de llamada EvtInterruptIsr . (Recuerde que la función de devolución de llamada EvtInterruptIsr de un controlador puede llamar a WdfInterruptQueueWorkItemForIsr o WdfInterruptQueueDpcForIsr, pero no ambas).
La mayoría de los controladores usan una sola función de devolución de llamada EvtInterruptWorkItem o EvtInterruptDpc para cada tipo de interrupción. Si el controlador crea varios objetos de interrupción del marco para cada dispositivo, considere la posibilidad de usar una devolución de llamada EvtInterruptWorkItem o EvtInterruptDpc independiente para cada interrupción.
Normalmente, los controladores completan las solicitudes de E/S en sus funciones de devolución de llamada EvtInterruptWorkItem o EvtInterruptDpc .
En el ejemplo de código siguiente se muestra cómo un controlador que usa interrupciones de nivel pasivo podría programar una devolución de llamada EvtInterruptWorkItem desde su función 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);
}
}
Sincronización de una interrupción de Passive-Level
Para evitar el interbloqueo, siga estas directrices al escribir un controlador que implemente el control de interrupciones de nivel pasivo:
Si AutomaticSerialization está establecido en TRUE, no envíe solicitudes sincrónicas desde una función de devolución de llamada EvtInterruptDpc o EvtInterruptWorkItem .
Libere el bloqueo de interrupción de nivel pasivo antes de completar las solicitudes de E/S.
Proporcione EvtInterruptDisable, EvtInterruptEnable y EvtInterruptWorkItem según sea necesario.
Si el controlador debe realizar un trabajo relacionado con interrupciones en un contexto de subproceso arbitrario, como en un controlador de solicitudes, use WdfInterruptTryToAcquireLock y WdfInterruptReleaseLock. No llame a WdfInterruptAcquireLock, WdfInterruptSynchronize, WdfInterruptEnable o WdfInterruptDisable desde un contexto de subproceso arbitrario. Para obtener un ejemplo de un escenario de interbloqueo que puede deberse a una llamada a WdfInterruptAcquireLock desde un contexto de subproceso arbitrario, vea la sección Comentarios de WdfInterruptAcquireLock.
Si se produce un error en la llamada a WdfInterruptTryToAcquireLock , el controlador puede posponer su trabajo relacionado con la interrupción en un elemento de trabajo. En ese elemento de trabajo, el controlador puede adquirir de forma segura el bloqueo de interrupción llamando a WdfInterruptAcquireLock. Para obtener más información, vea WdfInterruptTryToAcquireLock.
En un contexto de subproceso no arbitrario, como un elemento de trabajo, el controlador puede llamar a WdfInterruptAcquireLock o WdfInterruptSynchronize.
Para obtener más información sobre el uso de bloqueos de interrupción, vea Sincronizar código de interrupción.