/*
* 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.c 
//! \brief         This modules implements memory block management functions as part of MHW     dynamic state heap implementation 
//!
#include "mhw_block_manager.h"
#include "mhw_utilities.h"
#include "mos_os_specific.h"

MHW_BLOCK_MANAGER_PARAMS MhwBlockManagerParams_default =
{
    64,                                // Initial number of block objects in pool
    1024,                              // Maximum number of block objects in pool
    64,                                // Block pool increment size
    0x00080000,                        // Initial heap size         (512k)
    0x00080000,                        // Heap size increment       (512k)
    0x01000000,                        // Maximum overall heap size (16M)
    32,                                // Maximum number of heaps (32) (512k x 32 = 16M)
    0x0800,                            // Block granularity   = 2k (also represents min block alignment)
    0x0800                             // Min free block size = 2k (to reduce fragmentation)
};

const char *szListName[MHW_BLOCK_STATE_COUNT] = {
    "POOL",
    "FREE",
    "ALLOCATED",
    "SUBMITTED",
    "DELETED"
};

void Mhw_BlockManager_ReverseMergeSort_With_Index(const uint32_t *pdwSizes, int32_t iCount, uint8_t *pSortedIndex)
{
    uint8_t i, n;
    uint8_t *pSrc, *pDst;                               // Source and Destination groups (alternate)
    uint8_t Index1[MHW_BLOCK_MANAGER_MAX_BLOCK_ARRAY];  // Temporary sorted indexes 1
    uint8_t Index2[MHW_BLOCK_MANAGER_MAX_BLOCK_ARRAY];  // Temporary sorted indexes 2 (alternating with Index1)
    uint8_t *s1, *s2;                                   // Merge source groups 1 and 2
    uint8_t n1, n2;                                     // Merge sizes  groups 1 and 2
    uint8_t *d;                                         // Merge destination

    // Very simple cases
    if (iCount <= 1)
    {
        pSortedIndex[0] = 0;
        return;
    }
    else if (iCount == 2)
    {
        pSortedIndex[0] = (pdwSizes[0] < pdwSizes[1]) ? 1 : 0;
        pSortedIndex[1] = 1 - pSortedIndex[0];
        return;
    }

    // Initialize sorted index table
    for (i = 0; i < iCount; i++)
    {
        Index1[i] = i;
    }

    // Start alternating buffers (last will be the actual output)
    pSrc = Index1;
    pDst = Index2;

    // Merge sort algorithm:
    //      Sort will perform O(log n) passes; first pass sorts (iCount/2) groups of 2, then (iCount/4) groups of 4, and so on.
    //      Each pass requires traversal of the entire list - O(n)
    //      Algorithm is expected to be O(n * log n)
    for (n = 1; n < iCount; n *= 2)
    {
        // Setup sorted target output
        if (n*2 < iCount)
        {
            d = pDst;
        }
        else
        {   // Last pass, output goes to caller
            d = pSortedIndex;
        }

        // Group selection and merge - O(n)
        s1 = pSrc - n;    // First  group
        s2 = pSrc;        // Second group
        for (i = n; i < iCount; i += 2*n)   // i is the offset of the 2nd group
        {
            s1 += n;
            n1 = n;
            s2 += n;
            n2 = n;

            // Limit size of last group
            if (i + n > iCount)
            {
                n2 = iCount - i;
            }

            // Merge groups
            while (n1 > 0 && n2 > 0)
            {
                if (pdwSizes[*s1] >= pdwSizes[*s2])
                {
                    *(d++) = *(s1++);
                    n1--;
                }
                else
                {
                    *(d++) = *(s2++);
                    n2--;
                }
            }

            // Merge remaining items
            while (n1 > 0)
            {
                *(d++) = *s1++;
                n1--;
            }
            while (n2 > 0)
            {
                *(d++) = *s2++;
                n2--;
            }
        }

        // Copy the last group (not merge-sorted)
        for (i = i - n; i < iCount; i++)
        {
            *(d++) = *(s2++);
        }

        // New pass, switch Src/Dst sorted index buffers
        d    = pDst;
        pDst = pSrc;
        pSrc = d;
    }
}

void Mhw_BlockManager_ReverseMergeSort(uint32_t *pdwSizes, int32_t iCount)
{
    uint8_t i, n;
    uint32_t *pSrc, *pDst;                                     // Source and Destination groups (alternate)
    uint32_t Buffer1[2 * MHW_BLOCK_MANAGER_MAX_BLOCK_ARRAY];   // Temporary sorted buffer 1
    uint32_t Buffer2[2 * MHW_BLOCK_MANAGER_MAX_BLOCK_ARRAY];   // Temporary sorted buffer 1
    uint32_t *s1, *s2;                                         // Merge source groups 1 and 2
    uint8_t n1, n2;                                            // Merge sizes  groups 1 and 2
    uint32_t *d;                                               // Merge destination

    // Very simple cases
    if (iCount <= 1)
    {
        return;
    }
    else if (iCount == 2)
    {
        if (pdwSizes[0] < pdwSizes[1])
        {
            uint32_t tmp   = pdwSizes[1];
            pdwSizes[1] = pdwSizes[0];
            pdwSizes[0] = tmp;
        }
        return;
    }
    else if (iCount > 2 * MHW_BLOCK_MANAGER_MAX_BLOCK_ARRAY)
    {
        return;
    }

    // Merge sort algorithm:
    //      Sort will perform O(log n) passes; first pass sorts (iCount/2) groups of 2, then (iCount/4) groups of 4, and so on.
    //      Each pass requires traversal of the entire list - O(n)
    //      Algorithm is expected to be O(n * log n)
    pSrc = Buffer1;
    pDst = Buffer2;
    for (n = 1; n < iCount; n *= 2)
    {
        if (n == 1)
        {
            s1 = pdwSizes - 1;
            s2 = pdwSizes;
        }
        else
        {
            s1 = pSrc - n;
            s2 = pSrc;
        }

        // Setup sorted target output
        if (n*2 < iCount)
        {
            d = pDst;
        }
        else
        {   // Last pass, output goes to caller
            d = pdwSizes;
        }

        // Group selection and merge - O(n)
        for (i = n; i < iCount; i += 2*n)   // i is the offset of the 2nd group
        {
            s1 += n;
            n1 =  n;
            s2 += n;
            n2 =  n;

            // Limit size of last group
            if (i + n > iCount)
            {
                n2 = iCount - i;
            }

            // Merge groups
            while (n1 > 0 && n2 > 0)
            {
                if (*s1 >= *s2)
                {
                    *(d++) = *(s1++);
                    n1--;
                }
                else
                {
                    *(d++) = *(s2++);
                    n2--;
                }
            }

            // Merge remaining items
            while (n1 > 0)
            {
                *(d++) = *s1++;
                n1--;
            }
            while (n2 > 0)
            {
                *(d++) = *s2++;
                n2--;
            }
        }

        // Copy the last group (not merge-sorted)
        for (i = i - n; i < iCount; i++)
        {
            *(d++) = *(s2++);
        }

        // New pass, switch Src/Dst
        d    = pDst;
        pDst = pSrc;
        pSrc = d;
    }
}

MHW_BLOCK_MANAGER::MHW_BLOCK_MANAGER(PMHW_BLOCK_MANAGER_PARAMS pParams):
    m_pStateHeap(nullptr),
    m_MemoryPool(sizeof(MHW_STATE_HEAP_MEMORY_BLOCK), sizeof(void *))
{
    //Init Parameters
    if(pParams != nullptr)
    {
        m_Params = *pParams;
    }
    else
    {
        m_Params = MhwBlockManagerParams_default;
    }

    //Init Memory block list
    for (int32_t i = (int32_t)MHW_BLOCK_STATE_POOL; i < MHW_BLOCK_STATE_COUNT; i++)
    {
        MOS_ZeroMemory(&m_BlockList[i], sizeof(MHW_BLOCK_LIST));
        m_BlockList[i].BlockState    = (MHW_BLOCK_STATE) i;
        m_BlockList[i].pBlockManager = this;
        MOS_SecureStrcpy(m_BlockList[i].szListName, 16, szListName[i]);
    }

    //Extend Pool
    ExtendPool(m_Params.dwPoolInitialCount);
}

MHW_BLOCK_MANAGER::~MHW_BLOCK_MANAGER()
{

}

void MHW_BLOCK_MANAGER::SetStateHeap(PMHW_STATE_HEAP pStateHeap)
{
    if (pStateHeap)
    {
        m_pStateHeap = pStateHeap;
    }
    return;
}

MOS_STATUS MHW_BLOCK_MANAGER::RegisterStateHeap(
    PMHW_STATE_HEAP    pStateHeap)
{
    MOS_STATUS eStatus = MOS_STATUS_SUCCESS;

    // Setup state heap associated with the memory block manager
    if (!m_pStateHeap)
    {
        m_pStateHeap = pStateHeap;
    }

    // Indicates that state heap is now being managed by this block manager object
    pStateHeap->pBlockManager = this;

    // Get memory block object to represent the state heap memory (free)
    PMHW_STATE_HEAP_MEMORY_BLOCK pBlock = GetBlockFromPool();
    if (!pBlock)
    {
        return MOS_STATUS_NO_SPACE;
    }

    // Setup block parameters
    pBlock->pStateHeap          = pStateHeap;           // Points to state heap
    pBlock->pHeapPrev           = nullptr;                 // First Block in current heap
    pBlock->pHeapNext           = nullptr;                 // Last Block in current heap
    pBlock->dwOffsetInStateHeap = 0;                    // Offset is 0 = base of state heap
    pBlock->dwBlockSize         = pStateHeap->dwSize;   // Size of the entire state heap
    pBlock->dwSyncTagId         = MHW_BLOCK_MANAGER_INVALID_TAG;
    pBlock->bStatic             = 0;

    // Set first/last memory block in state heap
    pStateHeap->pMemoryHead     = pBlock;
    pStateHeap->pMemoryTail     = pBlock;

    // Reset special state heap controls
    pStateHeap->pDebugKernel    = nullptr;                 // Debug kernel loaded in this heap (if ISH)
    pStateHeap->pScratchSpace   = nullptr;                 // Active scratch space in this heap (if GSH)
    pStateHeap->dwScratchSpace  = 0;                    // Current scratch space size in this heap

    // Insert block at the end of the free memory pool
    AttachBlock(MHW_BLOCK_STATE_FREE, pBlock, MHW_BLOCK_POSITION_TAIL);

    return eStatus;
}

MOS_STATUS MHW_BLOCK_MANAGER::UnregisterStateHeap(
    PMHW_STATE_HEAP    pStateHeap)
{
    MHW_STATE_HEAP_MEMORY_BLOCK *pBlock;

    bool bReleaseHeap = true;

    // Verification loop - check if heap can be freed
    for (pBlock = pStateHeap->pMemoryHead; pBlock != nullptr; pBlock = pBlock->pHeapNext)
    {
        // Move blocks not in use to deleted queue - so they cannot be reused
        // NOTE: Blocks in SUBMITTED state need to wait for completion before releasing
        if (pBlock->BlockState == MHW_BLOCK_STATE_FREE ||
            pBlock->BlockState == MHW_BLOCK_STATE_ALLOCATED)
        {
            // Update heap usage
            if (pBlock->BlockState == MHW_BLOCK_STATE_FREE)
            {
                pStateHeap->dwFree -= pBlock->dwBlockSize;
            }
            else
            {
                pStateHeap->dwUsed -= pBlock->dwBlockSize;
            }

            DetachBlock(pBlock->BlockState, pBlock);
            AttachBlock(MHW_BLOCK_STATE_DELETED, pBlock, MHW_BLOCK_POSITION_TAIL);
        }
        // Block is allocated or submitted - mark block for deletion when released
        else if (pBlock->BlockState != MHW_BLOCK_STATE_DELETED)
        {
            pBlock->bStatic = false;    // Unlock block
            pBlock->bDelete = true;     // Mark block for deletion when done
            bReleaseHeap = false;       // Heap cannot be released at this moment
        }
    }

    // All blocks have been sucessfully deleted -> move all blocks to pool, unregister state heap
    if (bReleaseHeap)
    {
        // Return deleted blocks back to pool
        for (pBlock = pStateHeap->pMemoryHead; pBlock != nullptr; pBlock = pBlock->pHeapNext)
        {
            // Sanity check - all blocks must be in this state!
            if (pBlock->BlockState == MHW_BLOCK_STATE_DELETED)
            {
                DetachBlock(MHW_BLOCK_STATE_DELETED, pBlock);
                ReturnBlockToPool(pBlock);
            }
            else
            {
                // NEVER SUPPOSED TO HAPPEN DUE TO PREVIOUS LOOP
                MHW_ASSERTMESSAGE("ERROR: Mhw_BlockManager_UnregisterStateHeap: Invalid state, heap blocks are supposed to be all deleted by now");
            }
        }
        return MOS_STATUS_SUCCESS;
    }
    else
    {   // State heap cannot be unregistered because some blocks are still in use
        return MOS_STATUS_UNKNOWN;
    }
}

PMHW_STATE_HEAP_MEMORY_BLOCK MHW_BLOCK_MANAGER::GetBlockFromPool()
{
    PMHW_STATE_HEAP_MEMORY_BLOCK pBlock = nullptr;

    // Ran out of memory blocks... extend pool of memory block objects
    if (m_BlockList[MHW_BLOCK_STATE_POOL].iCount == 0)
    {
        ExtendPool(m_Params.dwPoolIncrement);
    }

    // Retrieve block object from head of the pool
    pBlock = DetachBlock(MHW_BLOCK_STATE_POOL, MHW_BLOCK_POSITION_HEAD);

    return pBlock;
}

void MHW_BLOCK_MANAGER::ExtendPool(uint32_t  dwCount)
{
    uint32_t dwBlockID = m_MemoryPool.m_dwObjCount; // Block ID starts from the current block count

    // Limits the number of memory blocks
    if (m_MemoryPool.m_dwCount + dwCount > m_Params.dwPoolMaxCount)
    {
        dwCount = m_Params.dwPoolMaxCount - m_MemoryPool.m_dwCount;
    }

    // Extend pool of block objects
    if (dwCount > 0)
    {
        // Allocate array of block objects into the pool
        MHW_STATE_HEAP_MEMORY_BLOCK *pBlockArray = (MHW_STATE_HEAP_MEMORY_BLOCK *) (m_MemoryPool.Allocate(dwCount));
        if (pBlockArray)
        {
            // Insert newly created block objects into the linked list of pool objects available to the memory block manager
            for (; dwCount > 0; dwCount--, pBlockArray++)
            {
                pBlockArray->dwBlockSize = 0;
                pBlockArray->pPrev       = pBlockArray->pNext = nullptr;
                pBlockArray->Reserved    = dwBlockID++;
                AttachBlock(MHW_BLOCK_STATE_POOL, pBlockArray, MHW_BLOCK_POSITION_TAIL);
            }
        }
    }
}

MOS_STATUS MHW_BLOCK_MANAGER::AttachBlock(
    MHW_BLOCK_STATE              BlockState,
    PMHW_STATE_HEAP_MEMORY_BLOCK pBlock,
    PMHW_STATE_HEAP_MEMORY_BLOCK pBlockPos)
{
    PMHW_BLOCK_LIST pList;
    MOS_STATUS      eStatus = MOS_STATUS_SUCCESS;

    // Verify parameters - objects cannot be null, block state must be valid
    if (pBlock == nullptr ||
        BlockState <  MHW_BLOCK_STATE_POOL ||
        BlockState >= MHW_BLOCK_STATE_COUNT)
    {
        return MOS_STATUS_INVALID_PARAMETER;
    }

    // Fails if block is still attached to a list
    if (pBlock->pPrev || pBlock->pNext)
    {
        return MOS_STATUS_INVALID_PARAMETER;
    }

    // Get list associated with block state; move block to the list
    pList = &m_BlockList[BlockState];
    BLOCK_MANAGER_CHK_STATUS(AttachBlockInternal(pList, BlockState, pBlock, pBlockPos));

    return MOS_STATUS_SUCCESS;
}

MOS_STATUS MHW_BLOCK_MANAGER::AttachBlockInternal(
    PMHW_BLOCK_LIST              pList,
    MHW_BLOCK_STATE              BlockState,
    PMHW_STATE_HEAP_MEMORY_BLOCK pBlock,
    PMHW_STATE_HEAP_MEMORY_BLOCK pBlockPos)
{

    // Check if this is the correct list!
    if (pList->BlockState != BlockState)
    {
        MHW_ASSERTMESSAGE("ERROR: Mhw_BlockManager_AttachBlock_Internal: Block state doesn't match the list provided");
        return MOS_STATUS_INVALID_PARAMETER;
    }

    // Setup the block state
    pBlock->BlockState = BlockState;

    // Attaches to the head of the list
    if (pBlockPos == MHW_BLOCK_POSITION_TAIL)
    {
        pBlock->pPrev = pList->pTail;
        pBlock->pNext = nullptr;
    }
    // Attaches to the tail of the list
    else if (pBlockPos == MHW_BLOCK_POSITION_HEAD)
    {
        pBlock->pPrev = nullptr;
        pBlock->pNext = pList->pHead;
    }
    // Insert after block provided - ensures that it belongs to the same list
    else if (pBlockPos->BlockState == BlockState)
    {
        pBlock->pPrev = pBlockPos;
        pBlock->pNext = pBlockPos->pNext;
    }
    // Insertion point does not belong to the same list
    else
    {
        MHW_ASSERTMESSAGE("ERROR: Mhw_BlockManager_AttachBlock_Internal: Reference block does not belong to the list provided");
        return MOS_STATUS_INVALID_PARAMETER;
    }

    // Modify previous block or head of the list
    if (pBlock->pPrev)
    {
        pBlock->pPrev->pNext = pBlock;
    }
    else
    {
        pList->pHead = pBlock;
    }

    // Modify next block or tail of the list
    if (pBlock->pNext)
    {
        pBlock->pNext->pPrev = pBlock;
    }
    else
    {
        pList->pTail = pBlock;
    }

    // Track size and number of blocks in the list
    pList->dwSize += pBlock->dwBlockSize;
    pList->iCount++;

    return MOS_STATUS_SUCCESS;
}

PMHW_STATE_HEAP_MEMORY_BLOCK MHW_BLOCK_MANAGER::DetachBlock(
    MHW_BLOCK_STATE              BlockState,
    PMHW_STATE_HEAP_MEMORY_BLOCK pBlockPos)
{
    PMHW_BLOCK_LIST              pList;
    PMHW_STATE_HEAP_MEMORY_BLOCK pBlock =  nullptr;

    // Verify parameters - object cannot be null, block state must be valid
    if (BlockState <  MHW_BLOCK_STATE_POOL ||
        BlockState >= MHW_BLOCK_STATE_COUNT)
    {
        return nullptr;
    }

    // Get list associated with block state
    pList = &m_BlockList[BlockState];

    // Remove block from list, performing sanity check (check if block is in correct list)
    pBlock = DetachBlockInternal(pList, pBlockPos);

    return pBlock;
}

PMHW_STATE_HEAP_MEMORY_BLOCK MHW_BLOCK_MANAGER::DetachBlockInternal(
    PMHW_BLOCK_LIST              pList,
    PMHW_STATE_HEAP_MEMORY_BLOCK pBlock)
{

    if(pList == nullptr)
    {
        return nullptr;
    }

#ifdef MHW_DYNAMIC_STATE_HEAP_LOGGING
    const char *szPos = "REF ";
    if (pBlock == MHW_BLOCK_POSITION_HEAD)
        szPos = "HEAD";
    else if (pBlock == MHW_BLOCK_POSITION_TAIL)
        szPos = "TAIL";
#endif

    // Get block from the head of the list
    if (pBlock == MHW_BLOCK_POSITION_HEAD)
    {
        pBlock = pList->pHead;
    }
    // Get block from the tail of the list
    else if (pBlock == MHW_BLOCK_POSITION_TAIL)
    {
        pBlock = pList->pTail;
    }
    // Block does not belong to correct list - ASSERT and get block from head of the list
    else if (pBlock->BlockState != pList->BlockState)
    {
        MHW_ASSERTMESSAGE("ERROR: Mhw_BlockManager_DetachBlock_Internal: Block provided does not belong to the list provided");
        pBlock = nullptr;
    }

    if (!pBlock)
    {
        return nullptr;
    }

    // Detach block from previous; if first, update head of the list
    if (pBlock->pPrev)
    {
        pBlock->pPrev->pNext = pBlock->pNext;
    }
    else
    {
        pList->pHead = pBlock->pNext;
    }

    // Detach block from next; if last, update tail of the list
    if (pBlock->pNext)
    {
        pBlock->pNext->pPrev = pBlock->pPrev;
    }
    else
    {
        pList->pTail = pBlock->pPrev;
    }

    // reset pointers - block is detached
    pBlock->pNext = pBlock->pPrev = nullptr;

    // track size and number of block in the list
    pList->dwSize -= pBlock->dwBlockSize;
    pList->iCount--;

    return pBlock;
}

MOS_STATUS MHW_BLOCK_MANAGER::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)
{
    MOS_STATUS      eStatus = MOS_STATUS_SUCCESS;

    BLOCK_MANAGER_CHK_NULL(pSrcList);
    BLOCK_MANAGER_CHK_NULL(pDstList);

    pBlock = DetachBlockInternal(pSrcList, pBlock);
    BLOCK_MANAGER_CHK_NULL(pBlock);

    eStatus = AttachBlockInternal(pDstList, pDstList->BlockState, pBlock, pBlockPos);

    if (eStatus != MOS_STATUS_SUCCESS)
    {
        MHW_ASSERTMESSAGE("ERROR: Mhw_BlockManager_MoveBlock_Internal: Failed to move block");
        AttachBlockInternal(pDstList, pDstList->BlockState, pBlock, MHW_BLOCK_POSITION_TAIL);
    }

    return eStatus;
}

void MHW_BLOCK_MANAGER::ReturnBlockToPool(PMHW_STATE_HEAP_MEMORY_BLOCK pBlock)
{
    pBlock->dwBlockSize = 0;
    pBlock->pPrev       = pBlock->pNext = nullptr;

    AttachBlock(MHW_BLOCK_STATE_POOL, pBlock, MHW_BLOCK_POSITION_TAIL);

    return;
}

MOS_STATUS MHW_BLOCK_MANAGER::Refresh(uint32_t dwSyncTag)
{
    PMHW_STATE_HEAP_MEMORY_BLOCK pBlock, pNext;
    PMHW_BLOCK_LIST              pList;
    MOS_STATUS                   eStatus = MOS_STATUS_SUCCESS;

    // Refresh status of SUBMITTED blocks
    pList  = &m_BlockList[MHW_BLOCK_STATE_SUBMITTED];
    pNext  = nullptr;
    for (pBlock = pList->pHead; pBlock != nullptr; pBlock = pNext)
    {
        // check of block may be released - if not, continue loop
        // NOTE - blocks are to be inserted in the sequence of execution,
        //        so we may finish the search as soon as we find the first
        //        block still "in use" (block tag > current sync tag)
        //        For now we're doing an exhaustive search, but it may not be needed - once
        //        we find the first block still in execution, we can probably stop the search

        // Save next pointer before moving the block to another queue
        pNext = pBlock->pNext;

        // Check if block is still in use, if so, continue search
        // NOTE - the following expression avoids sync tag wrapping around MAX_INT -> 0
        if ((int32_t)(dwSyncTag - pBlock->dwSyncTagId) < 0)
        {
            continue;
        }

        // Block is flagged for deletion
        if (pBlock->bDelete)
        {
            BLOCK_MANAGER_CHK_STATUS(FreeBlock(pBlock, dwSyncTag));
        }
        // Block is static -> move back to allocated list
        else if (pBlock->bStatic)
        {
            BLOCK_MANAGER_CHK_STATUS(MoveBlock(pList, &m_BlockList[MHW_BLOCK_STATE_ALLOCATED],
                           pBlock, MHW_BLOCK_POSITION_TAIL));
        }
        else
        // Block is non-static  -> free block
        {
            FreeBlock(pBlock, dwSyncTag);
        }
    }

    return eStatus;
}

void MHW_BLOCK_MANAGER::ConsolidateBlock(
    PMHW_STATE_HEAP_MEMORY_BLOCK pBlock)
{
    PMHW_STATE_HEAP_MEMORY_BLOCK pAux;

    // Check input parameters
    if (!pBlock || pBlock->BlockState != MHW_BLOCK_STATE_FREE)
    {
        return;
    }

    // Consolidate pBlock with previous blocks
    PMHW_BLOCK_LIST pFree = &m_BlockList[MHW_BLOCK_STATE_FREE];
    for (pAux = pBlock->pHeapPrev; (pAux != nullptr) && (pAux->BlockState == MHW_BLOCK_STATE_FREE); pAux = pBlock->pHeapPrev)
    {
        // Remove block from free block list
        DetachBlock(MHW_BLOCK_STATE_FREE, pAux);

        // Absorb free space into original block by adjusting offset/size
        pBlock->dwOffsetInStateHeap -= pAux->dwBlockSize;
        pBlock->dwBlockSize         += pAux->dwBlockSize;
        pFree->dwSize               += pAux->dwBlockSize;

        // Detach memory block from sequential memory list
        pBlock->pHeapPrev = pAux->pHeapPrev;
        if (pBlock->pHeapPrev)
        {
            pBlock->pHeapPrev->pHeapNext = pBlock;
        }
        else
        {
            pBlock->pStateHeap->pMemoryHead = pBlock;
        }

        // Memory block object no longer needed - return to pool after consolidation
        ReturnBlockToPool(pAux);
    }

    // Consolidate pBlock with following blocks
    for (pAux = pBlock->pHeapNext; (pAux != nullptr) && (pAux->BlockState == MHW_BLOCK_STATE_FREE); pAux = pBlock->pHeapNext)
    {
        // Remove block from free block list
        DetachBlock(MHW_BLOCK_STATE_FREE, pAux);

        // Absorb free space into original block by adjusting block size (offset remains the same)
        pBlock->dwBlockSize += pAux->dwBlockSize;
        pFree->dwSize       += pAux->dwBlockSize;

        // Detach memory block from sequential memory list
        pBlock->pHeapNext = pAux->pHeapNext;
        if (pBlock->pHeapNext)
        {
            pBlock->pHeapNext->pHeapPrev = pBlock;
        }
        else
        {
            pBlock->pStateHeap->pMemoryTail = pBlock;
        }

        // Memory block object no longer needed - return to pool after consolidation
        ReturnBlockToPool(pAux);
    }
}

MOS_STATUS MHW_BLOCK_MANAGER::AllocateBlockInternal(
    PMHW_STATE_HEAP_MEMORY_BLOCK pBlock,
    uint32_t                     dwAlignment)
{
    PMHW_STATE_HEAP pHeap;
    MOS_STATUS      eStatus = MOS_STATUS_SUCCESS;
    BLOCK_MANAGER_CHK_NULL(pBlock);

    // Remove block from free list - if block is not in free list, this operation will fail and return nullptr
    pBlock = DetachBlock( MHW_BLOCK_STATE_FREE, pBlock);
    BLOCK_MANAGER_CHK_NULL(pBlock);

    // Initialize block data structures
    pBlock->bDelete      = false;

    pBlock->dwDataOffset = MOS_ALIGN_CEIL(pBlock->dwOffsetInStateHeap, dwAlignment);
    pBlock->dwAlignment  = pBlock->dwDataOffset - pBlock->dwOffsetInStateHeap;
    pBlock->dwDataSize   = pBlock->dwBlockSize  - pBlock->dwAlignment;
    pBlock->pDataPtr     = (uint8_t*)pBlock->pStateHeap->pvLockedHeap + pBlock->dwDataOffset;

    // Move block to allocated list
    AttachBlock(MHW_BLOCK_STATE_ALLOCATED, pBlock, MHW_BLOCK_POSITION_TAIL);

    // Update available space in heap
    pHeap = pBlock->pStateHeap;
    pHeap->dwFree -= pBlock->dwBlockSize;
    pHeap->dwUsed += pBlock->dwBlockSize;

    return MOS_STATUS_SUCCESS;
}

MOS_STATUS MHW_BLOCK_MANAGER::SplitBlockInternal(
    PMHW_STATE_HEAP_MEMORY_BLOCK    pBlock,
    uint32_t                        dwSplitSize,
    uint32_t                        dwAlignment,
    bool                            bBackward)
{
    uint32_t                     dwSplitOffset = 0;
    MOS_STATUS                   eStatus = MOS_STATUS_SUCCESS;
    PMHW_STATE_HEAP_MEMORY_BLOCK pBlockL, pBlockH;

    BLOCK_MANAGER_CHK_NULL(pBlock);

    // Split cannot be less than min block size allowed
    dwSplitSize = MOS_MAX(dwSplitSize, m_Params.dwHeapBlockMinSize);
    if (pBlock->dwBlockSize < dwSplitSize)
    {
        return MOS_STATUS_UNKNOWN;
    }

    // Verify block state
    if (pBlock->BlockState <= MHW_BLOCK_STATE_POOL  ||          // Cannot split a block object from pool (contains invalid data)
        pBlock->BlockState >= MHW_BLOCK_STATE_DELETED)          // Cannot split a block being deleted
    {
        return MOS_STATUS_INVALID_PARAMETER;

    }

    // Select list
    PMHW_BLOCK_LIST pList = &(m_BlockList[pBlock->BlockState]);

    if (bBackward)
    {
        // Split FROM THE END of the block (higher offset)
        dwSplitOffset = MOS_ALIGN_FLOOR(pBlock->dwOffsetInStateHeap + pBlock->dwBlockSize - dwSplitSize, dwAlignment);
        dwSplitOffset = MOS_ALIGN_FLOOR(dwSplitOffset, m_Params.dwHeapGranularity);
    }
    else
    {
        // Split FROM THE BEGINNING of the block (lower offset)
        dwSplitOffset = MOS_ALIGN_CEIL(pBlock->dwOffsetInStateHeap, dwAlignment);
        dwSplitOffset = MOS_ALIGN_CEIL(dwSplitOffset + dwSplitSize, m_Params.dwHeapGranularity);
    }

    // Fail if block cannot be split
    if (dwSplitOffset < pBlock->dwOffsetInStateHeap + m_Params.dwHeapBlockMinSize ||                       // First fragment is too small
        pBlock->dwOffsetInStateHeap + pBlock->dwBlockSize < dwSplitOffset + m_Params.dwHeapBlockMinSize)   // Second fragment is too small
    {
        return MOS_STATUS_UNKNOWN;
    }

    if (bBackward)
    {
        pBlockH = pBlock;       // We'll keep the high end of the block
        pBlockL = GetBlockFromPool();
        BLOCK_MANAGER_CHK_NULL(pBlockL);

        uint32_t reserved = pBlockL->Reserved;
        *pBlockL = *pBlock;
        pBlockL->Reserved = reserved;

        if (pBlock->pPrev)
        {
            pBlock->pPrev->pNext = pBlockL;
        }
        else
        {
            pList->pHead = pBlockL;
        }

        if (pBlock->pHeapPrev)
        {
            pBlock->pHeapPrev->pHeapNext = pBlockL;
        }
        else
        {
            pBlock->pStateHeap->pMemoryHead = pBlockL;
        }
    }
    else
    {
        pBlockL = pBlock;       // We'll keep the low end of the block
        pBlockH = GetBlockFromPool();
        BLOCK_MANAGER_CHK_NULL(pBlockH);

        uint32_t reserved = pBlockH->Reserved;
        *pBlockH = *pBlock;
        pBlockH->Reserved = reserved;

        if (pBlock->pNext)
        {
            pBlock->pNext->pPrev = pBlockH;
        }
        else
        {
            pList->pTail = pBlockH;
        }

        if (pBlock->pHeapNext)
        {
            pBlock->pHeapNext->pHeapPrev = pBlockH;
        }
        else
        {
            pBlock->pStateHeap->pMemoryTail = pBlockH;
        }
    }

    // Update block adjacency list
    pBlockL->pHeapNext    = pBlockH;
    pBlockH->pHeapPrev    = pBlockL;

    // Ensures that the new block is tracked in the same list as the parent block, update block count
    pList->iCount++;
    pBlockL->pNext = pBlockH;
    pBlockH->pPrev = pBlockL;

    // Adjust Block sizes
    pBlockL->dwBlockSize         = dwSplitOffset - pBlockL->dwOffsetInStateHeap;   // Updates L block size based on split offset
    pBlockH->dwOffsetInStateHeap = dwSplitOffset;                                  // Sets 2nd block offset
    pBlockH->dwBlockSize        -= pBlockL->dwBlockSize;                           // Updates H block size by subtracting L block size

    // Adjust Block data related pointers/sizes only if block is not free
    if (pBlockL->BlockState != MHW_BLOCK_STATE_FREE)
    {
        pBlockL->dwDataSize  -= pBlockH->dwBlockSize;                           // Removes size of new block from amount of data available
        pBlockH->dwDataOffset = MOS_ALIGN_CEIL(dwSplitOffset, dwAlignment);     // Adjust offset to data (accounting for alignment)
        pBlockH->dwAlignment  = pBlockH->dwDataOffset - dwSplitOffset;          // Calculate alignment shift
        pBlockH->dwDataSize   = pBlockH->dwBlockSize - dwAlignment;             // Adjust amount of data available
        pBlockH->pDataPtr     = (uint8_t*)pBlockH->pStateHeap->pvLockedHeap + pBlockH->dwDataOffset; // Setup pointer to data (the heap is locked)
    }

    return eStatus;
}

MOS_STATUS MHW_BLOCK_MANAGER::MergeBlocksInternal(
    PMHW_STATE_HEAP_MEMORY_BLOCK    pBlockL,        // block in lower memory
    PMHW_STATE_HEAP_MEMORY_BLOCK    pBlockH,        // block in higher memory
    uint32_t                        dwAlignment,    // final block alignment
    bool                            bBackward)      // true if pBlockL (free) is merged into pBlockH; false if pBlockH (free) is merged into pBlockL
{
    PMHW_BLOCK_LIST pList;
    MOS_STATUS      eStatus = MOS_STATUS_SUCCESS;

    BLOCK_MANAGER_CHK_NULL(pBlockL);
    BLOCK_MANAGER_CHK_NULL(pBlockH);

    // Blocks must be contiguous in memory
    if (pBlockL->pHeapNext != pBlockH ||
        pBlockH->pHeapPrev != pBlockL)
    {
        return MOS_STATUS_INVALID_PARAMETER;
    }

    // 1st block (L) is merged into 2nd (H)
    if (bBackward)
    {
        if (pBlockL->BlockState != MHW_BLOCK_STATE_FREE ||      // 1st block must be free
            pBlockH->BlockState <  MHW_BLOCK_STATE_FREE ||      // 2nd block cannot be in pool
            pBlockH->BlockState >  MHW_BLOCK_STATE_SUBMITTED)   //           or deleted
        {
            return MOS_STATUS_INVALID_PARAMETER;
        }

        // Merge blocks
        pBlockL = DetachBlock(MHW_BLOCK_STATE_FREE, pBlockL);
        BLOCK_MANAGER_CHK_NULL(pBlockL);

        pBlockH->dwOffsetInStateHeap  = pBlockL->dwOffsetInStateHeap;
        pBlockH->dwBlockSize         += pBlockL->dwBlockSize;

        // Add size to the target block list
        pList = &(m_BlockList[pBlockH->BlockState]);
        pList->dwSize += pBlockL->dwBlockSize;

        // If block allocated or submitted, adjust data references (don't care if block is free)
        if (pBlockH->BlockState != MHW_BLOCK_STATE_FREE)
        {
            pBlockH->dwDataOffset   = MOS_ALIGN_CEIL(pBlockH->dwOffsetInStateHeap, dwAlignment);
            pBlockH->dwAlignment    = pBlockH->dwDataOffset - pBlockH->dwOffsetInStateHeap;
            pBlockH->dwDataSize     = pBlockH->dwBlockSize  - pBlockH->dwAlignment;
            pBlockH->pDataPtr       = (uint8_t*)pBlockH->pStateHeap->pvLockedHeap + pBlockH->dwDataOffset;

            // Free block is now in use - track heap usage
            pBlockH->pStateHeap->dwFree -= pBlockL->dwBlockSize;
            pBlockH->pStateHeap->dwUsed += pBlockL->dwBlockSize;
        }

        // Return block object to the pool
        ReturnBlockToPool(pBlockL);
    }
    else
    // 2nd block (H) is merged into 1st (L)
    {
        if (pBlockH->BlockState != MHW_BLOCK_STATE_FREE ||       // 2nd block must be free
            pBlockL->BlockState <  MHW_BLOCK_STATE_FREE ||       // 1nd block must not be in pool
            pBlockL->BlockState >  MHW_BLOCK_STATE_SUBMITTED)    //           or deleted
        {
            return MOS_STATUS_INVALID_PARAMETER;
        }

        // Merge blocks
        pBlockH = DetachBlock(MHW_BLOCK_STATE_FREE, pBlockH);
        BLOCK_MANAGER_CHK_NULL(pBlockH);

        pBlockL->dwBlockSize += pBlockH->dwBlockSize;
        if (pBlockL->BlockState != MHW_BLOCK_STATE_FREE)
        {
            pBlockL->dwDataSize         += pBlockH->dwBlockSize;
            pBlockL->pStateHeap->dwFree -= pBlockL->dwBlockSize;
            pBlockL->pStateHeap->dwUsed += pBlockL->dwBlockSize;
        }

        // Add size to the target block list
        pList = &(m_BlockList[pBlockL->BlockState]);
        pList->dwSize += pBlockH->dwBlockSize;

        // Return block object to the pool
        ReturnBlockToPool(pBlockH);
    }

    return eStatus;
}

MOS_STATUS MHW_BLOCK_MANAGER::ResizeBlock(
    PMHW_STATE_HEAP_MEMORY_BLOCK    pBlock,
    uint32_t                        dwNewSize,
    uint32_t                        dwAlignment,
    bool                            bBackward)      // false => Always grow/shrink forward; true => allow block to grow forward/backwards (moving its start offset)
{
    MOS_STATUS                   eStatus = MOS_STATUS_SUCCESS;
    PMHW_STATE_HEAP_MEMORY_BLOCK pNewBlock;

    BLOCK_MANAGER_CHK_NULL(pBlock);
    MHW_ASSERT(dwNewSize > 0);

    // Verify block state
    if (pBlock->BlockState <= MHW_BLOCK_STATE_POOL  ||          // Cannot touch a block object from pool - invalid data
        pBlock->BlockState >= MHW_BLOCK_STATE_DELETED)          // Cannot touch a block being deleted
    {
        return MOS_STATUS_INVALID_PARAMETER;
    }

    // Shrinking current block
    if (dwNewSize < pBlock->dwBlockSize)
    {
        // Split block into 2 - (bBackwards -> shrink the block by keeping the 2nd half (anchor end of the block)
        eStatus = SplitBlockInternal(pBlock, dwNewSize, dwAlignment, bBackward);
        if (eStatus != MOS_STATUS_SUCCESS)
        {
            // This error just means that the block cannot shrink - not an actual issue here
            if (eStatus == MOS_STATUS_UNKNOWN)
            {
                eStatus = MOS_STATUS_SUCCESS;
                return eStatus;
            }
        }

        // Select block to be freed
        pBlock = (bBackward) ? pBlock->pPrev : pBlock->pNext;
        BLOCK_MANAGER_CHK_NULL(pBlock);

        if (pBlock->BlockState == MHW_BLOCK_STATE_SUBMITTED)
        {
            // mark block for release as soon it is no longer in use by GPU
            pBlock->bStatic  = false;
        }
        else
        {
            // Free block - block is in allocated state
            FreeBlock(pBlock, MHW_INVALID_SYNC_TAG);
        }

        // Success!
        return MOS_STATUS_SUCCESS;
    }

    // Check for contiguous available space in forward direction first
    uint32_t dwAvailable = pBlock->dwDataSize;
    for (pNewBlock = pBlock->pHeapNext;
         (dwAvailable < dwNewSize) && (pNewBlock) && (pNewBlock->BlockState == MHW_BLOCK_STATE_FREE);
         pNewBlock = pNewBlock->pHeapNext)
    {
        dwAvailable += pNewBlock->dwBlockSize;
    }

    // Check for contiguous available space in backward direction
    if (bBackward)
    {
        // Update available space to account for block alignment
        dwAvailable += pBlock->dwAlignment - dwAlignment;
        for (pNewBlock = pBlock->pHeapPrev;
             (dwAvailable < dwNewSize) && (pNewBlock) && (pNewBlock->BlockState == MHW_BLOCK_STATE_FREE);
             pNewBlock = pNewBlock->pHeapPrev)
        {
            dwAvailable += pNewBlock->dwBlockSize;
        }
    }

    // Check if block can be resized
    if (dwAvailable < dwNewSize)
    {
        return MOS_STATUS_UNKNOWN;
    }

    // Start block expansion forward
    for (pNewBlock = pBlock->pHeapNext;
         (pBlock->dwDataSize < dwNewSize) && (pNewBlock) && (pNewBlock->BlockState == MHW_BLOCK_STATE_FREE);
         pNewBlock = pBlock->pHeapNext)
    {
        // Next block is too large - split the block before merging
        if (pBlock->dwDataSize + pNewBlock->dwBlockSize > dwNewSize)
        {
            SplitBlockInternal(pNewBlock, dwNewSize - pBlock->dwDataSize, dwAlignment, false);
        }

        // Merge block with next
        MergeBlocksInternal(pBlock, pNewBlock, dwAlignment, false);
    }

    // Continue expanding backward
    if (bBackward)
    {
        for (pNewBlock = pBlock->pHeapPrev;
             (dwAvailable < dwNewSize) && (pNewBlock) && (pNewBlock->BlockState == MHW_BLOCK_STATE_FREE);
             pNewBlock = pBlock->pHeapPrev)
        {
            // Prev block is too large - split the block before merging
            uint32_t dwAdjust = MOS_ALIGN_CEIL(pNewBlock->dwOffsetInStateHeap, dwAlignment) - pNewBlock->dwOffsetInStateHeap;
            if (pBlock->dwBlockSize + pNewBlock->dwBlockSize - dwAdjust > dwNewSize)
            {
                SplitBlockInternal(pNewBlock, dwNewSize - pBlock->dwBlockSize, dwAlignment, true);
            }

            // Merge block with previous
            MergeBlocksInternal(pNewBlock, pBlock, dwAlignment, true);
        }
    }

    return eStatus;
}

PMHW_STATE_HEAP_MEMORY_BLOCK MHW_BLOCK_MANAGER::AllocateWithScratchSpace(
    uint32_t            dwSize,
    uint32_t            dwAlignment,
    uint32_t            dwScratchSpace)
{
    MOS_STATUS                   eStatus    = MOS_STATUS_SUCCESS;
    PMHW_STATE_HEAP_MEMORY_BLOCK pBlock     = nullptr;
    PMHW_STATE_HEAP_MEMORY_BLOCK pScratch   = nullptr;

    // Fix alignment - must be a power of 2
    if (dwAlignment < m_Params.dwHeapGranularity)
    {
        // Blocks are already aligned
        dwAlignment = 1;
    }
    else
    {
        dwAlignment--;
        dwAlignment |= dwAlignment >>  1;
        dwAlignment |= dwAlignment >>  2;
        dwAlignment |= dwAlignment >>  4;
        dwAlignment |= dwAlignment >>  8;
        dwAlignment |= dwAlignment >> 16;
        dwAlignment++;
    }

    // Try to search search state heap with large enough scratch space
    // and with enough free space
    PMHW_STATE_HEAP pNextStateHeap;
    for (PMHW_STATE_HEAP pStateHeap = m_pStateHeap; (pStateHeap); pStateHeap = pNextStateHeap)
    {
        // Save next state heap
        pNextStateHeap = pStateHeap->pNext;

        // Space needed for block (accounting for alignment and for scratch space)
        uint32_t dwBlockNeeded = MOS_ALIGN_CEIL(dwSize + dwAlignment - 1, m_Params.dwHeapGranularity);

        // Scratch space needed = space requested + room for alignment - space already allocated
        uint32_t dwScratchNeeded = 0;
        if (dwScratchSpace > 0 && pStateHeap->dwScratchSpace < dwScratchSpace)
        {
            dwScratchNeeded = dwScratchSpace;
            if (m_Params.dwHeapGranularity < MHW_SCRATCH_SPACE_ALIGN)
            {
                dwScratchNeeded += MHW_SCRATCH_SPACE_ALIGN - m_Params.dwHeapGranularity;
            }
            if (pStateHeap->pScratchSpace)
            {
                dwScratchNeeded -= pStateHeap->pScratchSpace->dwBlockSize;
            }
        }

        if (pStateHeap->dwSize < dwScratchNeeded)
        {
            // Heap is too small for current scratch space size - mark for deletion
            pStateHeap->pMhwStateHeapInterface->ReleaseStateHeapDyn(pStateHeap);
            continue;
        }
        else if (pStateHeap->dwFree < (dwScratchNeeded + dwBlockNeeded))
        {
            // Heap can still be used, but currently full, try next heap
            continue;
        }

        // Allocate scratch space first
        if (dwScratchNeeded)
        {
            pScratch = nullptr;

            // Already present - EXPAND SCRATCH SPACE
            if (pStateHeap->pScratchSpace)
            {
                // Resize existing scratch space trying to use all free space towards the end of the heap and then growing towards the center.
                eStatus = ResizeBlock(pStateHeap->pScratchSpace, dwScratchSpace, MHW_SCRATCH_SPACE_ALIGN, true);
                if (eStatus == MOS_STATUS_SUCCESS)
                {
                    pScratch = pStateHeap->pScratchSpace;                // Indicates success
                    pStateHeap->dwScratchSpace = pScratch->dwDataSize;   // Available scratch space size (aligned)
                }
            }

            // Scratch space not present or failed to expand - find a new scratch space
            if (!pScratch)
            {
                // Search for scratch space at the end of the heap towards the beginning
                // This model allows for better growth without collision with media state heaps
                for (pScratch = pStateHeap->pMemoryTail; pScratch != pBlock; pScratch = pScratch->pHeapPrev)
                {
                    if (pScratch->BlockState == MHW_BLOCK_STATE_FREE &&
                        pScratch->dwBlockSize >= dwScratchNeeded)
                    {
                        // Found scratch space large enough
                        break;
                    }
                }

                // Not enough contiguous space for scratch in heap - try next heap
                if (!pScratch)
                {
                    continue;
                }

                // CREATE SCRATCH SPACE

                // Split block to the necessary size, using the higher portion (closer to end of the heap)
                // NOTE: Block select could be much larger than needed, even the size of the entire state heap - that's why it needs to be split
                eStatus = SplitBlockInternal(pScratch, dwScratchNeeded, MHW_SCRATCH_SPACE_ALIGN, true);
                if (eStatus == MOS_STATUS_UNKNOWN) eStatus = MOS_STATUS_SUCCESS;   // Don't care if block could not be split
                if (eStatus != MOS_STATUS_SUCCESS)
                {
                    continue;
                }

                // Move block to allocated list, mark it as static (do not release upon completion)
                AllocateBlockInternal(pScratch, MHW_SCRATCH_SPACE_ALIGN);
                pScratch->bStatic = true;

                // Free the old scratch space
                // NOTE: scratch spaces are also tracked by Sync Tags and maintained in submitted/allocated lists,
                //       If in use, it just clears the bStatic flag, so it will be freed when no longer in use.
                if (pStateHeap->pScratchSpace)
                {
                    FreeBlock(pStateHeap->pScratchSpace, 0);
                }

                // Setup new heap scratch space and size
                pStateHeap->pScratchSpace  = pScratch;
                pStateHeap->dwScratchSpace = pScratch->dwDataSize;
            }
        }

        // Try to allocate block in the same heap as the scratch space
        pBlock = AllocateBlock(dwSize, dwAlignment, pStateHeap);
        if (pBlock)
        {
            break;
        }
    }

    return pBlock;
}

PMHW_STATE_HEAP_MEMORY_BLOCK MHW_BLOCK_MANAGER::AllocateBlock(
    uint32_t            dwSize,
    uint32_t            dwAlignment,
    PMHW_STATE_HEAP     pHeapAffinity)
{
    PMHW_STATE_HEAP_MEMORY_BLOCK pBlock = nullptr;
    PMHW_BLOCK_LIST              pFree  = &m_BlockList[MHW_BLOCK_STATE_FREE];
    uint32_t                     dwAdjust;     // Offset adjustment for alignment purposes
    uint32_t                     dwAllocSize;  // Actual allocation size accounting for alignment and other restrictions
    MOS_STATUS                   eStatus = MOS_STATUS_SUCCESS;

    // Fix alignment - must be a power of 2 (minimum 1)
    if (dwAlignment) dwAlignment--;
    dwAlignment |= dwAlignment >>  1;
    dwAlignment |= dwAlignment >>  2;
    dwAlignment |= dwAlignment >>  4;
    dwAlignment |= dwAlignment >>  8;
    dwAlignment |= dwAlignment >> 16;
    dwAlignment++;

    // Search must include space for block granularity
    if (dwAlignment <= m_Params.dwHeapGranularity)
    {
        // Alignment should be fulfilled by the heap granularity
        dwAllocSize = dwSize;
    }
    else
    {
        // Worst case scenario - original implementation was checking alignment of
        // each free block, but it was overkill - so now we just consider the worst case
        dwAllocSize = dwSize + dwAlignment - m_Params.dwHeapGranularity;
    }

    // Enforce min block size
    dwAllocSize = MOS_MAX(m_Params.dwHeapBlockMinSize, dwAllocSize);

    // Search list of free blocks for the first match
    for (pBlock = pFree->pHead; pBlock != nullptr; pBlock = pBlock->pNext)
    {
        // Skip this heap if we are looking for allocation in a specific heap
        if (pHeapAffinity  && pBlock->pStateHeap != pHeapAffinity)
        {
            continue;
        }

        // Check if aligned block fits the request -> break with a successful block
        if (pBlock->dwBlockSize >= dwAllocSize)
        {
            break;
        }
    }

    // No block was found - fail search
    if (!pBlock)
    {
        return nullptr;
    }

    // Block was found, adjust the allocation size to account for
    // heap granularity and block alignment
    dwAdjust    = MOS_ALIGN_OFFSET(pBlock->dwOffsetInStateHeap, dwAlignment);                 // Increase in size to align data
    dwAllocSize = MOS_ALIGN_CEIL(dwSize + dwAdjust, m_Params.dwHeapGranularity); // Account for heap granularity (avoid odd addresses in heap)
    dwAllocSize = MOS_MAX(dwAllocSize, m_Params.dwHeapBlockMinSize);

    // Just a precaution - sanity check - in case of last block in heap, and total heap size is not a multiple of granularity
    if (pBlock->dwBlockSize < dwAllocSize)
    {
        // This should never happend because it is part of the search condition!
        MHW_ASSERT(pBlock->dwBlockSize >= (dwAdjust + dwSize));

        dwAllocSize = pBlock->dwBlockSize;
    }

    // Split block, move to allocated list
    if (pBlock->dwBlockSize > dwAllocSize)
    {
        // Split free block in 2, keep the first part (lower offset)
        eStatus = SplitBlockInternal(pBlock, dwAllocSize, dwAlignment, false);
        if (eStatus != MOS_STATUS_SUCCESS &&
            eStatus != MOS_STATUS_UNKNOWN)
        {
            MHW_ASSERTMESSAGE("ERROR: AllocateBlock: Failed to allocate block");
            return nullptr;
        }
    }

    // Move block from free to allocated queue
    DetachBlock(MHW_BLOCK_STATE_FREE,      pBlock);
    AttachBlock(MHW_BLOCK_STATE_ALLOCATED, pBlock, MHW_BLOCK_POSITION_TAIL);
    pBlock->pStateHeap->dwUsed += pBlock->dwBlockSize;
    pBlock->pStateHeap->dwFree -= pBlock->dwBlockSize;

    // Reset some fields
    pBlock->bDelete       = false;
    pBlock->dwSyncTagId   = 0;

    // Setup aligned offset, size and data
    pBlock->dwDataOffset = MOS_ALIGN_CEIL(pBlock->dwOffsetInStateHeap, dwAlignment);
    pBlock->dwAlignment  = pBlock->dwDataOffset - pBlock->dwOffsetInStateHeap;
    pBlock->dwDataSize   = pBlock->dwBlockSize  - pBlock->dwAlignment;
    pBlock->pDataPtr     = (uint8_t*)pBlock->pStateHeap->pvLockedHeap + pBlock->dwDataOffset;

    // return block to client
    return pBlock;
}

MOS_STATUS MHW_BLOCK_MANAGER::FreeBlock(
    PMHW_STATE_HEAP_MEMORY_BLOCK pBlock,
    uint32_t dwSyncTag)
{
    MOS_STATUS eStatus = MOS_STATUS_SUCCESS;

    BLOCK_MANAGER_CHK_NULL(pBlock);

    // Block still in use - mark for auto-release when complete
    if (pBlock->BlockState == MHW_BLOCK_STATE_SUBMITTED)
    {
        // sync tag not provided or block still in use - flag it for automatic release when complete
        if (dwSyncTag == MHW_INVALID_SYNC_TAG || (int32_t)(dwSyncTag - pBlock->dwSyncTagId) < 0)
        {
            pBlock->bStatic  = false;
            return eStatus;
        }
    }
    else if (pBlock->BlockState != MHW_BLOCK_STATE_ALLOCATED)
    {
        return MOS_STATUS_INVALID_PARAMETER;
    }

    // Remove block from its current list
    DetachBlock(pBlock->BlockState, pBlock);

    // If block is marked for deletion - move to deleted list so it cannot be reallocated
    if (pBlock->bDelete)
    {
        MHW_STATE_HEAP *pStateHeap = pBlock->pStateHeap;
        pStateHeap->dwUsed -= pBlock->dwBlockSize;

        AttachBlock(MHW_BLOCK_STATE_DELETED, pBlock, MHW_BLOCK_POSITION_TAIL);

        // Last block was removed from StateHeap -> state heap may be unregistered and deleted
        if (pStateHeap->dwUsed == 0)
        {
            pStateHeap->pMhwStateHeapInterface->ReleaseStateHeapDyn(pStateHeap);
        }
    }
    else
    {
        // Update state heap usage
        pBlock->pStateHeap->dwUsed -= pBlock->dwBlockSize;
        pBlock->pStateHeap->dwFree += pBlock->dwBlockSize;

        // Blocks are freed and placed at the beginning of the free block list
        AttachBlock(MHW_BLOCK_STATE_FREE, pBlock, MHW_BLOCK_POSITION_TAIL);

        // Consolidate memory immediately after release - so free block are ALWAYS merged
        ConsolidateBlock(pBlock);
    }

    return eStatus;
}

uint32_t MHW_BLOCK_MANAGER::CalculateSpaceNeeded(
    const uint32_t      *pdwSizes,
    int32_t             iCount,
    uint32_t            dwAlignment,
    bool                bHeapAffinity,
    PMHW_STATE_HEAP     pHeapAffinity)
{
    uint32_t                     dwNeeded = 0;
    uint8_t                      SortedIndex[MHW_BLOCK_MANAGER_MAX_BLOCK_ARRAY];
    uint32_t                     FreeBlockSizes[MHW_BLOCK_MANAGER_MAX_BLOCK_ARRAY * 2];
    uint32_t                     dwBlockOverhead = 0;   // Block size overhead to ensure alignment
    uint32_t                     dwAllocSize;

    // Check parameters
    if (iCount <= 0 || iCount > MHW_BLOCK_MANAGER_MAX_BLOCK_ARRAY)
    {
        return dwNeeded;
    }

    // This is the minimum block
    uint32_t dwBlockGranularity = m_Params.dwHeapGranularity;
    uint32_t dwBlockMinSize     = m_Params.dwHeapBlockMinSize;
    if (dwAlignment > dwBlockGranularity)
    {
        dwBlockOverhead = dwAlignment - dwBlockGranularity;
    }

    // Very simple case - single block search
    if (iCount == 1)
    {
        PMHW_STATE_HEAP_MEMORY_BLOCK pBlock = m_BlockList[MHW_BLOCK_STATE_FREE].pHead;
        dwNeeded = pdwSizes[0];
        for ( ; pBlock != nullptr && dwNeeded > 0; pBlock = pBlock->pNext)
        {
            if (bHeapAffinity && pHeapAffinity != pBlock->pStateHeap)
            {
                continue;
            }

            if (dwNeeded < pBlock->dwBlockSize)
            {
                dwNeeded = 0;
            }
        }

        return dwNeeded;
    }

    // Sort input block sizes (with index, to avoid modifying the input array)
    Mhw_BlockManager_ReverseMergeSort_With_Index(pdwSizes, iCount, SortedIndex);

    // Read all available blocks, keep the largest blocks
    int iIndex = 0;
    PMHW_STATE_HEAP_MEMORY_BLOCK pBlock = m_BlockList[MHW_BLOCK_STATE_FREE].pHead;
    for ( ; pBlock != nullptr; pBlock = pBlock->pNext)
    {
        // Select free blocks that fit the request
        if (bHeapAffinity && pHeapAffinity != pBlock->pStateHeap) continue;
        FreeBlockSizes[iIndex++] = pBlock->dwBlockSize;

        // If buffer is full, sort it, only take the largest blocks (overwrite remaining blocks)
        if (iIndex == MHW_BLOCK_MANAGER_MAX_BLOCK_ARRAY * 2)
        {
            Mhw_BlockManager_ReverseMergeSort(FreeBlockSizes, iIndex);
            iIndex = iCount;
        }
    }

    // Final sort after all block are in
    Mhw_BlockManager_ReverseMergeSort(FreeBlockSizes, iIndex);
    FreeBlockSizes[iIndex] = 0; // Null termination simplifies the algorithm that follows

    // Start process of fitting requested blocks with available blocks
    uint32_t *pFreeBlock = &FreeBlockSizes[0];
    for (iIndex = 0; iIndex < iCount; iIndex++)
    {
        dwAllocSize = MOS_ALIGN_CEIL(pdwSizes[SortedIndex[iIndex]] + dwBlockOverhead, dwBlockGranularity);
        dwAllocSize = MOS_MAX(dwAllocSize, dwBlockMinSize);

        // Block doesn't fit - add to space needed
        if (dwAllocSize > *pFreeBlock)
        {
            dwNeeded += dwAllocSize;
        }
        else
        {
            // Allocate block
            uint32_t dwRemaining = *pFreeBlock = *pFreeBlock - dwAllocSize;
            uint32_t          *pAux;

            // Remaining is out of order, keep list sorted using insertion sort
            if (dwRemaining < pFreeBlock[1])
            {
                for (pAux = pFreeBlock; dwRemaining < pAux[1]; pAux++) pAux[0] = pAux[1];
                pAux[0] = dwRemaining;
            }
        }
    }

    return dwNeeded;
}

MOS_STATUS MHW_BLOCK_MANAGER::SubmitBlock(
    PMHW_STATE_HEAP_MEMORY_BLOCK pBlock,
    uint32_t                     dwSyncTag)
{
    MOS_STATUS                   eStatus = MOS_STATUS_SUCCESS;

    BLOCK_MANAGER_CHK_NULL(pBlock);

    if (pBlock->BlockState == MHW_BLOCK_STATE_POOL    ||        // Block cannot be submitted directly from pool
        pBlock->BlockState == MHW_BLOCK_STATE_FREE    ||        // or from free (must be allocated first)
        pBlock->BlockState == MHW_BLOCK_STATE_DELETED)          // or from deleted (the block is being DELETED for crying out loud!)
    {
        MHW_ASSERTMESSAGE("ERROR: SubmitBlock: Block in invalid state, cannot be enqueued");
        return MOS_STATUS_UNKNOWN;
    }

    // Detach block from whatever list it's in - could be in SUBMITTED state (block is being reused - i.e., kernel or scratch space)
    pBlock = DetachBlock(pBlock->BlockState, pBlock);
    BLOCK_MANAGER_CHK_NULL(pBlock);

    // Set block sync tag - used for refreshing the block status
    pBlock->dwSyncTagId = dwSyncTag;
    BLOCK_MANAGER_CHK_STATUS(AttachBlock(MHW_BLOCK_STATE_SUBMITTED, pBlock, MHW_BLOCK_POSITION_TAIL));

    return eStatus;
}

PMHW_STATE_HEAP_MEMORY_BLOCK MHW_BLOCK_MANAGER::AllocateMultiple(
    uint32_t            *pdwSizes,
    int32_t             iCount,
    uint32_t            dwAlignment,
    bool                bHeapAffinity,
    PMHW_STATE_HEAP     pHeapAffinity)
{
    PMHW_STATE_HEAP                pStateHeap;
    uint32_t                       dwTotalSize = 0;
    uint8_t                        SortedIndex[MHW_BLOCK_MANAGER_MAX_BLOCK_ARRAY];  // uint8_t is only used because the array size is <256
    PMHW_STATE_HEAP_MEMORY_BLOCK   pBlockArray[MHW_BLOCK_MANAGER_MAX_BLOCK_ARRAY];  // This could be allocated dynamically, same for the above
    PMHW_STATE_HEAP_MEMORY_BLOCK   pBlock;

    // Clear the result
    pBlockArray[0] = nullptr;

    if (iCount <= 0 || iCount > MHW_BLOCK_MANAGER_MAX_BLOCK_ARRAY)
    {
        return nullptr;
    }

    // Get total allocation size
    for (int i = 0; i < iCount; i++)
    {
        dwTotalSize += pdwSizes[i];
    }

    Mhw_BlockManager_ReverseMergeSort_With_Index(pdwSizes, iCount, SortedIndex);

    if (bHeapAffinity)
    {
        if (pHeapAffinity)
        {
            // Set heap affinity - all blocks will be allocated from this one particular heap
            pStateHeap = pHeapAffinity;
        }
        else
        {
            // Heap affinity is not set - start from the current active heap (most recently allocated heap)
            pStateHeap = m_pStateHeap;
        }
    }
    else
    {   // Don't care about heap affinity, blocks can be spread across all available state heaps
        // Just calculate total space available to make sure we have it
        uint32_t dwTotalHeapSpace = 0;
        for (pStateHeap = m_pStateHeap; pStateHeap != nullptr; pStateHeap = pStateHeap->pNext)
        {
            // Calculate total free space in all available heaps
            // NOTE: Heap being deleted as free space set to 0
            //       and all Free blocks are now Deleted (in deleted queue)
            dwTotalHeapSpace += pStateHeap->dwFree;
        }

        // Not enough space
        if (dwTotalHeapSpace < dwTotalSize)
        {
            return pBlockArray[0];
        }
    }

    // Loop to try loading all kernels into the same heap
    do {
        if ( (!bHeapAffinity) ||                   // no affinity set -> blocks can be spread across multiple heaps
              pStateHeap->dwFree >= dwTotalSize)   // this heap has enough free space
        {
            int32_t i, j;

            // Allocate array according to size
            for (i = 0, pBlock = nullptr; i < iCount; i++)
            {   // NOTE - don't care about scratch space, already checked
                pBlock = pBlockArray[SortedIndex[i]] = AllocateBlock(pdwSizes[SortedIndex[i]], dwAlignment, pStateHeap);
                if (!pBlock)
                {
                    break;
                }
            }

            // Allocations all sucessfull, quit search
            if (pBlock)
            {
                break;
            }

            // One of the block allocations failed - free all blocks already allocated (try another heap)
            for (j = 0; j < i; j++)
            {
                FreeBlock(pBlockArray[SortedIndex[j]], 0);
            }

            // Clear the output
            pBlockArray[0] = nullptr;
        }

        // Try another heap if bHeapAffinity is set
        if (bHeapAffinity)
        {
            pStateHeap = (pHeapAffinity) ? nullptr : pStateHeap->pNext;
        }
    } while (pStateHeap);

    // Allocation successful, reorder blocks according to original request
    if (pBlockArray[0])
    {
        for (int32_t i = 0; i < iCount; i++)
        {
            pBlock = DetachBlock(MHW_BLOCK_STATE_ALLOCATED, pBlockArray[i]);
            AttachBlock(MHW_BLOCK_STATE_ALLOCATED, pBlock, MHW_BLOCK_POSITION_TAIL);
        }
    }

    return pBlockArray[0];
}
