/*
* 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.
*/
#include "cm_device.h"

#include <dlfcn.h>
#include <cstdio>

#include "cm_mem.h"
#include "cm_surface_manager.h"
#include "cm_queue.h"
#include "cm_timer.h"
#include "cm_debug.h"
#include "cm_extension_creator.h"

#if USE_EXTENSION_CODE
#include "cm_gtpin_external_interface.h"
#endif

#include <unistd.h>
#include <fcntl.h>

#ifndef ANDROID
uint32_t CmDevice_RT::m_vaReferenceCount = 0;
CSync CmDevice_RT::m_vaReferenceCountCriticalSection;
void  *CmDevice_RT::m_vaDrm = nullptr;
pfVAGetDisplayDRM CmDevice_RT::m_vaGetDisplayDrm = nullptr;
#endif
// current binary version, query by command "strings",
//       e.g. "strings  -a igfxcmrt64.so | grep current_version "
volatile static char cmrtCurrentVersion[] = "cmrt_current_version: " \
                          "6.0.0.9010\0";

CSync gDeviceCreationCriticalSection;

int32_t CmDevice_RT::Create( CmDevice_RT* &device, uint32_t createOption )
{
    INSERT_PROFILER_RECORD();

    int32_t result = CM_SUCCESS;

    device = new CmDevice_RT(nullptr, createOption);
    if( device )
    {
        result = device->Initialize(true);
        if( result != CM_SUCCESS )
        {
            CmAssert(0);
            Destroy(device);
        }
    }
    else
    {
        CmAssert( 0 );
        result = CM_OUT_OF_HOST_MEMORY;
    }

    return result;
}

int32_t CmDevice_RT::Create(VADisplay &vaDisplay , CmDevice_RT* &device, uint32_t createOption )
{
    INSERT_PROFILER_RECORD();

    int32_t result = CM_FAILURE;

    device = new (std::nothrow) CmDevice_RT(vaDisplay, createOption);
    if( device )
    {
        result = device->Initialize(false);
        if( result != CM_SUCCESS )
        {
            Destroy( device );
        }
    }
    else
    {
        CmAssert( 0 );
        result = CM_OUT_OF_HOST_MEMORY;
    }

    // leave critical section
    return result;
}

int32_t CmDevice_RT::Destroy( CmDevice_RT* &device )
{
    if(device == nullptr)
    {
        return CM_FAILURE;
    }

    // Destroy the cm device object
    device->FreeResources();

    //Destroy the Device at CMRT@UMD
    CM_DESTROYCMDEVICE_PARAM destroyCmDeviceParam;
    CmSafeMemSet(&destroyCmDeviceParam, 0, sizeof(CM_DESTROYCMDEVICE_PARAM));
    destroyCmDeviceParam.cmDeviceHandle = device->m_deviceInUmd;
    uint32_t inputDataLen = sizeof(CM_DESTROYCMDEVICE_PARAM);

    int32_t result = device->OSALExtensionExecute(CM_FN_DESTROYCMDEVICE,
                                                  &destroyCmDeviceParam,
                                                  inputDataLen);

    CmSafeRelease( device );
    CHK_FAILURE_RETURN(result);

    // leave critical section
    return destroyCmDeviceParam.returnValue;
}

CmDevice_RT::CmDevice_RT(
        VADisplay vaDisplay,
        uint32_t createOption
):
    m_cmVersion(0),
    m_deviceInUmd(nullptr),
    m_cmCreated ( true ),
    m_vaDisplay    (vaDisplay),
#ifdef ANDROID
    m_display       (nullptr),
#endif
    m_fvaCmExtSendReqMsg (nullptr),
    m_gtpinEnabled( false ),
    m_gtpinBufferUP0( nullptr ),
    m_gtpinBufferUP1( nullptr ),
    m_gtpinBufferUP2( nullptr ),
    m_printBuffer( nullptr ),
    m_printEnabled ( false),
    m_printBufferSize( 0),
    m_createOption(createOption),
    m_driverStoreEnabled(0)
{

    // New Surface Manager
    m_surfaceManager = new CmSurfaceManager(this);

    // New Kernel Debugger
    m_kernelDebugger = CmExtensionCreator<CmKernelDebugger>::CreateClass();
    
    //Initialize L3 cache config
    CmSafeMemSet(&m_l3Config, 0, sizeof(L3ConfigRegisterValues));

}

CmDevice_RT::~CmDevice_RT( void )
{
    if (m_cmCreated)
    {
        vaTerminate(m_vaDisplay);
#ifndef ANDROID
        FreeLibvaDrm();
#else
        free(m_display); //Android
#endif
    }

    if (m_kernelDebugger != nullptr)
    {
        delete m_kernelDebugger;
    }
}

int32_t CmDevice_RT::FreeResources()
{
    //Destroy Queue
    m_criticalSectionQueue.Acquire();
    for (auto iter = m_queue.begin(); iter != m_queue.end();)
    {
        if (*iter != nullptr)
        {
            CmQueue_RT::Destroy(*iter);
        }
        iter = m_queue.erase(iter);
    }
    m_criticalSectionQueue.Release();

    //Destroy GTPin Used BufferUp
    if( m_gtpinBufferUP0 != nullptr)
    {
        DestroyBufferUP(m_gtpinBufferUP0);
    }

    if( m_gtpinBufferUP1 != nullptr)
    {
        DestroyBufferUP(m_gtpinBufferUP1);
    }

    if( m_gtpinBufferUP2 != nullptr)
    {
        DestroyBufferUP(m_gtpinBufferUP2);
    }

    CmSafeRelease(m_surfaceManager);

    return CM_SUCCESS;
}

static int32_t CmrtVaSurfaceRelease(void *vaDisplay, void *vaSurface)
{
    VAStatus   vaStatus       = VA_STATUS_SUCCESS;
    VADisplay  *display       = (VADisplay *)(vaDisplay);

    //Destroy VaSurface
    vaStatus = vaDestroySurfaces(*display, (VASurfaceID *)vaSurface, 1);

    return vaStatus;
}

int32_t CmDevice_RT::Initialize( bool isCmCreated )
{
    int32_t result = CM_SUCCESS;

    m_cmCreated = isCmCreated;

    CLock locker(gDeviceCreationCriticalSection);

    CHK_RET(InitializeLibvaDisplay());

    CHK_RET(CreateDeviceInUmd());

    CHK_RET(CheckDdiVersionSupported(m_cmVersion));

#if USE_EXTENSION_CODE
    if(GTpinVariables.GTPinEnabled)
    {
        CHK_RET(EnableGtpin());
        CHK_RET(RegisterGtpinMarkerFunctions());

    }
#endif
    if (m_kernelDebugger != nullptr)
    {
        m_kernelDebugger->NotifyNewDevice(this, m_deviceInUmd, m_driverStoreEnabled);
    }

finish:
    return result;
}

int32_t CmDevice_RT::CreateDeviceInUmd()
{
    CmDeviceCreationParam createCmDeviceParam;
    CmSafeMemSet(&createCmDeviceParam, 0, sizeof(createCmDeviceParam));
    createCmDeviceParam.returnValue        = CM_FAILURE;
    createCmDeviceParam.createOption       = m_createOption;
    createCmDeviceParam.releaseSurfaceFunc = &CmrtVaSurfaceRelease;
    uint32_t inputDataLen = sizeof(createCmDeviceParam);

    int32_t result = OSALExtensionExecute(CM_FN_CREATECMDEVICE,
                                          &createCmDeviceParam, inputDataLen);

    CHK_FAILURE_RETURN(result);
    CHK_FAILURE_RETURN(createCmDeviceParam.returnValue);

    m_cmVersion = createCmDeviceParam.version;
    m_deviceInUmd = createCmDeviceParam.deviceHandleInUmd;
    m_driverStoreEnabled = createCmDeviceParam.driverStoreEnabled;
    return CM_SUCCESS;
}

//!
//! Create Libva Surface and wrap it as a CmSurface
//! It is CALLER's responsibility to allocation memory for all pointers to CmSurface2D 
//! Input :
//!     1) Surface's width  [in]
//!     2) Surface's height [in]
//!     3) Surface's format [in]
//!     4) Reference to created VASurfaceID [out]
//!     5) Reference to pointer of created Cm Surface [out]
//! Output: 
//!     CM_SUCCESS if all CmSurface2D are successfully created;
//!     CM_VA_SURFACE_NOT_SUPPORTED if libva surface creation fail;
//!     CM_FAILURE otherwise;
CM_RT_API int32_t CmDevice_RT::CreateVaSurface2D( uint32_t width, uint32_t height, CM_SURFACE_FORMAT format, VASurfaceID &vaSurface, CmSurface2D* &surface)
{
    INSERT_PROFILER_RECORD();

    return m_surfaceManager->CreateVaSurface2D(width, height, format, vaSurface, surface);
}

//!
//!
//! Create a CmSurface2D from an existing LIBVA surface 
//! Input :
//!     Reference to the pointer to the CmSurface2D .
//!     VASurfaceID 
//! Output: 
//!     CM_SUCCESS if the CmSurface2D are successfully created;
//!     CM_OUT_OF_HOST_MEMORY if out of system memory;
//!     CM_FAILURE otherwise;
//!
CM_RT_API int32_t CmDevice_RT::CreateSurface2D( VASurfaceID vaSurface, CmSurface2D* &surface )
{
    INSERT_PROFILER_RECORD();

    return m_surfaceManager->CreateSurface2D(vaSurface, surface);
}

//!
//! Create an array of CmSurface2D from an existing array of LIBVA surfaces, which are created by LIBVA's vaCreateSurfaces
//! It is CALLER's responsibility to allocation memory for all pointers to CmSurface2D 
//! Input :
//!     1) Pointer to the array of pointers pointing to LIBVA surface
//!     2) array size
//!     3) Pointer to the array of pointers pointing to CmSurface2D .
//! Output: 
//!     CM_SUCCESS if all CmSurface2D are successfully created;
//!     CM_OUT_OF_HOST_MEMORY if out of system memory;
//!     CM_FAILURE otherwise;
CM_RT_API int32_t CmDevice_RT::CreateSurface2D( VASurfaceID* vaSurfaceArray, const uint32_t surfaceCount, CmSurface2D**  surfaceArray)
{
    INSERT_PROFILER_RECORD();

    return m_surfaceManager->CreateSurface2D(vaSurfaceArray, surfaceCount, surfaceArray);
}

int32_t CmDevice_RT::OSALExtensionExecute(uint32_t functionId,
                                          void *inputData,
                                          uint32_t inputDataLength,
                                          void **resourceList,
                                          uint32_t resourceCount)
{
    CmAssert(inputData);

    //    uint32_t functionId    = functionId;
    //    void* inputData    = pInputData;
    //    uint32_t inputDataLen  = iInputDataLen;

    void* outputData   = m_deviceInUmd; // pass cm device handle to umd
    uint32_t outputDataLen = sizeof(m_deviceInUmd);
    uint32_t vaModuleId    = VAExtModuleCMRT;
    VAStatus hr        = VA_STATUS_SUCCESS;

    if (m_fvaCmExtSendReqMsg != nullptr)
    {
        hr = m_fvaCmExtSendReqMsg(m_vaDisplay, &vaModuleId, &functionId, inputData, &inputDataLength , 0, outputData , &outputDataLen);
    }
    return hr;
}

//Initalize LibVA's VADisplay
int32_t CmDevice_RT::InitializeLibvaDisplay()
{
    if ( m_cmCreated )
    {
        VAStatus vaStatus = VA_STATUS_SUCCESS;
        int vaMajorVersion, vaMinorVersion;

#ifndef ANDROID
    int32_t ret = GetLibvaDisplayDrm(m_vaDisplay);
    if ( ret != CM_SUCCESS)
    {
        CmAssert(0);
        return ret;
    }
#else
    m_display = (Display*)malloc(sizeof(Display));
    if (m_display == nullptr)
    {
        fprintf(stderr, "Can't connect X server!\n");
        return CM_INVALID_LIBVA_INITIALIZE;
    }

    *(m_display) = ANDROID_DISPLAY;
    m_vaDisplay = vaGetDisplay(m_display);
    if (m_vaDisplay == nullptr)
    {
        return CM_INVALID_LIBVA_INITIALIZE;
    }
#endif  //ANDROID

        vaStatus = vaInitialize(m_vaDisplay, &vaMajorVersion, &vaMinorVersion);
        if ( VA_STATUS_SUCCESS != vaStatus ) {
            return CM_INVALID_LIBVA_INITIALIZE;
        }
    }

    m_fvaCmExtSendReqMsg = (pvaCmExtSendReqMsg) vaGetLibFunc (m_vaDisplay, "vaCmExtSendReqMsg");

    if (m_fvaCmExtSendReqMsg == nullptr) {
        fprintf(stderr, "Cannot get function of m_fvaCmExtSendReqMsg!\n");
        return CM_INVALID_LIBVA_INITIALIZE;
    }
    else
    {
        return CM_SUCCESS;
    }
}

CM_RT_API int32_t CmDevice_RT::GetVaDpy(VADisplay* & vaDpy)
{
    INSERT_PROFILER_RECORD();

    vaDpy = &m_vaDisplay;
    return CM_SUCCESS;
}

#ifndef ANDROID
int32_t CmDevice_RT::GetLibvaDisplayDrm(VADisplay & vaDisplay)
{
    pfVAGetDisplayDRM vaGetDisplayDRM = nullptr;
    char *dlSymErr = nullptr;
    void *hLibVaDRM = nullptr;

    CLock locker(m_vaReferenceCountCriticalSection);

    if(m_vaReferenceCount > 0)
    {
        vaGetDisplayDRM    = m_vaGetDisplayDrm;
        m_vaReferenceCount ++;
    }
    else
    {
        //Load libva-drm.so
        dlerror();
        hLibVaDRM = dlopen( "libva-drm.so", RTLD_LAZY );

        if (!hLibVaDRM)
        {
            if ((dlSymErr = dlerror()) != nullptr)
            {
                fprintf(stderr, "%s\n", dlSymErr);
            }
            return CM_INVALID_LIBVA_INITIALIZE;
        }

        //dynamically load function vaGetDisplayDRM from libva-drm.so
        dlerror();
        vaGetDisplayDRM = (pfVAGetDisplayDRM)dlsym(hLibVaDRM, "vaGetDisplayDRM");
        if ((dlSymErr= dlerror()) != nullptr)  {
            fprintf(stderr, "%s\n", dlSymErr);
            return CM_INVALID_LIBVA_INITIALIZE;
        }

        m_vaReferenceCount ++;
        m_vaDrm        = hLibVaDRM;
        m_vaGetDisplayDrm = vaGetDisplayDRM;
    }

    // open the GPU device
    m_driFileDescriptor = open("/dev/dri/renderD128", O_RDWR);
    if (m_driFileDescriptor < 0)
    {
        fprintf(stderr,"Failed to open GPU device file node\n");
        return CM_INVALID_LIBVA_INITIALIZE;
    }

    if (m_vaGetDisplayDrm == nullptr)
    {
        fprintf(stderr, "m_vaGetDisplayDrm should not be nullptr.\n");
        return CM_INVALID_LIBVA_INITIALIZE;
    }

    // get the display handle.
    if (vaGetDisplayDRM == nullptr)
    {
        fprintf(stderr, "vaGetDisplayDRM should not be nullptr.\n");
        return CM_INVALID_LIBVA_INITIALIZE;
    }
    vaDisplay            = vaGetDisplayDRM(m_driFileDescriptor);

    return CM_SUCCESS;
 }

int32_t CmDevice_RT::FreeLibvaDrm()
{
    CLock locker(m_vaReferenceCountCriticalSection);
    if(m_vaReferenceCount > 1)
    {
        m_vaReferenceCount --;
    }
    else
    {
        dlclose(m_vaDrm);
        m_vaDrm  = nullptr;
        m_vaGetDisplayDrm = nullptr;

        m_vaReferenceCount --;
    }

    if ( m_driFileDescriptor != -1)
    {
        close(m_driFileDescriptor);
        m_driFileDescriptor = -1;
    }
    return CM_SUCCESS;
}
#endif

