/*
* Copyright (c) 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     codechal_allocator.cpp
//! \brief    Class that provides a generic resource allocation service
//!

#include "codechal_allocator.h"

uint64_t CodechalAllocator::GetResourceTag(uint16_t resourceID, Match level)
{
    if (!m_resourceList.empty())
    {
        for (auto& res : m_resourceList)
        {
            if (resourceID == GetResourceID(res.first, level))
                return res.first;
        }
    }

    return 0;
}

void* CodechalAllocator::GetResourcePointer(uint16_t resourceID, Match level)
{
    if (!m_resourceList.empty())
    {
        for (auto& res : m_resourceList)
        {
            if (resourceID == GetResourceID(res.first, level))
                return res.second;
        }
    }

    return nullptr;
}

void* CodechalAllocator::Allocate1DBuffer(uint64_t resourceTag, uint32_t size,
                                          const char* bufName, bool zeroOnAllocation, uint32_t dwMemType)
{
    MOS_RESOURCE* resource = MOS_New(MOS_RESOURCE);
    MOS_ZeroMemory(resource, sizeof(MOS_RESOURCE));

    MOS_ALLOC_GFXRES_PARAMS allocParams;
    MOS_ZeroMemory(&allocParams, sizeof(allocParams));
    allocParams.Type = MOS_GFXRES_BUFFER;
    allocParams.Format = Format_Buffer;
    allocParams.TileType = MOS_TILE_LINEAR;
    allocParams.dwBytes = size;
    allocParams.pBufName = bufName;
    allocParams.dwMemType = dwMemType;

    if (MOS_STATUS_SUCCESS != m_osInterface->pfnAllocateResource(
        m_osInterface, &allocParams, resource))
    {
        MOS_Delete(resource);
        MOS_OS_ASSERTMESSAGE("allocate 1D buffer error!");
        return nullptr;
    }

    // place the newly allocated resource onto the list
    m_resourceList[resourceTag] = resource;
    MOS_OS_NORMALMESSAGE("allocate 1D buffer = 0x%x, tag = 0x%llx", resource, (long long)resourceTag);

    if (zeroOnAllocation)
    {
        ClearResource(resource, size);
    }

    return resource;
}

void* CodechalAllocator::Allocate2DBuffer(
    uint64_t resourceTag, uint32_t width, uint32_t height, MOS_FORMAT format,
    MOS_TILE_TYPE tile, const char* bufName, bool zeroOnAllocation, uint32_t dwMemType)
{
    MOS_SURFACE* surface = MOS_New(MOS_SURFACE);
    MOS_ZeroMemory(surface, sizeof(MOS_SURFACE));

    MOS_ALLOC_GFXRES_PARAMS allocParams;
    MOS_ZeroMemory(&allocParams, sizeof(allocParams));
    allocParams.Type = MOS_GFXRES_2D;
    allocParams.Format = format;
    allocParams.TileType = tile;
    allocParams.dwWidth = width;
    allocParams.dwHeight = height;
    allocParams.pBufName = bufName;
    allocParams.dwMemType = dwMemType;

    if (MOS_STATUS_SUCCESS != m_osInterface->pfnAllocateResource(
        m_osInterface, &allocParams, &surface->OsResource))
    {
        MOS_Delete(surface);
        MOS_OS_ASSERTMESSAGE("allocate 2D buffer error!");
        return nullptr;
    }

    // place the newly allocated resource onto the list
    m_resourceList[resourceTag] = surface;
    MOS_OS_NORMALMESSAGE("allocate 2D buffer = 0x%x, tag = 0x%llx", surface, (long long)resourceTag);

    if (zeroOnAllocation)
    {
        ClearResource(&surface->OsResource, width * height);
    }

    return surface;
}

void* CodechalAllocator::AllocateBatchBuffer(uint64_t resourceTag, uint32_t size, bool zeroOnAllocation)
{
    MHW_BATCH_BUFFER* batch = MOS_New(MHW_BATCH_BUFFER);
    MOS_ZeroMemory(batch, sizeof(MHW_BATCH_BUFFER));

    if (MOS_STATUS_SUCCESS != Mhw_AllocateBb(m_osInterface, batch, nullptr, size))
    {
        MOS_Delete(batch);
        MOS_OS_ASSERTMESSAGE("allocate batch buffer error!");
        return nullptr;
    }

    // place the newly allocated resource onto the list
    m_resourceList[resourceTag] = batch;
    MOS_OS_NORMALMESSAGE("allocate batch buffer = 0x%x, tag = 0x%llx", batch, (long long)resourceTag);

    if (zeroOnAllocation)
    {
        Mhw_LockBb(m_osInterface, batch);

        MOS_ZeroMemory(batch->pData, size);

        Mhw_UnlockBb(m_osInterface, batch, false);
    }

    return batch;
}

void CodechalAllocator::ReleaseResource(uint16_t resourceID, Match level)
{
    if (!m_resourceList.empty())
    {
        uint64_t tag = 0;
        void* pointer = nullptr;

        for (auto& res : m_resourceList)
        {
            if (resourceID == GetResourceID(res.first, level))
            {
                tag = res.first;
                pointer = res.second;
                break;
            }
        }

        if (tag)
        {
            // deallocate the matched resource
            Deallocate(tag, pointer);

            // remove from resource list
            m_resourceList.erase(tag);
        }
    }
}

bool CodechalAllocator::Is1DBuffer(uint64_t resourceTag)
{
    uint16_t typeID = (uint16_t)resourceTag;
    return (typeID >> 14 & 3) == 0;
}

bool CodechalAllocator::Is2DBuffer(uint64_t resourceTag)
{
    uint16_t typeID = (uint16_t)resourceTag;
    return (typeID >> 14 & 3) == 1;
}

bool CodechalAllocator::IsBatchBuffer(uint64_t resourceTag)
{
    uint16_t typeID = (uint16_t)resourceTag;
    return (typeID >> 14 & 3) == 2;
}

void CodechalAllocator::ClearResource(MOS_RESOURCE* resource, size_t size)
{
    MOS_LOCK_PARAMS LockFlags;
    MOS_ZeroMemory(&LockFlags, sizeof(LockFlags));
    LockFlags.WriteOnly = 1;

    uint8_t* ptr = (uint8_t*)m_osInterface->pfnLockResource(
        m_osInterface, resource, &LockFlags);

    MOS_ZeroMemory(ptr, size);

    m_osInterface->pfnUnlockResource(m_osInterface, resource);
}

void CodechalAllocator::Deallocate(uint64_t tag, void* pointer)
{
    if (Is1DBuffer(tag))
    {
        MOS_RESOURCE* ptr = (MOS_RESOURCE*)pointer;
        m_osInterface->pfnFreeResource(m_osInterface, ptr);
        MOS_OS_NORMALMESSAGE("free 1D buffer = 0x%x", ptr);

        // free resource container
        MOS_Delete(ptr);
    }
    else if (Is2DBuffer(tag))
    {
        MOS_SURFACE* ptr = (MOS_SURFACE*)pointer;
        m_osInterface->pfnFreeResource(m_osInterface, &ptr->OsResource);
        MOS_OS_NORMALMESSAGE("free 2D buffer = 0x%x", ptr);

        // free resource container
        MOS_Delete(ptr);
    }
    else if (IsBatchBuffer(tag))
    {
        MHW_BATCH_BUFFER* ptr = (MHW_BATCH_BUFFER*)pointer;
        Mhw_FreeBb(m_osInterface, ptr, nullptr);
        MOS_OS_NORMALMESSAGE("free batch buffer = 0x%x", ptr);

        // free resource container
        MOS_Delete(ptr);
    }
    else
    {
        MOS_OS_ASSERTMESSAGE("Unknown resource = 0x%x", pointer);
    }
}

CodechalAllocator::CodechalAllocator(MOS_INTERFACE* osInterface)
    : m_osInterface(osInterface),
    m_resourceList{}
{
}

CodechalAllocator::~CodechalAllocator()
{
    if (!m_resourceList.empty())
    {
        for (auto& res : m_resourceList)
        {
            Deallocate(res.first, res.second);
        }

        m_resourceList.clear();
    }
}
