Prise en charge des interruptions de Passive-Level
À compter de la version 1.11 du framework, Kernel-Mode Driver Framework (KMDF) et User-Mode Driver Framework (UMDF) s’exécutant sur Windows 8 ou versions ultérieures du système d’exploitation peuvent créer des objets d’interruption qui nécessitent une gestion passive. Si le pilote configure un objet d’interruption pour la gestion des interruptions de niveau passif, l’infrastructure appelle la routine de service d’interruption (ISR) du pilote et d’autres fonctions de rappel d’événement d’objet d’interruption au niveau IRQL = PASSIVE_LEVEL tout en tenant un verrou d’interruption de niveau passif.
Si vous développez un pilote basé sur une infrastructure pour une plateforme Système sur puce (SoC), vous pouvez utiliser des interruptions en mode passif pour communiquer avec un appareil Hors SoC via un bus à basse vitesse, comme I²C, SPI ou UART.
Sinon, vous devez utiliser des interruptions qui nécessitent une gestion au niveau de l’IRQL (DIRQL) de l’appareil. Si votre pilote prend en charge les interruptions signalées par message (MSI), vous devez utiliser la gestion des interruptions DIRQL. Dans les versions 1.9 et antérieures, l’infrastructure traite toujours les interruptions à IRQL = DIRQL.
Cette rubrique explique comment créer, traiter et synchroniser des interruptions de niveau passif.
Création d’une interruption de Passive-Level
Pour créer un objet d’interruption de niveau passif, un pilote doit initialiser une structure WDF_INTERRUPT_CONFIG et la passer à la méthode WdfInterruptCreate . Dans la structure de configuration, le pilote doit :
- Définissez le membre PassiveHandling sur TRUE.
- Fournissez une fonction de rappel EvtInterruptIsr , à appeler au niveau passif.
- Définissez éventuellement automaticSerialization sur TRUE. Si le pilote définit AutomaticSerialization sur TRUE, l’infrastructure synchronise l’exécution des fonctions de rappel EvtInterruptDpc ou EvtInterruptWorkItem de l’objet d’interruption avec les fonctions de rappel d’autres objets qui se trouvent sous l’objet parent de l’interruption.
- Si vous le souhaitez, le pilote peut fournir une fonction de rappel EvtInterruptWorkItem , à appeler à l’adresse IRQL = PASSIVE_LEVEL, ou une fonction de rappel EvtInterruptDpc , à appeler à l’adresse IRQL = DISPATCH_LEVEL.
Pour plus d’informations sur la définition des membres ci-dessus de la structure de configuration, consultez WDF_INTERRUPT_CONFIG. Pour plus d’informations sur l’activation et la désactivation des interruptions de niveau passif, consultez Activation et désactivation des interruptions.
Maintenance d’une interruption de Passive-Level
La fonction de rappel EvtInterruptIsr , qui s’exécute à IRQL = PASSIVE_LEVEL avec le verrou d’interruption de niveau passif maintenu, planifie généralement un élément de travail d’interruption ou un DPC d’interruption pour traiter ultérieurement les informations liées à l’interruption. Les pilotes basés sur l’infrastructure implémentent des routines d’élément de travail ou DPC en tant que fonctions de rappel EvtInterruptWorkItem ou EvtInterruptDpc .
Pour planifier l’exécution d’une fonction de rappel EvtInterruptWorkItem , un pilote appelle WdfInterruptQueueWorkItemForIsr à partir de la fonction de rappel EvtInterruptIsr .
Pour planifier l’exécution d’une fonction de rappel EvtInterruptDpc , un pilote appelle WdfInterruptQueueDpcForIsr à partir de la fonction de rappel EvtInterruptIsr . (Rappelez-vous que la fonction de rappel EvtInterruptIsr d’un pilote peut appeler WdfInterruptQueueWorkItemForIsr ou WdfInterruptQueueDpcForIsr, mais pas les deux.)
La plupart des pilotes utilisent une seule fonction de rappel EvtInterruptWorkItem ou EvtInterruptDpc pour chaque type d’interruption. Si votre pilote crée plusieurs objets d’interruption de framework pour chaque appareil, envisagez d’utiliser un rappel EvtInterruptWorkItem ou EvtInterruptDpc distinct pour chaque interruption.
Les pilotes effectuent généralement des demandes d’E/S dans leurs fonctions de rappel EvtInterruptWorkItem ou EvtInterruptDpc .
L’exemple de code suivant montre comment un pilote utilisant des interruptions de niveau passif peut planifier un rappel EvtInterruptWorkItem à partir de sa fonction 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);
}
}
Synchronisation d’une interruption de Passive-Level
Pour éviter l’interblocage, suivez ces instructions lors de l’écriture d’un pilote qui implémente la gestion des interruptions de niveau passif :
Si AutomaticSerialization a la valeur TRUE, n’envoyez pas de requêtes synchrones à partir d’une fonction de rappel EvtInterruptDpc ou EvtInterruptWorkItem .
Relâchez le verrou d’interruption de niveau passif avant de terminer les demandes d’E/S.
Fournissez EvtInterruptDisable, EvtInterruptEnable et EvtInterruptWorkItem si nécessaire.
Si votre pilote doit effectuer un travail lié aux interruptions dans un contexte de thread arbitraire, par exemple dans un gestionnaire de requêtes, utilisez WdfInterruptTryToAcquireLock et WdfInterruptReleaseLock. N’appelez pas WdfInterruptAcquireLock, WdfInterruptSynchronize, WdfInterruptEnable ou WdfInterruptDisable à partir d’un contexte de thread arbitraire. Pour obtenir un exemple de scénario d’interblocage pouvant être provoqué par l’appel de WdfInterruptAcquireLock à partir d’un contexte de thread arbitraire, consultez la section Remarques de WdfInterruptAcquireLock.
Si l’appel à WdfInterruptTryToAcquireLock échoue, le pilote peut reporter son travail lié à l’interruption à un élément de travail. Dans cet élément de travail, le pilote peut acquérir en toute sécurité le verrou d’interruption en appelant WdfInterruptAcquireLock. Pour plus d’informations, consultez WdfInterruptTryToAcquireLock.
Dans un contexte de thread non arbitraire, tel qu’un élément de travail, le pilote peut appeler WdfInterruptAcquireLock ou WdfInterruptSynchronize.
Pour plus d’informations sur l’utilisation des verrous d’interruption, consultez Synchronisation du code d’interruption.