/*++

Copyright (C) Microsoft. All rights reserved.

Module Name:

    interrupt.c

Abstract:

    This file contains routines to handle interrupts originating from a GPIO
    controller.

    The master routine that handles a primary interrupt asserting (in response
    to a GPIO interrupt asserting) is GpiopServiceInterrupt.

    The main control flow of this module is as follows:

      {primary interrupt asserting: GpiopServiceInterrupt
       OR callbacks from intdebounce.c module: GpiopDebounceInvokeTargetIsrCallback,
                                               GpiopDebounceUnmaskInterruptCallback,
                                               GpiopDebounceLogSpuriousInterrupt}
        -> GpiopServiceInterruptOnPin - loop of:
           -> GpiopServiceInterruptStateMachine
           -> Calling into intdebounce.c:GpiopDebounceOnInterrupt


Environment:

    Kernel mode

--*/

//
// ------------------------------------------------------------------- Includes
//

#include "pch.h"
#include "clientinvk.h"
#include "hub.h"
#include "privdefs.h"

#if defined(EVENT_TRACING)
#include "interrupt.tmh"         // auto-generated by WPP
#endif

//
// -------------------------------------------------------------------- Defines
//

#define GPIO_INTERRUPT_SAVE_PREVIOUS_STATE(Context)                         \
    (Context)->PreviousState[(Context)->StateIndex] =                       \
        (Context)->State;                                                   \
    (Context)->StateIndex = ((Context)->StateIndex + 1) % GPIO_INTERRUPT_HISTORY_SIZE;

//
// Define macros to invoke HAL APIs to mask and unmask GPIO controller
// interrupt as well as run ISRs for a given GSIV.
//

#define HAL_MASK_CONTROLLER_INTERRUPT(Bank)                                  \
{                                                                            \
    GPIO_ASSERT(GpioHalSecondaryInformation.MaskInterrupt != NULL);          \
    GpioHalSecondaryInformation.MaskInterrupt((Bank)->InterruptData.Gsiv,    \
                                              HAL_MASK_UNMASK_FLAGS_NONE);   \
}

#define HAL_UNMASK_CONTROLLER_INTERRUPT(Bank)                                \
{                                                                            \
    GPIO_ASSERT(GpioHalSecondaryInformation.UnmaskInterrupt != NULL);        \
    GpioHalSecondaryInformation.UnmaskInterrupt((Bank)->InterruptData.Gsiv,  \
                                                HAL_MASK_UNMASK_FLAGS_NONE); \
}

//
// ----------------------------------------------------------------- Prototypes
//

BOOLEAN
GpiopCheckValidInterrupt (
    __in PGPIO_BANK_ENTRY GpioBank,
    __in PGPIO_PIN_INFORMATION_ENTRY PinInformation,
    __in PIN_NUMBER PinNumber,
    __in GPIO_INTERRUPT_EVENT_TYPE PrimaryEventType,
    __out PBOOLEAN ClearStatus
    );

PIN_NUMBER
GpiopFindInterruptingPinAndClear (
    __in PGPIO_BANK_ENTRY GpioBank
    );

_Must_inspect_result_
__drv_requiresIRQL(PASSIVE_LEVEL)
NTSTATUS
GpiopForwardInterruptRequestToQueue (
    __in PDEVICE_EXTENSION GpioExtension,
    __in PGPIO_INTERRUPT_REQUEST_PARAMETERS InterruptRequest
    );

BOOLEAN
GpiopInterruptBankIsr (
    __in PGPIO_BANK_ENTRY GpioBank
    );

VOID
GpiopIssueDeferredInterruptActivities (
    __in PGPIO_BANK_ENTRY GpioBank
    );

BOOLEAN
GpiopServiceInterrupt (
    __in PGPIO_BANK_ENTRY GpioBank
    );

VOID
GpiopServiceInterruptOnPin (
    __in PGPIO_INTERRUPT_DEBOUNCE_CONTROLLER_CONTEXT ControllerContext,
    __in GPIO_INTERRUPT_EVENT_TYPE PrimaryEventType,
    __in GPIO_INTERRUPT_EVENT_TYPE SecondaryEventType
    );

NTSTATUS
GpiopServiceInterruptStateMachine (
    __in PGPIO_BANK_ENTRY GpioBank,
    __in PGPIO_PIN_INFORMATION_ENTRY PinInformation,
    __in PIN_NUMBER PinNumber,
    __in GPIO_INTERRUPT_EVENT_TYPE PrimaryEventType,
    __in GPIO_INTERRUPT_EVENT_TYPE EventType,
    __in NTSTATUS EventStatus,
    __out PGPIO_INTERRUPT_ACTION_TYPE OutputActionType
    );

NTSTATUS
GpiopUpdateStatusAndMaskInformation (
    __in PGPIO_BANK_ENTRY GpioBank,
    __out PULONG64 NonEnabledActiveInterrupts,
    __out PULONG64 RawStatusRegister
    );

//
// -------------------------------------------------------------------- Pragmas
//

#pragma alloc_text(PAGE, GpioClxEnableInterrupt)
#pragma alloc_text(PAGE, GpioClxEvtHandleInterruptWorker)
#pragma alloc_text(PAGE, GpioClxQueryInterruptInformation)

//
// Internal routines.
//

#pragma alloc_text(PAGE, GpiopForwardInterruptRequestToQueue)
#pragma alloc_text(PAGE, GpiopProcessInterruptRequestIoctl)

//
// -------------------------------------------------------------------- Globals
//

HAL_SECONDARY_INTERRUPT_INFORMATION GpioHalSecondaryInformation;

//
// ------------------------------------------------- Interfaces exported to WDF
//

BOOLEAN
GpioClxEvtInterruptIsr (
    __in WDFINTERRUPT Interrupt,
    __in ULONG MessageID
    )

/*++

Routine Description:

    This routine is the interrupt service routine for the GPIO controller's
    interrupt. It determines the GPIO bank and forward to the request on the
    per-bank interrupt service routine.

Arguments:

    Interrupt - Supplies a handle to interrupt object (WDFINTERRUPT) for this
        device.

    MessageID - Supplies the MSI message ID for MSI-based interrupts.

Return Value:

    TRUE if the GPIO controller generated the interrupt. FALSE otherwise.

--*/

{

    PGPIO_BANK_ENTRY GpioBank;
    PGPIO_INTERRUPT_OBJECT_CONTEXT InterruptContext;

    UNREFERENCED_PARAMETER(MessageID);

    InterruptContext = GpioClxGetInterruptObjectContext(Interrupt);
    GpioBank = InterruptContext->Bank;
    return GpiopInterruptBankIsr(GpioBank);
}

BOOLEAN
GpioClxEvtInterruptPassiveIsr (
    __in WDFINTERRUPT Interrupt,
    __in ULONG MessageID
    )

/*++

Routine Description:

    This routine is the passive-level interrupt service routine for the GPIO
    controller's interrupt. It determines the GPIO bank and forward to the
    request on the per-bank interrupt service routine.

    N.B. This routine is called at PASSIVE_LEVEL but is not marked as
         PAGED_CODE to provide forward progress guarantee as it could be
         executed late in the hibernate or early in resume sequence (or the
         deep-idle sequence).

Arguments:

    Interrupt - Supplies a handle to interrupt object (WDFINTERRUPT) for this
        device.

    MessageID - Supplies the MSI message ID for MSI-based interrupts.

Return Value:

    TRUE if the GPIO controller generated the interrupt. FALSE otherwise.

--*/

{

    PGPIO_BANK_ENTRY GpioBank;
    PDEVICE_EXTENSION GpioExtension;
    PGPIO_INTERRUPT_OBJECT_CONTEXT InterruptContext;
    BOOLEAN InterruptClaimed;

    UNREFERENCED_PARAMETER(MessageID);

    InterruptContext = GpioClxGetInterruptObjectContext(Interrupt);
    GpioBank = InterruptContext->Bank;
    GpioExtension = GpioBank->GpioExtension;

    //
    // This worker thread should only be invoked if the interrupts are to be
    // serviced only at passive IRQL.
    //

    GPIO_ASSERT(GPIO_IS_PASSIVE_IRQL_ONLY_DEVICE(GpioExtension) != FALSE);

    //
    // The interrupt service routine needs to be called with the controller
    // lock held to guarantee that only one thread is manipulating the
    // controller state.
    //
    // WDF did not already acquire this lock automatically because
    // WDF_INTERRUPT_CONFIG::WaitLock was not set (no reason in particular
    // for not setting it).
    //

    GPIO_ACQUIRE_BANK_LOCK(GpioBank);

    //
    // Process the interrupts for the GPIO controller associated with this
    // workitem.
    //

    InterruptClaimed = GpiopServiceInterrupt(GpioBank);

    //
    // Release the controller lock.
    //

    GPIO_RELEASE_BANK_LOCK(GpioBank);

    return InterruptClaimed;
}

BOOLEAN
GpioClxEvtVirtualRootIsr (
    __in PVOID Context
    )

/*++

Routine Description:

    This routine is the interrupt service routine for the virtual root GPIO
    controller's simulated primary interrupt. It determines the GPIO bank and
    forwards the request on the per-bank interrupt service routine.

    This is only used in test environments.

    N.B. 1. The virtual root GPIO controller must only have a single bank and
            must be a memory-mapped controller.

         2. This routine runs at DISPATCH_LEVEL only.

Arguments:

    Context - Supplies a pointer to the context supplied at the time of
        registration. The context points to the GPIO instance extension.

Return Value:

    TRUE if the GPIO controller generated the interrupt. FALSE otherwise.

--*/

{

    BOOLEAN Claimed;
    PGPIO_BANK_ENTRY GpioBank;
    PDEVICE_EXTENSION GpioExtension;

    GpioExtension = (PDEVICE_EXTENSION)Context;

    GPIO_ASSERT(GpioExtension->TotalBanks == 1);
    GPIO_ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
    GPIO_ASSERT(GPIO_IS_PASSIVE_IRQL_ONLY_DEVICE(GpioExtension) == FALSE);

    GpioBank = &GpioExtension->Banks[0];
    WdfSpinLockAcquire(GpioBank->BankInterruptLock);
    Claimed = GpiopInterruptBankIsr(GpioBank);
    WdfSpinLockRelease(GpioBank->BankInterruptLock);
    return Claimed;
}

BOOLEAN
GpiopInterruptBankIsr (
    __in PGPIO_BANK_ENTRY GpioBank
    )

/*++

Routine Description:

    This routine is the interrupt service routine for the GPIO controller's
    interrupt. It queries the GPIO controller to determine the set of pins
    that are currently interrupting. For each pin, it will map it to the
    the corresponding GSIV and request the HAL to execute the ISR corresponding
    to that GSIV.

    If the GPIO controller is behind a serial bus, the kernel is calling us
    from a PASSIVE_LEVEL worker thread that it had deferred the underlying
    DIRQL interrupt processing to. If the interrupt is level-triggered, it had
    already called the HAL to mask it to prevent an interrupt storm.

Arguments:

    GpioBank - Supplies a pointer to the GPIO bank to be serviced.

Return Value:

    TRUE if the GPIO controller generated the interrupt. FALSE otherwise.

--*/

{

    PDEVICE_EXTENSION GpioExtension;
    BOOLEAN InterruptClaimed;

#ifdef DEBOUNCE_ELAPSED_TIME_ACCOUNTING

    ULONG64 EnterTime;

#endif

    GpioExtension = GpioBank->GpioExtension;

    GPIO_ASSERT(KeGetCurrentIrql() <= GpioExtension->SynchronizationIrql);

    //
    // Register the current thread servicing the interrupt as the implicit
    // owner of the interrupt lock.
    //

    GPIO_ASSERT(GpioBank->InterruptLockOwner == NULL);

    GpioBank->InterruptLockOwner = KeGetCurrentThread();

    //
    // Register the (approximate) time at which this interrupt fired.
    //

#ifdef DEBOUNCE_ELAPSED_TIME_ACCOUNTING

    GpioBank->InterruptData.LastInterruptTime =
        (LONGLONG)KeQueryInterruptTimePrecise(&EnterTime);

#endif

    //
    // Before servicing the interrupt, invoke the client driver's callback to
    // allow it to perform any pre-processing.
    //
    // Some GPIO controller implementations may require their status bits
    // to be snapshotted as soon as the interrupt fires (at DIRQL level)
    // even though the device is itself behind serial. If the snapshot is
    // delayed until the IRQL drops to passive, the status bits may get lost.
    // The interrupt pre-processing callback enables such handling.
    //
    // N.B. The status bits are copied to some memory location in this
    //      case by some hardware entity (otherwise the client driver will not
    //      be able to access it at DIRQL).
    //

    GpioClnInvokePreProcessControllerInterrupt(GpioBank);

    //
    // Check if the GPIO controller is capable of handling interrupts at
    // DIRQL level or not. If it is capable of handling interrupts at DIRQL,
    // then service the interrupts directly.
    //
    // Otherwise, the controller is off-SoC and needs to be accessed via some
    // some serial bus. Thus cannot handle interrupts at DIRQL. In such cases
    // a passive-level interrupt handler should have been registered. The
    // passive-level ISR would get called after this (DIRQL) ISR returns.
    //

    if (GPIO_IS_PASSIVE_IRQL_ONLY_DEVICE(GpioExtension) == FALSE) {
       InterruptClaimed = GpiopServiceInterrupt(GpioBank);

    } else {

        //
        // Since the device can't be probed directly, this ISR cannot determine
        // if this GPIO controller was indeed the one interrupting. Since the
        // interrupt line could be shared, it is desired to request the HAL to
        // cycle through all the handlers sharing the same interrupt line. If
        // this GPIO controller was the source of the interrupt, the HAL will
        // ignore it as spurious after it cycles through all the handlers and
        // no one else claims it.
        //
        // N.B. If the interrupt is level-triggered, the kernel will deal
        //      with masking of the interrupt line.
        //

        InterruptClaimed = FALSE;
    }

    //
    // Register the release of interrupt lock ownership. Note the lock will
    // actually be released by the kernel once the ISR finishes.
    //

    GpioBank->InterruptLockOwner = NULL;
    return InterruptClaimed;
}

VOID
GpioClxEvtInterruptDpc (
    __in WDFINTERRUPT Interrupt,
    __in WDFOBJECT Device
    )

/*++

Routine Description:

    This routine is the DPC callback for the ISR. This routine is responsible
    for queuing a work item to process the interrupts for this GPIO controller.


Arguments:

    Interrupt - Supplies a handle to interrupt object (WDFINTERRUPT) for this
        device.

    Device - Supplies a handle to the framework device object.

Return Value:

    None.

--*/

{

    PGPIO_BANK_ENTRY GpioBank;
    PDEVICE_EXTENSION GpioExtension;
    PGPIO_INTERRUPT_OBJECT_CONTEXT InterruptContext;
    LONG PreviousState;
    PLONG WorkerRunning;

    UNREFERENCED_PARAMETER(Device);

    //
    // If it is an off-SoC GPIO OR an on-SoC GPIO whose bank is not powered
    // on, then defer the activities to a worker thread. Otherwise, issue
    // the requests synchronously in the DPC context.
    //

    InterruptContext = GpioClxGetInterruptObjectContext(Interrupt);
    GpioBank = InterruptContext->Bank;
    GpioExtension = GpioBank->GpioExtension;
    if ((GPIO_IS_PASSIVE_IRQL_ONLY_DEVICE(GpioBank->GpioExtension) == FALSE) &&
        (GpioBank->PowerData.IsActive != FALSE)) {

        GpiopIssueDeferredInterruptActivities(GpioBank);

    //
    // Enqueue the workitem to the system's work queue if it is not already
    // queued or running.
    //

    } else {
        WorkerRunning = &GpioBank->InterruptData.InterruptWorkerRunning;
        PreviousState = InterlockedCompareExchange(WorkerRunning,
                                                   GPIO_WORKER_RUNNING,
                                                   GPIO_WORKER_NOT_RUNNING);

        if (PreviousState == GPIO_WORKER_NOT_RUNNING) {
            WdfWorkItemEnqueue(GpioBank->InterruptData.InterruptWorker);
        }
    }

    return;
}

VOID
GpioClxEvtHandleInterruptWorker (
    __in WDFWORKITEM WorkItem
    )

/*++

Routine Description:

    This routine is the worker for servicing deferred interrupt-related work.

Arguments:

    WorkItem -  Supplies a handle to the workitem supplying the context.

Return Value:

    None.

--*/

{

    PGPIO_INTERRUPT_WORK_ITEM_CONTEXT Context;
    PGPIO_BANK_ENTRY GpioBank;
    PLONG WorkerRunning;

    Context = GpiopGetInterruptWorkItemContext(WorkItem);
    GpioBank = Context->Bank;

    //
    // Mark the worker as done running. This will cause future pending mask
    // requests to queue another worker for processing.
    //

    WorkerRunning = &GpioBank->InterruptData.InterruptWorkerRunning;
    InterlockedExchange(WorkerRunning, GPIO_WORKER_NOT_RUNNING);
    GpiopIssueDeferredInterruptActivities(GpioBank);
    return;
}

VOID
GpioClxEvtDebounceTimerHandler (
    __in PEX_TIMER Timer,
    __inout PVOID DeferredContext
    )

/*++

Routine Description:

    This routine is the debounce timer handler. This routine is invoked when
    the debounce timer fires and is responsible for clearing the active
    debounce bit for the associated pin. For off-SoC GPIOs, this routine
    dispatches work to be called back from the passive-level worker.

    N.B. This routine can be invoked at DISPATCH_LEVEL or PASSIVE_LEVEL.

Arguments:

    Dpc - Supplies a pointer to the associated DPC.

    DeferredContext - Supplies a pointer to debounce timer context.

    SystemArgument1 - Unused.

    SystemArgument2 - Unused.

Return Value:

    None.

--*/

{

    GPIO_INTERRUPT_DEBOUNCE_CONTROLLER_CONTEXT ControllerContext;
    PGPIO_DEBOUNCE_NOISE_TIMER_WORK_ITEM_CONTEXT DebounceNoiseWorkerContext;
    PGPIO_DEBOUNCE_PIN_CONTEXT DebouncePinContext;
    BOOLEAN DebounceStateMachineCompleted;
    PGPIO_DEBOUNCE_TIMER_CONTEXT DebounceTimerContext;
    PGPIO_BANK_ENTRY GpioBank;
    PDEVICE_EXTENSION GpioExtension;
    BOOLEAN InterruptConnected;
    BOOLEAN PassiveGpio;
    PGPIO_PIN_INFORMATION_ENTRY PinInformation;
    PIN_NUMBER PinNumber;

    UNREFERENCED_PARAMETER(Timer);

    DebounceTimerContext = (PGPIO_DEBOUNCE_TIMER_CONTEXT)DeferredContext;
    GpioBank = DebounceTimerContext->Bank;
    GpioExtension = GpioBank->GpioExtension;
    PassiveGpio = GPIO_IS_PASSIVE_IRQL_ONLY_DEVICE(GpioExtension);

    //
    // If called from DPC context for off-SoC GPIOs, then dispatch a workitem
    // to be called back at passive-level.
    //

    if ((PassiveGpio != FALSE) && (KeGetCurrentIrql() == DISPATCH_LEVEL)) {

        DebounceNoiseWorkerContext =
            GpiopGetDebounceNoiseTimerWorkItemContext(
                DebounceTimerContext->DebounceNoiseTimerWorker);

        GPIO_ACQUIRE_BANK_INTERRUPT_LOCK(GpioBank);

        DebounceNoiseWorkerContext->ServiceDebounceTimer = TRUE;
        DebounceNoiseWorkerContext->TimerContext = DebounceTimerContext;

        //
        // Queue the worker if it not already queued.
        //

        if (DebounceNoiseWorkerContext->WorkerQueued == FALSE) {
            DebounceNoiseWorkerContext->WorkerQueued = TRUE;
            WdfWorkItemEnqueue(DebounceTimerContext->DebounceNoiseTimerWorker);
        }

        GPIO_RELEASE_BANK_INTERRUPT_LOCK(GpioBank);

        return;
    }

    //
    // Process the request.
    //

    PinNumber = DebounceTimerContext->PinNumber;
    PinInformation = DebounceTimerContext->PinInformation;
    DebouncePinContext = &PinInformation->DebounceContext;

    EventWrite_DEBOUNCE_TIMER_COMPLETE(&GpioBank->ActivityId, PinNumber);

    //
    // Initialize the controller context to be passed to debouncing module.
    //

    RtlZeroMemory(&ControllerContext,
                  sizeof(GPIO_INTERRUPT_DEBOUNCE_CONTROLLER_CONTEXT));

    ControllerContext.GpioBank = GpioBank;
    ControllerContext.PinNumber = PinNumber;
    ControllerContext.PinInformation = PinInformation;

    //
    // Acquire the passive-level bank lock for off-SoC GPIOs.
    //

    if (PassiveGpio != FALSE) {
        GPIO_ACQUIRE_BANK_LOCK(GpioBank);
    }

    //
    // Check if the interrupt is still connected or not. This is to protect
    // against cases where the interrupt gets disconnected while being
    // debounced.
    //

    if (PinInformation->Mode.Interrupt != 0) {
        InterruptConnected = TRUE;

    } else {
        InterruptConnected = FALSE;
    }

    //
    // Invoke the debounce engine.
    //

    if (InterruptConnected != FALSE) {

        //
        // Clear the active debounce mask bit for this pin to cause future
        // interrupts to be dispatched.
        //

        GPIO_CLEAR_PIN_ACTIVE_DEBOUNCE_MASK(GpioBank, PinNumber);

        //
        // Invoke the debounce engine on the debounce interval timer event.
        //

        GpiopDebounceOnDebounceTimer(DebouncePinContext,
                                     &ControllerContext,
                                     &DebounceStateMachineCompleted);

        //
        // If the noise timer is requested to be scheduled, then scheduled it
        // now. Note the noise timer may also be scheduled as a result of
        // reconfiguration failure which needs to be retried.
        //

        if (ControllerContext.ScheduleNoiseFilterTimers != FALSE) {

            GPIO_ASSERT(DebounceStateMachineCompleted == FALSE);

            GpiopScheduleNoiseFilterTimers(GpioBank);
        }

    } else {

        //
        // If the pin has already been disconnected, DebouncePinContext could
        // be gone.
        //

        DebounceStateMachineCompleted = FALSE;
    }

    if (DebounceStateMachineCompleted != FALSE) {

        //
        // While still holding the bank lock to prevent the pin from being
        // disconnected and DebouncePinContext disappearing, signal completion
        // if somebody is waiting.
        //

        GpiopDebounceSignalStateMachineCompletion(DebouncePinContext,
                                                  &ControllerContext);
    }

    if (PassiveGpio != FALSE) {
        GPIO_RELEASE_BANK_LOCK(GpioBank);
    }

    return;
}

VOID
GpioClxEvtNoiseFilterTimerHandler (
    __in PEX_TIMER Timer,
    __inout PVOID DeferredContext
    )

/*++

Routine Description:

    This routine is the noise filter timer handler. This routine notifies the
    debounce engine regarding the noise filter timer expiration.

    N.B. This routine can be invoked at DISPATCH_LEVEL or PASSIVE_LEVEL.

Arguments:

    Dpc - Supplies a pointer to the associated DPC.

    NoiseFilterTimerContext - Supplies a pointer to noise filter timer context.

    SystemArgument1 - Unused.

    SystemArgument2 - Unused.

Return Value:

    None.

--*/

{

    GPIO_INTERRUPT_DEBOUNCE_CONTROLLER_CONTEXT ControllerContext;
    PGPIO_DEBOUNCE_NOISE_TIMER_WORK_ITEM_CONTEXT DebounceNoiseWorkerContext;
    GPIO_DEBOUNCE_OUTPUT_ACTION DebounceOutputAction;
    PGPIO_DEBOUNCE_PIN_CONTEXT DebouncePinContext;
    BOOLEAN DebounceStateMachineCompleted;
    PGPIO_BANK_ENTRY GpioBank;
    PDEVICE_EXTENSION GpioExtension;
    BOOLEAN InterruptConnected;
    PGPIO_NOISE_FILTER_TIMER_CONTEXT NoiseFilterTimerContext;
    BOOLEAN PassiveGpio;
    PGPIO_PIN_INFORMATION_ENTRY PinInformation;
    PIN_NUMBER PinNumber;

    UNREFERENCED_PARAMETER(Timer);

    NoiseFilterTimerContext = (PGPIO_NOISE_FILTER_TIMER_CONTEXT)DeferredContext;
    GpioBank = NoiseFilterTimerContext->Bank;
    GpioExtension = GpioBank->GpioExtension;
    PinInformation = NoiseFilterTimerContext->PinInformation;
    PassiveGpio = GPIO_IS_PASSIVE_IRQL_ONLY_DEVICE(GpioExtension);

    //
    // If called from DPC context for off-SoC GPIOs, then dispatch a workitem
    // to be called back at passive-level.
    //

    if ((PassiveGpio != FALSE) && (KeGetCurrentIrql() == DISPATCH_LEVEL)) {

        DebounceNoiseWorkerContext =
            GpiopGetDebounceNoiseTimerWorkItemContext(
                NoiseFilterTimerContext->DebounceNoiseTimerWorker);

        GPIO_ACQUIRE_BANK_INTERRUPT_LOCK(GpioBank);

        DebounceNoiseWorkerContext->ServiceDebounceTimer = FALSE;
        DebounceNoiseWorkerContext->TimerContext = NoiseFilterTimerContext;

        //
        // Queue the worker if it not already queued.
        //

        if (DebounceNoiseWorkerContext->WorkerQueued == FALSE) {
            DebounceNoiseWorkerContext->WorkerQueued = TRUE;
            WdfWorkItemEnqueue(
                NoiseFilterTimerContext->DebounceNoiseTimerWorker);
        }

        GPIO_RELEASE_BANK_INTERRUPT_LOCK(GpioBank);

        return;
    }

    //
    // Capture the parameters. The debounce output action parameter can be
    // manipulated from the debounce engine and thus needs to be protected.
    // All other fields are set just once at creation.
    //

    PinNumber = NoiseFilterTimerContext->PinNumber;
    DebouncePinContext = &PinInformation->DebounceContext;

    GPIO_ACQUIRE_BANK_INTERRUPT_LOCK(GpioBank);

    DebounceOutputAction = NoiseFilterTimerContext->DebounceOutputAction;

    GPIO_RELEASE_BANK_INTERRUPT_LOCK(GpioBank);


    //
    // Initialize the controller context to be passed to debouncing module.
    //

    RtlZeroMemory(&ControllerContext,
                  sizeof(GPIO_INTERRUPT_DEBOUNCE_CONTROLLER_CONTEXT));

    ControllerContext.GpioBank = GpioBank;
    ControllerContext.PinNumber = PinNumber;
    ControllerContext.PinInformation = PinInformation;
    ControllerContext.InterruptEpoch = NoiseFilterTimerContext->InterruptEpoch;

    //
    // Acquire the passive-level bank lock for off-SoC GPIOs.
    //

    if (PassiveGpio != FALSE) {
        GPIO_ACQUIRE_BANK_LOCK(GpioBank);
    }

    //
    // Check if the interrupt is still connected or not. This is to protect
    // against cases where the interrupt gets disconnected while being
    // debounced.
    //

    if (PinInformation->Mode.Interrupt != 0) {
        InterruptConnected = TRUE;

    } else {
        InterruptConnected = FALSE;
    }

    //
    // Invoke the debounce engine on the noise filter timer event.
    //

    if (InterruptConnected != FALSE) {
        GpiopDebounceOnNoiseTimer(DebouncePinContext,
                                  &ControllerContext,
                                  &DebounceOutputAction,
                                  &DebounceStateMachineCompleted);

        //
        // If the noise timer is requested to be scheduled, then scheduled it
        // now. This will only happen here if a reconfiguration attempt failed
        // and thus needs to be retried after a retry interval (which same as
        // noise interval). This can only happen of off-SoC GPIO controllers.
        //

        if (ControllerContext.ScheduleNoiseFilterTimers != FALSE) {

            GPIO_ASSERT(PassiveGpio != FALSE);
            GPIO_ASSERT(DebounceStateMachineCompleted == FALSE);

            GpiopScheduleNoiseFilterTimers(GpioBank);
        }

    } else {

        //
        // If the pin has already been disconnected, DebouncePinContext could
        // be gone.
        //

        DebounceStateMachineCompleted = FALSE;
    }

    if (DebounceStateMachineCompleted != FALSE) {

        //
        // While still holding the bank lock to prevent the pin from being
        // disconnected and DebouncePinContext disappearing, signal completion
        // if somebody is waiting.
        //

        GpiopDebounceSignalStateMachineCompletion(DebouncePinContext,
                                                  &ControllerContext);
    }

    if (PassiveGpio != FALSE) {
        GPIO_RELEASE_BANK_LOCK(GpioBank);
    }

    return;
}

VOID
GpioClxEvtDebounceNoiseFilterExpirationWorker (
    __in WDFWORKITEM WorkItem
    )

/*++

Routine Description:

    This routine is the worker for processing debounce and noise filters
    timer expirations in a deferred manner.

    N.B. This routine is only ever expected to be invoked for off-SoC GPIO
         controllers.

Arguments:

    WorkItem -  Supplies a handle to the workitem supplying the context.

Return Value:

    None.

--*/

{

    PGPIO_DEBOUNCE_NOISE_TIMER_WORK_ITEM_CONTEXT DebounceNoiseWorkerContext;
    PGPIO_BANK_ENTRY GpioBank;
    PDEVICE_EXTENSION GpioExtension;
    BOOLEAN ServiceDebounceTimer;
    PVOID TimerContext;

    DebounceNoiseWorkerContext =
        GpiopGetDebounceNoiseTimerWorkItemContext(WorkItem);

    GpioBank = DebounceNoiseWorkerContext->Bank;
    GpioExtension = GpioBank->GpioExtension;

    GPIO_ASSERT(GPIO_IS_PASSIVE_IRQL_ONLY_DEVICE(GpioExtension) != FALSE);

    GPIO_ACQUIRE_BANK_INTERRUPT_LOCK(GpioBank);

    ServiceDebounceTimer = DebounceNoiseWorkerContext->ServiceDebounceTimer;
    TimerContext = DebounceNoiseWorkerContext->TimerContext;

    //
    // Mark the worker as no longer queued.
    //

    DebounceNoiseWorkerContext->WorkerQueued = FALSE;

    GPIO_RELEASE_BANK_INTERRUPT_LOCK(GpioBank);

    if (ServiceDebounceTimer != FALSE) {
        GpioClxEvtDebounceTimerHandler(NULL, TimerContext);

    } else {
        GpioClxEvtNoiseFilterTimerHandler(NULL, TimerContext);
    }

    return;
}

//
// -------------------------------- Interrupt interfaces exported to hub driver
//

__drv_sameIRQL
__drv_requiresIRQL(PASSIVE_LEVEL)
VOID
GpioClxInitializeHubVirqEntryCache (
    __inout volatile ULONG_PTR *HubVirqEntryCache
    )

/*++

Routine Description:

    This routine initializes the the hub's VIRQ entry's cache.

Arguments:

    HubVirqEntryCache - Supplies a pointer to the hub's VIRQ entry's cache,
        which was set by GpioClxEnableInterrupt().

        Opaque to the hub caller, this is actually the controller-relative pin
        number.

Return Value:

    None.

--*/

{

    (VOID)InterlockedExchangePointer((volatile PVOID *)HubVirqEntryCache,
                                     (PVOID)(ULONG_PTR)INVALID_PIN_NUMBER);
}

__drv_sameIRQL
__drv_requiresIRQL(PASSIVE_LEVEL)
NTSTATUS
GpioClxDisableInterrupt (
    __in PVOID Context,
    __in ULONG Virq
    )

/*++

Routine Description:

    This routine is the disable interrupt callback. This routine converts the
    request into an IO requests and forwards it to the WDF queues.

    This routine is not marked PAGED as it may be called before/after the boot
    device is in D0/D3 if boot device has GPIO dependencies.

Arguments:

    Context - Supplies a pointer to the context supplied at the time of
        registration. The context points to the GPIO instance extension.

    Virq - Supplies the VIRQ for interrupt line that should be disconnected.

Return Value:

    NTSTATUS code.

--*/

{

    BOOLEAN AcpiEvent;
    PDEVICE_EXTENSION GpioExtension;
    WDF_DEVICE_POWER_STATE PowerState;
    BOOLEAN RemovalContext;
    GPIO_INTERRUPT_REQUEST_PARAMETERS Request;
    NTSTATUS Status;

    GpioExtension = (PDEVICE_EXTENSION)Context;

    GPIO_CLX_VALIDATE_SIGNATURE(GpioExtension);

    //
    // Disable for ACPI event pins always arrives in the context of the
    // pre-interrupt disabled callback, by which time WDF has already stopped
    // queues. So posting a request onto the queue would pend forever (and
    // lead to a hang). To avoid this deadlock, perform the disable
    // synchronously.
    //
    // Also, when interrupts are being disabled as part of the GPIO controller
    // going away (e.g. shutdown, surprise-removal etc.), the WDFs queues have
    // already been paused. Dispatch directly to the GPIO class extension in
    // such cases.
    //
    // Check if the GSIV maps to an ACPI event pin or not.
    //

    AcpiEvent = GpiopIsAcpiEventInterrupt(GpioExtension, Virq);
    PowerState = GPIO_GET_DEVICE_POWER_STATE(GpioExtension);
    if ((AcpiEvent == FALSE) && (PowerState == WdfPowerDeviceD0)) {
        RtlZeroMemory(&Request, sizeof(GPIO_INTERRUPT_REQUEST_PARAMETERS));
        Request.Type = DisableInterruptRequest;
        Request.Virq = Virq;
        Status = GpiopForwardInterruptRequestToQueue(GpioExtension, &Request);

    } else {

        //
        // Check the device power state. The state is "D3Final" for cases where
        // the device is still present (e.g. shutdown) and "invalid" for cases
        // where the device is going away or already gone away
        // (surprise-removed).
        //

        if (PowerState == WdfPowerDeviceInvalid) {
            RemovalContext = TRUE;

        } else {
            RemovalContext = FALSE;
        }

        Status = GpiopDisableInterrupt(GpioExtension, Virq, RemovalContext);
    }

    return Status;
}

_Must_inspect_result_
__drv_requiresIRQL(PASSIVE_LEVEL)
NTSTATUS
GpioClxEnableInterrupt (
    __in PVOID Context,
    __in ULONG Virq,
    __in __drv_strictTypeMatch(__drv_typeConst) KINTERRUPT_MODE InterruptMode,
    __in __drv_strictTypeMatch(__drv_typeConst) KINTERRUPT_POLARITY Polarity,
    __in ULONG_PTR CallbackContext,
    __inout_opt volatile ULONG_PTR *HubVirqEntryCache
    )

/*++

Routine Description:

    This routine is the enable interrupt callback. This routine converts the
    request into an IO request and forwards it to the WDF queues.

Arguments:

    Context - Supplies a pointer to the context supplied at the time of
        registration. The context points to the GPIO instance extension.

    Virq - Supplies the VIRQ for interrupt line that should be enabled.

    InterruptMode - Supplies the trigger mode (edge or level) associated with
        this interrupt.

    Polarity - Supplies the polarity (active low or active high) associated with
        this interrupt.

    CallbackContext - Supplies a context that the caller of this routine should
        be passed with the interrupt line fires.

    HubVirqEntryCache - Supplies a variable that receives the
        controller-relative pin number.

Return Value:

    NTSTATUS code.

--*/

{

    BOOLEAN AcpiEvent;
    PDEVICE_EXTENSION GpioExtension;
    GPIO_INTERRUPT_REQUEST_PARAMETERS Request;
    NTSTATUS Status;

    PAGED_CODE();

    GpioExtension = (PDEVICE_EXTENSION)Context;

    GPIO_CLX_VALIDATE_SIGNATURE(GpioExtension);

    //
    // Enable for ACPI event pins always arrives in the context of the
    // ACPI eventing worker thread created by the GPIO class extension.
    // Since the worker runs asynchronously compared to the rest of the WDF
    // power engine, it is possible that WDF may issue a D3 request to
    // GPIO class extension (after stopping the queues) while enable requests
    // are still being dispatched by ACPI eventing worker thread.
    // In such case, posting a request onto the queue would pend forever (and
    // lead to a hang). To avoid this deadlock, perform the enable
    // synchronously.
    //
    //
    // N.B. 1. The ACPI event enables are only dispatched after the device
    //         enters D0. So it is safe to dispatch synchronously.
    //
    //      2. The D3 request if any will block on the ACPI mutex until all
    //         enable requests are completed and the mutex is released.
    //

    //
    // Check if the GSIV maps to an ACPI event pin or not.
    //

    AcpiEvent = GpiopIsAcpiEventInterrupt(GpioExtension, Virq);
    if (AcpiEvent == FALSE) {
        RtlZeroMemory(&Request, sizeof(GPIO_INTERRUPT_REQUEST_PARAMETERS));
        Request.Type = EnableInterruptRequest;
        Request.Virq = Virq;
        Request.Enable.InterruptMode = InterruptMode;
        Request.Enable.Polarity = Polarity;
        Request.Enable.CallbackContext = CallbackContext;
        Request.Enable.HubVirqEntryCache = HubVirqEntryCache;
        Status = GpiopForwardInterruptRequestToQueue(GpioExtension, &Request);

    } else {
        Status = GpiopEnableInterrupt(GpioExtension,
                                      Virq,
                                      InterruptMode,
                                      Polarity,
                                      CallbackContext,
                                      HubVirqEntryCache);
    }

    return Status;
}

__drv_sameIRQL
NTSTATUS
GpioClxMaskInterrupt (
    __in PVOID Context,
    __in ULONG Flags,
    __in ULONG Virq
    )

/*++

Routine Description:

    This routine is mask interrupt callback. It simply forwards the request
    to the internal handler.

    N.B. 1. This routine can be called at DIRQL level if the GPIO controller is
            directly CPU-accessible (i.e. memory-mapped) and DISPATCH_LEVEL by
            the kernel if the GPIO is off-SoC.

         2. Since the mask request is only valid for pins that are connected
            for interrupt, a power reference must have been taken and thus
            the device and bank must be powered ON.

         3. This routine cannot send a WDF request to itself (to deal with
            power-off cases) as that would introduce a deadlock. Some other
            thread may have been dispatched [w/ the WDF queue lock acquired] and
            may be waiting for the bank lock. Sending a self-request could
            block on the WDF queue lock w/ the bank lock already acquired (if
            called from within the ISR).

Arguments:

    Context - Supplies a pointer to the context supplied at the time of
        registration. The context points to the GPIO instance extension.

    Flags - Supplies flags that control mask/unmask behavior.

    Virq - Supplies the VIRQ for interrupt line that should be masked.

Return Value:

    NTSTATUS code.

--*/

{

    PDEVICE_EXTENSION GpioExtension;

    GpioExtension = (PDEVICE_EXTENSION)Context;

    GPIO_CLX_VALIDATE_SIGNATURE(GpioExtension);

    //
    // Convert from HAL flags to GPIO class extension flag values.
    //

    if (CHECK_FLAG(Flags, HAL_MASK_UNMASK_FLAGS_SERVICING_DEFERRED) != FALSE) {
        Flags = GPIO_REQUEST_FLAG_SERVICING_DEFERRED;

    } else {
        Flags = 0;
    }

    return GpiopMaskInterrupt(GpioExtension, Flags, Virq);
}

__drv_sameIRQL
NTSTATUS
GpioClxUnmaskInterrupt (
    __in PVOID Context,
    __in ULONG Flags,
    __in ULONG Virq
    )

/*++

Routine Description:

    This routine is the unmask interrupt callback. It simply forwards the request
    to the internal handler.

    N.B. 1. This routine can be called at DIRQL level if the GPIO controller is
            directly CPU-accessible (i.e. memory-mapped) and DISPATCH_LEVEL by
            the kernel if the GPIO is off-SoC.

         2. Since an unmask request is only valid for pins that are connected
            for interrupt, a power reference must have been taken and thus
            the device and bank must be powered ON.

         3. This routine cannot send a WDF request to itself (to deal with
            power-off cases) as that would introduce a deadlock. Some other
            thread may have been dispatched [w/ the WDF queue lock acquired] and
            may be waiting for the bank lock. Sending a self-request could
            block on the WDF queue lock w/ the bank lock already acquired (if
            called from within the ISR).

Arguments:

    Context - Supplies a pointer to the context supplied at the time of
        registration. The context points to the GPIO instance extension.

    Flags - Supplies flags that control mask/unmask behavior.

    Virq - Supplies the VIRQ for interrupt line that should be unmasked.

Return Value:

    NTSTATUS code.

--*/

{

    PDEVICE_EXTENSION GpioExtension;

    GpioExtension = (PDEVICE_EXTENSION)Context;

    GPIO_CLX_VALIDATE_SIGNATURE(GpioExtension);

    //
    // Convert from HAL flags to GPIO class extension flag values.
    //

    if (CHECK_FLAG(Flags, HAL_MASK_UNMASK_FLAGS_SERVICING_COMPLETE) != FALSE) {
        Flags = GPIO_REQUEST_FLAG_SERVICING_COMPLETE;

    } else {
        Flags = 0;
    }

    return GpiopUnmaskInterrupt(GpioExtension, Flags, Virq);
}

_Must_inspect_result_
__drv_requiresIRQL(PASSIVE_LEVEL)
NTSTATUS
GpioClxQueryInterruptInformation (
    __in PVOID Context,
    __in ULONG SecondaryGsiv,
    __out PULONG Gsiv,
    __out KIRQL *Irql,
    __out PGROUP_AFFINITY Affinity,
    __out KINTERRUPT_MODE *InterruptMode,
    __out PKINTERRUPT_POLARITY Polarity
    )

/*++

Routine Description:

    This routine queries the interrupt information (like mode, polarity, IRQL
    etc.) for the specified GPIO controller interrupt.

Arguments:

    Context - Supplies a pointer to the context supplied at the time of
        registration. The context points to the GPIO instance extension.

    SecondaryGsiv - Supplies the secondary GSIV to return information about.

    Gsiv - Supplies a pointer that receives the GSIV for the GPIO controller's
        interrupt.

    Irql - Supplies a pointer that receives the IRQL assigned to the GPIO
        controller's interrupt.

    Affinity - Supplies a pointer that receives the affinity of the processors
        the GPIO controller's interrupt is to be enabled to interrupt.

    InterruptMode - Supplies a pointer that receives the trigger mode (edge or
        level) associated with GPIO controller's interrupt.

    Polarity - Supplies the polarity (active low or active high) associated
        with the GPIO controller's interrupt.

Return Value:

    STATUS_SUCCESS always.

--*/

{

    PGPIO_BANK_ENTRY GpioBank;
    PDEVICE_EXTENSION GpioExtension;
    NTSTATUS Status;

    PAGED_CODE();

    GpioExtension = (PDEVICE_EXTENSION)Context;

    GPIO_CLX_VALIDATE_SIGNATURE(GpioExtension);

    //
    // This routine could be called early before the bank and pin information
    // is the context of querying for boot resources. Use default handling
    // in such cases. The GPIO bank and pin information and is only available
    // after the PrepareHardware callback has been invoked by WDF framework.
    //

    if (GPIO_GET_DEVICE_STATE(GpioExtension) >= DEVICE_STATE_INITIALIZED) {
        Status = GpiopQueryBankIdFromGsivOrConnectionId(
            GpioExtension,
            TRUE,
            SecondaryGsiv,
            &GpioBank,
            NULL,
            NULL);

    } else {
        GpioBank = NULL;
        Status = STATUS_UNSUCCESSFUL;
    }

    if (NT_SUCCESS(Status)) {
        *Gsiv = GpioBank->InterruptData.Gsiv;
        *Affinity = GpioBank->InterruptData.Affinity;
        *InterruptMode = GpioBank->InterruptData.InterruptMode;
        *Polarity = GpioBank->InterruptData.Polarity;

    } else {
        *Gsiv = GpioExtension->BaseInterruptGsiv;
        Affinity->Group = 0x0;
        Affinity->Mask = (KAFFINITY)-1;
        *InterruptMode = LevelSensitive;
        *Polarity = InterruptPolarityUnknown;
    }

    //
    // Always return the IRQL from the GpioExtension, which represents the
    // maximum IRQL of all banks in this controller.
    //

    *Irql = GpioExtension->SynchronizationIrql;
    return STATUS_SUCCESS;
}

__drv_sameIRQL
NTSTATUS
GpioClxRequestInterrupt (
    __in PVOID Context,
    __in ULONG Virq,
    __out PULONG NextGsiv
    )

/*++

Routine Description:

    This routine requests that the specified interrupt line should be considered
    to have asserted the next time this interrupt controllers line has asserted.

Arguments:

    Context - Supplies a pointer to the context supplied at the time of
        registration. The context points to the GPIO device extension.

    Virq - Supplies the VIRQ for interrupt line that should be asserted.

    NextGsiv - Supplies a pointer to a variable which receives the GSIV that
        needs to be asserted to ensure the eventual servicing the requested
        interrupt, if applicable.

Return Value:

    NTSTATUS code.

--*/

{

    return GpiopRequestInterrupt(Context, Virq, NextGsiv);
}

__drv_sameIRQL
NTSTATUS
GpioClxQueryPinInformation (
    __in PVOID Context,
    __in ULONG Gsiv,
    __in volatile ULONG_PTR *HubVirqEntryCache,
    __out PUSHORT PinIndex,
    __out PVOID *ControllerId
    )

/*++

Routine Description:

    This routine queries controller-specific information for the given GSIV.
    The information includes pin number and controller ID. The controller ID
    in this case maps to the power handle.

    N.B. This routine must be called at HIGH_LEVEL with all other CPUs asleep.

Arguments:

    Context - Supplies a pointer to the context supplied at the time of
        registration. The context points to the GPIO instance extension.

    Gsiv - Supplies the GSIV to be queried.

    HubVirqEntryCache - Supplies a pointer to the hub's VIRQ entry's cache,
        which was set by GpioClxEnableInterrupt().

        Opaque to the hub caller, this is actually the controller-relative pin
        number.

    PinIndex - Supplies a pointer that receives the controller-relative pin
        number.

    ControllerId - Supplies a pointer that receives the controller ID.

Return Value:

    NTSTATUS code.

--*/

{

    PIN_NUMBER AbsolutePinNumber;
    PDEVICE_EXTENSION GpioExtension;
    ULONG_PTR HubVirqEntryCacheValue;
    PGPIO_PIN_INFORMATION_ENTRY PinInformation;
    NTSTATUS Status;

    GPIO_ASSERT(KeGetCurrentIrql() == HIGH_LEVEL);

    Status = STATUS_SUCCESS;
    GpioExtension = (PDEVICE_EXTENSION)Context;

    //
    // N.B. This could even be changed to a read with no fence, since all the
    //      writes from the other CPUs have already committed to memory.
    //

    HubVirqEntryCacheValue = *HubVirqEntryCache;
    AbsolutePinNumber = (PIN_NUMBER)HubVirqEntryCacheValue;

    GPIO_ASSERT(AbsolutePinNumber == HubVirqEntryCacheValue);

    //
    // If a context switch had occurred in the interrupt enable path, the VIRQ
    // entry cache might not have been set yet. The interrupt would not have
    // been enabled in hardware either.
    //

    if (AbsolutePinNumber == INVALID_PIN_NUMBER) {
        Status = STATUS_INVALID_PARAMETER;
        goto QueryPinInformationEnd;
    }

    //
    // Get the entry from the pin information table that maps to this VIRQ.
    // The hub will only request the class extension to request an interrupt if
    // it manages the supplied VIRQ.
    //

    PinInformation = GpiopGetPinEntryFromPinNumber(GpioExtension, AbsolutePinNumber);

    //
    // Since the pin number is valid, the pin must be available (although not
    // necessarily connected).
    //

    GPIO_ASSERT(PinInformation != NULL);

    //
    // If a context switch had occurred in the middle of the interrupt disable
    // path, the pin information entry Virq may have been cleared.
    //

    if (Gsiv != PinInformation->Virq) {
       TraceEvents(GpioExtension->LogHandle,
                    Error,
                    DBG_INTERRUPT,
                    "%s: Gsiv supplied is invalid! Extn = 0x%p, Gsiv = %#x\n",
                    __FUNCTION__,
                    GpioExtension,
                    Gsiv);

        Status = STATUS_INVALID_PARAMETER;
        goto QueryPinInformationEnd;
    }

    NT_ASSERT(GpioExtension->PowerHandle != NULL);

    *PinIndex = AbsolutePinNumber;
    *ControllerId = GpioExtension->PowerHandle;

QueryPinInformationEnd:
    return Status;
}

//
// ------------------------------------------------ Exported to class extension
//

_Must_inspect_result_
__drv_requiresIRQL(PASSIVE_LEVEL)
NTSTATUS
GpiopAllocateInterruptProcessingResources (
    __in PDEVICE_EXTENSION GpioExtension
    )

/*

Routine Description:

    This routine pre-allocates the worker thread needed for interrupt
    processing. Pre-allocation eliminates the chances for runtime failures
    during interrupt processing due to lack of resources.

    N.B. This routine assumes the client driver has been queried to determine
         whether it is a memory-mapped device or serial device.

Arguments:

    GpioExtension - Supplies a pointer to the GPIO's device extension.

Return Value:

    NTSTATUS code.

--*/

{

    WDF_OBJECT_ATTRIBUTES Attributes;
    PGPIO_INTERRUPT_WORK_ITEM_CONTEXT Context;
    PGPIO_BANK_ENTRY GpioBank;
    BANK_ID Index;
    PGPIO_INTERRUPT_DATA InterruptData;
    NTSTATUS Status;
    WDF_WORKITEM_CONFIG WorkItemConfiguration;

    PAGED_CODE();

    //
    // Create a workitem to process interrupts.
    //

    WDF_OBJECT_ATTRIBUTES_INIT(&Attributes);
    Attributes.ParentObject = GpioExtension->Device;
    WDF_OBJECT_ATTRIBUTES_SET_CONTEXT_TYPE(&Attributes,
                                           GPIO_INTERRUPT_WORK_ITEM_CONTEXT);

    //
    // Initialize the worker routine and create a new workitem.
    //

    WDF_WORKITEM_CONFIG_INIT(&WorkItemConfiguration,
                             GpioClxEvtHandleInterruptWorker);

    //
    // Disable automatic serialization by the framework for the worker thread.
    // The parent device object is being serialized at device level (i.e.,
    // WdfSynchronizationScopeDevice), and the framework requires it be
    // passive level (i.e., WdfExecutionLevelPassive) if automatic
    // synchronization is desired.
    //

    WorkItemConfiguration.AutomaticSerialization = FALSE;

    //
    // Create the work item object per bank and initialize the context.
    //

    for (Index = 0; Index < GpioExtension->TotalBanks; Index += 1) {
        GpioBank = &GpioExtension->Banks[Index];
        InterruptData = &GpioBank->InterruptData;
        Status = WdfWorkItemCreate(&WorkItemConfiguration,
                                   &Attributes,
                                   &InterruptData->InterruptWorker);

        if (!NT_SUCCESS(Status)) {
            TraceEvents(GpioExtension->LogHandle,
                        Error,
                        DBG_INTERRUPT,
                        "%s: Failed to allocate work item! Status = %#x\n",
                        __FUNCTION__,
                        Status);

            goto AllocateInterruptResourcesEnd;
        }

        //
        // Initialize the context to be supplied to the workitem handler.
        //

        Context = GpiopGetInterruptWorkItemContext(
                      InterruptData->InterruptWorker);

        Context->Bank = GpioBank;
    }

    Status = STATUS_SUCCESS;

    //
    // On failure, the resources will be cleaned up by WDF when the device gets
    // deleted.
    //

AllocateInterruptResourcesEnd:
    return Status;
}

_Must_inspect_result_
__drv_requiresIRQL(PASSIVE_LEVEL)
NTSTATUS
GpiopProcessInterruptRequestIoctl (
    __in PDEVICE_EXTENSION GpioExtension,
    __in WDFREQUEST Request,
    __in PGPIO_INTERRUPT_REQUEST_PARAMETERS RequestParameters,
    __out PULONG BytesWritten
    )

/*++

Routine Description:

    This routine is a hander for interrupt-related requests (enable, disable,
    unmask etc). This routine figures out the parameters of the request
    (e.g. the VIRQ, interrupt polarity etc.) and calls into the class extension
    to perform the requested action.

Arguments:

    GpioExtension - Supplies a pointer to the GPIO's device extension.

    Request - Supplies a handle to a framework request object.

    RequestParameters - Supplies a pointer to the parameters associated
        with the request.

    BytesWritten - Supplies a pointer to a variable that receives the total
        number of bytes written.

Return Value:

    NTSTATUS code.

--*/

{

    NTSTATUS Status;

    PAGED_CODE();

    UNREFERENCED_PARAMETER(Request);

    //
    // N.B. Since the IOCTL is buffered, WdfRequestRetrieveOutputBuffer &
    //      WdfRequestRetrieveInputBuffer return the same buffer pointer. So
    //      all the information from the buffer must be read prior to writing
    //      to it.
    //

    //
    // Perform the operation requested on the specified VIRQ.
    //

    switch (RequestParameters->Type) {

    case EnableInterruptRequest:
        Status = GpiopEnableInterrupt(GpioExtension,
                                      RequestParameters->Virq,
                                      RequestParameters->Enable.InterruptMode,
                                      RequestParameters->Enable.Polarity,
                                      RequestParameters->Enable.CallbackContext,
                                      RequestParameters->Enable.HubVirqEntryCache);

        break;

    case DisableInterruptRequest:
        Status = GpiopDisableInterrupt(GpioExtension,
                                       RequestParameters->Virq,
                                       FALSE);

        break;

    case MaskInterruptRequest:
        Status = GpiopMaskInterrupt(GpioExtension,
                                    RequestParameters->Flags,
                                    RequestParameters->Virq);

        break;

    case UnmaskInterruptRequest:
        Status = GpiopUnmaskInterrupt(GpioExtension,
                                      RequestParameters->Flags,
                                      RequestParameters->Virq);

        break;

    default:
        Status = STATUS_NOT_SUPPORTED;
        break;
    }

    if (ARGUMENT_PRESENT(BytesWritten) != FALSE) {
        *BytesWritten = 0;
    }

    return Status;
}

//
// --------------------------------------------------------- Internal Functions
//

PIN_NUMBER
GpiopFindInterruptingPinAndClear (
    __in PGPIO_BANK_ENTRY GpioBank
    )

/*++

Routine Description:

    This routine examines the GPIO controller's status register and determines
    the next interrupting pin number.

    N.B. This routine will clear the return bit number in the buffer on exit.

Arguments:

    GpioBank - Supplies a pointer to the GPIO bank.

Return Value:

    PIN_NUMBER - The interrupting pin's number. INVALID_PIN_NUMBER if no pin
        is marked as interrupting.

--*/

{

    PGPIO_INTERRUPT_DATA InterruptData;
    PIN_NUMBER PinNumber;

    //
    // Find the first set bit in the status register that is not currently
    // masked.
    //

    InterruptData = &GpioBank->InterruptData;
    PinNumber = RtlFindLeastSignificantBit(InterruptData->StatusRegister);
    if (PinNumber == 0xFF) {
        PinNumber = INVALID_PIN_NUMBER;

    } else {
        InterruptData->StatusRegister &= ~((ULONG64)1 << PinNumber);
    }

    return PinNumber;
}

_Must_inspect_result_
__drv_requiresIRQL(PASSIVE_LEVEL)
NTSTATUS
GpiopForwardInterruptRequestToQueue (
    __in PDEVICE_EXTENSION GpioExtension,
    __in PGPIO_INTERRUPT_REQUEST_PARAMETERS InterruptRequest
    )

/*++

Routine Description:

    This routine forwards the input request to the default I/O queue for the
    device.

Arguments:

    GpioExtension - Supplies a pointer to the GPIO's device extension.

    InterruptRequest - Supplies a pointer to the parameters of the request.

Return Value:

    NTSTATUS code.

--*/

{

    WDF_MEMORY_DESCRIPTOR MemoryDescriptor;
    ULONG Size;
    NTSTATUS Status;

    PAGED_CODE();

    //
    // Set up a WDF memory descriptor for the parameters.
    //

    Size = sizeof(GPIO_INTERRUPT_REQUEST_PARAMETERS);
    WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&MemoryDescriptor,
                                      InterruptRequest,
                                      Size);

    //
    // Format the request.
    //

    Status = WdfIoTargetSendInternalIoctlSynchronously(
                 GpioExtension->SelfIoTarget,
                 NULL,
                 IOCTL_GPIO_INTERRUPT_REQUEST,
                 &MemoryDescriptor,
                 NULL,
                 NULL,
                 NULL);

    return Status;
}

VOID
GpiopInvokeTargetIsr (
    __in PGPIO_BANK_ENTRY GpioBank,
    __in ULONG64 PinMask
    )

/*++

Routine Description:

    This routine performs target ISR invocations for the given set of pins.
    For each pin, it requests the HAL to invoke the corresponding ISR.

    N.B. 1. Entry IRQL: D-IRQL for memory-mapped GPIOs and PASSIVE_LEVEL for
            off-SoC GPIOs.

         2. This routine is invoked with the D-IRQL bank interrupt lock held
            for memory-mapped GPIO controllers and passive-level bank lock held
            for off-SoC GPIO controllers.

Arguments:

    GpioBank - Supplies a pointer to the GPIO's bank to be manipulated.

    PinMask - Supplies a bitmask of pins for which ISR invocations need to
        be performed.

Return Value:

    None.

--*/

{

    ULONG_PTR CallbackContext;
    PDEVICE_EXTENSION GpioExtension;
    BOOLEAN IsrWasDispatched;
    KIRQL Irql;
    BOOLEAN PassiveGpio;
    PGPIO_PIN_INFORMATION_ENTRY PinInformation;
    PIN_NUMBER PinNumber;

    GpioExtension = GpioBank->GpioExtension;

    //
    // Validate the IRQL and locking requirements.
    //

    PassiveGpio = GPIO_IS_PASSIVE_IRQL_ONLY_DEVICE(GpioExtension);
    if (PassiveGpio != FALSE) {

        GPIO_ASSERT((GPIO_BANK_PASSIVE_LOCK_OWNER(GpioBank) != FALSE) &&
                    (KeGetCurrentIrql() == PASSIVE_LEVEL));

    } else {

        GPIO_ASSERT(GPIO_BANK_INTERRUPT_LOCK_OWNER(GpioBank) != FALSE);
    }

    //
    // Walk through all the pins specified in the pin mask and invoke ISR on
    // each of them.
    //

    while (PinMask > 0) {
        PinNumber = RtlFindLeastSignificantBit(PinMask);
        PinMask &= ~((ULONG64)1 << PinNumber);
        PinInformation = GpiopGetPinEntry(GpioBank, PinNumber);

        GPIO_ASSERT((PinInformation != NULL) &&
                    (PinInformation->Mode.Interrupt == 1));

        //
        // For non-ACPI event pins, request the HAL to invoke the ISR mapped to
        // the pin. For ACPI events, update the mask and schedule a DPC
        // to invoke ACPI method corresponding to the pin.
        //

        if (PinInformation->Mode.AcpiEvent == 1) {

            GPIO_ACQUIRE_ACPI_EVENT_LOCK(GpioExtension, &Irql);

            GpioUtilUpdatePinValue(GpioBank,
                                   PinNumber,
                                   AcpiRegisterTypePendingEvents,
                                   TRUE);

            GPIO_RELEASE_ACPI_EVENT_LOCK(GpioExtension, Irql);

            //
            // If this is a level-triggered ACPI event pin, then it needs to
            // be masked until the corresponding ACPI method is run. Otherwise,
            // the GPIO controller interrupt would fire continuously.
            //
            // N.B. 1. Given the likelihood of multiple ACPI event pins
            //         interrupting at the same time is small, each ACPI event
            //         pin is individually masked for simplicity.
            //
            //      2. The mask request may ideally need to be routed through
            //         the HAL to keep its view consistent. However, it is
            //         not done here for performance. The HAL information is
            //         needed by the PEP when the last processor is going
            //         down, which shouldn't happen while ACPI events are
            //         still to be processed (after which point the pin
            //         will be unmasked anyway).
            //

            if (PinInformation->InterruptMode == LevelSensitive) {
                GpioClxMaskInterrupt(GpioExtension,
                                     HAL_MASK_UNMASK_FLAGS_SERVICING_DEFERRED,
                                     PinInformation->Virq);
            }

            //
            // Asynchronously, this will call the ACPI method and then unmask
            // the pin.
            //

            GpiopProcessAcpiEvents(GpioExtension);

        } else {
            TraceEvents(GpioBank->GpioExtension->LogHandle,
                        Info,
                        DBG_INTERRUPT,
                        "%s: Invoking ISR for Bank = %p, Relative Pin = %#x\n",
                        __FUNCTION__,
                        GpioBank,
                        PinNumber);

            //
            // Request the HAL and invoke the ISR mapped to pin.
            //

            CallbackContext = PinInformation->InterruptCallbackContext;
            EventWrite_INTERRUPT_INVOKE_DEVICE_ISR_START(
                &GpioBank->ActivityId,
                PinNumber,
                PinInformation->Virq);

            IsrWasDispatched = FALSE;
            GpiopInvokeIsrForGsiv(PinInformation->Virq,
                                  CallbackContext,
                                  &IsrWasDispatched);

            //
            // For Emulated ActiveBoth, if no pin ISR was invoked -- because
            // all of the pin's KINTERRUPT object(s) were masked -- reconfigure
            // the current polarity to the same as before the pin had asserted.
            //
            // This ensures that at least one pin ISR is always called for
            // assertions of alternating polarities (e.g. button, press,
            // release, press, release, ...), preventing the ISR from getting
            // out of sync with the GPIO class extension.
            //
            // This handles the race caused by the following sequence of events:
            //
            //   1. The Emulated ActiveBoth pin asserts.
            //
            //   2. A driver on another thread calls IoReportInterruptInactive()
            //      on all the pin's KINTERRUPT objects(s).
            //
            //   3. The GPIO class extension makes the above
            //      GpiopInvokeIsrForGsiv() call, but no ISR is invoked.
            //
            // N.B. Note the difference between GpiopMaskInterrupt() &
            //      IoReportInterruptInactive():
            //
            //       GpiopMaskInterrupt() masks a pin in hardware, stopping a
            //       pin from asserting.  By itself, it does not prevent
            //       GpiopInvokeIsrForGsiv() from dispatching any ISRs.
            //
            //       IoReportInterruptInactive() masks a pin's KINTERRUPT
            //       object in software.  This blocks the an ISR from being
            //       dispatched.  If all of the pin's KINTERRUPT objects are
            //       masked, the kernel/HAL will call GpiopMaskInterrupt().
            //

            if (PinInformation->Mode.EmulateActiveBoth == 1) {

                NT_ASSERT(PinInformation->CurrentInterruptMode ==
                          LevelSensitive);

                if (IsrWasDispatched == FALSE) {

                    //
                    // Flip the polarity back to what it was before.
                    //

                    TraceEvents(GpioExtension->LogHandle,
                                Error,
                                DBG_INTERRUPT,
                                "%s: IsrNotDispatched: Flipping the polarity! "
                                "Bank = %p, Relative PinNumber = %#x\n",
                                __FUNCTION__,
                                GpioBank,
                                PinNumber);

                    //
                    // The is masked so it is safe to flip the polarity:
                    //
                    // Since the appropriate bank lock is held, a racing
                    // GpiopDeferredUnmaskRequestHandler() could not have
                    // run the client driver to unmask the pin, after
                    // GpiopInvokeIsrForGsiv() discovered that the pin
                    // was masked.
                    //

                    (VOID)GpiopFlipPinPolarity(GpioBank, PinNumber);
                    break;
                }
            }

            EventWrite_INTERRUPT_INVOKE_DEVICE_ISR_COMPLETE(
                &GpioBank->ActivityId);
        }
    }

    return;
}

VOID
GpiopIssueDeferredInterruptActivities (
    __in PGPIO_BANK_ENTRY GpioBank
    )

/*++

Routine Description:

    This routine is called from a DPC or a worker thread to work on
    interrupt-related requests that have been deferred.

    N.B. This routine can be called from DISPATCH_LEVEL or PASSIVE_LEVEL.

Arguments:

    GpioBank - Supplies a pointer to the GPIO's bank to be manipulated.

Return Value:

    NTSTATUS code.

--*/

{

    ULONG Value;

    //
    // Read and clear the pending interrupt activity flag.
    //

    Value = InterlockedExchange(
                &GpioBank->InterruptData.PendingInterruptActions,
                0);

    //
    // If there is some activity to be performed then run the workers.
    //
    // The workers are run in the order of precendence:
    // Debounce timers > Pin Reconfigurations > Disconnect
    //          > Mask > Status clear > Unmask request.
    //

    if (Value == 0x1) {
        EventWrite_DEFERRED_INTERRUPT_ACTIVITIES_START(
            &GpioBank->ActivityId,
            GpioBank->GpioExtension->BiosName.Buffer,
            GpioBank->BankId);

        GpiopScheduleDebounceTimers(GpioBank);
        GpiopDeferredReconfigureRequestHandler(GpioBank);
        GpiopDeferredDisableInterruptHandler(GpioBank, FALSE);
        GpiopDeferredMaskRequestHandler(GpioBank);
        GpiopDeferredStatusClearRequestHandler(GpioBank);
        GpiopDeferredUnmaskRequestHandler(GpioBank);

        EventWrite_DEFERRED_INTERRUPT_ACTIVITIES_COMPLETE(
            &GpioBank->ActivityId);
    }

    return;
}

__drv_sameIRQL
NTSTATUS
GpiopScheduleDeferredInterruptActivities (
    __in PGPIO_BANK_ENTRY GpioBank
    )

/*++

Routine Description:

    This routine schedules a DPC or a worker thread to work on
    interrupt-related requests that have been deferred.


    N.B. This routine can be called from DIRQL, DISPATCH_LEVEL, or
         PASSIVE_LEVEL.

Arguments:

   GpioBank - Supplies a pointer to the GPIO's bank to be lazily manipulated.

Return Value:

    NTSTATUS code.

--*/

{

    KIRQL Irql;
    ULONG Value;

    //
    // Mark the fact there is some pending interrupt activity to be performed.
    //

    Value = InterlockedCompareExchange(
                &GpioBank->InterruptData.PendingInterruptActions,
                1,
                0);

    if (Value == 0x0) {
        Irql = KeGetCurrentIrql();

        //
        // If called from DIRQL and no DPC has been scheduled yet, then schedule
        // one.
        //

        if (Irql > DISPATCH_LEVEL) {
            WdfInterruptQueueDpcForIsr(GpioBank->BankInterrupt);

        //
        // If called from within ISR context for an off-SoC GPIO (passive-level),
        // then dispatch immediately.
        //
        // N.B. The IRQL also needs to be checked here in addition to the
        //      thread to cover the case where a GPIO DPC (e.g. debounce timer)
        //      fires on top of a thread that is servicing an interrupt from the
        //      same GPIO controller. Without the IRQL check, the deferred
        //      activities would assume they were initiated directly from within
        //      the ISR (because InterruptThread would be equal to
        //      KeGetCurrentThread()), which would be wrong.
        //

        } else if ((GPIO_IS_REQUEST_IN_INTERRUPT_CONTEXT(GpioBank) != FALSE) &&
                   (Irql == PASSIVE_LEVEL)) {

            GpiopIssueDeferredInterruptActivities(GpioBank);

        //
        // If called from PASSIVE_LEVEL in a non-interrupt-context or
        // DISPATCH_LEVEL, schedule a passive-level worker if one has not been
        // scheduled yet.
        //
        // It would be odd to be called at PASSIVE_LEVEL in a
        // non-interrupt-context because the caller could have just executed
        // the code to be deferred.
        //

        } else {
            WdfWorkItemEnqueue(GpioBank->InterruptData.InterruptWorker);
        }
    }

    return STATUS_SUCCESS;
}

BOOLEAN
GpiopServiceInterrupt (
    __in PGPIO_BANK_ENTRY GpioBank
    )

/*++

Routine Description:

    This routine is the master routine that services GPIO interrupts. It queries
    the GPIO controller to determine the set of pins that are currently
    interrupting. For each pin, it will map it to the the corresponding GSIV
    and request the HAL to execute the ISR corresponding to that GSIV.

    If this routine is called from a worker thread context, then it will also
    unmask the GPIO controller interrupt (if it was masked inside the ISR).

    N.B. Caller requirements:

           Off-SoC: The bank lock must be held (i.e. PASSIVE_LEVEL)
           On-SoC: The interrupt lock must be held (i.e. >= DISPATCH_LEVEL)

Arguments:

    GpioBank - Supplies a pointer to the GPIO's bank.

Return Value:

    TRUE if the GPIO controller generated the interrupt. FALSE otherwise.

--*/

{

    BOOLEAN AcpiEvents;
    LONG64 CapturedReplayRegister;
    GPIO_INTERRUPT_DEBOUNCE_CONTROLLER_CONTEXT ControllerContext;
    KIRQL CurrentIrql;
    BOOLEAN DebouncePinAsserted;
    ULONG64 DeferredEdgeIsrMask;
    ULONG64 EnableRegister;
    CONTROLLER_ATTRIBUTE_FLAGS Flags;
    PDEVICE_EXTENSION GpioExtension;
    PGPIO_INTERRUPT_DATA InterruptData;
    BOOLEAN InterruptRecognized;
    ULONG64 NonEnabledActiveInterrupts;
    KIRQL OldIrql;
    BOOLEAN PassiveGpio;
    PGPIO_PIN_INFORMATION_ENTRY PinInformation;
    PIN_NUMBER PinNumber;
    GPIO_DEBOUNCE_EVENT_TYPE PrimaryEventType;
    ULONG64 RawStatusRegister;
    BOOLEAN ReplayPass;
    BOOLEAN ScheduleDebounceTimers;
    BOOLEAN ServiceActiveBothInterrupts;
    ULONG Size;
    NTSTATUS Status;
    ULONG64 StatusClearMask;

    //
    // By default, assume that this GPIO controller did not generate the
    // interrupt. The GPIO controller interrupt line could be shared and a
    // different device may have actually interrupted.
    //

    AcpiEvents = FALSE;
    CurrentIrql = KeGetCurrentIrql();
    DeferredEdgeIsrMask = 0;
    InterruptRecognized = FALSE;
    NonEnabledActiveInterrupts = 0;
    OldIrql = 0;
    PrimaryEventType = InterruptEventOnInterrupt;
    ScheduleDebounceTimers = FALSE;
    ServiceActiveBothInterrupts = FALSE;

    //
    // Set the current thread as the interrupt servicing thread for this bank.
    //

    GpioBank->InterruptThread = KeGetCurrentThread();
    GpioExtension = GpioBank->GpioExtension;

    EventWrite_INTERRUPT_START(&GpioBank->ActivityId,
                               GpioExtension->BiosName.Buffer,
                               GpioBank->BankId,
                               GpioBank->InterruptData.Gsiv);

    //
    // Query the currently active/asserting interrupts from the GPIO client
    // driver. This filters interrupts that are masked (in HW) or non-enabled
    // and yet reported asserting. Masked interrupts could still be considered
    // asserting if status clear was delayed (until a future unmask) on those
    // pins.
    //
    // A GPIO client driver could report interrupts that were never enabled
    // from GPIO class extension as asserting. Such pins may have been
    // configured by FW or outside of GPIO class extension control.
    //
    // Note if the device is capable of handling interrupts at DIRQL, then the
    // device should be memory-mapped, in which case, reading the interrupt
    // status register is not expected to fail.
    //

    Status = GpiopUpdateStatusAndMaskInformation(GpioBank,
                                                 &NonEnabledActiveInterrupts,
                                                 &RawStatusRegister);

    if (!NT_SUCCESS(Status)) {
        TraceEvents(GpioExtension->LogHandle,
                    Error,
                    DBG_INTERRUPT,
                    "GpiopServiceInterrupt: "
                    "Failed to query active and enabled interrupt status from "
                    "client! Status = %#x\n",
                    Status);

        goto ServiceInterruptEnd;
    }

    TraceEvents(GpioExtension->LogHandle,
                Info,
                DBG_INTERRUPT,
                "%s: Interrupts to service: Bank = %p, PinMask = 0x%I64x, "
                "RawPinMask = %I64x\n",
                __FUNCTION__,
                GpioBank,
                GpioBank->InterruptData.StatusRegister,
                RawStatusRegister);

    if (NonEnabledActiveInterrupts != 0x0) {
        TraceEvents(GpioExtension->LogHandle,
                    Info,
                    DBG_INTERRUPT,
                    "%s: Non-enabled active interrupts pinmask: 0x%I64x\n",
                    __FUNCTION__,
                    NonEnabledActiveInterrupts);
    }

    //
    // Capture and clear out the interrupts that need to be replayed.
    //

    CapturedReplayRegister =
        InterlockedExchange64(&GpioBank->InterruptData.ReplayRegister, 0);

    if (CapturedReplayRegister != 0x0) {
        TraceEvents(GpioExtension->LogHandle,
                    Info,
                    DBG_INTERRUPT,
                    "%s: ReplayRegister: 0x%I64x\n",
                    __FUNCTION__,
                    CapturedReplayRegister);
    }

    EventWrite_INTERRUPT_PIN_STATE(
        &GpioBank->ActivityId,
        GpioBank->InterruptData.EnableRegister,
        GpioBank->InterruptData.InterruptMask,
        GpioBank->InterruptData.StatusRegister,
        NonEnabledActiveInterrupts,
        CapturedReplayRegister);

    //
    // Loop over all pins that are interrupting and invoke corresponding ISRs
    // for each of them.
    //
    // This starts with the Servicing Pass, then switches to the Replay Pass.
    //


    DebouncePinAsserted = FALSE;
    StatusClearMask = 0x0;
    PassiveGpio = GPIO_IS_PASSIVE_IRQL_ONLY_DEVICE(GpioExtension);
    Flags = GpioExtension->ClientInformation.Flags;
    PinNumber = 0;
    InterruptData = &GpioBank->InterruptData;
    ReplayPass = FALSE;
    WHILE(TRUE) {

        //
        // If in the Servicing Pass, find the next interrupting pin number.
        //

        if (ReplayPass == FALSE) {
            PinNumber = GpiopFindInterruptingPinAndClear(GpioBank);

            //
            // If there are no more pins in the Servicing Pass, switch to
            // the Replay Pass.
            //

            if (PinNumber == INVALID_PIN_NUMBER) {
                ReplayPass = TRUE;
                PrimaryEventType = InterruptEventOnReplayInterrupt;
                EnableRegister = GPIO_READ_ENABLE_REGISTER_VALUE(GpioBank);
                CapturedReplayRegister &= EnableRegister;
            }
        }

        //
        // If in the Replay Pass, find the next queued interrupt.
        //

        if (ReplayPass != FALSE) {

            //
            // If there are no more pins in the Replay Pass, exit the loop.
            //

            if (CapturedReplayRegister == 0) {
                break;

            } else {
                PinNumber = RtlFindLeastSignificantBit(CapturedReplayRegister);
            }
        }

        PinInformation = GpiopGetPinEntry(GpioBank, PinNumber);
        GPIO_ASSERT(PinInformation != NULL);

        //
        // Servicing Pass:
        //   The bit is being processed now so clear it from the replay
        //   register so that it will not be processed again during the replay
        //   pass.
        //
        //   Exception:
        //
        //        Hardware ActiveBoth interrupt (always edge-triggered): The
        //        interrupt being signalled from software (the "replay") could
        //        be for the first edge and the one in the hardware could be
        //        for the opposite edge.
        //
        //   N.B. Emulated ActiveBoth interrupt using level-triggered emulation:
        //        The interrupt should be treated as level-triggered and the
        //        pin should be cleared from the replay register to collapse
        //        servicing and replay pass to single ISR invocation. Otherwise
        //        the pin may get masked twice during interrupt processing
        //        and only get unmasked once post-reconfiguration (and thus
        //        remain masked until disconnected!)
        //
        // Replay Pass:
        //   The bit is being processed now so clear it from the replay
        //   register.
        //

        if ((PinInformation->Polarity != InterruptActiveBoth) ||
            (ReplayPass != FALSE) ||
            (PinInformation->Mode.EmulateActiveBoth == 1)) {

            CapturedReplayRegister &= ~(1ULL << PinNumber);
        }

        //
        // Initialize the controller context to be passed to the debouncing
        // module.
        //

        Size = sizeof(GPIO_INTERRUPT_DEBOUNCE_CONTROLLER_CONTEXT);
        RtlZeroMemory(&ControllerContext, Size);
        ControllerContext.GpioBank = GpioBank;
        ControllerContext.PinNumber = PinNumber;
        ControllerContext.PinInformation = PinInformation;
        ControllerContext.DeferActions = TRUE;

        //
        // Invoke the per-pin servicing routine. This routine will inturn
        // invoke the interrupt state machine on the pin.
        //

        GpiopServiceInterruptOnPin(&ControllerContext,
                                   PrimaryEventType,
                                   InterruptEventNone);

        //
        // Record all deferred actions that need to be performed later.
        //

        if (ControllerContext.ScheduleDebounceTimers != FALSE) {
            ScheduleDebounceTimers = TRUE;
        }

        if (ControllerContext.IsrInvocationDeferred != FALSE) {
            SET_BIT(DeferredEdgeIsrMask, PinNumber);
        }

        if (ControllerContext.ClearStatus != FALSE) {
            StatusClearMask |= ((ULONG64)1 << PinNumber);
        }

        //
        // If a debounce pin asserted, then record the fact. This will be used
        // in determining whether or not to acknowledge the GPIO controller
        // interrupt.
        //

        if (PinInformation->Mode.EmulateDebounce == 1) {
            DebouncePinAsserted = TRUE;
        }

        if (ControllerContext.ServiceActiveBothInterrupts != FALSE) {
            ServiceActiveBothInterrupts = TRUE;
        }
    }

    //
    // If interrupts were masked because the interrupt is going to be serviced
    // in a deferred manner, then those masks requests are pended to be
    // executed lazily. Execute such deferred mask requests now.
    //

    GpiopDeferredMaskRequestHandler(GpioBank);

    //
    // For each of the interrupts that was dealt with above, clear the
    // corresponding bits in the interrupt status register to let the GPIO
    // controller know those interrupts were handled. If the GPIO controller
    // indicates that the status gets auto-cleared on read, then this step is
    // not required.
    //
    // Also include interrupts that reportedly fired but were never enabled.
    // This will avoid interrupt storm due to spurious edge-triggered
    // interrupts. If the interrupt is configured for level, and the line is
    // asserted, the interrupt would fire continuously resulting in an interrupt
    // storm.
    //

    if (((StatusClearMask > 0x0) ||
        (NonEnabledActiveInterrupts > 0x0)) &&
        (Flags.ActiveInterruptsAutoClearOnRead == FALSE)) {

        GPIO_SET_PENDING_STATUS_CLEAR_PINMASK(GpioBank, StatusClearMask);


        GPIO_SET_PENDING_STATUS_CLEAR_PINMASK(GpioBank,
                                              NonEnabledActiveInterrupts);

        GpiopDeferredStatusClearRequestHandler(GpioBank);
    }

    //
    // Claim the GPIO interrupt if at least one of its *enabled and unmasked*
    // pins had asserted. In the case where masked pins or non-enabled pins
    // have asserted, do not claim the interrupt to let the interrupt chain
    // be walked (if shared) as there may be real interrupt asserted on a
    // different bank or device.
    //
    // Additionally, if an emulated debounce pin asserted, then recognize the
    // GPIO controller interrrupt. The status clear operation may be deferred
    // to some future debounce phases (e.g. when debounce timer fires) or may
    // have been committed synchronously in such cases. In either case, the
    // GPIO controller interrupt should still be recognized.
    //

    if ((StatusClearMask > 0x0) || (DebouncePinAsserted != FALSE)) {
        InterruptRecognized = TRUE;

    } else {
        InterruptRecognized = FALSE;
    }

    if (ServiceActiveBothInterrupts != FALSE) {
        GpiopServiceActiveBothInterrupts(GpioBank);
    }

    //
    // Run all the pending edge-triggered interrupt ISRs that were deferred to
    // be invoked post status clear operation.
    //

    GpiopInvokeTargetIsr(GpioBank, DeferredEdgeIsrMask);

    //
    // N.B. Spurious or unclaimed interrupts:
    //
    //      At this point if it was a spurious edge-triggered interrupt then
    //      it was ignored (desired). If it was a spurious level-triggered (a
    //      really severe situation) or it goes unclaimed (likely a
    //      driver/device issue), then then it will be unmasked as soon as this
    //      routine returns. This could result in an interrupt storm which would
    //      need to be fixed. Note for passive-level device interrupts or
    //      off-SoC GPIOs, the interrupt storm will be a silent one.
    //

    //
    // On-SoC GPIO: If debouncing needs to be emulated on any pin, then schedule
    // a DPC to schedule the debounce timers.
    //
    // Off-SoC GPIO: If there are any deferred activities to be performed (incl.
    // scheduling of debounce timers, then perform them now).
    //

    if ((ScheduleDebounceTimers != FALSE) ||
        (GPIO_IS_PASSIVE_IRQL_ONLY_DEVICE(GpioExtension) != FALSE)) {

        GpiopScheduleDeferredInterruptActivities(GpioBank);
    }

    //
    // Clear the interrupt thread ID as the interrupt servicing is done.
    //

ServiceInterruptEnd:
    EventWrite_INTERRUPT_COMPLETE(&GpioBank->ActivityId);
    GpioBank->InterruptThread = NULL;
    return InterruptRecognized;
}

VOID
GpiopServiceInterruptOnPin (
    __in PGPIO_INTERRUPT_DEBOUNCE_CONTROLLER_CONTEXT ControllerContext,
    __in GPIO_INTERRUPT_EVENT_TYPE PrimaryEventType,
    __in GPIO_INTERRUPT_EVENT_TYPE SecondaryEventType
    )

/*++

Routine Description:

    This routine services GPIO interrupts for a specified pin. It calls into
    the interrupt state machine to determine what actions need to be taken
    on the given pin to service its interrupt and performs those actions.

    Entry IRQL:
        1. This routine can be called at D-IRQL or DISPATCH_LEVEL for
           memory-mapped GPIOs and PASSIVE_LEVEL for off-SoC GPIOs.

    Calling contexts:

    1. Synchronously from within the ISR context. Bank interrupt lock
       held for memory-mapped controllers and passive-level bank lock held
       for off-SoC GPIO controllers.

    2. In the context of debounce completion. The Bank interrupt lock
       held for memory-mapped controllers and passive-level bank lock held
       for off-SoC GPIO controllers.

Arguments:

    ControllerContext - Supplies a pointer to the controller context
        providing the GPIO bank, pin number and pin information data.

    PrimaryEventType - Supplies the type of event the interrupt engine is being
        invoked for.

    SecondaryEventType - Supplies the secondary event the interrupt engine is
        being invoked for. This value is only valid if this is due to
        debounce engine notification (i.e. PrimaryEventType =
        InterruptEventOnDebounceEngineNotification).

Return Value:

    None.

--*/

{

    BOOLEAN AcquireInterruptLock;
    GPIO_INTERRUPT_ACTION_TYPE ActionType;
    BOOLEAN ClearStatus;
    PGPIO_DEBOUNCE_PIN_CONTEXT DebounceContext;
    BOOLEAN DebounceStateMachineCompleted;
    NTSTATUS EventStatus;
    GPIO_INTERRUPT_EVENT_TYPE EventType;
    CONTROLLER_ATTRIBUTE_FLAGS Flags;
    PGPIO_BANK_ENTRY GpioBank;
    PDEVICE_EXTENSION GpioExtension;
    BOOLEAN PassiveGpio;
    PGPIO_PIN_INFORMATION_ENTRY PinInformation;
    PIN_NUMBER PinNumber;
    NTSTATUS Status;
    BOOLEAN ValidInterrupt;

    ActionType = InterruptActionNone;
    EventStatus = STATUS_SUCCESS;
    GpioBank = ControllerContext->GpioBank;
    PinInformation = ControllerContext->PinInformation;
    PinNumber = ControllerContext->PinNumber;
    DebounceContext = &PinInformation->DebounceContext;
    GpioExtension = GpioBank->GpioExtension;
    Flags = GpioExtension->ClientInformation.Flags;

    //
    // Validate the IRQL and locking requirements.
    //

    PassiveGpio = GPIO_IS_PASSIVE_IRQL_ONLY_DEVICE(GpioExtension);
    if (PassiveGpio != FALSE) {

        GPIO_ASSERT((KeGetCurrentIrql() == PASSIVE_LEVEL) &&
                    (GPIO_BANK_PASSIVE_LOCK_OWNER(GpioBank) != FALSE));

    } else {

        GPIO_ASSERT((KeGetCurrentIrql() >= DISPATCH_LEVEL) &&
                    (GPIO_BANK_INTERRUPT_LOCK_OWNER(GpioBank) != FALSE));
    }

    //
    // Set the incoming secondary event if necessary.
    //

    if (PrimaryEventType == InterruptEventOnDebounceEngineNotification) {
        EventType = SecondaryEventType;

    } else {
        EventType = InterruptEventNone;
    }

    //
    // The interrupt state is protected by the D-IRQL bank interrupt lock
    // in case of memory-mapped GPIOs and DPC-level bank interrupt lock in
    // case of off-SoC GPIOs.
    //

    if (PassiveGpio != FALSE) {
        AcquireInterruptLock = TRUE;

    } else {
        AcquireInterruptLock = FALSE;
    }

    //
    // Acquire the interrupt lock if needed.
    //

    if (AcquireInterruptLock != FALSE) {
        GPIO_ACQUIRE_BANK_INTERRUPT_LOCK(GpioBank);
    }

    do {

        Status = GpiopServiceInterruptStateMachine(
                     GpioBank,
                     PinInformation,
                     PinNumber,
                     PrimaryEventType,
                     EventType,
                     EventStatus,
                     &ActionType);

        switch (ActionType) {

        case InterruptActionNone:

            // NOTHING;

            break;

        case InterruptActionCheckValidInterrupt:

            //
            // OK to hold the interrupt lock across the operation.
            //

            ValidInterrupt = GpiopCheckValidInterrupt(GpioBank,
                                                      PinInformation,
                                                      PinNumber,
                                                      PrimaryEventType,
                                                      &ClearStatus);

            //
            // N.B. This request is only ever expected to be originating in
            //      the context of an incoming.
            //

            if (ClearStatus != FALSE) {
                if (ControllerContext->DeferActions != FALSE) {
                    ControllerContext->ClearStatus = TRUE;

                } else if (Flags.ActiveInterruptsAutoClearOnRead == FALSE) {


                    GPIO_ASSERT(FALSE);

                    GPIO_SET_PENDING_STATUS_CLEAR_PINMASK(
                        GpioBank,
                        GPIO_PIN_NUMBER_TO_PIN_MASK(PinNumber));

                    GpiopDeferredStatusClearRequestHandler(GpioBank);
                }
            }

            if (ValidInterrupt != FALSE) {
                EventStatus = STATUS_SUCCESS;
            } else {
                EventStatus = STATUS_UNSUCCESSFUL;
            }

            EventType = InterruptEventRequestCompletion;
            break;

        case InterruptActionDebounceInterrupt:

            //
            // Memory-mapped GPIOs: The debounce engine expects to be invoked
            // with interrupt lock acquired by the caller in the "OnInterrupt"
            // case. OK to hold the interrupt lock across the operation.
            //
            // Off-SoC GPIOs: The debounce engine expects to be invoked with
            // interrupt lock *not* acquired by the caller in the "OnInterrupt".
            // Need to drop the interrupt lock across the operation.
            //

            if (PassiveGpio != FALSE) {

                GPIO_ASSERT(AcquireInterruptLock != FALSE);

                GPIO_RELEASE_BANK_INTERRUPT_LOCK(GpioBank);
            }

            EventStatus = GpiopDebounceOnInterrupt(DebounceContext,
                                                   ControllerContext,
                                                   &DebounceStateMachineCompleted);

            //
            // For simplicity, don't call GpiopDebounceSignalStateMachineCompletion()
            // since the current IRQL could be too high (DIRQL).
            //
            // Currently, that routine's effect is only observed for masked pins
            // and we could not have reached here if the current pin was
            // masked.
            //

            UNREFERENCED_PARAMETER(DebounceStateMachineCompleted);

            //
            // Reacquire the interrupt lock prior to entering the state machine
            // again.
            //

            if (PassiveGpio != FALSE) {

                GPIO_ACQUIRE_BANK_INTERRUPT_LOCK(GpioBank);
            }

            if (ControllerContext->DeferActions == FALSE) {
                GpiopScheduleDeferredInterruptActivities(GpioBank);
            }

            EventType = InterruptEventRequestCompletion;
            break;

        case InterruptActionMaskInterrupt:

            //
            // OK to hold the interrupt lock across the operation.
            //

            if (ControllerContext->DeferActions != FALSE) {
                GpiopIncrementMaskCount(GpioBank, PinInformation, PinNumber);

            } else {
                GpiopMaskInterrupt(GpioExtension,
                                   GPIO_REQUEST_FLAG_NONE,
                                   PinInformation->Virq);
            }

            EventStatus = STATUS_SUCCESS;
            EventType = InterruptEventRequestCompletion;
            break;

        case InterruptActionReconfigureActiveBothInterrupt:

            GPIO_SET_PIN_PENDING_ACTIVE_BOTH(GpioBank, PinNumber);
            if (ControllerContext->DeferActions != FALSE) {
                ControllerContext->ServiceActiveBothInterrupts = TRUE;

            } else {

                //
                // Memory-mapped GPIOs: The OK to hold the interrupt lock across
                // the active both reconfiguration operation.
                //
                // Off-SoC GPIOs: Need to drop the interrupt lock across the
                // active both reconfiguration to ensure the servicing routine
                // is called at correct IRQL.
                //

                if (PassiveGpio != FALSE) {

                    GPIO_ASSERT(AcquireInterruptLock != FALSE);

                    GPIO_RELEASE_BANK_INTERRUPT_LOCK(GpioBank);
                }

                GpiopServiceActiveBothInterrupts(GpioBank);

                //
                // Reacquire the interrupt lock prior to entering the interrupt
                // state machine again.
                //

                if (PassiveGpio != FALSE) {
                    GPIO_ACQUIRE_BANK_INTERRUPT_LOCK(GpioBank);
                }
            }

            EventType = InterruptEventRequestCompletion;
            break;


            //
            // Invoke the target ISR unless deferred action is requested.
            //
            // N.B. The deferred action behavior only applies to edge-triggered
            //      interrupts in this case. Thus level-triggered interrupts
            //      are always invoked directly in all cases.
            //

        case InterruptActionInvokeTargetIsr:

            if ((PinInformation->InterruptMode == LevelSensitive) ||
                (ControllerContext->DeferActions == FALSE)) {

                //
                // Memory-mapped GPIOs: The OK to hold the interrupt lock across
                //  the ISR invocation.
                //
                // Off-SoC GPIOs: Need to drop the interrupt lock across the
                // operation otherwise target ISR may not be called at the
                // correct IRQL.
                //

                if (PassiveGpio != FALSE) {

                    GPIO_ASSERT(AcquireInterruptLock != FALSE);

                    GPIO_RELEASE_BANK_INTERRUPT_LOCK(GpioBank);
                }

                GpiopInvokeTargetIsr(GpioBank,
                                     GPIO_PIN_NUMBER_TO_PIN_MASK(PinNumber));


                //
                // Reacquire the interrupt lock prior to entering the interrupt
                // state machine again.
                //

                if (PassiveGpio != FALSE) {
                    GPIO_ACQUIRE_BANK_INTERRUPT_LOCK(GpioBank);
                }

            } else {
                ControllerContext->IsrInvocationDeferred = TRUE;
            }

            EventStatus = STATUS_SUCCESS;
            EventType = InterruptEventRequestCompletion;
            break;

        case InterruptActionClearStatusRegister:

            //
            // OK to hold the interrupt lock across the operation.
            //

            //
            // Set the pending status clear for this pin. This is not needed
            // if interrupts are auto-cleared on read.
            //
            // N.B. Even if defer flag is set to TRUE, this request could
            //      be committed to the hardware on the next unmask request
            //      (that takes the count down to zero).
            //

            if  (Flags.ActiveInterruptsAutoClearOnRead == FALSE) {
                GPIO_SET_PENDING_STATUS_CLEAR_PINMASK(
                    GpioBank,
                    GPIO_PIN_NUMBER_TO_PIN_MASK(PinNumber));
            }

            //
            // If Defer is set to TRUE, record the fact that a status clear
            // operation was requested on the pin. Note this needs to be done
            // even if the controller has auto-cleared on read flag set.
            // This is ensure that interrupt will be acknowledged in the ISR.
            //
            // The one exception is emulated debounce pins. For such pins, the
            // status clear is effected immediately to ensure that it happens
            // before any potential unmask operation that may originate from
            // the debounce engine.
            //
            //      N.B. The status clear flag is not updated to ensure that
            //           the ISR doesn't accidentally acknowledge an interrupt
            //           after it has been unmasked. The main interrupt service
            //           routine will still correctly acknowledge the GPIO
            //           controller interrupt in this case.
            //

            if ((ControllerContext->DeferActions != FALSE) &&
                (PinInformation->Mode.EmulateDebounce == 0)) {

                ControllerContext->ClearStatus = TRUE;

            //
            // For emulated debounce pins and non-deferred cases, issue a
            // synchronous status clear operation.
            //

            } else {

                //
                // Memory-mapped GPIOs: The OK to hold the interrupt lock across
                // the status clear operation.
                //
                // Off-SoC GPIOs: Need to drop the interrupt lock across the
                // status clear operation to ensure the routine is called at
                // correct IRQL.
                //

                if (PassiveGpio != FALSE) {

                    GPIO_ASSERT(AcquireInterruptLock != FALSE);

                    GPIO_RELEASE_BANK_INTERRUPT_LOCK(GpioBank);
                }

                GpiopDeferredStatusClearRequestHandler(GpioBank);

                //
                // Reacquire the interrupt lock prior to entering the interrupt
                // state machine again.
                //

                if (PassiveGpio != FALSE) {
                    GPIO_ACQUIRE_BANK_INTERRUPT_LOCK(GpioBank);
                }
            }

            EventStatus = STATUS_SUCCESS;
            EventType = InterruptEventRequestCompletion;
            break;

        default:

            GPIO_ASSERT(FALSE);

            Status = STATUS_UNSUCCESSFUL;
        }

    } while (Status == STATUS_MORE_PROCESSING_REQUIRED);

    //
    // Release the interrupt lock if acquired.
    //

    if (AcquireInterruptLock != FALSE) {
        GPIO_RELEASE_BANK_INTERRUPT_LOCK(GpioBank);
    }

    return;
}

NTSTATUS
GpiopServiceInterruptStateMachine (
    __in PGPIO_BANK_ENTRY GpioBank,
    __in PGPIO_PIN_INFORMATION_ENTRY PinInformation,
    __in PIN_NUMBER PinNumber,
    __in GPIO_INTERRUPT_EVENT_TYPE PrimaryEventType,
    __in GPIO_INTERRUPT_EVENT_TYPE EventType,
    __in NTSTATUS EventStatus,
    __out PGPIO_INTERRUPT_ACTION_TYPE OutputActionType
    )

/*++

Routine Description:

    This routine implements the interrupt state machine for a given pin. It
    takes the input events and the current state for the pin and determines
    what the next state for the pin should be. The state machine progresses
    until a callback state is reached (i.e. a state where an action needs to be
    taken) or a termination state is reached.

    Entry IRQL:
        1. This routine can be called at D-IRQL for memory-mapped GPIOs and
           DISPATCH_LEVEL for off-SoC GPIOs.

    Calling contexts:

    1. Synchronously from within the ISR context. Bank interrupt lock
       held for both on-SoC and off-SoC GPIO controllers.

    2. Asynchronously from within the debounce completion. Bank interrupt lock
       held for both on-SoC and off-SoC GPIO controllers.

Arguments:

    GpioBank - Supplies a pointer to the GPIO bank.

    PinInformation - Supplies a pointer to the pin information for the specified
        pin.

    PinNumber - Supplies the bank relative pin number.

    PrimaryEventType - Supplies the type of event the debounce engine is being
        invoked for.

    EventType - Supplies the type of secondary event the interrupt state machine
        is being invoked for.

    EventStatus - Supplies the status associated with the secondary event.

    OutputActionType - Supplies a pointer that receives the output action to
        be performed.

Return Value:

    None.

--*/

{

    GPIO_INTERRUPT_ACTION_TYPE ActionType;
    PGPIO_DEBOUNCE_PIN_CONTEXT DebounceContext;
    ULONG64 DebounceMask;
    GPIO_DEBOUNCE_MODEL DebounceModel;
    PDEVICE_EXTENSION GpioExtension;
    GPIO_INTERRUPT_STATE NextState;
    NTSTATUS ReturnStatus;
    NTSTATUS Status;

    GPIO_ASSERT(KeGetCurrentIrql() >= DISPATCH_LEVEL);

    ActionType = InterruptActionNone;
    DebounceContext = &PinInformation->DebounceContext;
    GpioExtension = GpioBank->GpioExtension;
    NextState = PinInformation->State;
    ReturnStatus = STATUS_SUCCESS;
    Status = STATUS_MORE_PROCESSING_REQUIRED;

    //
    // The primary and secondary event types supplied must be valid. These are
    // supplied from within the interrupt module and thus trusted.
    //

    GPIO_ASSERT((PrimaryEventType >= InterruptEventOnInterrupt) &&
                (PrimaryEventType < InterruptEventMaximum));

    GPIO_ASSERT((EventType >= InterruptEventNone) &&
                (EventType < InterruptEventOnInterrupt));

    //
    // Loop through the state machine until a "callback" state is reached or
    // a termination state is reached. A callback state is one where the
    // GPIO class extension needs to be called back for some operation.
    //

    do {

        switch(NextState) {

        case InterruptStateInvalid:

            GPIO_ASSERT(FALSE);
            ReturnStatus = STATUS_UNSUCCESSFUL;
            Status = STATUS_UNSUCCESSFUL;
            break;

            //
            // On interrupt event:
            //   - The initial interrupt has arrived. The next step is check if
            //     the interrupt is valid and should be serviced or ignored.
            //
            // On replay event:
            //   - The next step is to check if interrupt is valid or not.
            //     The interrupt would be considered invalid if the pin being
            //     replayed is currently masked.
            //
            //     N.B. The pin may have gotten masked on the prior servicing
            //          (i.e. OnInterrupt) path.
            //
            // On debounce engine notification:
            // - The debounce engine may call back into the interrupt engine
            //   to notify the incoming interrupt was deemed to be valid, a
            //   noise interrupt or when debounce engine is about to complete.
            //   The next step is to process the debounce notification.
            //

        case InterruptStateOnInitialInterrupt:

            if (PrimaryEventType ==
                        InterruptEventOnDebounceEngineNotification) {

                NextState = InterruptStateProcessDebounceNotification;

            } else {
                NextState = InterruptStateCheckValidInterrupt;
            }

            break;

            //
            // Check if the interrupt is valid or not. Only valid interrupts
            // should be serviced. All invalid interrupts will be ignored
            // (they may get acknowledged).
            //

        case InterruptStateCheckValidInterrupt:

            GPIO_ASSERT((PrimaryEventType == InterruptEventOnInterrupt) ||
                        (PrimaryEventType == InterruptEventOnReplayInterrupt));

            if (EventType == InterruptEventNone) {
                ActionType = InterruptActionCheckValidInterrupt;
                ReturnStatus = STATUS_MORE_PROCESSING_REQUIRED;
                Status = STATUS_SUCCESS;

            } else if (EventType == InterruptEventRequestCompletion) {
                EventType = InterruptEventNone;

                //
                // If the interupt is valid, then:
                //  1. Interupt fast path: For non-emulated debounced and
                //     non-emulated-ActiveBoth and non-ACPI-events, proceed
                //     directly to invoking the ISR.
                //
                //  2. For replay interrupts: Proceed to servicing ActiveBoth
                //     interrupts. No debouncing is necessary.
                //         N.B. It is assumed that the caller (viz. the PEP)
                //              has already handled debouncing or pin has
                //              already been debounced due to the natural delay
                //              in waking from platform deep-idle state.
                //
                //  3. In all other cases, proceed to debounce the interrupt.
                //
                // If the interrupt is not valid, then ignore the interrupt.
                //
                //

                if (EventStatus == STATUS_SUCCESS) {
                    if (PrimaryEventType == InterruptEventOnReplayInterrupt) {
                        NextState = InterruptStateProcessActiveBoth;

                    } else if ((PinInformation->Mode.EmulateDebounce == 0) &&
                               (PinInformation->Mode.EmulateActiveBoth == 0) &&
                               (PinInformation->Mode.AcpiEvent == 0)) {

                        NextState = InterruptStateProcessLevelEdgeInterrupt;

                    } else {
                        NextState = InterruptStateDebounceInterrupt;
                    }

                } else {
                    NextState = InterruptStateClearStatusOnInvalidInterrupt;
                }
            }

            break;

            //
            // If the pin is being emulated debounced, then the next step is
            // to debounce the interrupt for hardware generated interrupts.
            //

        case InterruptStateDebounceInterrupt:

            GPIO_ASSERT (PrimaryEventType !=
                        InterruptEventOnDebounceEngineNotification);

            if ((PinInformation->Mode.EmulateDebounce == 1) &&
                (PrimaryEventType == InterruptEventOnInterrupt)) {

                //
                // In all cases the next steps to be taken post debounce
                // completion (for e.g. reconfiguring interrupt in
                // legacy or PossiblyEnhanced cases) is done as a result of
                // notifications from debounce engine. Thus no addition work is
                // required here.
                //

                ActionType = InterruptActionDebounceInterrupt;
                NextState = InterruptStateOnInitialInterrupt;
                ReturnStatus = STATUS_SUCCESS;
                Status = STATUS_SUCCESS;

            //
            // For no debounce case:
            // Go to the next step, which is processing of ActiveBoth
            // interrupts.
            //

            } else {
                NextState = InterruptStateProcessActiveBoth;
            }

            break;

            //
            // If ActiveBoth is being emulated on this pin, then reconfigure the
            // pin.
            // For emulated debounce pins:
            // - Legacy model: Perform the active both reconfiguration.
            //
            // - Enhanced model: It is assumed the reconfiguration will happen
            //      (inside of the debounce engine) once the interrupt is deemed
            //      genuine. This is true for only regular interrupts. For
            //      ActiveBoth interrupts being replayed, the ActiveBoth
            //      reconfiguration needs to be done here.
            //
            // - Possibly enhanced model: Treated same as legacy model in this
            //      case.
            //
            // For non-ActiveBoth interrupts, the next step depends on
            // whether this is a notification from debounce engine or a regular
            // interrupt.
            //  - Debounce engine notification: Go back to the initial state to
            //    process more notifications or the next interrupt.
            //
            //  - For regular interrupts: Next step is to process ACPI events.
            //

        case InterruptStateProcessActiveBoth:

            DebounceModel = DebounceContext->Flags.DebounceModel;

            GPIO_ASSERT((PinInformation->Mode.EmulateDebounce == 0) ||
                        (DebounceModel != DebounceModelEnhanced) ||
                        (PrimaryEventType == InterruptEventOnReplayInterrupt));

            if ((PinInformation->Polarity == InterruptActiveBoth) &&
                (PinInformation->Mode.EmulateActiveBoth == 1)) {

                NextState = InterruptStateMaskInterruptActiveBoth;

            } else if (PrimaryEventType ==
                            InterruptEventOnDebounceEngineNotification) {

                NextState = InterruptStateOnInitialInterrupt;
                ActionType = InterruptActionNone;
                ReturnStatus = STATUS_SUCCESS;
                Status = STATUS_SUCCESS;

            } else {
                NextState = InterruptStateProcessAcpiEvent;
            }

            break;

            //
            // Process interrupts on GPIO pins configured for ACPI eventing.
            // Note if debouncing is being emulated on this pin, and the
            // processing is resuming post interrupt debounce completion, then
            // the ISR (ACPI method) has already been invoked for that pin.
            //
            // Invoking the ISR for ACPI event pins will automatically mask the
            // interrupt. Thus in all cases, proceed to the next step which is
            // to process normal level/edge/ACPI events.
            //

        case InterruptStateProcessAcpiEvent:
            NextState = InterruptStateProcessLevelEdgeInterrupt;
            break;

        case InterruptStateMaskInterruptActiveBoth:

            if (EventType == InterruptEventNone) {
                ActionType = InterruptActionMaskInterrupt;
                ReturnStatus = STATUS_MORE_PROCESSING_REQUIRED;
                Status = STATUS_SUCCESS;

            } else if (EventType == InterruptEventRequestCompletion) {
                EventType = InterruptEventNone;
                NextState = InterruptStateReconfigureActiveBothInterrupt;
            }

            break;

            //
            // Reconfigure the interrupt. The next step is to either go back
            // to the initial state for debounce engine notifications, or
            // process ACPI events for non-emulated-debounced interrupts.
            //
            // N.B. Reconfiguring ActiveBoth interrupts involves an implicit
            //      unmask on request completion. Thus they are not explicitly
            //      unmasked here.
            //

        case InterruptStateReconfigureActiveBothInterrupt:

            if (EventType == InterruptEventNone) {
                ActionType = InterruptActionReconfigureActiveBothInterrupt;
                ReturnStatus = STATUS_MORE_PROCESSING_REQUIRED;
                Status = STATUS_SUCCESS;

            } else if (EventType == InterruptEventRequestCompletion) {
                EventType = InterruptEventNone;

                if (PrimaryEventType ==
                            InterruptEventOnDebounceEngineNotification) {

                    NextState = InterruptStateOnInitialInterrupt;
                    ReturnStatus = STATUS_SUCCESS;
                    Status = STATUS_SUCCESS;

                } else {
                    NextState = InterruptStateProcessAcpiEvent;
                }
            }

            break;

            //
            // Processing of level, edge and ACPI event case. For non-emulated
            // debounce cases, service the interrupt by invoking the target
            // ISR in both interrupt and replay cases. Proceed to the invoke
            // ISR step.
            //
            // If the pin has debouncing emulated, then:
            //   - For hardware generated interrupts: The target ISR has
            //         already been invoked in legacy, possibly legacy and
            //         enhanced debouncing models. Thus it doesn't need to
            //         invoked here.
            //
            //   - For replay interrupts: The pin doesn't get debounced in
            //         this case and thus the the target ISR needs to be
            //         invoked just like in the non-debounced case.
            //

        case InterruptStateProcessLevelEdgeInterrupt:

            if ((PinInformation->Mode.EmulateDebounce == 1) &&
                (PrimaryEventType != InterruptEventOnReplayInterrupt)) {

                NextState = InterruptStateOnInitialInterrupt;
                ReturnStatus = STATUS_SUCCESS;
                Status = STATUS_SUCCESS;

            } else {
                NextState = InterruptStateInvokeTargetIsr;
            }

            break;

        //
        // Request the target ISR to be invoked. Once that is completed,
        // proceed to clearing the status register.
        //

        case InterruptStateInvokeTargetIsr:

            GPIO_ASSERT(PrimaryEventType !=
                                InterruptEventOnDebounceEngineNotification);

            if (EventType == InterruptEventNone) {
                ActionType = InterruptActionInvokeTargetIsr;
                ReturnStatus = STATUS_MORE_PROCESSING_REQUIRED;
                Status = STATUS_SUCCESS;

            } else if (EventType == InterruptEventRequestCompletion) {
                EventType = InterruptEventNone;
                NextState = InterruptStateClearStatusRegister;
            }

            break;

        //
        // Request the status register to be cleared. This needs to be only
        // done for hardware generated interrupts (and not replay).
        //
        // Otherwise, reset back to the initial state and wait for the next
        // interrupt (or notifications from debounce engine) to arrive.
        //

        case InterruptStateClearStatusRegister:

            if ((PrimaryEventType == InterruptEventOnInterrupt) ||
                (PrimaryEventType ==
                            InterruptEventOnDebounceEngineNotification)) {

                ActionType = InterruptActionClearStatusRegister;
            }

            NextState = InterruptStateOnInitialInterrupt;
            ReturnStatus = STATUS_SUCCESS;
            Status = STATUS_SUCCESS;
            break;

        //
        // Request the status register to be cleared for invalid interrupts.
        // This needs to be only done for hardware generated interrupts.
        //

        case InterruptStateClearStatusOnInvalidInterrupt:

            GPIO_ASSERT(PrimaryEventType !=
                                InterruptEventOnDebounceEngineNotification);

            if (PrimaryEventType == InterruptEventOnInterrupt) {
                DebounceMask = GPIO_READ_ACTIVE_DEBOUNCE_MASK_VALUE(GpioBank);
                if ((PinInformation->InterruptMode == LevelSensitive) ||
                    (CHECK_BIT_SET(DebounceMask, PinNumber) != 0)) {

                    ActionType = InterruptActionClearStatusRegister;
                }
            }

            NextState = InterruptStateOnInitialInterrupt;
            ReturnStatus = STATUS_SUCCESS;
            Status = STATUS_SUCCESS;
            break;

        //
        // Process debounce engine notification. The debounce notifies when the
        // incoming interrupt was deemed to be valid or noise, and when
        // when debounce engine is about to complete. The next step depends
        // on the type of notification.
        //
        // Valid interrupt: This implies the interrupt was deemed valid and
        //      the target ISR was invoked.
        //      - For legacy and PossiblyEnhanced debounce model cases, process
        //        reconfiguration of emulated-ActiveBoth interrupts.
        //
        //      - For enhanced model, assume that the reconfiguration was
        //        already done by the debounce engine (for noise detection).
        //
        // Debounce completion: Acknowledge the interrupt (i.e. clear the
        //      status register) for debounce models.
        //
        //      N.B. The primary reason why interrupt is acknowledged here
        //           is that if the processing is being done in the context
        //           of an incoming interrupt, the main interrupt service
        //           routine needs to be told to ensure that it acknowledges
        //           the GPIO controller interrupt. Otherwise it will assume
        //           that the interrupt was not meant for this controller.
        //
        //           There are also cases when a status clear should not be
        //           issued to the hardware. It should not be done if the
        //           controller has set auto-status-clear flag.
        //
        // Noise interrupt: No processing required. This can only arrive for
        //      enhanced debounce model cases. Assume that the reconfiguration
        //      back to original polarity is done by the debounce engine.
        //      Thus no further processing is required here.
        //
        // Note in all cases, after finishing through the activities, the state
        // machine should reset back to InterruptStateOnInitialInterrupt to
        // receive and process more notifications from debounce engine.
        //

        case InterruptStateProcessDebounceNotification:

            GPIO_ASSERT(PrimaryEventType ==
                            InterruptEventOnDebounceEngineNotification);

            if (EventType == InterruptEventReportDebounceCompletion) {
                NextState = InterruptStateClearStatusRegister;

            } else if (EventType == InterruptEventReportValidInterrupt) {
                DebounceModel = DebounceContext->Flags.DebounceModel;
                if (DebounceModel != DebounceModelEnhanced) {
                    NextState = InterruptStateProcessActiveBoth;

                } else {
                    NextState = InterruptStateOnInitialInterrupt;
                    ReturnStatus = STATUS_SUCCESS;
                    Status = STATUS_SUCCESS;
                }

            } else {

                GPIO_ASSERT(EventType == InterruptEventReportSpuriousInterrupt);

                NextState = InterruptStateOnInitialInterrupt;
                ReturnStatus = STATUS_SUCCESS;
                Status = STATUS_SUCCESS;
            }

            //
            // Other states may depend on event type being none initially, so
            // clear it.
            //

            EventType = InterruptEventNone;
            break;

        default:

            GPIO_ASSERT(FALSE);

            Status = STATUS_UNSUCCESSFUL;
            ReturnStatus = STATUS_UNSUCCESSFUL;
        }

        GPIO_INTERRUPT_SAVE_PREVIOUS_STATE(PinInformation);

        PinInformation->State = NextState;

    } while (Status == STATUS_MORE_PROCESSING_REQUIRED);

    GPIO_ASSERT((Status != STATUS_MORE_PROCESSING_REQUIRED) ||
                (ActionType != InterruptActionNone));

    *OutputActionType = ActionType;
    return ReturnStatus;
}

__drv_requiresIRQL(PASSIVE_LEVEL)
VOID
GpiopInitializeInterruptState (
    __in PGPIO_PIN_INFORMATION_ENTRY PinInformation
    )

/*++

Routine Description:

    This routine initializes the state used by the interrupt state machine.
    It is called when an interrupt is being connected (or re-connected) or
    being disconnected.

Arguments:

    PinInformation - Supplies a pointer to the pin information for the specified
        pin.
Return Value:

    None.

--*/

{

    PinInformation->State = InterruptStateOnInitialInterrupt;
    return;
}

BOOLEAN
GpiopCheckValidInterrupt (
    __in PGPIO_BANK_ENTRY GpioBank,
    __in PGPIO_PIN_INFORMATION_ENTRY PinInformation,
    __in PIN_NUMBER PinNumber,
    __in GPIO_INTERRUPT_EVENT_TYPE PrimaryEventType,
    __out PBOOLEAN ClearStatus
    )

/*++

Routine Description:

    This routine determines if the newly arrived interrupt is valid for the
    given pin or not. Interrupts may arrive on pins that are logically masked
    but are yet to be committed to the hardware. Likewise, interrupts may
    also arrive on pins that are pending a disconnect.

    This routine assumes that it is not invoked on replay pass (i.e. invoked
    only for real interrupts).

    Entry IRQL:
        1. This routine can be called at D-IRQL for memory-mapped GPIOs and
           DISPATCH_LEVEL for off-SoC GPIOs.

    Calling contexts:

    1. Invoked directly from within the interrupt state machine. This routine
       assumes that the bank interrupt lock held for both on-SoC and off-SoC
       GPIO controllers.

Arguments:

    GpioBank - Supplies a pointer to the GPIO bank.

    PinInformation - Supplies a pointer to the pin information for the specified
        pin.

    PinNumber - Supplies the bank relative pin number.

    PrimaryEventType - Supplies the type of event the interrupt engine is being
        invoked for.

    ClearStatus - Supplies a pointer that receives whether the interrupt needs
        to be acknowledged or not (i.e. whether status register needs to be
        cleared or not).

Return Value:

    TRUE if the interrupt is valid. FALSE otherwise.

--*/

{

    ULONG64 DebounceMask;
    LONG MaskCount;
    ULONG64 PendingDisconnect;
    ULONG64 PendingInterruptMask;
    ULONG64 PinMaskValue;
    BOOLEAN ValidInterrupt;

    PendingDisconnect = GPIO_READ_PENDING_DISCONNECT_VALUE(GpioBank);
    PinMaskValue = GPIO_PIN_NUMBER_TO_PIN_MASK(PinNumber);
    PendingInterruptMask = GPIO_READ_PENDING_MASK_REGISTER_VALUE(GpioBank);

    //
    // Interrupts should only originate from unmasked interrupt pins or
    // pins for which interrupt mask or disconnect is still pending.
    //

    MaskCount =
        InterlockedCompareExchange(&PinInformation->InterruptMaskCount,
                                   0,
                                   0);

    GPIO_ASSERT((PrimaryEventType == InterruptEventOnReplayInterrupt) ||
                ((PinInformation->Mode.Interrupt == 1) &&
                 ((MaskCount == 0) ||
                   ((PendingInterruptMask & PinMaskValue) != 0) ||
                    ((PendingDisconnect & PinMaskValue) != 0))));

    //
    // Check if this is an interrupt on a pin for which an interrupt mask
    // request or disconnect is still pending. If so, then swallow the
    // interrupt (disconnect case) or delay playback of the interrupt (mask
    // case). In the mask case, the interrupt will eventually be delivered
    // when the pin is unmasked.
    //
    // N.B.:
    //
    // 1. The interrupt may have been temporarily masked due to the pin
    //    being debounced (and not necessarily as a result of a mask
    //    operation).
    //
    // 2. The pending mask register may be temporarily zeroed while
    //    calling into the GPIO client driver. The interrupt is still not
    //    yet masked. Hence the interrupt mask count is used, which should
    //    be accurate.
    //
    // 3. For edge-triggered interrupts, the status register is neither
    //    cleared nor is marked as deferred clear *unless* it is being
    //    actively debounced. For pins not being debounced, this is done
    //    intentionally to cause an interrupt to fire when the pin is
    //    eventually unmasked (to avoid losing any device interrupts). For
    //    debounced pins, all intervening interrupts are to be ignored.
    //

    *ClearStatus = FALSE;
    ValidInterrupt = TRUE;
    if ((MaskCount > 0) || ((PendingDisconnect & PinMaskValue) != 0))  {
        ValidInterrupt = FALSE;
        if (PrimaryEventType == InterruptEventOnInterrupt) {
            DebounceMask = GPIO_READ_ACTIVE_DEBOUNCE_MASK_VALUE(GpioBank);
            if ((PinInformation->InterruptMode == LevelSensitive) ||
                ((DebounceMask & ((ULONG64)1 << PinNumber)) != 0)) {

                 *ClearStatus = TRUE;
            }
        }
    }

    return ValidInterrupt;
}

NTSTATUS
GpiopUpdateStatusAndMaskInformation (
    __in PGPIO_BANK_ENTRY GpioBank,
    __out PULONG64 NonEnabledActiveInterrupts,
    __out PULONG64 RawStatusRegister
    )

/*++

Routine Description:

    This routine queries the currently active/asserting interrupts from the
    client driver and updates the information within the GPIO extension.

    N.B. This routine assumes that the caller has held appropriate locks to
         prevent status and mask registers within the device extension from
         being simultaneously written by other threads.

Arguments:

    GpioBank - Supplies a pointer to the GPIO bank.

    NonEnabledActiveInterrupts - Supplies a pointer that receives the set of
        interrupts that are not enabled yet but are interrupting.

Return Value:

    NT status code.

--*/

{

    ULONG64 EnableRegister;
    PGPIO_INTERRUPT_DATA InterruptData;
    ULONG64 MaskedInterrupts;
    ULONG64 PendingInterruptMask;
    NTSTATUS Status;

    //
    // Verify that the GPIO class extension and the client driver agree on
    // which interrupts are active.
    //

    GpiopCheckEnabledInterrupts(GpioBank);

    //
    // Query the client driver for which of the enabled interrupts are active.
    //

    Status = GpioClnInvokeQueryActiveInterrupts(GpioBank);
    if (!NT_SUCCESS(Status)) {
        TraceEvents(GpioBank->GpioExtension->LogHandle,
                    Error,
                    DBG_INTERRUPT,
                    "%s: Failed to query active interrupts from client driver! "
                    "Status = %#x\n",
                    __FUNCTION__,
                    Status);

        goto UpdateStatusAndMaskInformationEnd;
    }

    //
    // Mask off active interrupts that are not currently enabled.
    //

    InterruptData = &GpioBank->InterruptData;
    *RawStatusRegister = InterruptData->StatusRegister;
    EnableRegister = GPIO_READ_ENABLE_REGISTER_VALUE(GpioBank);
    *NonEnabledActiveInterrupts =
        InterruptData->StatusRegister & ~EnableRegister;

    InterruptData->StatusRegister &= EnableRegister;

    //
    // Mask off active interrupts that have been already masked and committed
    // to HW (i.e., not pending). Such interrupts may be reported as actively
    // asserting as their status clears may have been delayed until a future
    // unmask (in the GPIO_REQUEST_FLAG_SERVICING_DEFERRED case).
    //
    // N.B. Retain interrupts that have their mask operation still pended.
    //      Such interrupts may have failed mask operation and would need to be
    //      masked again.
    //

    PendingInterruptMask = GPIO_READ_PENDING_MASK_REGISTER_VALUE(GpioBank);
    MaskedInterrupts =
        InterruptData->InterruptMask & ~PendingInterruptMask;

    InterruptData->StatusRegister &= ~MaskedInterrupts;

UpdateStatusAndMaskInformationEnd:
    return Status;
}

//
// ----------------------------------------- Callbacks from the debounce module
//

NTSTATUS
GpiopDebounceMaskInterruptCallback (
    __in PGPIO_DEBOUNCE_PIN_CONTEXT DebouncePinContext,
    __in PGPIO_INTERRUPT_DEBOUNCE_CONTROLLER_CONTEXT ControllerContext
    )

/*++

Routine Description:

    This routine masks the specified debounce pin. This is a callback from
    the debounce state machine.

    Entry IRQL:
        1. This routine can be called at D-IRQL for memory-mapped GPIOs and
           PASSIVE_LEVEL for off-SoC GPIOs.

    Calling contexts:
        1. Synchronously from within the debounce state machine with the bank
           interrupt lock held for on-SoC GPIOs only.

        2. The passive-level bank interrupt lock is held for off-SoC GPIO
           controllers.

Arguments:

    DebouncePinContext - Supplies a pointer to per-pin debounce context.

    ControllerContext - Supplies a pointer to the controller context
        containing the bank and pin information.

Return Value:

    STATUS_SUCCESS always.

--*/

{

    PGPIO_BANK_ENTRY GpioBank;
    PGPIO_PIN_INFORMATION_ENTRY PinInformation;
    PIN_NUMBER PinNumber;

    GpioBank = ControllerContext->GpioBank;
    PinInformation = CONTAINING_RECORD(DebouncePinContext,
                                       GPIO_PIN_INFORMATION_ENTRY,
                                       DebounceContext);

    PinNumber = ControllerContext->PinNumber;

    //
    // Mask the pin until the debounce timer fires. If the action is deferred,
    // then only the mask count is updated here. The actual mask operation will
    // happen in a deferred manner in the actual ISR routine.
    //

    if (ControllerContext->DeferActions != FALSE) {
        GpiopIncrementMaskCount(GpioBank, PinInformation, PinNumber);

    } else {
        GpiopMaskInterrupt(GpioBank->GpioExtension,
                           GPIO_REQUEST_FLAG_NONE,
                           PinInformation->Virq);
    }

    return STATUS_SUCCESS;
}

NTSTATUS
GpiopDebouceScheduleDebounceTimerCallback (
    __in PGPIO_DEBOUNCE_PIN_CONTEXT DebouncePinContext,
    __in PGPIO_INTERRUPT_DEBOUNCE_CONTROLLER_CONTEXT ControllerContext
    )

/*++

Routine Description:

    This routine schedules the debounce timer. This is a callback from
    the debounce state machine.

    Entry IRQL:
        1. This routine can be called at D-IRQL for memory-mapped GPIOs and
           DISPATCH_LEVEL for off-SoC GPIOs.

    Calling contexts:

        1. Synchronously from within the debounce state machine with the bank
           interrupt lock held for both on-SoC and off-SoC GPIO controllers.

Arguments:

    DebouncePinContext - Supplies a pointer to per-pin debounce context.

    ControllerContext - Supplies a pointer to the controller context
        containing the bank and pin information.

Return Value:

    STATUS_SUCCESS always.

--*/

{

    PGPIO_BANK_ENTRY GpioBank;
    PIN_NUMBER PinNumber;

    UNREFERENCED_PARAMETER(DebouncePinContext);

    GpioBank = ControllerContext->GpioBank;
    PinNumber = ControllerContext->PinNumber;

    //
    // Register the fact that debounce timer needs to be scheduled. The actual
    // timer will be scheduled in a deferred manner. For memory-mapped
    // GPIO controllers, this will happen within the context of the DPC. For
    // off-SoC GPIO controllers, this will happen synchronously after all
    // the pins are serviced.
    //

    GPIO_SET_PIN_ACTIVE_DEBOUNCE_MASK(GpioBank, PinNumber);
    GPIO_SET_PIN_DEBOUNCE_TIMER_MASK(GpioBank, PinNumber);
    ControllerContext->ScheduleDebounceTimers = TRUE;
    return STATUS_SUCCESS;
}

VOID
GpiopDebouceScheduleNoiseFilterTimerCallback (
    __in PGPIO_DEBOUNCE_PIN_CONTEXT DebouncePinContext,
    __in PGPIO_INTERRUPT_DEBOUNCE_CONTROLLER_CONTEXT ControllerContext,
    __in PGPIO_DEBOUNCE_OUTPUT_ACTION AssociatedOutputAction
    )

/*++

Routine Description:

    This routine schedules the noise filter timer. This is a callback from
    the debounce state machine.

    Entry IRQL:
        1. This routine can be called at D-IRQL for memory-mapped GPIOs and
           DISPATCH_LEVEL for off-SoC GPIOs.

    Calling contexts:

        1. Synchronously from within the debounce state machine with the bank
           interrupt lock held for both on-SoC and off-SoC GPIO controllers.
           The passive-level bank lock is held for off-SoC GPIOs only.

Arguments:

    DebouncePinContext - Supplies a pointer to per-pin debounce context.

    ControllerContext - Supplies a pointer to the controller context
        containing the bank and pin information.

    AssociatedOutputAction - Supplies a pointer to debounce output action buffer
        that should be passed back to the debounce engine when the noise timer
        fires.

Return Value:

    None.

--*/

{

    PGPIO_BANK_ENTRY GpioBank;
    PGPIO_PIN_INFORMATION_ENTRY PinInformation;
    PIN_NUMBER PinNumber;
    PGPIO_NOISE_FILTER_TIMER_CONTEXT NoiseFilterTimerContext;

    UNREFERENCED_PARAMETER(DebouncePinContext);

    GpioBank = ControllerContext->GpioBank;
    PinInformation = CONTAINING_RECORD(DebouncePinContext,
                                       GPIO_PIN_INFORMATION_ENTRY,
                                       DebounceContext);

    PinNumber = ControllerContext->PinNumber;
    NoiseFilterTimerContext = PinInformation->NoiseFilterTimerContext;
    NoiseFilterTimerContext->DebounceOutputAction = *AssociatedOutputAction;

    //
    // Register the fact that noise filter timer needs to be scheduled. The
    // actual timer will be scheduled in a deferred manner in the context of the
    // debounce DPC.
    //

    GPIO_SET_PIN_NOISE_FILTER_TIMER_MASK(GpioBank, PinNumber);
    ControllerContext->ScheduleNoiseFilterTimers = TRUE;
    return;
}

VOID
GpiopDebounceInvokeTargetIsrCallback (
    __in PGPIO_DEBOUNCE_PIN_CONTEXT DebouncePinContext,
    __in PGPIO_INTERRUPT_DEBOUNCE_CONTROLLER_CONTEXT ControllerContext
    )

/*++

Routine Description:

    This routine invokes the target ISR. This is a callback from the debounce
    state machine.

    Entry IRQL:
        1. This routine can be called at D-IRQL for memory-mapped GPIOs and
           PASSIVE_LEVEL for off-SoC GPIOs.

    Calling contexts:
        1. Synchronously from within the debounce state machine with the bank
           interrupt lock held for on-SoC GPIOs only.

        2. The passive-level bank interrupt lock is held for off-SoC GPIO
           controllers.

Arguments:

    DebouncePinContext - Supplies a pointer to per-pin debounce context.

    ControllerContext - Supplies a pointer to the controller context
        containing the bank and pin information.

Return Value:

    None.

--*/

{

    PGPIO_BANK_ENTRY GpioBank;
    PDEVICE_EXTENSION GpioExtension;
    BOOLEAN PassiveGpio;
    PGPIO_PIN_INFORMATION_ENTRY PinInformation;
    PIN_NUMBER PinNumber;

    GpioBank = ControllerContext->GpioBank;
    GpioExtension = GpioBank->GpioExtension;
    PinInformation = CONTAINING_RECORD(DebouncePinContext,
                                       GPIO_PIN_INFORMATION_ENTRY,
                                       DebounceContext);

    PinNumber = ControllerContext->PinNumber;

    //
    // Validate the IRQL and locking requirements.
    //

    PassiveGpio = GPIO_IS_PASSIVE_IRQL_ONLY_DEVICE(GpioExtension);
    if (PassiveGpio != FALSE) {

        GPIO_ASSERT((GPIO_BANK_PASSIVE_LOCK_OWNER(GpioBank) != FALSE) &&
                    (KeGetCurrentIrql() == PASSIVE_LEVEL));

    } else {

        GPIO_ASSERT(GPIO_BANK_INTERRUPT_LOCK_OWNER(GpioBank) != FALSE);
    }

    //
    // For edge-triggered pins using legacy debounce model, defer the ISR
    // invocation. This enables interrupts to be acknowledged at the hardware
    // without incurring target ISR delay (which could be significant in the
    // passive-level ISR case). This prevents edge-triggered interrupts
    // from being missed.
    //
    // In all other cases, invoke the ISR.
    //

    if ((DebouncePinContext->Flags.DebounceModel == DebounceModelLegacy) &&
        (PinInformation->InterruptMode == Latched)) {

        ControllerContext->IsrInvocationDeferred = TRUE;

    } else {
        GpiopInvokeTargetIsr(GpioBank, GPIO_PIN_NUMBER_TO_PIN_MASK(PinNumber));
    }

    //
    // Notify the interrupt state machine of debounce interrupt completion.
    //

    GpiopServiceInterruptOnPin(ControllerContext,
                               InterruptEventOnDebounceEngineNotification,
                               InterruptEventReportValidInterrupt);

    //
    // Record the fact that this interrupt was considered genuine.
    //

    PinInformation->LastInterruptWasValid = TRUE;

    return;
}

VOID
GpiopDebouceCancelNoiseFilterTimerCallback (
    __in PGPIO_DEBOUNCE_PIN_CONTEXT DebouncePinContext,
    __in PGPIO_INTERRUPT_DEBOUNCE_CONTROLLER_CONTEXT ControllerContext
    )

/*++

Routine Description:

    This routine schedules the noise filter timer. This is a callback from
    the debounce state machine.

    Entry IRQL:
        1. This routine can be called at D-IRQL for memory-mapped GPIOs and
           DISPATCH_LEVEL for off-SoC GPIOs.

    Calling contexts:
        1. Synchronously from within the debounce state machine with the bank
           interrupt lock held for both on-SoC and off-SoC GPIO controllers.

Arguments:

    DebouncePinContext - Supplies a pointer to per-pin debounce context.

    ControllerContext - Supplies a pointer to the controller context
        containing the bank and pin information.

Return Value:

    None.

--*/

{

    PGPIO_BANK_ENTRY GpioBank;
    ULONG64 NoiseMask;
    PGPIO_PIN_INFORMATION_ENTRY PinInformation;
    PIN_NUMBER PinNumber;

    UNREFERENCED_PARAMETER(DebouncePinContext);

    GpioBank = ControllerContext->GpioBank;
    PinInformation = CONTAINING_RECORD(DebouncePinContext,
                                       GPIO_PIN_INFORMATION_ENTRY,
                                       DebounceContext);

    PinNumber = ControllerContext->PinNumber;

    //
    // Attempt to cancel the noise filter timer by clearing the bit in the
    // noise filter mask. Note this will only have an effect if the
    // noise filter timer is yet to be queued. If it has already been queued,
    // then the timer won't actually be cancelled.
    //

    NoiseMask = GPIO_CLEAR_PIN_NOISE_FILTER_TIMER_MASK(GpioBank, PinNumber);

    //
    // For off-SoC GPIOs, attempt to cancel the noise timer if it has already
    // been scheduled (and thus the bit would be clear). This can't be done
    // for on-SoC GPIOs as the incoming IRQL is too high for such an operation.
    //
    // N.B. In cases where the timer can't be cancelled, the the interrupt epoch
    //      would been updated by the debounce state machine to mark the arrival
    //      of a new interrupt (which resulted in timer being cancelled). The
    //      interrupt epoch value associated with the noise filter timer would
    //      become stale and the timer invocation once it fires will be ignored.
    //

    if ((CHECK_BIT_SET(NoiseMask, PinNumber) == 0) &&
        (KeGetCurrentIrql() <= DISPATCH_LEVEL)) {

        ExCancelTimer(
            ((PGPIO_NOISE_FILTER_TIMER_CONTEXT)
             PinInformation->NoiseFilterTimerContext)->NoiseFilterTimer,
            NULL);
    }

    return;
}

NTSTATUS
GpiopDebounceUnmaskInterruptCallback (
    __in PGPIO_DEBOUNCE_PIN_CONTEXT DebouncePinContext,
    __in PGPIO_INTERRUPT_DEBOUNCE_CONTROLLER_CONTEXT ControllerContext
    )

/*++

Routine Description:

    This routine unmasks the debounce pin. This is a callback from
    the debounce state machine.

    Entry IRQL:
        1. This routine can be called at D-IRQL for memory-mapped GPIOs and
           PASSIVE_LEVEL for off-SoC GPIOs.

    Calling contexts:
        1. Synchronously from within the debounce state machine with the bank
           interrupt lock held for on-SoC GPIOs only.

        2. The passive-level bank interrupt lock is held for off-SoC GPIO
           controllers.

Arguments:

    DebouncePinContext - Supplies a pointer to per-pin debounce context.

    ControllerContext - Supplies a pointer to the controller context
        containing the bank and pin information.

Return Value:

    NTSTATUS code.

--*/

{

    PGPIO_BANK_ENTRY GpioBank;
    PGPIO_PIN_INFORMATION_ENTRY PinInformation;
    PIN_NUMBER PinNumber;
    NTSTATUS Status;

    GpioBank = ControllerContext->GpioBank;
    PinInformation = CONTAINING_RECORD(DebouncePinContext,
                                       GPIO_PIN_INFORMATION_ENTRY,
                                       DebounceContext);

    PinNumber = ControllerContext->PinNumber;

    //
    // Notify the interrupt state machine of debounce completion (or near
    // about there). This will cause the interrupt state machine to issue a
    // pending status clear, which must be done to acknowledge the original
    // interrupt.
    //
    // N.B. It is done here to ensure the status clear is performed before
    //      the unmask (which likely would bring the count to zero and
    //      physically unmask the line).
    //

    GpiopServiceInterruptOnPin(ControllerContext,
                               InterruptEventOnDebounceEngineNotification,
                               InterruptEventReportDebounceCompletion);

    Status = GpiopUnmaskInterrupt(GpioBank->GpioExtension,
                                  GPIO_REQUEST_FLAG_NONE,
                                  PinInformation->Virq);

    return Status;
}

NTSTATUS
GpiopDebounceReconfigureInterruptCallback (
    __in PGPIO_DEBOUNCE_PIN_CONTEXT DebouncePinContext,
    __in PGPIO_INTERRUPT_DEBOUNCE_CONTROLLER_CONTEXT ControllerContext
    )

/*++

Routine Description:

    This routine reconfigures the debounce pin for the opposite polarity. This
    is a callback from the debounce state machine.

    Entry IRQL:
        1. This routine can be called at D-IRQL for memory-mapped GPIOs and
           PASSIVE_LEVEL for off-SoC GPIOs.

    Calling contexts:
        1. Synchronously from within the debounce state machine with the bank
           interrupt lock held for on-SoC GPIOs only.

        2. The passive-level bank interrupt lock is held for off-SoC GPIO
           controllers.

Arguments:

    DebouncePinContext - Supplies a pointer to per-pin debounce context.

    ControllerContext - Supplies a pointer to the controller context
        containing the bank and pin information.

Return Value:

    NTSTATUS code.

--*/

{

    PGPIO_BANK_ENTRY GpioBank;
    PIN_NUMBER PinNumber;
    NTSTATUS Status;

    UNREFERENCED_PARAMETER(DebouncePinContext);

    GpioBank = ControllerContext->GpioBank;
    PinNumber = ControllerContext->PinNumber;
    Status = GpiopReconfigureInterrupts(GpioBank, PinNumber);
    return Status;
}

ULONG
GpiopDebounceQueryNoiseFilterEpoch (
    __in PGPIO_DEBOUNCE_PIN_CONTEXT DebouncePinContext,
    __in PGPIO_INTERRUPT_DEBOUNCE_CONTROLLER_CONTEXT ControllerContext
    )

/*++

Routine Description:


    Entry IRQL:
        1. This routine can be called at D-IRQL for memory-mapped GPIOs and
           DISPATCH_LEVEL for off-SoC GPIOs.

    Calling contexts:
        1. Synchronously from within the debounce state machine with the bank
           interrupt lock held for both on-SoC and off-SoC GPIO controllers.

Arguments:

    DebouncePinContext - Supplies a pointer to per-pin debounce context.

    ControllerContext - Supplies a pointer to the controller context
        containing the bank and pin information.

Return Value:

    The noise filter epoch value.

--*/

{

    UNREFERENCED_PARAMETER(DebouncePinContext);

    return ControllerContext->InterruptEpoch;
}

VOID
GpiopDebounceLogSpuriousInterrupt (
    __in PGPIO_DEBOUNCE_PIN_CONTEXT DebouncePinContext,
    __in PGPIO_INTERRUPT_DEBOUNCE_CONTROLLER_CONTEXT ControllerContext
    )

/*++

Routine Description:

    This routine logs a spurious interrupt event to the event log.

    Entry IRQL:
        1. This routine can be called at D-IRQL for memory-mapped GPIOs and
           DISPATCH_LEVEL for off-SoC GPIOs.

    Calling contexts:
        1. Synchronously from within the debounce state machine with the bank
           interrupt lock held for both on-SoC and off-SoC GPIO controllers.


Arguments:

    DebouncePinContext - Supplies a pointer to per-pin debounce context.

    ControllerContext - Supplies a pointer to the controller context
        containing the bank and pin information.

Return Value:

    None.

--*/

{

    GPIO_DEBOUNCE_MODEL DebounceModel;
    PGPIO_BANK_ENTRY GpioBank;
    PDEVICE_EXTENSION GpioExtension;
    PGPIO_PIN_INFORMATION_ENTRY PinInformation;
    PIN_NUMBER PinNumber;

    GpioBank = ControllerContext->GpioBank;
    GpioExtension = GpioBank->GpioExtension;
    PinInformation = CONTAINING_RECORD(DebouncePinContext,
                                       GPIO_PIN_INFORMATION_ENTRY,
                                       DebounceContext);

    PinNumber = ControllerContext->PinNumber;

    //
    // GPIO pins that have "press" bouncing will likely also have "release"
    // bouncing that happens when the button/switch is released (or the plug
    // is disconnected.) The release bouncing will automatically be eliminated
    // by the enhanced debouncing logic as spurious interrupt. However such
    // bouncing is natural to the device/part and thus should not be flagged
    // as noise interrupt as doing so would likely cause confusion to OEMs.
    // Genuine noise signals should be flagged to allow easier diagnosis.
    //
    // To solve this problem, the state of the previous interrupt is tracked
    // and used as a hueristic to determine whether or not log spurious
    // interrupt event.  If the previous interrupt was genuine, then a noise
    // immediately following it is *likely* due to release bouncing and thus
    // shouldn't be logged. Otherwise, this is noise that happened after the
    // release bouncing or while the line was in its quiescent state and thus
    // should be logged.
    //
    // It is possible that any noise that immediately follows a valid signal
    // will not be logged. This is a compromise that comes with the above
    // scheme. Since this only affects ETW logging, the above scheme was
    // considered a reasonable approach over any complex mechanism to detect
    // whether or not to log ETW events.
    //
    // The above is only relevant for non-emulated ActiveBoth interrupts that
    // are being debounced. For emulated ActiveBoth pins, both "press" and
    // "release" are considered valid and thus any time something is detected
    // as a noise, it should be logged.
    //

    if ((PinInformation->Mode.EmulateActiveBoth == 1) ||
        (PinInformation->LastInterruptWasValid == FALSE)) {

        EventWrite_SPURIOUS_INTERRUPT_DETECTED(
            &GpioBank->ActivityId,
            PinNumber,
            PinInformation->Virq);
    }

    PinInformation->LastInterruptWasValid = FALSE;

    //
    // If the spurious interrupt is being logged then it implies that an
    // incoming interrupt was deemed as noise. Notify the interrupt state
    // machine of noise interrupt. This allows the interrupt state machine
    // to perform any activities on its side (viz. clearing the status
    // register to acknowledge the interrupt).
    //
    // N.B. This will be a recursive call back into the interrupt state machine.
    //
    // This callback is only expected to be called for enhanced debounce model.
    //

    DebounceModel = DebouncePinContext->Flags.DebounceModel;

    GPIO_ASSERT(DebounceModel == DebounceModelEnhanced);

    //
    // Notify the interrupt state machine of a spurious interrupt.
    //

    GpiopServiceInterruptOnPin(ControllerContext,
                               InterruptEventOnDebounceEngineNotification,
                               InterruptEventReportSpuriousInterrupt);

    return;
}

VOID
GpiopDebounceAcquireLock (
    __in PGPIO_DEBOUNCE_PIN_CONTEXT DebouncePinContext,
    __in PGPIO_INTERRUPT_DEBOUNCE_CONTROLLER_CONTEXT ControllerContext
    )

/*++

Routine Description:

    This routine acquires the D-IRQL bank interrupt lock for memory mapped
    GPIO controllers and DPC-level bank lock for off-SoC GPIOs.

Arguments:

    DebouncePinContext - Supplies a pointer to per-pin debounce context.

    ControllerContext - Supplies a pointer to the controller context
        containing the bank and pin information.

Return Value:

    None.

--*/

{

    UNREFERENCED_PARAMETER(DebouncePinContext);

    GPIO_ACQUIRE_BANK_INTERRUPT_LOCK(ControllerContext->GpioBank);
    return;
}

VOID
GpiopDebounceReleaseLock (
    __in PGPIO_DEBOUNCE_PIN_CONTEXT DebouncePinContext,
    __in PGPIO_INTERRUPT_DEBOUNCE_CONTROLLER_CONTEXT ControllerContext
    )

/*++

Routine Description:

    This routine acquires the D-IRQL bank interrupt lock for memory mapped
    GPIO controllers and DPC-level bank lock for off-SoC GPIOs.

Arguments:

    DebouncePinContext - Supplies a pointer to per-pin debounce context.

    ControllerContext - Supplies a pointer to the controller context
        containing the bank and pin information.

Return Value:

    None.

--*/

{

    UNREFERENCED_PARAMETER(DebouncePinContext);

    GPIO_RELEASE_BANK_INTERRUPT_LOCK(ControllerContext->GpioBank);
    return;
}

VOID
GpiopDebounceTracePin (
    __in PGPIO_DEBOUNCE_PIN_CONTEXT DebouncePinContext,
    __in PGPIO_INTERRUPT_DEBOUNCE_CONTROLLER_CONTEXT ControllerContext,
    __in PCSTR Message,
    __in ULONG DataCount,
    __in ULONG Data
    )

/*++

Routine Description:

    This routine writes a line of debug tracing.

Arguments:

    DebouncePinContext - Supplies a pointer to per-pin debounce context.

    ControllerContext - Supplies a pointer to the controller context
        containing the bank and pin information.

    Message - Supplies a pointer to a string to include in the message.
        The string should not contain any printf-style formatting.

    DataCount - Supplies the number of data items to write.  Must be 0 or 1.

    Data - Supplies any numeric data to write immediately after Message.

Return Value:

    None.

--*/

{

    PGPIO_BANK_ENTRY GpioBank;

    UNREFERENCED_PARAMETER(DebouncePinContext);

    GpioBank = ControllerContext->GpioBank;
    if (DataCount == 0) {
        TraceEvents(GpioBank->GpioExtension->LogHandle,
                    Info,
                    DBG_INTERRUPT,
                    "%s: Bank = %p, Relative Pin = %#x: %s\n",
                    __FUNCTION__,
                    GpioBank,
                    ControllerContext->PinNumber,
                    Message);

    } else {

        GPIO_ASSERT(DataCount == 1);

        TraceEvents(GpioBank->GpioExtension->LogHandle,
                    Info,
                    DBG_INTERRUPT,
                    "%s: Bank = %p, Relative Pin = %#x: %s0x%x\n",
                    __FUNCTION__,
                    GpioBank,
                    ControllerContext->PinNumber,
                    Message,
                    Data);
    }

    return;
}

//
// ------------------------------------------------- Failure handling Functions
//

VOID
GpiopHandleInterruptMaskRequestCompletion (
    __in PGPIO_BANK_ENTRY GpioBank,
    __in ULONG64 PinsToMask,
    __in ULONG64 FailedMask
    )

/*++

Routine Description:

    This routine handles interrupt mask failures and successes.

    If a pin could not be masked a certain number of times, it calls into the
    HAL to disable the controller's interrupt (on its parent), and logs error
    information.

    If a pin could be masked, it resets that count of the number of times.

    N.B. 1. For on-SoC controllers, this routine does nothing since mask
            operations cannot fail.

         2. For off-SoC controllers, this routine is always called with the
            bank lock held and interrupt lock held (DIRQL level).

Arguments:

    GpioBank - Supplies a pointer to the GPIO bank.

    PinsToMask - Supplies a bitmask of pins that the client driver tried to
                 mask.

    FailedMask - Supplies a bitmask of pins that the client driver failed to
                 mask.

Return Value:

    None.

--*/

{

    CONTROLLER_ERROR_FLAGS Errors;
    PGPIO_PIN_INFORMATION_ENTRY PinInformation;
    PIN_NUMBER PinNumber;
    ULONG64 SucceededMask;

    if (!GPIO_IS_PASSIVE_IRQL_ONLY_DEVICE(GpioBank->GpioExtension)) {
        return;
    }

    GPIO_ASSERT(GPIO_BANK_PASSIVE_LOCK_OWNER(GpioBank));
    GPIO_ASSERT(GPIO_BANK_INTERRUPT_LOCK_OWNER(GpioBank));
    GPIO_ASSERT((FailedMask & ~PinsToMask) == 0);

    //
    // For pins that could be masked, reset the number of retries, regardless
    // of whether another thread will be unmasking any of them soon.
    //

    SucceededMask = PinsToMask & ~FailedMask;
    while (SucceededMask > 0) {
        PinNumber = RtlFindLeastSignificantBit(SucceededMask);
        SucceededMask &= ~(1ULL << PinNumber);
        PinInformation = GpiopGetPinEntry(GpioBank, PinNumber);

        GPIO_ASSERT(PinInformation != NULL);

        PinInformation->MaskUnmaskFailureCount = 0;
    }

    //
    // Ignore mask failures for pins that are going to be unmasked by another
    // thread.
    //

    FailedMask &= GpioBank->InterruptData.InterruptMask;
    if (FailedMask == 0) {
        return;
    }

    //
    // For pins that could not be masked, set them in the pending mask
    // register (for retry) and deal with the failure appropriately.
    //

    GPIO_SET_PENDING_INTERRUPT_MASK_PINMASK(GpioBank, FailedMask);
    while (FailedMask > 0) {
        PinNumber = RtlFindLeastSignificantBit(FailedMask);
        FailedMask &= ~(1ULL << PinNumber);
        PinInformation = GpiopGetPinEntry(GpioBank, PinNumber);

        GPIO_ASSERT(PinInformation != NULL);

        //
        // Mask the controller's interrupt on the parent. This is only required
        // if the GPIO controller consumes any interrupt.
        //

        PinInformation->MaskUnmaskEverFailed = 1;
        if (PinInformation->MaskUnmaskFailureCount != MAXLONG) {
            PinInformation->MaskUnmaskFailureCount += 1;
        }

        if (PinInformation->MaskUnmaskFailureCount >
            MASK_UNMASK_FAILURE_COUNT_THRESHOLD) {

            if ((GPIO_IS_INTERRUPT_SPECIFIED(GpioBank) != FALSE) &&
                (GpioBank->GpioExtension->Errors.
                    ParentInterruptMaskedDueToMaskFailure == 0)) {

                GPIO_ASSERT(FALSE);
                HAL_MASK_CONTROLLER_INTERRUPT(GpioBank);

                //
                // Record that the controller failed to mask some interrupt pins
                // and consequently the controller's interrupt was masked.
                //

                Errors.AsULONG = 0;
                Errors.InterruptMaskFailure = 1;
                Errors.ParentInterruptMaskedDueToMaskFailure = 1;
                InterlockedOr(
                    (volatile LONG *)&GpioBank->GpioExtension->Errors.AsULONG,
                    Errors.AsULONG);
            }
        }
    }

    if (GpioBank->GpioExtension->Errors.
            ParentInterruptMaskedDueToMaskFailure == 0) {

        GpiopScheduleDeferredInterruptActivities(GpioBank);
    }

    return;
}

VOID
GpiopHandleInterruptUnmaskRequestCompletion (
    __in PGPIO_BANK_ENTRY GpioBank,
    __in ULONG64 PinsToUnmask,
    __in ULONG64 FailedUnmask
    )

/*++

Routine Description:

    This routine handles interrupt unmask failures and successes.

    If a pin could not be unmasked a certain number of times, it logs error
    information.

    If a pin could be unmasked, it resets that count of the number of times.

    N.B. 1. For on-SoC controllers, this routine does nothing since unmask
            operations cannot fail.

         2. For off-SoC controllers, this routine is always called with the
            bank lock held and interrupt lock held (DIRQL level).

Arguments:

    GpioBank - Supplies a pointer to the GPIO bank.

    PinsToUnmask - Supplies a bitmask of pins that the client driver tried to
                   unmask.

    FailedUnmask - Supplies a bitmask of pins that the client driver failed to
                   unmask.

Return Value:

    None.

--*/

{

    CONTROLLER_ERROR_FLAGS Errors;
    PGPIO_PIN_INFORMATION_ENTRY PinInformation;
    PIN_NUMBER PinNumber;
    ULONG64 SucceededUnmask;

    if (!GPIO_IS_PASSIVE_IRQL_ONLY_DEVICE(GpioBank->GpioExtension)) {
        return;
    }

    GPIO_ASSERT(GPIO_BANK_PASSIVE_LOCK_OWNER(GpioBank));
    GPIO_ASSERT(GPIO_BANK_INTERRUPT_LOCK_OWNER(GpioBank));
    GPIO_ASSERT((FailedUnmask & ~PinsToUnmask) == 0);

    //
    // For pins that could be unmasked, reset the number of retries, regardless
    // of whether another thread will be masking any of them soon.
    //

    SucceededUnmask = PinsToUnmask & ~FailedUnmask;
    while (SucceededUnmask > 0) {
        PinNumber = RtlFindLeastSignificantBit(SucceededUnmask);
        SucceededUnmask &= ~(1ULL << PinNumber);
        PinInformation = GpiopGetPinEntry(GpioBank, PinNumber);

        GPIO_ASSERT(PinInformation != NULL);

        PinInformation->MaskUnmaskFailureCount = 0;
    }

    //
    // Ignore unmask failures for pins that are going to be masked by another
    // thread.
    //

    FailedUnmask &= ~GpioBank->InterruptData.InterruptMask;
    if (FailedUnmask == 0) {
        return;
    }

    //
    // For pins that could not be unmasked, set them in the pending mask
    // register (for retry) and deal with the failure appropriately.
    //

    GPIO_SET_PENDING_INTERRUPT_UNMASK_PINMASK(GpioBank, FailedUnmask);
    while (FailedUnmask > 0) {
        PinNumber = RtlFindLeastSignificantBit(FailedUnmask);
        FailedUnmask &= ~(1ULL << PinNumber);
        PinInformation = GpiopGetPinEntry(GpioBank, PinNumber);

        GPIO_ASSERT(PinInformation != NULL);

        //
        // Mask the controller's interrupt on the parent. This is only required
        // if the GPIO controller consumes any interrupt.
        //

        PinInformation->MaskUnmaskEverFailed = 1;
        if (PinInformation->MaskUnmaskFailureCount != MAXLONG) {
            PinInformation->MaskUnmaskFailureCount += 1;
        }

        if (PinInformation->MaskUnmaskFailureCount >
            MASK_UNMASK_FAILURE_COUNT_THRESHOLD) {

            //
            // For CHK builds, break into the debugger the first time the
            // failure count threshold is reached.
            //

            GPIO_ASSERT(PinInformation->MaskUnmaskFailureCount !=
                        (MASK_UNMASK_FAILURE_COUNT_THRESHOLD + 1));

            //
            // Record that the controller failed to unmask some interrupt pins.
            //

            Errors.AsULONG = 0;
            Errors.InterruptUnmaskFailure = 1;
            InterlockedOr(
                (volatile LONG *)&GpioBank->GpioExtension->Errors.AsULONG,
                Errors.AsULONG);

            //
            // Clear the pending interrupt unmask to prevent this pin from
            // future unmask attempts.
            //

            GPIO_CLEAR_PIN_PENDING_INTERRUPT_UNMASK(GpioBank, PinNumber);
        }
    }

    GpiopScheduleDeferredInterruptActivities(GpioBank);
    return;
}

VOID
GpiopHandleStatusClearRequestCompletion (
    __in PGPIO_BANK_ENTRY GpioBank,
    __in ULONG64 PinsToClear,
    __in ULONG64 FailedStatusClear
    )

/*++

Routine Description:

    This routine handles status clear failures and successes.

    If a pin could not be cleared a certain number of times, it calls into the
    HAL to disable the controller's interrupt (on its parent), and logs error
    information.

    If a pin could be cleared, it resets that count of the number of times.

    N.B. 1. For on-SoC controllers, this routine does nothing since clear
            operations cannot fail.

         2. For off-SoC controllers, this routine is always called with the
            bank lock held (PASSIVE_LEVEL).

         3. A non-enabled pin may be set in the mask.

Arguments:

    GpioBank - Supplies a pointer to the GPIO bank.

    PinsToClear - Supplies a bitmask of pins that the client driver tried to
                  clear.

    FailedClear - Supplies a bitmask of pins that the client driver failed to
                  clear.

Return Value:

    None.

--*/

{

    CONTROLLER_ERROR_FLAGS Errors;
    PGPIO_PIN_INFORMATION_ENTRY PinInformation;
    PIN_NUMBER PinNumber;
    ULONG64 SucceededStatusClear;

    if (!GPIO_IS_PASSIVE_IRQL_ONLY_DEVICE(GpioBank->GpioExtension)) {
        return;
    }

    GPIO_ASSERT(GPIO_BANK_PASSIVE_LOCK_OWNER(GpioBank));
    GPIO_ASSERT((FailedStatusClear & ~PinsToClear) == 0);

    //
    // For pins that could be cleared, reset the number of retries.
    //

    SucceededStatusClear = PinsToClear & ~FailedStatusClear;
    while (SucceededStatusClear > 0) {
        PinNumber = RtlFindLeastSignificantBit(SucceededStatusClear);
        SucceededStatusClear &= ~(1ULL << PinNumber);
        PinInformation = GpiopGetPinEntry(GpioBank, PinNumber);

        GPIO_ASSERT(PinInformation != NULL);

        PinInformation->StatusClearFailureCount = 0;
    }

    if (FailedStatusClear == 0) {
        return;
    }

    //
    // For pins that could not be cleared, set them in the pending mask
    // register (for retry).
    //

    GPIO_SET_PENDING_STATUS_CLEAR_PINMASK(GpioBank, FailedStatusClear);
    while (FailedStatusClear > 0) {
        PinNumber = RtlFindLeastSignificantBit(FailedStatusClear);
        FailedStatusClear &= ~(1ULL << PinNumber);
        PinInformation = GpiopGetPinEntry(GpioBank, PinNumber);

        GPIO_ASSERT(PinInformation != NULL);

        //
        // If the pin is not configured for interrupt (possible if a
        // non-enabled pin asserted) or has already been disconnected, then
        // ignore this interrupt.
        //

        if (PinInformation->Mode.Interrupt == 0x0) {
            continue;
        }

        //
        // Mask the controller's interrupt on the parent. This is only required
        // if the GPIO controller consumes any interrupt.
        //

        PinInformation->StatusClearEverFailed = 1;
        if (PinInformation->StatusClearFailureCount != MAXLONG) {
            PinInformation->StatusClearFailureCount += 1;
        }

        if (PinInformation->StatusClearFailureCount >
            STATUS_CLEAR_FAILURE_COUNT_THRESHOLD) {

            if ((GPIO_IS_INTERRUPT_SPECIFIED(GpioBank) != FALSE) &&
                (GpioBank->GpioExtension->Errors.
                    ParentInterruptMaskedDueToStatusClearFailure == 0)) {

                GPIO_ASSERT(FALSE);
                HAL_MASK_CONTROLLER_INTERRUPT(GpioBank);

                //
                // Record that the controller failed to clear the status of
                // some interrupt pins and consequently the controller's
                // interrupt was masked.
                //

                Errors.AsULONG = 0;
                Errors.InterruptMaskFailure = 1;
                Errors.ParentInterruptMaskedDueToStatusClearFailure = 1;
                InterlockedOr(
                    (volatile LONG *)&GpioBank->GpioExtension->Errors.AsULONG,
                    Errors.AsULONG);
            }
        }
    }

    if (GpioBank->GpioExtension->Errors.
            ParentInterruptMaskedDueToStatusClearFailure == 0) {

        GpiopScheduleDeferredInterruptActivities(GpioBank);
    }

    return;
}

VOID
GpiopHandleDisableInterruptRequestFailure (
    __in PGPIO_BANK_ENTRY GpioBank
    )

/*++

Routine Description:

    This routine handles off-SoC disconnect interrupt failures. If a pin could
    not be disconnected a certain number of times, it calls into the HAL to
    disable the controller's interrupt (on its parent), and logs error
    information.

    N.B. It assumes that the caller has held the PASSIVE_LEVEL bank lock prior
         to invocation.

Arguments:

    GpioBank - Supplies a pointer to the GPIO bank.

Return Value:

    None.

--*/

{

    CONTROLLER_ERROR_FLAGS Errors;
    ULONG64 FailedDisconnectMask;
    PGPIO_PIN_INFORMATION_ENTRY PinInformation;
    PIN_NUMBER PinNumber;

    GPIO_ASSERT(GPIO_IS_PASSIVE_IRQL_ONLY_DEVICE(GpioBank->GpioExtension));
    GPIO_ASSERT(GPIO_BANK_PASSIVE_LOCK_OWNER(GpioBank));

    FailedDisconnectMask = GPIO_READ_PENDING_DISCONNECT_VALUE(GpioBank);
    while (FailedDisconnectMask > 0) {
        PinNumber = RtlFindLeastSignificantBit(FailedDisconnectMask);
        FailedDisconnectMask &= ~(1ULL << PinNumber);
        PinInformation = GpiopGetPinEntry(GpioBank, PinNumber);

        GPIO_ASSERT(PinInformation != NULL);

        //
        // Mask the controller's interrupt on the parent. This is only required
        // if the GPIO controller consumes any interrupt.
        //

        PinInformation->DisconnectEverFailed = 1;
        if (PinInformation->DisconnectFailureCount != MAXLONG) {
            PinInformation->DisconnectFailureCount += 1;
        }

        if (PinInformation->DisconnectFailureCount >
            STATUS_CLEAR_FAILURE_COUNT_THRESHOLD) {

            if ((GPIO_IS_INTERRUPT_SPECIFIED(GpioBank) != FALSE) &&
                (GpioBank->GpioExtension->Errors.
                    ParentInterruptMaskedDueToDisconnectFailure == 0)) {

                GPIO_ASSERT(FALSE);
                HAL_MASK_CONTROLLER_INTERRUPT(GpioBank);

                //
                // Record that the controller failed to disconnect some
                // interrupt pins and consequently the controller's interrupt
                // was masked.
                //

                Errors.AsULONG = 0;
                Errors.InterruptMaskFailure = 1;
                Errors.ParentInterruptMaskedDueToDisconnectFailure = 1;
                InterlockedOr(
                    (volatile LONG *)&GpioBank->GpioExtension->Errors.AsULONG,
                    Errors.AsULONG);
            }
        }
    }

    if (GpioBank->GpioExtension->Errors.
            ParentInterruptMaskedDueToDisconnectFailure == 0) {

        GpiopScheduleDeferredInterruptActivities(GpioBank);
    }

    return;
}

VOID
GpiopSetInterruptState (
    __in PGPIO_BANK_ENTRY GpioBank,
    __in WDFINTERRUPT Interrupt,
    __in BOOLEAN ActiveState
    )

/*++

Routine Description:

    This routine handles reporting the specified interrupt as active or
    inactive.

    Synchronization:
    This routine does not perform any synchronization and assume the caller has
    handled the race between calling active/inactive. This routine can be called
    in two scenarios:
        1. Either when a power-managed bank is going ACTIVE or IDLE (at
           DISPATCH_LEVEL), or

        2. When an emulated-debounce pin connect/disconnect (first/last
           transition).

    These two scenarios are mutually exclusive (for connect/disconnect the
    power reference is first taken and held) and thus do not race with each
    other. The connect/disconnect operations (#2) can race but those transitions
    are synchronized using the passive-level bank lock.

    Note this routine is not marked PAGED as it may be called before/after
    the boot device is in D0/D3 if boot device has GPIO dependencies.

Arguments:

    GpioBank - Supplies a pointer to the GPIO bank.

    Interrupt - Supplies a handle to the WDF interrupt object. This value could
        be NULL.

    ActiveState - Supplies whether the interrupt should be marked active (TRUE)
        or inactive (FALSE).

Return Value:

    None.

--*/

{

    IO_REPORT_INTERRUPT_ACTIVE_STATE_PARAMETERS Parameters;
    PKINTERRUPT WdmInterrupt;

    UNREFERENCED_PARAMETER(GpioBank);

    //
    // Get the WDM interrupt objects corresponding to the WDF interrupt objects
    // for a given bank.
    //

    WdmInterrupt = NULL;
    if (Interrupt != NULL) {
        GPIO_ASSERT((Interrupt == GpioBank->BankInterrupt) ||
                    (Interrupt == GpioBank->BankPassiveInterrupt));

        WdmInterrupt = WdfInterruptWdmGetInterrupt(Interrupt);
    }

    //
    // Get the WDM interrupt objects corresponding to the WDF interrupt objects
    // for a given bank.
    //

    if (WdmInterrupt != NULL) {
        RtlZeroMemory(&Parameters,
                     sizeof(IO_REPORT_INTERRUPT_ACTIVE_STATE_PARAMETERS));

        Parameters.Version = CONNECT_FULLY_SPECIFIED;
        Parameters.ConnectionContext.InterruptObject = WdmInterrupt;
        if (ActiveState != FALSE) {
            IoReportInterruptActive(&Parameters);

        } else {
            IoReportInterruptInactive(&Parameters);
        }
    }

    return;
}


