Udostępnij przez


Obsługa przerwań typu Active-Both

Uwaga Ten temat dotyczy tylko Kernel-Mode Driver Framework (KMDF) w wersji 1.13 i starszych.

Wiele urządzeń ma rejestry sprzętowe kontrolujące generowanie i maskowanie przerwań. Zazwyczaj sterowniki KMDF i UMDF dla takich urządzeń korzystają z wbudowanej obsługi przerwań platformy.

Jednak proste urządzenie na platformie sprzętowej Systemu na mikroukładach (SoC) może nie mieć rejestrów sprzętowych na potrzeby przerwań. W związku z tym sterownik takiego urządzenia może nie być w stanie kontrolować, kiedy przerwanie zostanie wygenerowane lub może zamaskować przerwanie w sprzęcie. Jeśli urządzenie przerywa działanie tuż po podłączeniu, a sterownik korzysta z obsługi przerwań platformy, istnieje możliwość, że przerwanie wystąpi, zanim platforma w pełni zainicjuje obiekt przerwania. W związku z tym sterownik KMDF musi wywoływać procedury WDM bezpośrednio w celu nawiązania połączenia i rozłączenia przerwań. Ponieważ sterownik UMDF nie może wywoływać tych metod, nie można napisać sterownika UMDF dla takiego urządzenia.

W tym temacie opisano, jak sterownik KMDF może obsługiwać przerwania dla takiego urządzenia.

Na platformach sprzętowych SoC przerwania aktywne obustronnie są zwykle używane w przypadku bardzo prostych urządzeń, takich jak przyciski. Gdy użytkownik naciśnie przycisk, linia sygnału przerwania z urządzenia przechodzi z niskiej do wysokiej lub z wysokiej do niskiej. Gdy użytkownik zwolni przycisk, linia przerwania przechodzi w przeciwnym kierunku. pl-PL: Wyprowadzenie GPIO skonfigurowane jako wejście przerwania aktywne na obu zboczach generuje przerwania zarówno przy przejściach z niskiego na wysoki, jak i z wysokiego na niski poziom, co powoduje, że system wywołuje procedurę obsługi przerwania sterownika urządzenia peryferyjnego (ISR) w obu przypadkach. Jednak sterownik nie otrzymuje wskazania, czy przejście jest z niskiego do wysokiego, czy z wysokiego do niskiego.

Aby odróżnić przejścia z niskiego do wysokiego poziomu i z wysokiego do niskiego poziomu, sterownik musi śledzić stan każdego przerwania. W tym celu sterownik może zachować wartość logicznego stanu przerwania, która jest FALSE, gdy stan linii przerwania jest niski i TRUE, gdy stan linii jest wysoki.

Rozważmy przykład, w którym stan wiersza jest domyślnie niski po uruchomieniu systemu. Sterownik inicjuje wartość stanu FALSE w funkcji wywołania zwrotnego EvtDevicePrepareHardware. Następnie za każdym razem, gdy ISR sterownika jest wywoływany, sygnalizując zmianę stanu, sterownik odwraca wartość stanu w swojej ISR.

Jeśli stan linii jest wysoki przy uruchomieniu systemu, przerwanie zostaje uruchomione natychmiast po jego włączeniu. Ponieważ sterownik bezpośrednio wywołuje procedurę IoConnectInterruptEx, zamiast wywoływać WdfInterruptCreate, gwarantuje się odbiór możliwego natychmiastowego przerwania.

To rozwiązanie wymaga, aby kontroler GPIO obsługiwał przerwania aktywne w obu trybach w sprzęcie albo aby sterownik kontrolera GPIO emulował przerwania aktywne w obu trybach w oprogramowaniu. Aby uzyskać informacje na temat emulowania przerwań aktywnych obustronnie, zobacz opis członka EmulateActiveBoth struktury CONTROLLER_ATTRIBUTE_FLAGS.

Poniższy przykład kodu pokazuje, jak sterownik KMDF dla urządzenia peryferyjnego może śledzić polaryzację przerwań.

typedef struct _INTERRUPT_CONTEXT INTERRUPT_CONTEXT, *PINTERRUPT_CONTEXT;
typedef struct _DEVICE_CONTEXT DEVICE_CONTEXT, *PDEVICE_CONTEXT;


struct _INTERRUPT_CONTEXT
{
               BOOLEAN State;
               PDEVICE_CONTEXT DeviceContext;
};

struct _DEVICE_CONTEXT
{
               PKINTERRUPT Interrupt;
               INTERRUPT_CONTEXT InterruptContext;
               PDEVICE_OBJECT PhysicalDeviceObject;
               KSPIN_LOCK SpinLock;
};

...

BOOLEAN
YourInterruptIsr(
               __in PKINTERRUPT Interrupt,
               __in PVOID ServiceContext
               )
{
               PINTERRUPT_CONTEXT InterruptContext = (PINTERRUPT_CONTEXT)ServiceContext;
               PDEVICE_CONTEXT DeviceContext = InterruptContext->DeviceContext;

               //
               // Flip the state.
               //
               InterruptContext->State = !InterruptContext->State;

               IoRequestDpc(DeviceContext->PhysicalDeviceObject, DeviceContext->PhysicalDeviceObject->CurrentIrp, InterruptContext);
}

VOID
YourInterruptDpc(
               __in PKDPC Dpc,
               __in PDEVICE_OBJECT DeviceObject,
               __inout PIRP Irp,
               __in_opt PVOID ContextPointer
               )
{
               PINTERRUPT_CONTEXT InterruptContext = (PINTERRUPT_CONTEXT)ContextPointer;

               ...
}

NTSTATUS
EvtDriverDeviceAdd(
               __in  WDFDRIVER Driver,
               __in  PWDFDEVICE_INIT DeviceInit
               )
{
               WDFDEVICE Device;
               PDEVICE_CONTEXT DeviceContext;

               ...

               DeviceContext->Interrupt = NULL;
               DeviceContext->PhysicalDeviceObject = WdfDeviceWdmGetPhysicalDevice(Device);
               KeInitializeSpinLock(&DeviceContext->SpinLock);

               IoInitializeDpcRequest(DeviceContext->PhysicalDeviceObject, YourInterruptDpc);
}

NTSTATUS
EvtDevicePrepareHardware(
               __in  WDFDEVICE Device,
               __in  WDFCMRESLIST ResourcesRaw,
               __in  WDFCMRESLIST ResourcesTranslated
               )
{
               PDEVICE_CONTEXT DeviceContext = YourGetDeviceContext(Device);

               for (ULONG i = 0; i < WdfCmResourceListGetCount(ResourcesTranslated); i++)
               {
                              PCM_PARTIAL_RESOURCE_DESCRIPTOR descriptor = WdfCmResourceListGetDescriptor(ResourcesTranslated, i);

                              if (descriptor->Type == CmResourceTypeInterrupt)
                              {
                                             IO_CONNECT_INTERRUPT_PARAMETERS params;
                                             RtlZeroMemory(&params, sizeof(params));

                                             params.Version = CONNECT_FULLY_SPECIFIED;
                                             params.FullySpecified.PhysicalDeviceObject = DeviceContext->PhysicalDeviceObject;
                                             params.FullySpecified.InterruptObject = &DeviceContext->Interrupt;
                                             params.FullySpecified.ServiceRoutine = YourInterruptIsr;
                                             params.FullySpecified.ServiceContext = (PVOID)&DeviceContext->InterruptContext;
                                             params.FullySpecified.SpinLock = &DeviceContext->SpinLock;
                                             params.FullySpecified.Vector = descriptor->u.Interrupt.Vector;
                                             params.FullySpecified.Irql = (KIRQL)descriptor->u.Interrupt.Level;
                                             params.FullySpecified.SynchronizeIrql = (KIRQL)descriptor->u.Interrupt.Level;
                                             params.FullySpecified.InterruptMode = (descriptor->Flags & CM_RESOURCE_INTERRUPT_LATCHED) ? Latched : LevelSensitive;
                                             params.FullySpecified.ProcessorEnableMask = descriptor->u.Interrupt.Affinity;
                                             params.FullySpecified.ShareVector = descriptor->ShareDisposition;

                                             //
                                             // Default state is low.
                                             //
                                             DeviceContext->InterruptContext.State = 0;
                                             DeviceContext->InterruptContext.DeviceContext = DeviceContext;

                                             return IoConnectInterruptEx(&params);
                              }
               }

               return STATUS_SUCCESS;
}

NTSTATUS
EvtDeviceReleaseHardware(
               __in  WDFDEVICE Device,
               __in  WDFCMRESLIST ResourcesTranslated
)
{
               PDEVICE_CONTEXT DeviceContext = YourGetDeviceContext(Device);

               if (NULL != DeviceContext->Interrupt)
               {
                              IO_DISCONNECT_INTERRUPT_PARAMETERS params;

                              params.Version = CONNECT_FULLY_SPECIFIED;
                              params.ConnectionContext.InterruptObject = DeviceContext->Interrupt;

                              IoDisconnectInterruptEx(&params);
               }

               return STATUS_SUCCESS;
}

W poprzednim przykładzie kodu funkcja wywołania zwrotnego EvtDriverDeviceAdd konfiguruje kontekst urządzenia, a następnie wywołuje IoInitializeDpcRequest w celu zarejestrowania procedury DpcForIsr.

Procedura InterruptService sterownika odwraca wartość stanu przerwania, a następnie wywołuje IoRequestDpc do zakolejkowania DPC.

W funkcji wywołania zwrotnego EvtDevicePrepareHardware sterownik ustawia wartość stanu na FALSE, a następnie wywołuje IoConnectInterruptEx. W swojej funkcji wywołania zwrotnego EvtDeviceReleaseHardware, sterownik wywołuje IoDisconnectInterruptEx, aby wyrejestrować jego ISR.