/*++

Copyright (c) Microsoft Corporation

Module Name:

    FxDmaEnabler.hpp

Abstract:

    WDF DMA Enabler support

Environment:

    Kernel mode only.

Notes:


Revision History:

--*/

#ifndef __FX_DMA_ENABLER_HPP__
#define __FX_DMA_ENABLER_HPP__

#include "fxdmaenablercallbacks.hpp"

//
// Dma Description structure
//
typedef struct _FxDmaDescription {

    DEVICE_DESCRIPTION     DeviceDescription;

    PDMA_ADAPTER           AdapterObject;

    //
    // The size of a preallocated lookaside list for this DMA adapter
    //
    size_t                 PreallocatedSGListSize;

    size_t                 MaximumFragmentLength;

    ULONG                  NumberOfMapRegisters;

} FxDmaDescription;

enum FxDuplexDmaDescriptionType {
    FxDuplexDmaDescriptionTypeRead = 0,
    FxDuplexDmaDescriptionTypeWrite,
    FxDuplexDmaDescriptionTypeMax,
};

//
// Make sure the two enums which match to the channels in the enabler match
// corresponding values.
//

C_ASSERT(((ULONG) FxDuplexDmaDescriptionTypeRead) == ((ULONG) WdfDmaDirectionReadFromDevice));
C_ASSERT(((ULONG) FxDuplexDmaDescriptionTypeWrite) == ((ULONG) WdfDmaDirectionWriteToDevice));

//
// Declare the FxDmaEnabler class
//
class FxDmaEnabler : public FxNonPagedObject {

    friend class FxDmaTransactionBase;
    friend class FxDmaPacketTransaction;
    friend class FxDmaScatterGatherTransaction;
    friend class FxDmaSystemTransaction;

public:

    FxDmaEnabler(
        __in PFX_DRIVER_GLOBALS FxDriverGlobals
        );

    ~FxDmaEnabler();

    virtual
    BOOLEAN
    Dispose(
        VOID
        );

    _Must_inspect_result_
    NTSTATUS
    Initialize(
        __in    PWDF_DMA_ENABLER_CONFIG  Config,
        __inout FxDeviceBase            *Device
        );

    _Must_inspect_result_
    NTSTATUS
    ConfigureSystemAdapter(
        __in PWDF_DMA_SYSTEM_PROFILE_CONFIG Config,
        __in WDF_DMA_DIRECTION              ConfigDirection
        );

    VOID
    AllocateCommonBuffer(
        __in         size_t        Length,
        __deref_out_opt PVOID    * BufferVA,
        __out PHYSICAL_ADDRESS   * BufferPA
        );

    VOID
    FreeCommonBuffer(
        __in size_t               Length,
        __in PVOID                BufferVA,
        __in PHYSICAL_ADDRESS     BufferPA
        );

    _Must_inspect_result_
    NTSTATUS
    PowerUp(
        VOID
        );

    _Must_inspect_result_
    NTSTATUS
    PowerDown(
        VOID
        );

    VOID
    RevokeResources(
        VOID
        );

    VOID
    InitializeTransferContext(
        __out PVOID             Context,
        __in  WDF_DMA_DIRECTION Direction
        );

    __inline
    size_t
    GetMaximumLength(
        VOID
        )
    {
        //
        // This value is same for all the channels and equal to the value
        // provided in the DMA_ENABLER_CONFIG.
        //
        return GetReadDmaDescription()->DeviceDescription.MaximumLength;
    }

    __inline
    size_t
    GetAlignment(
        VOID
        )
    {
        return m_CommonBufferAlignment;
    }

    __inline
    WDFDMAENABLER
    GetHandle(
        VOID
        )
    {
        return (WDFDMAENABLER) GetObjectHandle();
    }

    __inline
    WDFDEVICE
    GetDeviceHandle(
        VOID
        )
    {

        return m_DeviceBase->GetHandle();
    }

    __inline
    size_t
    GetMaxSGElements(
        VOID
        )
    {
        return m_MaxSGElements;
    }

    __inline
    VOID
    SetMaxSGElements(
        __in size_t MaximumSGElements
        )
    {
        m_MaxSGElements = (ULONG) MaximumSGElements;
    }

    __inline
    WDF_DMA_PROFILE
    GetProfile(
        VOID
        )
    {
        return m_Profile;
    }

    __inline
    BOOLEAN
    SupportsChainedMdls(
        VOID
        )
    {
        //
        // The only case where we don't support chained MDLS is DMAV2
        // with packet mode.
        //

        if ((UsesDmaV3() == false) &&
            (m_IsBusMaster == TRUE) &&
            (m_IsScatterGather == FALSE)) {
            return false;
        } else {
            return true;
        }
    }

    __inline
    BOOLEAN
    IsBusMaster(
        VOID
        )
    {
        return m_IsBusMaster;
    }

    __inline
    BOOLEAN
    IsPacketBased(
        )
    {
        return m_IsScatterGather ? FALSE : TRUE ;
    }

    __inline
    FxDmaDescription*
    GetDmaDescription(
        __in WDF_DMA_DIRECTION Direction
        )
    {
        if (m_IsDuplexTransfer) {
            return &m_DuplexAdapterInfo[Direction];
        }
        else {
            return &m_SimplexAdapterInfo;
        }
    }


    __inline
    FxDmaDescription*
    GetWriteDmaDescription(
        VOID
        )
    {
        if (m_IsDuplexTransfer) {
            return &m_DuplexAdapterInfo[FxDuplexDmaDescriptionTypeWrite];
        } else {
            return &m_SimplexAdapterInfo;
        }
    }

    __inline
    FxDmaDescription*
    GetReadDmaDescription(
        VOID
        )
    {
        if (m_IsDuplexTransfer) {
            return &m_DuplexAdapterInfo[FxDuplexDmaDescriptionTypeRead];
        } else {
            return &m_SimplexAdapterInfo;
        }
    }

    BOOLEAN
    UsesDmaV3(
        VOID
        )
    {
        FxDmaDescription* description;

        //
        // It doesn't matter which direction we use below.  Direction is
        // ignored for the simplex enabler, and will be the same for both
        // channels in a duplex enabler.
        //

        description = GetDmaDescription(WdfDmaDirectionReadFromDevice);

        return description->DeviceDescription.Version == DEVICE_DESCRIPTION_VERSION3;
    }

    USHORT
    GetTransferContextSize(
        VOID
        )
    {
        return UsesDmaV3() ? DMA_TRANSFER_CONTEXT_SIZE_V1 : 0;
    }

public:
    //
    // Link into list of FxDmaEnabler pointers maintained by the pnp package.
    //
    FxTransactionedEntry m_TransactionLink;

protected:

    PDEVICE_OBJECT          m_FDO;

    PDEVICE_OBJECT          m_PDO;

    union {
        //
        // Used if the dma profile is not duplex. Common for both read & write.
        // All the information specific to the channel are stored in the struct.
        //
        FxDmaDescription        m_SimplexAdapterInfo;

        //
        // Used if the dma profile is duplex.
        //
        FxDmaDescription        m_DuplexAdapterInfo[FxDuplexDmaDescriptionTypeMax];
    };

    //
    // The profile of the DMA enabler.
    //
    WDF_DMA_PROFILE         m_Profile;

    //
    // Whether the enabler object is added to the device enabler list.
    //
    BOOLEAN                 m_IsAdded : 1;

    //
    // Whether the DMA enabler has been configured with DMA resources & has its
    // adapters.
    //
    BOOLEAN                 m_IsConfigured : 1;

    //
    // The DMA profile broken out into individual flags.
    //
    BOOLEAN                 m_IsBusMaster : 1;

    BOOLEAN                 m_IsScatterGather : 1;

    BOOLEAN                 m_IsDuplexTransfer : 1;

    //
    // Has the preallocated scatter gather list (single list or lookaside,
    // depending on profile) been allocated.  Indicates initialization state
    // of m_SGList below.
    //
    BOOLEAN                 m_IsSGListAllocated: 1;

    //
    // This value is larger of aligment value returned by HAL(which is always 1)
    // and the alignment value set on the device by WdfDeviceSetAlignmentRequirement.
    //
    ULONG                   m_CommonBufferAlignment;

    //
    // The maximum DMA transfer the enabler should support (saved from the enabler
    // config)
    //
    ULONG                   m_MaximumLength;

    ULONG                   m_MaxSGElements;

    //
    // The size of the preallocated SGList entries, in bytes.  This is for entries
    // on the lookaside list or the single entry list.
    //
    size_t                  m_SGListSize;

    //
    // Storage for scatter gather lists.  Whether the lookaside or the single entry
    // the size in bytes is described by m_SGListSize
    //
    // The m_IsSGListAllocated bit above indicates whether we've
    // initialized this structure or not.
    //
    union {

        //
        // For the scatter gather profile we have a lookaside list of SGLists
        // We allocate these dynamically because (a) we can be mapping
        // multiple transactions in parallel and (b) the number of map registers
        // for SG DMA may be very large and we don't necessarily need one per
        // transaction at all times
        //
        // For duplex channels, the entry size is larger of read & write channels.
        //
        struct {
            NPAGED_LOOKASIDE_LIST   Lookaside;
        } ScatterGatherProfile;

        //
        // A single SGList for use with system DMA transfers.  We can use a single
        // list because (a) there is only one system DMA transaction being mapped
        // at any given time (for this adapter) and (b) we don't use the physical
        // addresses or give them to the driver so they don't need to be preserved
        // very long (the list is only so the HAL has enough scratch space to tell
        // the HAL DMA extension which PA's comprise the transfer)
        //
        struct {
            PSCATTER_GATHER_LIST    List;
        } SystemProfile;

        //
        // NOTE: there is no preallocated entry for packet based bus mastering DMA
        //       because we could be mapping multiple transactions in parallel but
        //       the size of the SGList is static and can be allocated on the stack
        //

    } m_SGList;

private:
    //
    // Power-related callbacks.
    //
    FxEvtDmaEnablerFillCallback               m_EvtDmaEnablerFill;
    FxEvtDmaEnablerFlushCallback              m_EvtDmaEnablerFlush;
    FxEvtDmaEnablerEnableCallback             m_EvtDmaEnablerEnable;
    FxEvtDmaEnablerDisableCallback            m_EvtDmaEnablerDisable;
    FxEvtDmaEnablerSelfManagedIoStartCallback m_EvtDmaEnablerSelfManagedIoStart;
    FxEvtDmaEnablerSelfManagedIoStopCallback  m_EvtDmaEnablerSelfManagedIoStop;

    //
    // Note that these fields form an informal state engine.
    //
    BOOLEAN   m_DmaEnablerFillFailed;
    BOOLEAN   m_DmaEnablerEnableFailed;
    BOOLEAN   m_DmaEnablerSelfManagedIoStartFailed;

    _Must_inspect_result_
    NTSTATUS
    ConfigureBusMasterAdapters(
        __in PDEVICE_DESCRIPTION DeviceDescription,
        __in PWDF_DMA_ENABLER_CONFIG Config
        );

    _Must_inspect_result_
    NTSTATUS
    ConfigureDmaAdapter(
        __in PDEVICE_DESCRIPTION DeviceDescription,
        __in WDF_DMA_DIRECTION   ConfigDirection
        );

    _Must_inspect_result_
    NTSTATUS
    InitializeResources(
        __inout FxDmaDescription *AdapterInfo
        );

    VOID
    ReleaseResources(
        VOID
        );

    VOID
    FreeResources(
        __inout FxDmaDescription *AdapterInfo
        );

};  // end of class

#endif // _FXDMAENABLER_HPP_