/*
 * SPDX-FileCopyrightText: Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 * SPDX-License-Identifier: MIT
 *
 * 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.
 */

#define NVOC_CRASHCAT_ENGINE_H_PRIVATE_ACCESS_ALLOWED
#include "crashcat/crashcat_engine.h"
#include "crashcat/crashcat_queue.h"
#include "crashcat/crashcat_wayfinder.h"
#include "utils/nvassert.h"
#include "nv-crashcat-decoder.h"

static NV_INLINE NvU64 _crashcatEngineComputeDescriptorKey(NV_CRASHCAT_MEM_APERTURE, NvU64);
static CrashCatBufferDescriptor *_crashcatEngineCreateBufferDescriptor(CrashCatEngine *,
                                                                       NV_CRASHCAT_MEM_APERTURE,
                                                                       NvU64, NvU64, void *);
static NV_INLINE void _crashcatEngineDestroyBufferDescriptor(CrashCatEngine *,
                                                             CrashCatBufferDescriptor *);
static void *_crashcatEngineMapBufferDescriptor(CrashCatEngine *, CrashCatBufferDescriptor *);
static void _crashcatEngineUnmapBufferDescriptor(CrashCatEngine *, CrashCatBufferDescriptor *);

NV_STATUS crashcatEngineLoadWayfinder_IMPL(CrashCatEngine *pCrashCatEng)
{
    NvU32 wfl0Offset = crashcatEngineGetWFL0Offset(pCrashCatEng);
    NvU32 wfl0 = crashcatEnginePriRead(pCrashCatEng, wfl0Offset);

    // Has the L0 wayfinder been populated yet?
    if (!crashcatWayfinderL0Valid(wfl0))
        return NV_WARN_NOTHING_TO_DO;

    NV_CHECK_OK_OR_RETURN(LEVEL_ERROR,
        objCreate(&pCrashCatEng->pWayfinder, pCrashCatEng, CrashCatWayfinder,
                  crashcatWayfinderL0Version(wfl0), // halspec args
                  wfl0));                           // constructor args

    return NV_OK;
}

CrashCatReport *crashcatEngineGetNextCrashReport_IMPL(CrashCatEngine *pCrashCatEng)
{
    // Don't attempt to probe for reports if CrashCat is not configured
    if (!crashcatEngineConfigured(pCrashCatEng))
        return NULL;

    // No reports if there's no wayfinder yet
    if ((pCrashCatEng->pWayfinder == NULL) &&
        (crashcatEngineLoadWayfinder(pCrashCatEng) != NV_OK))
        return NULL;

    CrashCatQueue *pQueue = crashcatWayfinderGetReportQueue_HAL(pCrashCatEng->pWayfinder);
    if (pQueue != NULL)
        return crashcatQueueConsumeNextReport_HAL(pQueue);

    return NULL;
}

NV_STATUS crashcatEngineConstruct_IMPL
(
    CrashCatEngine *pCrashCatEng
)
{
    mapInitIntrusive(&pCrashCatEng->registeredCrashBuffers);
    mapInitIntrusive(&pCrashCatEng->mappedCrashBuffers);

    return NV_OK;
}

void crashcatEngineDestruct_IMPL
(
    CrashCatEngine *pCrashCatEng
)
{
    crashcatEngineUnload(pCrashCatEng);

    // All buffers should be unmapped and unregistered before the destructor is called
    NV_ASSERT(mapCount(&pCrashCatEng->mappedCrashBuffers) == 0);
    NV_ASSERT(mapCount(&pCrashCatEng->registeredCrashBuffers) == 0);

    mapDestroy(&pCrashCatEng->mappedCrashBuffers);
    mapDestroy(&pCrashCatEng->registeredCrashBuffers);
}

void crashcatEngineUnload_IMPL
(
    CrashCatEngine *pCrashCatEng
)
{
    objDelete(pCrashCatEng->pWayfinder);
}

// Non-NVOC wrapper to handle variadic arguments
void crashcatEnginePrintf(CrashCatEngine *pCrashCatEng, NvU64 errorId, const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    // Dispatches virtual function with va_list
    crashcatEngineVprintf(pCrashCatEng, errorId, fmt, args);
    va_end(args);
}

static NV_INLINE NvU64 _crashcatEngineComputeDescriptorKey
(
    NV_CRASHCAT_MEM_APERTURE aperture,
    NvU64 offset
)
{
    // Offset should be at least 8-byte aligned so that the aperture bits can be stuffed
    NV_ASSERT_CHECKED((offset & (sizeof(NvU64) - 1)) == 0);
    return (offset | (NvU64)aperture);
}

static CrashCatBufferDescriptor *_crashcatEngineCreateBufferDescriptor
(
    CrashCatEngine *pCrashCatEng,
    NV_CRASHCAT_MEM_APERTURE aperture,
    NvU64 offset,
    NvU64 size,
    void *pEngPriv
)
{
    CrashCatBufferDescriptor *pBufDesc = portMemAllocNonPaged(sizeof(*pBufDesc));
    if (pBufDesc == NULL)
        return NULL;

    portMemSet(pBufDesc, 0, sizeof(*pBufDesc));

    pBufDesc->bRegistered = NV_FALSE;
    pBufDesc->aperture = aperture;
    pBufDesc->physOffset = offset;
    pBufDesc->size = size;
    pBufDesc->pEngPriv = pEngPriv;

    return pBufDesc;
}

static NV_INLINE void _crashcatEngineDestroyBufferDescriptor
(
    CrashCatEngine *pCrashCatEng,
    CrashCatBufferDescriptor *pBufDesc
)
{
    portMemFree(pBufDesc);
}

static void *_crashcatEngineMapBufferDescriptor
(
    CrashCatEngine *pCrashCatEng,
    CrashCatBufferDescriptor *pBufDesc
)
{
    void *ptr = NULL;

    switch (pBufDesc->aperture)
    {
        case NV_CRASHCAT_MEM_APERTURE_SYSGPA:
        case NV_CRASHCAT_MEM_APERTURE_FBGPA:
        {
            ptr = crashcatEngineMapBufferDescriptor(pCrashCatEng, pBufDesc);
            NV_CHECK_OR_RETURN(LEVEL_ERROR, ptr != NULL, NULL);
            break;
        }
        case NV_CRASHCAT_MEM_APERTURE_DMEM:
        case NV_CRASHCAT_MEM_APERTURE_EMEM:
        {
            ptr = portMemAllocNonPaged(pBufDesc->size);
            NV_CHECK_OR_RETURN(LEVEL_ERROR, ptr != NULL, NULL);
            break;
        }
        default:
            NV_PRINTF(LEVEL_WARNING,
                "Unknown CrashCat aperture ID 0x%02x (offset = 0x%" NvU64_fmtx
                ", size = 0x%" NvU64_fmtx ")\n",
                pBufDesc->aperture, pBufDesc->physOffset, pBufDesc->size);
            break;
    }

    return ptr;
}

static void _crashcatEngineUnmapBufferDescriptor
(
    CrashCatEngine *pCrashCatEng,
    CrashCatBufferDescriptor *pBufDesc
)
{
    switch (pBufDesc->aperture)
    {
        case NV_CRASHCAT_MEM_APERTURE_SYSGPA:
        case NV_CRASHCAT_MEM_APERTURE_FBGPA:
            crashcatEngineUnmapBufferDescriptor(pCrashCatEng, pBufDesc);
            break;
        case NV_CRASHCAT_MEM_APERTURE_DMEM:
        case NV_CRASHCAT_MEM_APERTURE_EMEM:
            portMemFree(pBufDesc->pMapping);
            break;
        default:
            NV_PRINTF(LEVEL_WARNING,
                "Unknown CrashCat aperture ID 0x%02x (offset = 0x%" NvU64_fmtx
                ", size = 0x%" NvU64_fmtx ")\n",
                pBufDesc->aperture, pBufDesc->physOffset, pBufDesc->size);
            break;
    }
}

NV_STATUS crashcatEngineRegisterCrashBuffer_IMPL
(
    CrashCatEngine *pCrashCatEng,
    NV_CRASHCAT_MEM_APERTURE aperture,
    NvU64 offset,
    NvU64 size,
    void *pEngPriv
)
{
    NV_CHECK_OR_RETURN(LEVEL_INFO, (aperture == NV_CRASHCAT_MEM_APERTURE_SYSGPA) ||
                                   (aperture == NV_CRASHCAT_MEM_APERTURE_FBGPA),
                       NV_ERR_INVALID_ARGUMENT);
    NV_CHECK_OR_RETURN(LEVEL_INFO, size > 0, NV_ERR_INVALID_ARGUMENT);
    NV_CHECK_OR_RETURN(LEVEL_INFO, pEngPriv != NULL, NV_ERR_INVALID_ARGUMENT);

    // Create a crashcat buffer descriptor and register in the registeredCrashBuffers
    CrashCatBufferDescriptor *pBufDesc = _crashcatEngineCreateBufferDescriptor(pCrashCatEng,
                                                                               aperture,
                                                                               offset, size,
                                                                               pEngPriv);
    if (pBufDesc == NULL)
        return NV_ERR_NO_MEMORY;

    pBufDesc->bRegistered = NV_TRUE;

    NvU64 key = _crashcatEngineComputeDescriptorKey(aperture, offset);
    if (!mapInsertExisting(&pCrashCatEng->registeredCrashBuffers, key, pBufDesc))
    {
        _crashcatEngineDestroyBufferDescriptor(pCrashCatEng, pBufDesc);
        return NV_ERR_INSERT_DUPLICATE_NAME;
    }

    return NV_OK;
}

void crashcatEngineUnregisterCrashBuffer_IMPL
(
    CrashCatEngine *pCrashCatEng,
    NV_CRASHCAT_MEM_APERTURE aperture,
    NvU64 offset,
    NvU64 size
)
{
    NvU64 key = _crashcatEngineComputeDescriptorKey(aperture, offset);
    CrashCatBufferDescriptor *pBufDesc = mapFind(&pCrashCatEng->registeredCrashBuffers, key);
    if (pBufDesc == NULL)
        return;

    NV_ASSERT_CHECKED(pBufDesc->size == size);

    //
    // CrashCat should be unloaded from the engine before unregistering the crash buffer.
    // Unload will unmap all buffers.
    //
    NV_ASSERT_CHECKED(pBufDesc->pMapping == NULL);

    mapRemove(&pCrashCatEng->registeredCrashBuffers, pBufDesc);
    _crashcatEngineDestroyBufferDescriptor(pCrashCatEng, pBufDesc);
}

void *crashcatEngineMapCrashBuffer_IMPL
(
    CrashCatEngine *pCrashCatEng,
    NV_CRASHCAT_MEM_APERTURE aperture,
    NvU64 offset,
    NvU64 size
)
{
    NvU64 key = _crashcatEngineComputeDescriptorKey(aperture, offset);
    CrashCatBufferDescriptor *pBufDesc = mapFind(&pCrashCatEng->registeredCrashBuffers, key);

    // Sysmem buffers always need to be pre-registered
    if ((aperture != NV_CRASHCAT_MEM_APERTURE_SYSGPA) && (pBufDesc == NULL))
        pBufDesc = _crashcatEngineCreateBufferDescriptor(pCrashCatEng, aperture,
                                                         offset, size, NULL);

    NV_CHECK_OR_RETURN(LEVEL_ERROR, pBufDesc != NULL, NULL);
    NV_ASSERT_CHECKED(pBufDesc->size == size);

    pBufDesc->pMapping = _crashcatEngineMapBufferDescriptor(pCrashCatEng, pBufDesc);

    if ((pBufDesc->pMapping == NULL) ||
        !mapInsertExisting(&pCrashCatEng->mappedCrashBuffers, (NvU64)pBufDesc->pMapping, pBufDesc))
    {
        if (pBufDesc->pMapping != NULL)
        {
            _crashcatEngineUnmapBufferDescriptor(pCrashCatEng, pBufDesc);
            pBufDesc->pMapping = NULL;
        }

        //
        // If this wasn't a registered buffer descriptor, it was created above, so destroy it
        // before returning.
        //
        if (!pBufDesc->bRegistered)
            _crashcatEngineDestroyBufferDescriptor(pCrashCatEng, pBufDesc);

        return NULL;
    }

    return pBufDesc->pMapping;
}

void crashcatEngineUnmapCrashBuffer_IMPL
(
    CrashCatEngine *pCrashCatEng,
    void *ptr
)
{
    CrashCatBufferDescriptor *pBufDesc = mapFind(&pCrashCatEng->mappedCrashBuffers, (NvU64)ptr);
    if (pBufDesc == NULL)
        return;

    NV_ASSERT_CHECKED(ptr == pBufDesc->pMapping);

    mapRemove(&pCrashCatEng->mappedCrashBuffers, pBufDesc);
    _crashcatEngineUnmapBufferDescriptor(pCrashCatEng, pBufDesc);
    pBufDesc->pMapping = NULL;

    // If this was not a registered buffer, destroy the buffered descriptor now
    if (!pBufDesc->bRegistered)
        _crashcatEngineDestroyBufferDescriptor(pCrashCatEng, pBufDesc);
}

void crashcatEngineSyncCrashBuffer_IMPL
(
    CrashCatEngine *pCrashCatEng,
    void *ptr,
    NvU32 offset,
    NvU32 size
)
{
    CrashCatBufferDescriptor *pBufDesc = mapFind(&pCrashCatEng->mappedCrashBuffers, (NvU64)ptr);

    NV_ASSERT_OR_RETURN_VOID(pBufDesc != NULL);

    // Direct-map buffers don't require any sync
    if ((pBufDesc->aperture == NV_CRASHCAT_MEM_APERTURE_SYSGPA) ||
        (pBufDesc->aperture == NV_CRASHCAT_MEM_APERTURE_FBGPA))
        return;

    crashcatEngineSyncBufferDescriptor(pCrashCatEng, pBufDesc, offset, size);
}