/*
 * Copyright (c) 2014-2019, Texas Instruments Incorporated
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * *  Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * *  Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * *  Neither the name of Texas Instruments Incorporated nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/**
 *  \file utils_prf.c
 *
 *  \brief This file has implementation for UTILS PERF API/
 *
 */

 /* ========================================================================== */
/*                             Include Files                                  */
/* ========================================================================== */

#include <stdint.h>
#include <stdio.h>
#include <string.h>

/* XDC/BIOS includes */
#include <ti/sysbios/BIOS.h>
#include <ti/sysbios/knl/Task.h>
#include <ti/sysbios/utils/Load.h>
#include <xdc/runtime/Types.h>
#include <xdc/runtime/Timestamp.h>
#include <xdc/runtime/Error.h>
#include <xdc/runtime/Memory.h>

/* OSAL Header files */
#include <ti/osal/osal.h>

#if defined (SOC_AM65XX)
/* UART Header files */
#include <ti/drv/uart/UART.h>
#include <ti/drv/uart/UART_stdio.h>
#endif

/* CSL Header files */
#include <ti/csl/csl_types.h>

/* Module Header file */
#include <utils_prf.h>

/* ========================================================================== */
/*                           Macros & Typedefs                                */
/* ========================================================================== */

#define UTILS_PRF_MAX_TASK_INST             (2U)

/* ========================================================================== */
/*                         Structure Declarations                             */
/* ========================================================================== */

/* Structure containing profiling information for a task. */
typedef struct
{
    uint32_t isAlloc;
    /**< Flag to indicate if this instance is allocated or not */
    Osal_TaskHandle pTsk;
    /**< Handle to the task */
    char name[32U];
    /**< name of the task */
    uint64_t totalTskThreadTime;
    /**< Total time the task has spent */
} Utils_PrfTsHndl;

/**
 *
 *  \brief  Structure for storing load information for the core.
 *
 *          This structure stores the complete load information
 *          of the core. It keeps time spent on Hwi, Swi etc.
 *
*/
typedef struct
{
    uint32_t totalSwiThreadTimeHi;
    /**< Upper 32bits of Total time spent in swi */
    uint32_t totalSwiThreadTimeLo;
    /**< Lower 32bits of Total time spent in swi */
    uint32_t totalHwiThreadTimeHi;
    /**< Upper 32bits of Total time spent in hwi */
    uint32_t totalHwiThreadTimeLo;
    /**< Lower 32bits of Total time spent in hwi */
    uint32_t totalTimeHi;
    /**< Upper 32bits of Total time spent */
    uint32_t totalTimeLo;
    /**< Lower 32bits of Total time spent */
    uint32_t totalIdlTskTimeHi[2U];
    /**< Upper 32bits of Total time spent in idle task
     * Note:  creating array of 2U for SMP where both cores idle load needs
     *        to be taken separately
     */
    uint32_t totalIdlTskTimeLo[2U];
    /**< Lower 32bits of Total time spent in idle task
     * Note:  creating array of 2U for SMP where both cores idle load needs
     *        to be taken separately
     */
} System_LinkStatsAccPrfLoadObj;

/**
 *
 *  \brief  Structure containing profiling information for a task.
 *
*/
typedef struct
{
    uint32_t isAlloc;
    /**< Flag to indicate if this instance is allocated or not */
    char name[32U];
    /**< name of the task */
    uint32_t totalTskThreadTimeHi;
    /**< Upper 32bits of Total time the task has spent */
    uint32_t totalTskThreadTimeLo;
    /**< Lower 32bits of Total time the task has spent */
} System_LinkStatsPrfLoadObj;

/**
 *
 *  \brief  Structure containing profiling information for all tasks.
 *
*/
typedef struct
{
    Utils_PrfTsHndl                tskHndlObj[2U];
    /**< Array of Utils_PrfTsHndl objects */
    System_LinkStatsPrfLoadObj     pPrfLoadObj[2U];
    /**< Pointer to the Performance Load information for the tasks
         stored in shared location.
         This memory gets updated only when Copy command is received */
    System_LinkStatsAccPrfLoadObj   accPrfLoadObj;
    /**< Local Copy of the accumulated performance load object */
} Utils_PrfObj;

/* ========================================================================== */
/*                          Function Declarations                             */
/* ========================================================================== */


/* ========================================================================== */
/*                            Global Variables                                */
/* ========================================================================== */

/**
 * \brief An instance of Utils_PrfObj
 */
Utils_PrfObj gUtils_prfObj;

/**
 * \brief Varibale to enable or disable Load calculation
 */
uint32_t gUtils_startLoadCalc = 0U;


/* ========================================================================== */
/*                          Function Definitions                              */
/* ========================================================================== */

/**
 *
 * \brief OSAL task sleep function
 *
 * \param  sleepTime [IN] Sleep time in milli seconds.
 *
 * \return  None
 *
 */
void Osal_TaskSleep(uint32_t sleepTime)
{
    Task_sleep(sleepTime);
}

/**
 *
 * \brief OSAL task create function
 *
 * \param  pCbFn [IN] Function pointer to task main function.
 *
 * \return  None
 *
 */
void Osal_TaskCreate(void* pCbFn)
{

    Task_Params     taskParams;
    Error_Block eb;

    Error_init(&eb);

    Task_Params_init(&taskParams);
    taskParams.priority = 10U;
    taskParams.instance->name = "EmacPollPkt";
    Task_create((ti_sysbios_knl_Task_FuncPtr)pCbFn, &taskParams, &eb);
}

/**
 *
 * \brief OSAL task create function with callback argument.
 *
 * \param  pCbFn [IN] Function pointer to task main function.
 *
 * \param  arg [IN] Callback argument
 *
 * \return  None
 *
 */
void Osal_TaskCreate_v2(void* pCbFn, uint32_t arg)
{

    Task_Params     taskParams;
    Error_Block eb;

    Error_init(&eb);

    Task_Params_init(&taskParams);
    taskParams.arg0 = arg;
    taskParams.priority = 10U;
    taskParams.instance->name = "EmacPollPkt";
    Task_create((ti_sysbios_knl_Task_FuncPtr)pCbFn, &taskParams, &eb);
}

/**
 *
 * \brief The function is used to allocate a memory block of the specified size.
 *
 * \param  num_bytes [IN] Size of the memory block being cleaned up.
 *
 * \return  Allocated block address
 *
 */
void* Osal_malloc(uint32_t num_bytes)
{
    Error_Block     errorBlock;
    Void*           ptr;

    /* Allocate memory */
    ptr = Memory_alloc(NULL, num_bytes, 4U, &errorBlock);
    if (ptr == NULL)
    {
        return NULL;
    }

    /* Return the allocated memory block. */
    return ptr;
}

/**
 *
 * \brief The function is used to clean up a specific memory block
 *
 * \param  ptr     [IN] Pointer to the memory block to be cleaned up.
 * \param  num_bytes [IN] Size of the memory block being cleaned up.
 *
 * \return  None
 *
 */
void Osal_free(void* ptr, uint32_t num_bytes)
{
    Memory_free(NULL, ptr, num_bytes);
}

/**
 *
 * \brief Call this function before using any peformance or Timestamp utils
 *
 * \return  0U on success
 *
 */
int32_t Utils_prfInit(void)
{
    memset(&gUtils_prfObj, 0U, sizeof(gUtils_prfObj));

    /* Due to a bug in the load module, you will need to call Load_reset().
       If this not called, then the load is sometimes incorrect the very
       first time, This will be fixed in future Bios release */
    Load_reset();

   /* Reset allocated Task Handle Object */
    memset(&gUtils_prfObj.tskHndlObj[0U], 0U,
        sizeof(Utils_PrfTsHndl) * UTILS_PRF_MAX_TASK_INST);

    return 0U;
}


/**
 *
 * \brief Get the 64-bit timer ticks
 *
 * \return  64-bit timer ticks
 *
 */
uint64_t Utils_prfTsGet64(void)
{
    Types_Timestamp64 ts64;
    uint64_t curTs;

    Timestamp_get64(&ts64);

    curTs = ((uint64_t) ts64.hi << 32U) | ts64.lo;

    return curTs;
}

/**
 *
 * \brief Register a task for load calculation
 *
 * \param  pTsk     [IN] Task Handle
 * \param  name     [IN] A name for the task
 *
 * \return  SYSTEM_LINK_STATUS_SOK on success
 *
 */
int32_t Utils_prfLoadRegister(Osal_TaskHandle pTsk, char *name)
{
    uint32_t hndlId;
    uint32_t cookie;
    int32_t status = -1;
    Utils_PrfTsHndl *pHndl;

    cookie = HwiP_disable();

    for (hndlId = 0U; hndlId < UTILS_PRF_MAX_TASK_INST; hndlId++)
    {
        pHndl = &gUtils_prfObj.tskHndlObj[hndlId];

        if (pHndl->isAlloc == (uint32_t)FALSE)
        {
            pHndl->isAlloc = (uint32_t)TRUE;
            pHndl->pTsk = pTsk;
            gUtils_prfObj.pPrfLoadObj[hndlId].isAlloc = (uint32_t)TRUE;
            strncpy(pHndl->name, name, sizeof(pHndl->name) - 1U);
            pHndl->name[sizeof(pHndl->name) - 1U] = (char)'\0';
            status = 0U;
            break;
        }
    }

    if (0U != status)
    {
        UART_printf(" UTILS: PRF: ##### Cannot allocate Object for %s ####\n",
            name);
    }

    HwiP_restore(cookie);

    return status;
}

/**
 *
 * \brief Un-Register the task for load caculation
 *
 * \param  pTsk    [IN] Task Handle
 *
 * \return  SYSTEM_LINK_STATUS_SOK on success
 *
 */
int32_t Utils_prfLoadUnRegister(Osal_TaskHandle pTsk)
{
    uint32_t hndlId;
    uint32_t cookie;
    int32_t status = -1;
    Utils_PrfTsHndl *pHndl;

    cookie = HwiP_disable();

    for (hndlId = 0U; hndlId < UTILS_PRF_MAX_TASK_INST; hndlId++)
    {
        pHndl = &gUtils_prfObj.tskHndlObj[hndlId];

        if (((uint32_t)TRUE == pHndl->isAlloc) && (pHndl->pTsk == pTsk))
        {
            pHndl->isAlloc = (uint32_t)FALSE;

            gUtils_prfObj.pPrfLoadObj[hndlId].isAlloc = (uint32_t)FALSE;
            status = 0U;
            break;
        }
    }

    HwiP_restore(cookie);

    return status;
}


int32_t Utils_prfGetLoad(Utils_SystemLoadStats *pStats)
{
    uint32_t cpuLoad;
    uint32_t cookie;
    uint64_t idlTime64, totalTime64, temp;

    cookie = HwiP_disable();

    idlTime64 = (uint64_t)gUtils_prfObj.accPrfLoadObj.totalIdlTskTimeLo[0U] &
        0xFFFFFFFFU;
    temp = (uint64_t)gUtils_prfObj.accPrfLoadObj.totalIdlTskTimeHi[0U] &
        0xFFFFFFFFU;
    idlTime64 = idlTime64 | (temp << 32U);

    /* Local Call, so getting performance numbers from the local object */
    totalTime64 = (uint64_t)gUtils_prfObj.accPrfLoadObj.totalTimeLo &
        0xFFFFFFFFU;
    temp = (uint64_t)gUtils_prfObj.accPrfLoadObj.totalTimeHi & 0xFFFFFFFFU;
    totalTime64 = totalTime64 | (temp << 32U);

    cpuLoad = 1000U - (uint32_t)((idlTime64 * 1000U) / totalTime64);

    HwiP_restore(cookie);

    pStats->totalLoadParams.integerValue    = cpuLoad/10U;
    pStats->totalLoadParams.fractionalValue = cpuLoad%10U;

    return 0U;
}

/**
 *
 * \brief Print loads for all the registered tasks
 *
 * \param  printTskLoad    [IN] print task load if true
 *
 * \return  0U on success
 *
 */
int32_t Utils_prfLoadPrintAll(Bool printTskLoad)
{
    uint32_t hwiLoad, swiLoad, tskLoad, totalLoad, otherLoad;
    uint32_t cpuLoad;
    uint32_t hndlId;
    Utils_PrfTsHndl *pHndl;
    System_LinkStatsAccPrfLoadObj *pAccPrfLoadObj;
    uint64_t totalTime, time64, temp;

    /* Reading from Local Object */
    pAccPrfLoadObj = &gUtils_prfObj.accPrfLoadObj;

    hwiLoad = swiLoad = tskLoad = 0U;

    totalTime = (uint64_t)pAccPrfLoadObj->totalTimeLo & 0xFFFFFFFFU;
    temp = (uint64_t)pAccPrfLoadObj->totalTimeHi & 0xFFFFFFFFU;
    totalTime = totalTime | (temp << 32U);

    time64 = (uint64_t)pAccPrfLoadObj->totalHwiThreadTimeLo & 0xFFFFFFFFU;
    temp = (uint64_t)pAccPrfLoadObj->totalHwiThreadTimeHi & 0xFFFFFFFFU;
    time64 = time64 | (temp << 32U);
    hwiLoad = (uint32_t)((time64 * 1000U) / totalTime);

    time64 = (uint64_t)pAccPrfLoadObj->totalSwiThreadTimeLo & 0xFFFFFFFFU;
    temp = (uint64_t)pAccPrfLoadObj->totalSwiThreadTimeHi & 0xFFFFFFFFU;
    time64 = time64 | (temp << 32U);
    swiLoad = (uint32_t)((time64 * 1000U) / totalTime);

    time64 = (uint64_t)pAccPrfLoadObj->totalIdlTskTimeLo[0U] & 0xFFFFFFFFU;
    temp = (uint64_t)pAccPrfLoadObj->totalIdlTskTimeHi[0U] & 0xFFFFFFFFU;
    time64 = time64 | (temp << 32U);
    cpuLoad = (uint32_t)((uint64_t)1000U - ((time64 * 1000U) / totalTime));

    totalLoad = hwiLoad+swiLoad;

    if (totalTime != 0U)
    {
        UART_printf("\nLOAD: CPU: %d.%d%% HWI: %d.%d%%, SWI:%d.%d%%",
                    cpuLoad/10U, cpuLoad%10U,
                    hwiLoad/10U, hwiLoad%10U,
                    swiLoad/10U, swiLoad%10U
                    );

        if (printTskLoad)
        {
            UART_printf("\n");

            for (hndlId = 0U; hndlId < UTILS_PRF_MAX_TASK_INST; hndlId++)
            {
                pHndl = &gUtils_prfObj.tskHndlObj[hndlId];

                if ((uint32_t)TRUE == pHndl->isAlloc)
                {
                    tskLoad = 0U;

                    tskLoad = (uint32_t)((pHndl->totalTskThreadTime *
                        (uint64_t)1000U) / totalTime);

                    totalLoad += tskLoad;

                    if (tskLoad)
                    {
                        UART_printf(" LOAD: TSK: %s: %d.%d%% \r\n",
                               pHndl->name,
                               tskLoad/10U,
                               tskLoad%10U
                            );
                    }
                }
            }

            otherLoad = cpuLoad - totalLoad;

            UART_printf("MISC: %d.%d%% \r\n",
                   otherLoad/10U,
                   otherLoad%10U
                );
        }
        UART_printf("\n");
    }

    return 0U;
}

/**
 *
 * \brief Start taking the performance load for all the registered tasks
 *
 */
Void Utils_prfLoadCalcStart(void)
{
    uint32_t cookie;
    cookie = HwiP_disable();
    gUtils_startLoadCalc = (uint32_t)TRUE;
    HwiP_restore(cookie);
}

/**
 *
 * \brief Stop taking the load for all the registered tasks
 *
 */
Void Utils_prfLoadCalcStop(void)
{
    uint32_t cookie;

    cookie = HwiP_disable();
    gUtils_startLoadCalc = (uint32_t)FALSE;
    HwiP_restore(cookie);
}

/**
 *
 * \brief Reset the load calculation mainly for next cycle of run
 *
 * \return  None
 *
 */
Void Utils_prfLoadCalcReset(void)
{
    uint32_t hndlId;
    uint32_t cookie;
    Utils_PrfTsHndl *pHndl;

    cookie = HwiP_disable();

    /* Reset Local Parameters */
    gUtils_prfObj.accPrfLoadObj.totalHwiThreadTimeHi = 0U;
    gUtils_prfObj.accPrfLoadObj.totalSwiThreadTimeHi = 0U;
    gUtils_prfObj.accPrfLoadObj.totalTimeHi = 0U;
    gUtils_prfObj.accPrfLoadObj.totalIdlTskTimeHi[0U] = 0U;
    gUtils_prfObj.accPrfLoadObj.totalIdlTskTimeHi[1U] = 0U;
    gUtils_prfObj.accPrfLoadObj.totalHwiThreadTimeLo = 0U;
    gUtils_prfObj.accPrfLoadObj.totalSwiThreadTimeLo = 0U;
    gUtils_prfObj.accPrfLoadObj.totalTimeLo = 0U;
    gUtils_prfObj.accPrfLoadObj.totalIdlTskTimeLo[0U] = 0U;
    gUtils_prfObj.accPrfLoadObj.totalIdlTskTimeLo[1U] = 0U;


    /* Reset the performace loads accumulator */
    for (hndlId = 0U; hndlId < UTILS_PRF_MAX_TASK_INST; hndlId++)
    {
        pHndl = &gUtils_prfObj.tskHndlObj[hndlId];

        if (((uint32_t)TRUE == pHndl->isAlloc) &&
            (NULL != pHndl->pTsk))
        {
            pHndl->totalTskThreadTime = 0U;
        }
    }

    HwiP_restore(cookie);
}


/**
 *
 * \brief Function called by Loadupdate for each update cycle
 *
 */
Void Utils_prfLoadUpdate(void)
{
    Utils_PrfTsHndl *pHndlLcl;
    Load_Stat hwiLoadStat, swiLoadStat, tskLoadStat, idlTskLoadStat[2U];
    Task_Handle idlTskHndl[2U] = {NULL};
    uint32_t hndlId;
    System_LinkStatsAccPrfLoadObj *pAccPrfLoadLcl;
    uint64_t time64, temp;

    if ((uint32_t)TRUE == gUtils_startLoadCalc)
    {
        /* Get the Local Object */
        pAccPrfLoadLcl = &gUtils_prfObj.accPrfLoadObj;

        /* Get the all loads first */
        idlTskHndl[0U] = Task_getIdleTaskHandle(0U);
        Load_getTaskLoad(idlTskHndl[0U], &idlTskLoadStat[0U]);
        Load_getGlobalHwiLoad(&hwiLoadStat);
        Load_getGlobalSwiLoad(&swiLoadStat);

        time64 = (uint64_t)pAccPrfLoadLcl->totalIdlTskTimeLo[0U] & 0xFFFFFFFFU;
        temp = (uint64_t)pAccPrfLoadLcl->totalIdlTskTimeHi[0U] & 0xFFFFFFFFU;
        time64 = time64 | (temp << 32U);
        time64 += idlTskLoadStat[0U].threadTime;
        pAccPrfLoadLcl->totalIdlTskTimeLo[0U] = (uint32_t)time64 & 0xFFFFFFFFU;
        pAccPrfLoadLcl->totalIdlTskTimeHi[0U] = (uint32_t)(((uint32_t)(time64 >> 32U)) & 0xFFFFFFFFU);

        time64 = (uint64_t)pAccPrfLoadLcl->totalHwiThreadTimeLo & 0xFFFFFFFFU;
        temp = (uint64_t)pAccPrfLoadLcl->totalHwiThreadTimeHi & 0xFFFFFFFFU;
        time64 = time64 | (temp << 32U);
        time64 += hwiLoadStat.threadTime;
        pAccPrfLoadLcl->totalHwiThreadTimeLo = time64 & 0xFFFFFFFFU;
        pAccPrfLoadLcl->totalHwiThreadTimeHi = (time64 >> 32U) & 0xFFFFFFFFU;

        time64 = (uint64_t)pAccPrfLoadLcl->totalSwiThreadTimeLo & 0xFFFFFFFFU;
        temp = (uint64_t)pAccPrfLoadLcl->totalSwiThreadTimeHi & 0xFFFFFFFFU;
        time64 = time64 | (temp << 32U);
        time64 += swiLoadStat.threadTime;
        pAccPrfLoadLcl->totalSwiThreadTimeLo = time64 & 0xFFFFFFFFU;
        pAccPrfLoadLcl->totalSwiThreadTimeHi = (time64 >> 32U) & 0xFFFFFFFFU;

        time64 = (uint64_t)pAccPrfLoadLcl->totalTimeLo & 0xFFFFFFFFU;
        temp = (uint64_t)pAccPrfLoadLcl->totalTimeHi & 0xFFFFFFFFU;
        time64 = time64 | (temp << 32U);
        time64 += hwiLoadStat.totalTime;
        pAccPrfLoadLcl->totalTimeLo = time64 & 0xFFFFFFFFU;
        pAccPrfLoadLcl->totalTimeHi = (time64 >> 32U) & 0xFFFFFFFFU;

        /* Call the load updated function of each registered task one by one
         * along with the swiLoad, hwiLoad, and Task's own load */
        for (hndlId = 0U; hndlId < UTILS_PRF_MAX_TASK_INST; hndlId++)
        {
            /* Get the local object */
            pHndlLcl = &gUtils_prfObj.tskHndlObj[hndlId];

            if (((uint32_t)TRUE == pHndlLcl->isAlloc) &&
                (NULL != pHndlLcl->pTsk))
            {
                Load_getTaskLoad(pHndlLcl->pTsk, &tskLoadStat);
                pHndlLcl->totalTskThreadTime += tskLoadStat.threadTime;
            }
        }

    }
}

/**
 * \brief   This API will print on UART/CCS Console.
 *
 * \param   pcString        string to be printed.
 *
 * \return  None.
 */
void Utils_consolePrintf(const char *pcString, ...)
{
    char printBuffer[256U];
    va_list arguments;

    /* Start the varargs processing. */
    va_start(arguments, pcString);
    vsnprintf (printBuffer, sizeof(printBuffer), pcString, arguments);
    printf(printBuffer);
#if defined (SOC_AM65XX)
        UART_printf(printBuffer);
#endif
    /* End the varargs processing. */
    va_end(arguments);
}

/* ========================================================================== */
/*                          Static Function Definitions                       */
/* ========================================================================== */

/* EoF */

