Обработка прерываний Active-Both

Примечание Этот раздел относится только к Kernel-Mode Driver Framework (KMDF) версии 1.13 и более ранних версий.

Многие устройства имеют аппаратные регистры, управляющие созданием и маскированием прерываний. Как правило, драйверы KMDF и UMDF для таких устройств используют встроенную поддержку прерываний платформы.

Однако простое устройство на аппаратной платформе System on a Chip (SoC) может не иметь аппаратных регистров для прерываний. В результате драйвер для такого устройства может не управлять моментом создания прерывания или маскировать прерывание на оборудовании. Если устройство прерывается сразу после подключения и драйвер использует поддержку прерываний платформы, прерывание может срабаваться до того, как платформа полностью инициализирует объект прерывания платформы. В результате драйвер KMDF должен вызывать подпрограммы WDM напрямую для подключения и отключения прерываний. Так как драйвер UMDF не может вызывать эти методы, нельзя написать драйвер UMDF для такого устройства.

В этом разделе описывается, как драйвер KMDF может обрабатывать прерывания для такого устройства.

На аппаратных платформах SoC прерывания активных обычно используются для очень простых устройств, таких как аппаратные кнопки. Когда пользователь нажимает кнопку, сигнальная линия прерывания с устройства переходит от низкого к высокому или от высокого к низкому. Когда пользователь отпускает кнопку, линия прерывания переходит в противоположном направлении. Контакт GPIO, настроенный как входные данные прерывания "активный", создает прерывания при переходах от низкого к высокому и высокому, что приводит к тому, что система вызывает подпрограмму прерывания драйвера периферийного устройства (ISR) в обоих случаях. Однако драйвер не получает указания, является ли переход низким к высокому или высокий к низкому.

Чтобы различать переходы от низкого к высокому и высокому, драйвер должен отслеживать состояние каждого прерывания. Для этого драйвер может поддерживать логическое значение состояния прерывания, равное FALSE , если состояние строки прерывания — низкое, и TRUE , если состояние строки — высокое.

Рассмотрим пример, в котором состояние строки по умолчанию является низким при запуске системы. Драйвер инициализирует значение состояния false в функции обратного вызова EvtDevicePrepareHardware . Затем при каждом вызове ISR драйвера, сигнализуя об изменении состояния, драйвер инвертирует значение состояния в своем ISR.

Если состояние строки является высоким при запуске системы, прерывание срабатывает сразу после включения. Так как драйвер вызывает подпрограмму IoConnectInterruptEx напрямую, вместо вызова WdfInterruptCreate он гарантирует получение возможного немедленного прерывания.

Для этого решения требуется, чтобы контроллер GPIO поддерживал прерывания "активный— оба" на оборудовании или чтобы драйвер для контроллера GPIO эмулировал активные прерывания в программном обеспечении. Сведения об эмуляции прерываний типа "активный — оба" см. в описании элемента EmulateActiveBoth структуры CONTROLLER_ATTRIBUTE_FLAGS .

В следующем примере кода показано, как драйвер KMDF для периферийного устройства может отслеживать полярность прерываний.

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;
}

В предыдущем примере кода функция обратного вызова EvtDriverDeviceAdd драйвера настраивает контекст устройства, а затем вызывает IoInitializeDpcRequest для регистрации подпрограммы DpcForIsr .

Подпрограмма InterruptService драйвера инвертирует значение состояния прерывания, а затем вызывает IoRequestDpc для постановки DPC в очередь.

В функции обратного вызова EvtDevicePrepareHardware драйвер инициализирует значение состояния false , а затем вызывает IoConnectInterruptEx. В функции обратного вызова EvtDeviceReleaseHardware драйвер вызывает IoDisconnectInterruptEx , чтобы отменить регистрацию ISR.