/*++

Copyright (c) Microsoft Corporation

Module Name:

    FxInterrupt.hpp

Abstract:

    This module implements a frameworks managed interrupt object

Author:




Environment:

    Both kernel and user mode

Revision History:


--*/

#ifndef _FXINTERRUPT_H_
#define _FXINTERRUPT_H_

#include "fxwakeinterruptstatemachine.hpp"

//
// We need two parameters for KeSynchronizeExecution when enabling
// and disabling interrupts, so we use this structure on the stack since its
// a synchronous call.
//
struct FxInterruptEnableParameters {
    FxInterrupt*        Interrupt;
    NTSTATUS            ReturnVal;
};

typedef FxInterruptEnableParameters FxInterruptDisableParameters;


class FxInterrupt : public FxNonPagedObject {

    friend FxPkgPnp;

private:

    //
    // User supplied configuration
    //
    WDF_TRI_STATE                   m_ShareVector;

    //
    // Kernel Interupt object
    //
    struct _KINTERRUPT*             m_Interrupt;

    //
    // Kernel spinlock for Interrupt
    //
    MdLock*                         m_SpinLock;

    KIRQL                           m_OldIrql;
    volatile KIRQL                  m_SynchronizeIrql;

    //
    // Built in SpinLock/PassiveLock
    //
    MxLock                          m_BuiltInSpinLock;

    //
    // Passive-level interrupt handling.
    //
    FxWaitLock*                     m_WaitLock;

    //
    // DpcForIsr and WorkItemForIsr support. Note that a DPC is still
    // needed even if the driver opts to use WorkItemForIsr when
    // driver handles interrupts at DIRQL.
    //
#if ((FX_CORE_MODE)==(FX_CORE_KERNEL_MODE))
    KDPC                            m_Dpc;
#endif
    FxSystemWorkItem*               m_SystemWorkItem;

    //
    // Automatic serialization: this is the callback lock for the object the DPC or
    //       work-item will synchronize with.
    //
    FxCallbackLock*                 m_CallbackLock;

    //
    // Set to TRUE when WDF is responsible for disposing the wait-lock.
    //
    BOOLEAN                         m_DisposeWaitLock;

    //
    // Value provided by driver. When TRUE we use IoReportActive/Inactive to
    // do soft connect/disconnect on explicit power transitions.
    //
    BOOLEAN                         m_UseSoftDisconnect;

    //
    // Set to TRUE for passive-level interrupt handling.
    //
    BOOLEAN                         m_PassiveHandling;

    // set to TRUE once the interrupt has been added to the pnp package's
    // interrupt list
    BOOLEAN                         m_AddedToList;

    //
    // Indicates whether the driver has forced a disconnect.  If so, then
    // we should stop automatically managing the connected state.
    //
    BOOLEAN                         m_Connected;
    BOOLEAN                         m_ForceDisconnected;

    //
    // Indicates whether the m_EvtInterruptPostEnable succeeded or not.
    //
    BOOLEAN                         m_Enabled;

    //
    // Save floating point when the ISR runs
    //
    BOOLEAN                         m_FloatingSave;

    //
    // Set to TRUE if interrupt is created in the prepare hardware callback.
    //
    BOOLEAN                         m_CreatedInPrepareHardware;

    //
    // State machine to manage a wake capable interrupt
    //
    FxWakeInterruptMachine*         m_WakeInterruptMachine;


#if ((FX_CORE_MODE)==(FX_CORE_KERNEL_MODE))
    //
    // Set to true on successful connect or when driver reports active.
    // (this field is mainly for aid in debugging)
    //
    BOOLEAN                         m_Active;
#endif

    //
    // Interrupt policy
    //
    BOOLEAN                         m_SetPolicy;
    WDF_INTERRUPT_POLICY            m_Policy;
    WDF_INTERRUPT_PRIORITY          m_Priority;
    GROUP_AFFINITY                  m_Processors;

    //
    // Callbacks
    //
    PFN_WDF_INTERRUPT_ENABLE        m_EvtInterruptEnable;
    PFN_WDF_INTERRUPT_DISABLE       m_EvtInterruptDisable;

    PFN_WDF_INTERRUPT_ISR           m_EvtInterruptIsr;
    PFN_WDF_INTERRUPT_DPC           m_EvtInterruptDpc;
    PFN_WDF_INTERRUPT_WORKITEM      m_EvtInterruptWorkItem;

#if ((FX_CORE_MODE)==(FX_CORE_USER_MODE))
    //
    // Rd interrupt object
    //
    RD_INTERRUPT_CONTEXT            m_RdInterruptContext;

    //
    // Each interrupt object has this structure which comprises an event and a
    // wait structure. The wait struture  is associted with interrupt's callback
    // and the event, and is queued to threadpool. The callback is invoked when
    // the event is set.
    //
    FxInterruptWaitblock* m_InterruptWaitblock;

    //
    // True if the interrupt callback can queue another interrupt wait.
    // Set to true when interrupt is connected and false when interrupts
    // callbacks and waits are flushed.
    //
    BOOLEAN m_CanQueue;

    //
    // UMDF's handling of interrupt is split in two parts:
    // 1. framwork code- runs at passive always and therefore uses mode-agnostic
    //    code meant for passive-level handling, tracked through m_PassiveLevel
    //    field of interrupt object.
    // 2. redirector code- does passive handling of all of level-triggered
    //    interrupt and DIRQL handing of all others (edge and msi). Driver
    //    doesn't have any choice in that. The PassiveHandling field in the
    //    interrupt config is always set for passive for UMDF (through UMDF's
    //    init function).
    //
    // This field stores the type of handling done by redirector as opposed to
    // m_PassiveHandling which stores user's choice.
    //
    BOOLEAN m_PassiveHandlingByRedirector;
#endif

    //
    // PnP data about the interrupt.
    //
    WDF_INTERRUPT_INFO              m_InterruptInfo;

    //
    // Weak ref to the translated resource interrupt descriptor.
    // It is valid from prepare hardware callback to release hardware callback.
    //
    PCM_PARTIAL_RESOURCE_DESCRIPTOR  m_CmTranslatedResource;

#if (FX_CORE_MODE == FX_CORE_KERNEL_MODE)
    //
    // Callback used to set m_Disconnecting, synchronized to running ISRs.
    // Only runs if m_IsEdgeTriggeredNonMsiInterrupt is TRUE.
    //
    static
    MdInterruptSynchronizeRoutineType _InterruptMarkDisconnecting;

    //
    // Backup KINTERRUPT pointer, captured from the KMDF ISR thunk. We need it
    // because valid interrupts may arrive before IoConnectInterruptEx sets
    // FxInterrupt.m_Interrupt. Non-NULL only if m_IsEdgeTriggeredNonMsiInterrupt is TRUE.
    //
    struct _KINTERRUPT* m_InterruptCaptured;
#endif

    //
    // Used to mark the interrupt disconnect window, and to discard interrupts
    // that arrive within this window. Only set if m_IsEdgeTriggeredNonMsiInterrupt is TRUE.
    //
    BOOLEAN m_Disconnecting;

    //
    // Set if this is an Edge-Triggered non-MSI interrupt. These interrupts are
    // stateful and it is important not to drop any around the connection window.
    //
    BOOLEAN m_IsEdgeTriggeredNonMsiInterrupt;

protected:

    LIST_ENTRY  m_PnpList;

public:
    FxInterrupt(
        __in PFX_DRIVER_GLOBALS FxDriverGlobals
        );

    virtual
    ~FxInterrupt(
        VOID
        );

    _Must_inspect_result_
    static
    NTSTATUS
    _CreateAndInit(
        __in PFX_DRIVER_GLOBALS FxDriverGlobals,
        __in CfxDevice * Device,
        __in_opt FxObject * Parent,
        __in PWDF_OBJECT_ATTRIBUTES Attributes,
        __in PWDF_INTERRUPT_CONFIG Configuration,
        __out FxInterrupt ** Interrupt
        );

    _Must_inspect_result_
    NTSTATUS
    CreateWakeInterruptMachine(
        VOID
        );

    _Must_inspect_result_
    NTSTATUS
    Initialize(
        __in CfxDevice* Device,
        __in FxObject*  Parent,
        __in PWDF_INTERRUPT_CONFIG Configuration
        );

    _Must_inspect_result_
    NTSTATUS
    InitializeWorker(
        __in FxObject*  Parent,
        __in PWDF_INTERRUPT_CONFIG Configuration
        );

    _Must_inspect_result_
    NTSTATUS
    InitializeInternal(
        __in FxObject*  Parent,
        __in PWDF_INTERRUPT_CONFIG Configuration
        );

    virtual
    BOOLEAN
    Dispose(
        VOID
        );

    virtual
    VOID
    DeleteObject(
        VOID
        );

    VOID
    OnPostReleaseHardware(
        VOID
        );

    VOID
    DpcHandler(
        __in_opt PVOID SystemArgument1,
        __in_opt PVOID SystemArgument2
        );

    BOOLEAN
    QueueDpcForIsr(
        VOID
        );

    BOOLEAN
    Synchronize(
        __in  PFN_WDF_INTERRUPT_SYNCHRONIZE Callback,
        __in  WDFCONTEXT                    Context
        );

    struct _KINTERRUPT*
    GetInterruptPtr(
        VOID
        );

    __inline
    BOOLEAN
    IsWakeCapable(
        VOID
        )
    {
        return ((m_WakeInterruptMachine != NULL) ? TRUE:FALSE);
    }

    VOID
    SetActiveForWake(
        __in BOOLEAN ActiveForWake
        )
    {
        m_WakeInterruptMachine->m_ActiveForWake = ActiveForWake;
    }

    BOOLEAN
    IsActiveForWake(
        VOID
        )
    {
        if ((m_WakeInterruptMachine != NULL) &&
            (m_WakeInterruptMachine->m_ActiveForWake)) {
            return TRUE;
        } else {
            return FALSE;
        }
    }

    VOID
    ProcessWakeInterruptEvent(
        __in FxWakeInterruptEvents Event
        )
    {
        m_WakeInterruptMachine->ProcessEvent(Event);
    }


#if ((FX_CORE_MODE)==(FX_CORE_KERNEL_MODE))

    VOID
    ReportActive(
        _In_ BOOLEAN Internal = FALSE
        );

    VOID
    ReportInactive(
        _In_ BOOLEAN Internal = FALSE
        );

    BOOLEAN
    IsSoftDisconnectCapable(
        VOID
        )
    {
        if (m_UseSoftDisconnect &&
            FxLibraryGlobals.IoReportInterruptInactive != NULL &&
            m_Interrupt != NULL &&
            m_Connected) {
            return TRUE;
        }
        else {
            return FALSE;
        }
    }

#elif ((FX_CORE_MODE)==(FX_CORE_USER_MODE))

    BOOLEAN
    IsSoftDisconnectCapable(
        VOID
        )
    {
        //
        // Not implemented for UMDF
        //
        return FALSE;
    }

    VOID
    ReportActive(
        _In_ BOOLEAN Internal = FALSE
        )
    {
        UNREFERENCED_PARAMETER(Internal);
        //
        // Not implemented for UMDF
        //
    }

    VOID
    ReportInactive(
        _In_ BOOLEAN Internal = FALSE
        )
    {
        UNREFERENCED_PARAMETER(Internal);
        //
        // Not implemented for UMDF
        //
    }

#endif

    VOID
    WorkItemHandler(
        VOID
        );

    BOOLEAN
    QueueWorkItemForIsr(
        VOID
        );

    __inline
    BOOLEAN
    IsPassiveHandling(
        VOID
        )
    {
        return m_PassiveHandling;
    }

    __inline
    BOOLEAN
    IsPassiveConnect(
        VOID
        )
    {
        //
        // UMDF's handling of interrupt is split in two parts:
        // 1. framework code that runs at passive always in host process and
        //    therefore uses mode-agnostic code meant for passive-level handling,
        //    tracked through m_PassiveHandling member.
        //    field of interrupt object.
        // 2. redirector code that does passive handling of all of level-triggered
        //    interrupt and DIRQL handing of all others (edge and msi). Driver
        //    doesn't have any choice in that. The m_PassiveHandling field in the
        //    interrupt config is always set for passive for UMDF (through UMDF's
        //    init function). m_PasiveHandlingByRedirector member is present to
        //    this part of code.
        // In summary, m_PassiveHandling and m_PasiveHandlingByRedirector
        // effectively maintain how the interrupt is connected (passive or DIRQL),
        // for KMDF and UMDF respectively. This routine tells how the
        // interrupt is connnected by looking at these members.
        //
#if (FX_CORE_MODE == FX_CORE_KERNEL_MODE)
        return IsPassiveHandling();
#else
        return m_PassiveHandlingByRedirector;
#endif
    }

    __inline
    BOOLEAN
    IsAutomaticSerialization(
        VOID
        )
    {
        return m_CallbackLock != NULL ? TRUE : FALSE;
    }

    VOID
    AcquireLock(
        VOID
        );

    BOOLEAN
    TryToAcquireLock(
        VOID
        );

    VOID
    ReleaseLock(
        VOID
        );

    CfxDevice*
    GetDevice(
        VOID
        )
    {
        return m_Device;
    }

    PWDF_INTERRUPT_INFO
    GetInfo(
        VOID
        );

    WDFINTERRUPT
    GetHandle(
        VOID
        )
    {
        return (WDFINTERRUPT) GetObjectHandle();
    }

    BOOLEAN
    IsSharedSpinLock(
        VOID
        )
    {
        return m_SpinLock != &m_BuiltInSpinLock.Get() ? TRUE : FALSE;
    }

    BOOLEAN
    IsSyncIrqlSet(
        VOID
        )
    {
        return m_SynchronizeIrql != PASSIVE_LEVEL ? TRUE : FALSE;
    }

    KIRQL
    GetSyncIrql(
        VOID
        )
    {
        return m_SynchronizeIrql;
    }

    KIRQL
    GetResourceIrql(
        VOID
        )
    {
        return m_InterruptInfo.Irql;
    }

    BOOLEAN
    SharesLock(
        FxInterrupt* Interrupt
        )
    {
        return m_SpinLock == Interrupt->m_SpinLock ? TRUE : FALSE;
    }

private:
    VOID
    Reset(
        VOID
        );

    VOID
    ResetInternal(
        VOID
        );

    VOID
    SetSyncIrql(
        KIRQL SyncIrql
        )
    {
        m_SynchronizeIrql = SyncIrql;
    }

    //
    // Called from workitem to perform final flushing of any
    // outstanding DPC's and dereferencing of objects.
    //
    VOID
    FlushAndRundown(
        VOID
        );

    VOID
    FlushAndRundownInternal(
        VOID
        );

    static
    MdInterruptServiceRoutineType _InterruptThunk;

    static
    EVT_SYSTEMWORKITEM _InterruptWorkItemCallback;

    static
    MdInterruptSynchronizeRoutineType _InterruptSynchronizeThunk;

#if ((FX_CORE_MODE)==(FX_CORE_KERNEL_MODE))

    static
    MdDeferredRoutineType _InterruptDpcThunk;

#elif ((FX_CORE_MODE)==(FX_CORE_USER_MODE))

    static
    MX_WORKITEM_ROUTINE _InterruptWorkItemThunk;

    VOID
    ThreadpoolWaitCallback(
        VOID
        );

    VOID
    QueueSingleWaitOnInterruptEvent(
        VOID
        );

    VOID
    StartThreadpoolWaitQueue(
        VOID
        );

    VOID
    StopAndFlushThreadpoolWaitQueue(
        VOID
        );

#endif

    //
    // Helper functions to enable an interrupt.
    // Sequence:
    //  (1) InterruptEnable
    //  (2) _InterruptEnableThunk
    //  (3) InterruptEnableInvokeCallback
    //
    NTSTATUS
    InterruptEnable(
        VOID
        );

    static
    MdInterruptSynchronizeRoutineType _InterruptEnableThunk;


    NTSTATUS
    InterruptEnableInvokeCallback(
        VOID
        );

    //
    // Helper functions to disable an interrupt.
    // Sequence:
    //  (1) InterruptDisable
    //  (2) _InterruptDisableThunk
    //  (3) InterruptDisableInvokeCallback
    //
    NTSTATUS
    InterruptDisable(
        VOID
        );

    static
    MdInterruptSynchronizeRoutineType _InterruptDisableThunk;


    NTSTATUS
    InterruptDisableInvokeCallback(
        VOID
        );
public:
    static
    BOOLEAN
    _IsMessageInterrupt(
        __in USHORT ResourceFlags
        )
    {
        if (ResourceFlags & CM_RESOURCE_INTERRUPT_MESSAGE) {
            return TRUE;
        }
        else {
            return FALSE;
        }
    }

    static
    BOOLEAN
    _IsWakeHintedInterrupt(
        __in USHORT ResourceFlags
        )
    {
        if (ResourceFlags & CM_RESOURCE_INTERRUPT_WAKE_HINT) {
            return TRUE;
        }
        else {
            return FALSE;
        }
    }

    _Must_inspect_result_
    NTSTATUS
    Connect(
        __in ULONG NotifyFlags
        );

    _Must_inspect_result_
    NTSTATUS
    ConnectInternal(
        VOID
        );

    _Must_inspect_result_
    NTSTATUS
    Disconnect(
        __in ULONG NotifyFlags
        );

    VOID
    DisconnectInternal(
        VOID
        );

    _Must_inspect_result_
    NTSTATUS
    ForceDisconnect(
        VOID
        );

    _Must_inspect_result_
    NTSTATUS
    ForceReconnect(
        VOID
        );

    VOID
    FilterResourceRequirements(
        __inout PIO_RESOURCE_DESCRIPTOR IoResourceDescriptor
        );

    VOID
    AssignResources(
        __in PCM_PARTIAL_RESOURCE_DESCRIPTOR CmDescRaw,
        __in PCM_PARTIAL_RESOURCE_DESCRIPTOR CmDescTrans
        );

    PCM_PARTIAL_RESOURCE_DESCRIPTOR
    GetResources(
        VOID
        )
    {
        // Weak ref to the translated resource interrupt descriptor.
        // It is valid from prepare hardware callback to release hardware callback.
        return m_CmTranslatedResource;
    }

    VOID
    AssignResourcesInternal(
        __in PCM_PARTIAL_RESOURCE_DESCRIPTOR CmDescRaw,
        __in PCM_PARTIAL_RESOURCE_DESCRIPTOR CmDescTrans,
        __in PWDF_INTERRUPT_INFO InterruptConfig
        );

    VOID
    RevokeResources(
        VOID
        );

    VOID
    RevokeResourcesInternal(
        VOID
        );

    VOID
    SetPolicy(
        __in WDF_INTERRUPT_POLICY   Policy,
        __in WDF_INTERRUPT_PRIORITY Priority,
        __in PGROUP_AFFINITY        TargetProcessorSet
        );

    VOID
    SetPolicyInternal(
        __in WDF_INTERRUPT_POLICY   Policy,
        __in WDF_INTERRUPT_PRIORITY Priority,
        __in PGROUP_AFFINITY        TargetProcessorSet
        );

    VOID
    FlushQueuedDpcs(
        VOID
        );

    VOID
    FlushQueuedWorkitem(
        VOID
        );

    VOID
    InvokeWakeInterruptEvtIsr(
        VOID
        );

    BOOLEAN
    WakeInterruptIsr(
        VOID
        );

    BOOLEAN
    IsLevelTriggered(
        __in ULONG Flags
        )
    {
        return ((Flags & CM_RESOURCE_INTERRUPT_LEVEL_LATCHED_BITS)
            == CM_RESOURCE_INTERRUPT_LEVEL_SENSITIVE);
    }

    __inline
    BOOLEAN
    QueueDeferredRoutineForIsr(
        VOID
        )
    {
    //
    // Queue DPC for KMDF and workitem for UMDF. Note that driver can either
    // specify EvtInterruptDpc or EvtInterruptWorkItem, and therefore it can
    // either call WdfInterruptQueueDpcForisr or WdfInterruptQueueWorkitemForIsr.
    //




    //
#if (FX_CORE_MODE == FX_CORE_KERNEL_MODE)
        return QueueDpcForIsr();
#else
        return QueueWorkItemForIsr();
#endif
     }

};

BOOLEAN
_SynchronizeExecution(
    __in MdInterrupt  Interrupt,
    __in MdInterruptSynchronizeRoutine  SynchronizeRoutine,
    __in PVOID  SynchronizeContext
    );

#endif // _FXINTERRUPT_H_