/*
* Copyright (c) 2015-2017, Intel Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
//!
//! \file      mhw_block_manager.h 
//! \brief         This modules implements state heap block manager used in MHW 
//!
#ifndef __MHW_BLOCK_MANAGER_H__
#define __MHW_BLOCK_MANAGER_H__

#include "mos_os.h"
#include "mhw_state_heap.h"
#include "mhw_memory_pool.h"

#define BLOCK_MANAGER_CHK_STATUS(_stmt)                                               \
    MOS_CHK_STATUS_RETURN(MOS_COMPONENT_HW, MOS_HW_SUBCOMP_ALL, _stmt)

#define BLOCK_MANAGER_CHK_STATUS_MESSAGE(_stmt, _message, ...)                        \
    MOS_CHK_STATUS_MESSAGE_RETURN(MOS_COMPONENT_HW, MOS_HW_SUBCOMP_ALL, _stmt, _message, ##__VA_ARGS__)

#define BLOCK_MANAGER_CHK_NULL(_ptr)                                                  \
    MOS_CHK_NULL_RETURN(MOS_COMPONENT_HW, MOS_HW_SUBCOMP_ALL, _ptr)

#define MHW_BLOCK_MANAGER_INVALID_TAG ((uint32_t)-1)

// Maximum allocation array size
#define MHW_BLOCK_MANAGER_MAX_BLOCK_ARRAY  64

typedef struct _MHW_BLOCK_MANAGER_PARAMS
{
    uint32_t                     dwPoolInitialCount;     //!< Initial number of memory blocks in pool
    uint32_t                     dwPoolMaxCount;         //!< Maximum number of memory blocks in pool
    uint32_t                     dwPoolIncrement;        //!< Memory block pool increment
    uint32_t                     dwHeapInitialSize;      //!< Initial heap size
    uint32_t                     dwHeapIncrement;        //!< Heap size increment
    uint32_t                     dwHeapMaxSize;          //!< Maximum overall heap size
    uint32_t                     dwHeapMaxCount;         //!< Maximum number of heaps
    uint32_t                     dwHeapGranularity;      //!< Block granularity
    uint32_t                     dwHeapBlockMinSize;     //!< Minimum block fragment size (never create a block smaller than this)
} MHW_BLOCK_MANAGER_PARAMS, *PMHW_BLOCK_MANAGER_PARAMS;

#define MHW_BLOCK_POSITION_TAIL (NULL)
#define MHW_BLOCK_POSITION_HEAD ((PMHW_STATE_HEAP_MEMORY_BLOCK) -1)

typedef struct _MHW_BLOCK_LIST
{
    PMHW_BLOCK_MANAGER              pBlockManager;      //!< Block Manager the list belongs to
    PMHW_STATE_HEAP_MEMORY_BLOCK    pHead;              //!< Head of the list
    PMHW_STATE_HEAP_MEMORY_BLOCK    pTail;              //!< Tail of the list
    MHW_BLOCK_STATE                 BlockState;         //!< Described the type of block the list contains
    int32_t                         iCount;             //!< Number of elements on the list
    uint32_t                        dwSize;             //!< Total memory in the list
    char                            szListName[16];     //!< List name for debugging purposes
} MHW_BLOCK_LIST, *PMHW_BLOCK_LIST;

struct MHW_BLOCK_MANAGER
{
private:
    MHW_BLOCK_MANAGER_PARAMS m_Params;                              //!< Block Manager configuration
    MHW_MEMORY_POOL          m_MemoryPool;                          //!< Memory pool of PMHW_STATE_HEAP_MEMORY_BLOCK objects
    MHW_BLOCK_LIST           m_BlockList[MHW_BLOCK_STATE_COUNT];    //!< Block lists associated with each block state
    PMHW_STATE_HEAP          m_pStateHeap;                          //!< Points to state heap

public:

    //!
    //! \brief    Initializes the MHW_BLOCK_MANAGER
    //! \details  Constructor of MHW_BLOCK_MANAGER which initializes all parameters and members
    //! \param    [in] pParams
    //!           Pointer to block manager params. If it is null, default param will be used.
    //!
    MHW_BLOCK_MANAGER(PMHW_BLOCK_MANAGER_PARAMS pParams);

    //!
    //! \brief    Destructor of MHW_BLOCK_MANAGER
    ~MHW_BLOCK_MANAGER();

    //!
    //! \brief    Adds newly created state heap to block manager
    //! \details  Adds a new state heap to memory block manager. A new memory free memory block is added to
    //!           the free list representing the new available heap.
    //! \param    [in] pStateHeap
    //!           Pointer to the newly created state heap
    //! \return   MOS_STATUS
    //!           Returns the status of the operation
    //!
    MOS_STATUS RegisterStateHeap(PMHW_STATE_HEAP    pStateHeap);

    //!
    //! \brief    Unregisters state heap from block manager prior to deallocation
    //! \details  Removes state heap from block manager. Memory blocks must be all deleted.
    //! \param    [in] pStateHeap
    //!           Pointer to the newly created state heap
    //! \return   MOS_STATUS
    //!           Returns the status of the operation
    //!
    MOS_STATUS UnregisterStateHeap(PMHW_STATE_HEAP    pStateHeap);

    //!
    //! \brief    Update block states based on last executed tag
    //! \details  Update block states based on last executed tag
    //!           submitted unlocked blocks are released;
    //!           move to allocated
    //! \param    [in] dwSyncTag
    //!           sync tag
    //! \return   MOS_STATUS
    //!           Returns the status of the operation
    //!
    MOS_STATUS Refresh(uint32_t dwSyncTag);

    //!
    //! \brief    Allocate memory block with scratch space
    //! \details  Allocate memory block with scratch space
    //! \param    [in] dwSize
    //!           Size of memory block to allocate 
    //! \param    [in] dwAlignment
    //!           Alignment
    //! \param    [in] dwScratchSpace
    //!           Scratch space size
    //! \return   PMHW_STATE_HEAP_MEMORY_BLOCK
    //!           Pointer to the allocated memory block
    //!
    PMHW_STATE_HEAP_MEMORY_BLOCK AllocateWithScratchSpace(
        uint32_t            dwSize,
        uint32_t            dwAlignment,
        uint32_t            dwScratchSpace);

    //!
    //! \brief    Allocate free block of memory
    //! \details  Allocate free memory block of memory for use by the client
    //! \param    [in] dwSize
    //!           Size of memory to be allocated
    //! \param    [in] dwAlignment
    //!           Memory alignment
    //! \param    [in] pHeapAffinity
    //!           Pointer to heap affinity
    //! \return   PMHW_STATE_HEAP_MEMORY_BLOCK
    //!           Pointer to allocated memory block
    //!
    PMHW_STATE_HEAP_MEMORY_BLOCK AllocateBlock(
        uint32_t            dwSize,
        uint32_t            dwAlignment,
        PMHW_STATE_HEAP     pHeapAffinity);

    //!
    //! \brief    Free memory block
    //! \details  Free memory block according to the sync tag
    //! \param    [in] pBlock
    //!           Pointer to memory block to be freed
    //! \param    [in] dwSyncTag
    //!           Sync tag
    //! \return   MOS_STATUS
    //!           Returns the status of the operation
    //!
    MOS_STATUS FreeBlock(
        PMHW_STATE_HEAP_MEMORY_BLOCK pBlock,
        uint32_t dwSyncTag);

    //!
    //! \brief    Calculate the memory of space required
    //! \details  Calculate the memory of space required
    //! \param    [in] pdwSizes
    //!           Pointer to memory block to be freed
    //! \param    [in] iCount
    //!           Sync tag
    //! \param    [in] dwAlignment
    //!           alignment
    //! \param    [in] bHeapAffinity 
    //!           heap affinity
    //! \param    [in] pHeapAffinity 
    //!           pointer to state heap affinity
    //! \return   uint32_t Size of space needed by client
    //!
    uint32_t CalculateSpaceNeeded(
        const uint32_t      *pdwSizes,
        int32_t             iCount,
        uint32_t            dwAlignment,
        bool                bHeapAffinity,
        PMHW_STATE_HEAP     pHeapAffinity);

    //!
    //! \brief    Submit memory block
    //! \details  Submit memory block according to the sync tag
    //! \param    [in] pBlock
    //!           Pointer to memory block to be submitted
    //! \param    [in] dwSyncTag
    //!           Sync tag
    //! \return   MOS_STATUS
    //!           Returns the status of the operation
    //!
    MOS_STATUS SubmitBlock(
        PMHW_STATE_HEAP_MEMORY_BLOCK pBlock,
        uint32_t                     dwSyncTag);

    //!
    //! \brief    Set state heap to block manager
    //! \details  Set state heap to block manager
    //! \param    [in] pStateHeap
    //!           Pointer to state heap
    //!
    void SetStateHeap(PMHW_STATE_HEAP pStateHeap);

    // Multiple Block Allocation algorithm description (multiple kernel load):
    //
    // Multiple blocks must be efficiently allocated in multiple heaps or in a single heap.
    //
    // Each load may introduce block fragmentation. The more kernels are loaded, the greater
    // are the chances to fragment the heap, reducing the chances of finding contiguous for a
    // larger kernel. Therefore, the efficient way to use the heap is to load larger kernels
    // first ensuring they have enough contiguous space, and then load the smaller kernels,
    // which are more likely to fit in remaining blocks.
    //
    // So the algorithm is the following:
    //
    // 1) The total size of all blocks is calculated to check if there's even a fighting chance
    //    to load them all - if the amount available is insufficient, other measures will be taken.
    //
    // 2) In order to allocate blocks according to size, we first sort the array of sizes
    //    using an index array (never touching the source array!) - this is done by a merge sort
    //    implementation, which is O(n*log(n)) - may try using other algorithm such as QuickSort -
    //    although quicksort is not always the best (may be O(n^2) in same cases)
    //
    // 3) Select a specific heap (or nullptr if bHeapAffinity is false). Check if there is enough space
    //    to load all blocks - ignoring the fragmentation for now. Only by traversing the list of
    //    blocks we will be able to determine if the heap can be used or not. Trying to load larger
    //    kernels FIRST helps identifying heap fragmentation issues faster than leaving it for last.
    //
    // 4) Load the array of kernels one at a time according to size (larger first). Select a specific
    //    heap or nullptr (if any heap, don't care).
    //
    // 5) If all succeeded, then we are ready to return the list of blocks to the client.
    //
    // 6) If it fails, release the already allocated blocks. the algorithm does check for space in the
    //    current heap before even starting the loop. The failure will not happen for lack of space,
    //    but because of fragmentation of free space. In case of bHeapAffinity = false, we also check
    //    the aggregate free space.
    //
    // 7) If the heap affinity is selected and the intended heap is also provided, we're done - if we
    //    were not able to load the blocks, the overall operation has failed. If affinity is selected but
    //    the heap was not provided, we try the next heap until no more heaps are available. This particular
    //    situation is intended for loading all blocks in the same heap without specifying which one.
    //

    //!
    //! \brief    Allocate multiple blocks 
    //! \details  Allocate multiple blocks 
    //! \param    [in]  pdwSizes
    //!           Pointer to sizes
    //! \param    [in]  iCount
    //!           Count of blocks to be allocated
    //! \param    [in] dwAlignment
    //!           Alignment
    //! \param    [in] bHeapAffinity
    //!           true if all blocks must be allocated in the same heap 
    //! \param    [in] pHeapAffinity
    //!           Pointer to heap affinity 
    //! \return   PMHW_STATE_HEAP_MEMORY_BLOCK
    //!           Pointer to allocated memory block
    //!
    PMHW_STATE_HEAP_MEMORY_BLOCK AllocateMultiple(
        uint32_t            *pdwSizes,
        int32_t             iCount,
        uint32_t            dwAlignment,
        bool                bHeapAffinity,
        PMHW_STATE_HEAP     pHeapAffinity);

private:

    //!
    //! \brief    Gets memory block from pool
    //! \details  Gets memory block from pool, extending the pool if necessary
    //! \return   PMHW_STATE_HEAP_MEMORY_BLOCK
    //!           Returns a pointer to the memory block, nullptr if failed to allocate or
    //!           all blocks are in use and the pool reached its maximum size.
    //!
    PMHW_STATE_HEAP_MEMORY_BLOCK GetBlockFromPool();

    //!
    //! \brief    Extends pool of MHW_STATE_HEAP_MEMORY_BLOCK objects
    //! \details  Allocates an array of MHW_STATE_HEAP_MEMORY_BLOCK objects and appends to the pool of objects for reuse.
    //!           The allocation of memory block structures is limited to the maximum number defined when creating
    //!           the Memory Block Manager object. The increase in number of memory blocks may be due to increased
    //!           block fragmentation, system load or other factors (increase in queue depth, increas in memory
    //!           blocks maintained by the client - such as number of kernels loaded).
    //! \param    [in] dwCount
    //!           Number of additional blocks to add to the pool
    //! \return   N/A
    //!
    void ExtendPool(uint32_t dwCount);

    //!
    //! \brief    Attach block into appropriate memory block list
    //! \details  Sets block state and inserts it into the memory block list associated with the new state.
    //!           The block may be inserted at the "Head" or "Tail" of the list, or after another block from the same list.
    //! \param    [in]BlockState
    //!           New block state, defines which list the block is to be inserted.
    //!           NOTE: The block must not belong to any other list before calling this function (pPrev/pNext = nullptr).
    //! \param    [in] pBlock
    //!           Pointer to memory block
    //! \param    [in] pBlockPos
    //!           Pointer to memory block after which the block is to be inserted (inserted AFTER pBlockPos)
    //!           If set to MHW_BLOCK_POSITION_HEAD, inserts block at the head of the list.
    //!           If set to MHW_BLOCK_POSITION_TAIL, inserts block at the tail of the list.
    //! \return   MOS_STATUS
    //!           Returns error code
    //!
    MOS_STATUS AttachBlock(
        MHW_BLOCK_STATE              BlockState,
        PMHW_STATE_HEAP_MEMORY_BLOCK pBlock,
        PMHW_STATE_HEAP_MEMORY_BLOCK pBlockPos);

    //!
    //! \brief    INTERNAL function to insert a block into a block list.
    //! \details  Insert a block into a block list at a given position (head, tail, insert after block)
    //!           Ensures that the list and the block position are both valid.
    //!           Does not check if the block is still attached to another list.
    //!           IMPORTANT: Block must be detached, at the risk of corrupting other block lists.
    //!                      This function does not track state heap usage
    //! \param    [in] pList
    //!           Pointer to memory block list structure
    //! \param    [in] BlockState
    //!           New block state, defines the state of the new block,
    //!           must match the state associated with the list and reference block
    //! \param    [in] pBlock
    //!           Pointer to memory block to be inserted
    //! \param    [in] pBlockPos
    //!           Pointer to memory block after which the block is to be inserted (inserted AFTER pBlockPos)
    //!           If set to MHW_BLOCK_POSITION_HEAD, inserts block at the head of the list.
    //!           If set to MHW_BLOCK_POSITION_TAIL, inserts block at the tail of the list.
    //! \return   MOS_STATUS
    //!           Returns error code
    //!
    MOS_STATUS AttachBlockInternal(
        PMHW_BLOCK_LIST              pList,
        MHW_BLOCK_STATE              BlockState,
        PMHW_STATE_HEAP_MEMORY_BLOCK pBlock,
        PMHW_STATE_HEAP_MEMORY_BLOCK pBlockPos);

    //!
    //! \brief    Removes a block from a memory block list
    //! \details  Removes a block from a memory block list from a given 
    //! \param    [in] BlockState
    //!           Block state defines from which list the block is to be removed.
    //! \param    [in] pBlockPos
    //!           Defines the object in list to be detached.
    //!           If set to MHW_BLOCK_POSITION_HEAD, detaches the first block in the list.
    //!           If set to MHW_BLOCK_POSITION_TAIL, detaches the last block in the list.
    //! \return   PMHW_STATE_HEAP_MEMORY_BLOCK
    //!           Returns a pointer to the block detached, nullptr if the list is empty or invalid.
    //!
    PMHW_STATE_HEAP_MEMORY_BLOCK DetachBlock(
        MHW_BLOCK_STATE              BlockState,
        PMHW_STATE_HEAP_MEMORY_BLOCK pBlockPos);

    //!
    //! \brief    INTERNAL function to remove a block from a block list.
    //! \details  Removes a block from a list provided. The caller may specify the block
    //!           to remove, or request the head or tail of the list.
    //!           Ensures that the list and the block position are both valid.
    //!           Does not check if the block is still attached to another list.
    //!           IMPORTANT: Block must be detached, at the risk of corrupting other block lists.
    //!                      This function does not track state heap usage
    //! \param    [in] pList
    //!           Pointer to memory block list structure
    //! \param    [in] pBlock
    //!           Pointer to memory block to be inserted
    //! \return   PMHW_STATE_HEAP_MEMORY_BLOCK
    //!           Returns block, nullptr if failure or no block available
    //!
    PMHW_STATE_HEAP_MEMORY_BLOCK DetachBlockInternal(
        PMHW_BLOCK_LIST              pList,
        PMHW_STATE_HEAP_MEMORY_BLOCK pBlock);

    //!
    //! \brief    Interface to move block from one list to another
    //! \details  Moves a block from a list into another
    //!           Ensures that the list and the block position are both valid.
    //!           Does not check if the block is still attached to another list.
    //!           IMPORTANT: Block must be detached, at the risk of corrupting other block lists.
    //!                      This function does not track state heap usage
    //! \param    [in] pSrcList
    //!           Pointer to source list
    //! \param    [in] pDstList
    //!           Pointer to destination list
    //! \param    [in] pBlock
    //!           Pointer to memory block to be moved
    //! \param    [in] pBlockPos
    //!           Pointer to memory block after which the block is to be inserted (inserted AFTER pBlockPos)
    //!           If set to MHW_BLOCK_POSITION_HEAD, inserts block at the head of the list.
    //!           If set to MHW_BLOCK_POSITION_TAIL, inserts block at the tail of the list.
    //! \return   MOS_STATUS
    //!           Returns error code
    //!
    MOS_STATUS MoveBlock(
        PMHW_BLOCK_LIST              pSrcList,      // Source list
        PMHW_BLOCK_LIST              pDstList,      // Destination list
        PMHW_STATE_HEAP_MEMORY_BLOCK pBlock,        // Block to be moved (or HEAD/TAIL of source list)
        PMHW_STATE_HEAP_MEMORY_BLOCK pBlockPos);    // Position to insert (or HEAD/TAIL of target list)

    //!
    //! \brief    Returns memory block object to pool
    //! \details  Returns memory block object to pool
    //! \param    [in] pBlock
    //!           Pointer to memory block to be returned back to pool
    //! \return   N/A
    //!
    void ReturnBlockToPool(PMHW_STATE_HEAP_MEMORY_BLOCK pBlock);

    //!
    //! \brief    Consolidate free memory
    //! \details  Consolidate free memory blocks adjacent to a given free block (within the same state heap).
    //! \param    [in] pBlock
    //!           Pointer to free memory block
    //! \return    N/A
    //!
    void ConsolidateBlock(PMHW_STATE_HEAP_MEMORY_BLOCK pBlock);

    //!
    //! \brief    INTERNAL: Move block from free to allocated list
    //! \details  Move block from free to allocated list, setting up pointer/offset to data
    //! \param    [in] pBlock
    //!           Pointer to free memory block
    //! \param    uint32_t dwAlignment
    //!           [in] Data alignment, assured to be power of 2 by caller
    //! \return   MOS_STATUS
    //!           Returns the status of the operation
    //!
    MOS_STATUS AllocateBlockInternal(
        PMHW_STATE_HEAP_MEMORY_BLOCK pBlock,
        uint32_t                     dwAlignment);

    //!
    //! \brief    Split block internal
    //! \details  Split block internal
    //! \param    [in] pBlock
    //!           Pointer to block to be splitted
    //! \param    [in] dwSplitSize
    //!           Size of the block to be splitted 
    //! \param    [in] dwAlignment
    //!           Data alignment, assured to be power of 2 by caller
    //! \param    [in] bBackward
    //!           if true, use higher part of the block
    //! \return   MOS_STATUS
    //!           Returns the status of the operation
    //!
    MOS_STATUS SplitBlockInternal(
        PMHW_STATE_HEAP_MEMORY_BLOCK    pBlock,
        uint32_t                        dwSplitSize,
        uint32_t                        dwAlignment,
        bool                            bBackward);

    //!
    //! \brief    Merge blocks
    //! \details  Merge blocks
    //! \param    [in]  pBlockL
    //!           Pointer to block in lower memory
    //! \param    [in]  pBlockH
    //!           Pointer to block in higher memory
    //! \param    [in] dwAlignment
    //!           Data alignment, assured to be power of 2 by caller
    //! \param    [in] bBackward
    //!           if True, pBlcokL is merged into pBlockH
    //!           if False, pBlockH is merged into pBlockL
    //! \return   MOS_STATUS
    //!           Returns the status of the operation
    //!
    MOS_STATUS MergeBlocksInternal(
        PMHW_STATE_HEAP_MEMORY_BLOCK    pBlockL,
        PMHW_STATE_HEAP_MEMORY_BLOCK    pBlockH,
        uint32_t                        dwAlignment,
        bool                            bBackward);

    //!
    //! \brief    ResizeBlock
    //! \details  ResizeBlock
    //! \param    [in] pBlock
    //!           Pointer to block to be resized
    //! \param    [in]   dwNewSize
    //!           new size of block
    //! \param    [in] dwAlignment
    //!           Data alignment, assured to be power of 2 by caller
    //! \param    [in] bBackward
    //!           if True,  allow block to grow forward/backwards (moving its start offset)
    //!           if False, Always grow/shrink forward;
    //! \return   MOS_STATUS
    //!           Returns the status of the operation
    //!
    MOS_STATUS ResizeBlock(
        PMHW_STATE_HEAP_MEMORY_BLOCK    pBlock,
        uint32_t                        dwNewSize,
        uint32_t                        dwAlignment,
        bool                            bBackward);
};

#endif // __MHW_BLOCK_MANAGER_H__
