/* Copyright (c) 2016-2019, NVIDIA CORPORATION.  All rights reserved.
 *
 * NVIDIA CORPORATION and its licensors retain all intellectual property
 * and proprietary rights in and to this software, related documentation
 * and any modifications thereto.  Any use, reproduction, disclosure or
 * distribution of this software and related documentation without an express
 * license agreement from NVIDIA CORPORATION is strictly prohibited.
 */

#include "signal.h"
#include "err_handler.h"
#include "capture.h"
#include "img_dev.h"
#include "os_common.h"

static uint32_t
_ErrHandlerThreadFunc(void *data)
{
    NvErrHandlerContext *errHandlerCtx = (NvErrHandlerContext *)data;
    NvMediaStatus status;
    uint32_t link = 0u;

    while (!(*errHandlerCtx->quit)) {
        status = ExtImgDevWaitForError(errHandlerCtx->extImgDevice);
        LOG_DBG("%s: ExtImgDevWaitForError() returned\n", __func__);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: ExtImgDevWaitForError() failed\n", __func__);
            *errHandlerCtx->quit = NVMEDIA_TRUE;
            goto done;
        }

        if (errHandlerCtx->errWaitAbortedFlag == NVMEDIA_TRUE) {
            LOG_DBG("%s: ExtImgDevWaitForError() was aborted\n", __func__);
            goto done;
        }

        if ((*errHandlerCtx->suspend) == NVMEDIA_TRUE) {
            continue;
        }

        /* not check error type, only get the failed link */
        status = ExtImgDevGetError(errHandlerCtx->extImgDevice, &link, NULL);
        if (status != NVMEDIA_STATUS_OK && status != NVMEDIA_STATUS_NOT_SUPPORTED) {
            LOG_ERR("%s: ExtImgDevGetError() failed\n", __func__);
            *errHandlerCtx->quit = NVMEDIA_TRUE;
            goto done;
        }
    }
done:
    LOG_INFO("%s: Error Handler thread exited\n", __func__);
    errHandlerCtx->exitedFlag = NVMEDIA_TRUE;
    return NVMEDIA_STATUS_OK;
}

NvMediaStatus
ErrHandlerInit(NvMainContext *mainCtx)
{
    NvErrHandlerContext *errHandlerCtx  = NULL;
    NvCaptureContext *captureCtx = NULL;

    /* Use error handler only if ISC root device is present */
    captureCtx = mainCtx->ctxs[CAPTURE_ELEMENT];
    if (captureCtx->extImgDevice->iscRoot == NULL) {
        return NVMEDIA_STATUS_OK;
    }

   /* allocating error handler context */
    mainCtx->ctxs[ERR_HANDLER_ELEMENT]= malloc(sizeof(NvErrHandlerContext));
    if (!mainCtx->ctxs[ERR_HANDLER_ELEMENT]){
        LOG_ERR("%s: Failed to allocate memory for error handler context\n", __func__);
        return NVMEDIA_STATUS_OUT_OF_MEMORY;
    }

    errHandlerCtx = mainCtx->ctxs[ERR_HANDLER_ELEMENT];
    memset(errHandlerCtx,0,sizeof(NvErrHandlerContext));

    /* initialize context */
    errHandlerCtx->quit = &mainCtx->quit;
    errHandlerCtx->suspend = &mainCtx->suspend;
    errHandlerCtx->extImgDevice = captureCtx->extImgDevice;
    errHandlerCtx->exitedFlag = NVMEDIA_TRUE;
    errHandlerCtx->slave = mainCtx->testArgs->slaveTegra;
    errHandlerCtx->errWaitAbortedFlag = NVMEDIA_FALSE;

    return NVMEDIA_STATUS_OK;
}

NvMediaStatus
ErrHandlerFini(NvMainContext *mainCtx)
{
    NvErrHandlerContext *errHandlerCtx = NULL;

    if (!mainCtx)
        return NVMEDIA_STATUS_OK;

    errHandlerCtx = mainCtx->ctxs[ERR_HANDLER_ELEMENT];
    if (!errHandlerCtx)
        return NVMEDIA_STATUS_OK;

    free(errHandlerCtx);

    LOG_INFO("%s: done\n", __func__);

    return NVMEDIA_STATUS_OK;
}

NvMediaStatus
ErrHandlerProc(NvMainContext *mainCtx)
{
    NvErrHandlerContext *errHandlerCtx = NULL;
    NvMediaStatus status = NVMEDIA_STATUS_OK;

    errHandlerCtx = mainCtx->ctxs[ERR_HANDLER_ELEMENT];
    if (errHandlerCtx == NULL) {
        return NVMEDIA_STATUS_OK;
    }

    /* Create error handler thread */
    if (errHandlerCtx->slave != NVMEDIA_TRUE) {
        errHandlerCtx->exitedFlag = NVMEDIA_FALSE;
        status = NvThreadCreate(&errHandlerCtx->errHandlerThread,
                                &_ErrHandlerThreadFunc,
                                (void *)errHandlerCtx,
                                NV_THREAD_PRIORITY_NORMAL);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Failed to create error handler thread\n",
                    __func__);
            errHandlerCtx->exitedFlag = NVMEDIA_TRUE;
        }
    }

    return status;
}

NvMediaStatus
ErrHandlerSuspend(NvMainContext *mainCtx)
{
    NvErrHandlerContext *errHandlerCtx = NULL;
    NvMediaStatus status;

    if (!mainCtx)
        return NVMEDIA_STATUS_OK;

    errHandlerCtx = mainCtx->ctxs[ERR_HANDLER_ELEMENT];
    if (!errHandlerCtx)
        return NVMEDIA_STATUS_OK;

    errHandlerCtx->errWaitAbortedFlag = NVMEDIA_TRUE;

    if (errHandlerCtx->errHandlerThread) {

        status = ExtImgDevAbortWaitForError(errHandlerCtx->extImgDevice);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: ExtImgDevAbortWaitForError() failed\n", __func__);
        }

        /* wait for thread to exit */
        while (!errHandlerCtx->exitedFlag) {
            LOG_DBG("%s: Waiting for error handler thread to quit\n",
                    __func__);
        }

        status = NvThreadDestroy(errHandlerCtx->errHandlerThread);
        if (status != NVMEDIA_STATUS_OK)
            LOG_ERR("%s: Failed to destroy error handler thread\n",
                    __func__);
    }

    LOG_INFO("%s: done\n", __func__);

    return NVMEDIA_STATUS_OK;
}


