/*
 * SPDX-FileCopyrightText: Copyright (c) 2020-2025 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.
 */
#include "containers/map.h"
#include "containers/multimap.h"
#include "nvctassert.h"
#include "nvmisc.h"
#include "nvport/sync.h"
#include "nvrm_registry.h"
#include "nvsecurityinfo.h"
#include "os/os.h"
#include "rmapi/control.h"
#include "rmapi/rmapi.h"
#include "rmapi/rmapi_cache_handlers.h"
#include "rmapi/rmapi_utils.h"
#include "ctrl/ctrl0000/ctrl0000system.h"
#include "ctrl/ctrl2080/ctrl2080gpu.h"
#include "ctrl/ctrl2080/ctrl2080fifo.h"
#include "ctrl/ctrl2080/ctrl2080bus.h"
#include "ctrl/ctrl2080/ctrl2080bios.h"
#include "ctrl/ctrl2080/ctrl2080ce.h"
#include "gpu/gpu.h"

typedef struct
{
    void* params;
    size_t paramSize;
    NvU32 rmctrlFlags;
} RmapiControlCacheEntry;

#define CACHE_GPU_FLAGS_SHIFT 32

//
// Stores the mapping of client object to the corresponding GPU attributes
// The key is generated by _handlesToGpuAttrKey with client and object handle
//
// The value is the combination of attribute of the GPU the object is linked to.
// The low 32 bits are used to store the GPU instance.
// THe high 32 bits are used for the above CACHE_GPU_FLAG* flags
//
MAKE_MAP(ObjectToGpuAttrMap, NvU64);

//
// Stores the cached control value.
// Each submap in the multimap stores the cached control value for one GPU.
// The key to find a submap is GPU Instance stored in ObjectToGpuAttrMap
//
// The key inside the submap is the control command
// The value inside the submap is the cached control value for the command
//
MAKE_MULTIMAP(GpusControlCache, RmapiControlCacheEntry);

ct_assert(sizeof(NvHandle) <= 4);

#define CLIENT_KEY_SHIFT (sizeof(NvHandle) * 8)

static inline NvBool _isCmdSystemWide(NvU32 cmd)
{
    return DRF_VAL(XXXX, _CTRL_CMD, _CLASS, cmd) == NV01_ROOT;
}

static inline NvHandle _gpuAttrKeyToClient(NvU64 key)
{
    return (key >> CLIENT_KEY_SHIFT);
}

static inline NvU64 _handlesToGpuAttrKey(NvHandle hClient, NvHandle hObject)
{
    return ((NvU64)hClient << CLIENT_KEY_SHIFT) | hObject;
}

static inline NvU32 _getGpuInstFromGpuAttr(NvU64 gpuAttr)
{
    return (NvU32)(gpuAttr & (NVBIT64(CACHE_GPU_FLAGS_SHIFT) - 1));
}

static inline NvU32 _getCacheGpuFlagsFromGpuAttr(NvU64 gpuAttr)
{
    return (NvU32)(gpuAttr >> CACHE_GPU_FLAGS_SHIFT);
}

static NvU64 _getGpuAttrFromGpu(OBJGPU *pGpu)
{
    NvU64 gpuAttr = 0;
    NvU32 cacheGpuFlags = 0;

    gpuAttr |= pGpu->gpuInstance;
    gpuAttr |= ((NvU64)cacheGpuFlags) << CACHE_GPU_FLAGS_SHIFT;

    return gpuAttr;
}

static struct {
    GpusControlCache gpusControlCache;
    ObjectToGpuAttrMap objectToGpuAttrMap;
    NvU32 mode;
    PORT_RWLOCK *pLock;
} RmapiControlCache;

enum CACHE_LOCK_TYPE
{
    LOCK_EXCLUSIVE,
    LOCK_SHARED
};

static void _cacheLockAcquire(enum CACHE_LOCK_TYPE lockType)
{
    if (lockType == LOCK_EXCLUSIVE)
        portSyncRwLockAcquireWrite(RmapiControlCache.pLock);
    else
        portSyncRwLockAcquireRead(RmapiControlCache.pLock);
}

static void _cacheLockRelease(enum CACHE_LOCK_TYPE lockType)
{
    if (lockType == LOCK_EXCLUSIVE)
        portSyncRwLockReleaseWrite(RmapiControlCache.pLock);
    else
        portSyncRwLockReleaseRead(RmapiControlCache.pLock);
}

static inline
NvBool _cacheIsDisabled(void)
{
    return (rmapiControlCacheGetMode() ==
                NV0000_CTRL_SYSTEM_RMCTRL_CACHE_MODE_CTRL_MODE_DISABLE);
}

static RmapiControlCacheEntry* _setCacheEntry(NvU64 key1, NvU64 key2, NvU32 allocSize,
                                              NvU32 rmctrlFlags, NvBool *pbParamsAllocated);
static RmapiControlCacheEntry* _getCacheEntry(NvU64 key1, NvU64 key2);

NvBool rmapiControlIsCacheable(NvU32 flags, NvU32 accessRight, NvBool bAllowInternal)
{
    if (_cacheIsDisabled())
        return NV_FALSE;

    if (!(flags & RMCTRL_FLAGS_CACHEABLE_ANY))
        return NV_FALSE;

    //
    // RMCTRL with access right requires access right check.
    // We need resource ref and client object to check the access right, both not
    // available here. Do not cache RMCTRLs with access right.
    //
    if (accessRight != 0)
        return NV_FALSE;

    // Allow internal RMCTRL of all privilege with bAllowInternal flag
    if (flags & RMCTRL_FLAGS_INTERNAL)
        return bAllowInternal;

    return NV_TRUE;
}

NvBool rmapiCmdIsCacheable(NvU32 cmd, NvBool bAllowInternal)
{
    NvU32 flags;
    NvU32 accessRight;

    if (rmapiutilGetControlInfo(cmd, &flags, &accessRight, NULL) != NV_OK)
        return NV_FALSE;

    return rmapiControlIsCacheable(flags, accessRight, bAllowInternal);
}

NV_STATUS rmapiControlCacheInit(void)
{
#if   defined(DEBUG)
    // Beware that verification only mode will not work during GCOFF.
    RmapiControlCache.mode = NV0000_CTRL_SYSTEM_RMCTRL_CACHE_MODE_CTRL_MODE_VERIFY_ONLY;
#else
    RmapiControlCache.mode = NV0000_CTRL_SYSTEM_RMCTRL_CACHE_MODE_CTRL_MODE_ENABLE;
#endif

    NvU32 mode;

    if (osReadRegistryDword(NULL, NV_REG_STR_RM_CACHEABLE_CONTROLS, &mode) == NV_OK)
    {
        RmapiControlCache.mode = mode;
    }
    NV_PRINTF(LEVEL_INFO, "using cache mode %d\n", RmapiControlCache.mode);

    multimapInit(&RmapiControlCache.gpusControlCache, portMemAllocatorGetGlobalNonPaged());
    mapInit(&RmapiControlCache.objectToGpuAttrMap, portMemAllocatorGetGlobalNonPaged());
    RmapiControlCache.pLock = portSyncRwLockCreate(portMemAllocatorGetGlobalNonPaged());
    if (RmapiControlCache.pLock == NULL)
    {
        NV_PRINTF(LEVEL_ERROR, "failed to create rw lock\n");
        multimapDestroy(&RmapiControlCache.gpusControlCache);
        mapDestroy(&RmapiControlCache.objectToGpuAttrMap);
        return NV_ERR_NO_MEMORY;
    }
    return NV_OK;
}

NV_STATUS rmapiControlCacheSetGpuAttrForObject
(
    NvHandle hClient,
    NvHandle hObject,
    OBJGPU *pGpu
)
{
    NV_STATUS status = NV_OK;
    NvU64 *entry;
    NvU64 gpuAttr;

    if (pGpu == NULL)
        return NV_ERR_INVALID_ARGUMENT;

    gpuAttr = _getGpuAttrFromGpu(pGpu);

    NV_PRINTF(LEVEL_INFO, "gpu attr set for 0x%x 0x%x: 0x%llx\n", hClient, hObject, gpuAttr);

    _cacheLockAcquire(LOCK_EXCLUSIVE);
    entry = mapFind(&RmapiControlCache.objectToGpuAttrMap, _handlesToGpuAttrKey(hClient, hObject));

    if (entry != NULL)
    {
        // set happens in object allocation, should not exist in cache already
        NV_PRINTF(LEVEL_WARNING,
                  "set existing gpu attr 0x%x 0x%x was 0x%llx is 0x%llx\n",
                  hClient, hObject, *entry, gpuAttr);
        status = NV_ERR_INVALID_PARAMETER;
        goto done;
    }

    entry = mapInsertNew(&RmapiControlCache.objectToGpuAttrMap, _handlesToGpuAttrKey(hClient, hObject));

    if (entry == NULL)
    {
        status = NV_ERR_NO_MEMORY;
        goto done;
    }

    *entry = gpuAttr;

done:
    _cacheLockRelease(LOCK_EXCLUSIVE);
    return status;
}

// Need to hold rmapi control cache read/write lock
static NV_STATUS _rmapiControlCacheGetGpuAttrForObject
(
    NvHandle hClient,
    NvHandle hObject,
    NvU32 *pGpuInst,
    NvU32 *pCacheGpuFlags
)
{
    NV_STATUS status = NV_ERR_OBJECT_NOT_FOUND;
    NvU64* entry = mapFind(&RmapiControlCache.objectToGpuAttrMap, _handlesToGpuAttrKey(hClient, hObject));

    NV_PRINTF(LEVEL_INFO, "cached gpu attr lookup for 0x%x 0x%x\n", hClient, hObject);

    if (entry != NULL)
    {
        NV_PRINTF(LEVEL_INFO, "cached gpu attr for 0x%x 0x%x: 0x%llx\n", hClient, hObject, *entry);

        if (pGpuInst != NULL)
            *pGpuInst = _getGpuInstFromGpuAttr(*entry);

        if (pCacheGpuFlags != NULL)
            *pCacheGpuFlags = _getCacheGpuFlagsFromGpuAttr(*entry);

        status = NV_OK;
    }

    return status;
}

// Need to hold rmapi control cache write lock
static void _rmapiControlCacheFreeGpuAttrForObject
(
    NvHandle hClient,
    NvHandle hObject
)
{
    const NvU64 key = _handlesToGpuAttrKey(hClient, hObject);
    NvU64* entry = mapFind(&RmapiControlCache.objectToGpuAttrMap, key);

    if (entry != NULL)
    {
        mapRemove(&RmapiControlCache.objectToGpuAttrMap, entry);
        NV_PRINTF(LEVEL_INFO, "Gpu Inst entry with key 0x%llx freed\n", key);
    }
}

// Need to hold rmapi control cache write lock
static void _rmapiControlCacheFreeGpuAttrForClient(NvHandle hClient)
{
    while (NV_TRUE)
    {
        NvU64* entry = mapFindGEQ(&RmapiControlCache.objectToGpuAttrMap, _handlesToGpuAttrKey(hClient, 0));
        NvU64 key;

        if (entry == NULL)
            break;

        key = mapKey(&RmapiControlCache.objectToGpuAttrMap, entry);

        if (_gpuAttrKeyToClient(key) != hClient)
            break;

        mapRemove(&RmapiControlCache.objectToGpuAttrMap, entry);
        NV_PRINTF(LEVEL_INFO, "Gpu Inst entry with key 0x%llx freed\n", key);
    }
}

static NV_STATUS _rmapiControlCacheGetCacheable
(
    NvHandle hClient,
    NvHandle hObject,
    NvU32    cmd,
    void*    params,
    NvU32    paramsSize
)
{
    RmapiControlCacheEntry *entry;
    NvU32 gpuInst;
    NV_STATUS status = NV_OK;

    _cacheLockAcquire(LOCK_SHARED);

    if (_cacheIsDisabled())
    {
        // unexpected mode change.
        status = NV_ERR_INVALID_STATE;
        goto done;
    }

    if (_isCmdSystemWide(cmd))
    {
       gpuInst = NV_MAX_DEVICES;
    }
    else
    {
        status = _rmapiControlCacheGetGpuAttrForObject(hClient, hObject, &gpuInst, NULL);
        if (status != NV_OK)
            goto done;
    }

    entry = _getCacheEntry(gpuInst, cmd);
    if (entry == NULL || entry->params == NULL)
    {
        status = NV_ERR_OBJECT_NOT_FOUND;
        goto done;
    }

    if (!(entry->rmctrlFlags & RMCTRL_FLAGS_CACHEABLE))
    {
        status = NV_ERR_INVALID_STATE;
        goto done;
    }

    if (entry->paramSize == 0)
    {
        status = NV_ERR_INVALID_STATE;
        goto done;
    }

    if (entry->paramSize > paramsSize)
    {
        status = NV_ERR_BUFFER_TOO_SMALL;
        goto done;
    }

    portMemCopy(params, paramsSize, entry->params, entry->paramSize);
done:
    _cacheLockRelease(LOCK_SHARED);
    return status;
}

static NV_STATUS _rmapiControlCacheGetByInput
(
    NvHandle hClient,
    NvHandle hObject,
    NvU32 cmd,
    void* params,
    NvU32 paramsSize
);

/*!
 * Look up an entry keyed with (hClient, hObject, cmd) in RMCTRL cache.
 * Handle it either as a CACHEABLE or CACHEABLE_BY_INPUT entry according to its stored type.
 */
static NV_STATUS _rmapiControlCacheGetAny
(
    NvHandle hClient,
    NvHandle hObject,
    NvU32 cmd,
    void* params,
    NvU32 paramsSize,
    API_SECURITY_INFO *pSecInfo
)
{
    RmapiControlCacheEntry *entry;
    NvU32 gpuInst;
    NV_STATUS status = NV_OK;
    NvU32 rmctrlFlags = 0;

    _cacheLockAcquire(LOCK_SHARED);

    if (_cacheIsDisabled())
    {
        // unexpected mode change.
        status = NV_ERR_INVALID_STATE;
        goto fail_release;
    }

    if (_isCmdSystemWide(cmd))
    {
       gpuInst = NV_MAX_DEVICES;
    }
    else
    {
        status = _rmapiControlCacheGetGpuAttrForObject(hClient, hObject, &gpuInst, NULL);
        if (status != NV_OK)
        {
            goto fail_release;
        }
    }

    entry = _getCacheEntry(gpuInst, cmd);
    if (entry == NULL || entry->params == NULL)
    {
        status = NV_ERR_OBJECT_NOT_FOUND;
        goto fail_release;
    }

    rmctrlFlags = entry->rmctrlFlags;

    status = rmControlValidateClientPrivilegeAccess(hClient, hObject, cmd, rmctrlFlags, pSecInfo);
    if (status != NV_OK)
        goto fail_release;

    _cacheLockRelease(LOCK_SHARED);

    // Re-acquire the lock when we do the actual lookup
    switch ((rmctrlFlags & RMCTRL_FLAGS_CACHEABLE_ANY))
    {
        case RMCTRL_FLAGS_CACHEABLE:
            status = _rmapiControlCacheGetCacheable(hClient, hObject, cmd, params, paramsSize);
            break;
        case RMCTRL_FLAGS_CACHEABLE_BY_INPUT:
            status = _rmapiControlCacheGetByInput(hClient, hObject, cmd, params, paramsSize);
            break;
        default:
            NV_PRINTF(LEVEL_ERROR, "Invalid cacheable flag 0x%x for cmd 0x%x\n", rmctrlFlags, cmd);
            status = NV_ERR_INVALID_PARAMETER;
            break;
    }

    return status;

fail_release:
    _cacheLockRelease(LOCK_SHARED);
    return status;
}

static NV_STATUS _rmapiControlCacheSet
(
    NvHandle hClient,
    NvHandle hObject,
    NvU32 cmd,
    NvU32 rmctrlFlags,
    const void* params,
    NvU32 paramsSize
)
{
    NV_STATUS status = NV_OK;
    RmapiControlCacheEntry* entry = NULL;
    NvU32 gpuInst;
    NvBool bParamsAllocated;

    _cacheLockAcquire(LOCK_EXCLUSIVE);

    if (_cacheIsDisabled())
    {
        // unexpected mode change.
        status = NV_ERR_INVALID_STATE;
        goto done;
    }

    if (_isCmdSystemWide(cmd))
    {
        gpuInst = NV_MAX_DEVICES;
    }
    else
    {
        status = _rmapiControlCacheGetGpuAttrForObject(hClient, hObject, &gpuInst, NULL);
        if (status != NV_OK)
            goto done;
    }

    entry = _setCacheEntry(gpuInst, cmd, paramsSize, rmctrlFlags, &bParamsAllocated);
    if (entry == NULL)
    {
        status = NV_ERR_NO_MEMORY;
        goto done;
    }

    //
    // A succeeded getOrInit call without params allocated implies
    // duplicated cache insertion that should be skipped.
    // Duplicated cache set happens when
    // 1. Parallel controls call into RM before first cache set.
    //    All threads will attempt cache set after the control calls.
    // 2. Cache already set by RPC to GSP path
    // 3. Cache in verify only mode
    //
    if (!bParamsAllocated)
    {
        if (RmapiControlCache.mode == NV0000_CTRL_SYSTEM_RMCTRL_CACHE_MODE_CTRL_MODE_VERIFY_ONLY)
        {
            NV_ASSERT(portMemCmp(entry->params, params, paramsSize) == 0);
        }
        status = NV_OK;
        goto done;
    }

    portMemCopy(entry->params, paramsSize, params, paramsSize);

done:
    _cacheLockRelease(LOCK_EXCLUSIVE);
    return status;
}

//
// Create new cache entry without taking any locks.
// Requires cache lock to be held before calling.
//
//   key1 [IN]
//     First key for the multimap entry
//   key2 [IN]
//     Second key for the multimap entry
//   bSet [IN]
//     If the query is for cache set or cache get
//   allocSize [IN]
//     The size to allocate for the new cache entry.
//   pbParamsAllocated [OUT]
//     Indicate if we allocated new memory for cache entry
//     A cache set without memory allocation implies there's an existng entry.
//
static RmapiControlCacheEntry*
_setCacheEntry
(
    NvU64 key1,
    NvU64 key2,
    NvU32 allocSize,
    NvU32 rmctrlFlags,
    NvBool *pbParamsAllocated
)
{
    RmapiControlCacheEntry *entry = NULL;
    GpusControlCacheSubmap *insertedSubmap = NULL;

    entry = multimapFindItem(&RmapiControlCache.gpusControlCache, key1, key2);

    // for cache set, try to init entry if not valid
    if (entry == NULL)
    {
        if (multimapFindSubmap(&RmapiControlCache.gpusControlCache, key1) == NULL)
        {
            insertedSubmap = multimapInsertSubmap(&RmapiControlCache.gpusControlCache, key1);
            if (insertedSubmap == NULL)
                goto failed;
        }

        entry = multimapInsertItemNew(&RmapiControlCache.gpusControlCache, key1, key2);
    }

    if (entry == NULL)
        goto failed_free_submap;

    if (entry->params == NULL)
    {
        entry->params = portMemAllocNonPaged(allocSize);
        if (entry->params == NULL)
            goto failed_free_entry;

        portMemSet(entry->params, 0, allocSize);
        entry->paramSize = allocSize;
        entry->rmctrlFlags = rmctrlFlags;

        if (pbParamsAllocated != NULL)
            *pbParamsAllocated = NV_TRUE;
    }
    else if (pbParamsAllocated != NULL)
    {
        *pbParamsAllocated = NV_FALSE;
    }

    return entry;

failed_free_entry:
    if (entry != NULL)
        multimapRemoveItem(&RmapiControlCache.gpusControlCache, entry);
failed_free_submap:
    if (insertedSubmap != NULL)
        multimapRemoveSubmap(&RmapiControlCache.gpusControlCache, insertedSubmap);
failed:
    return NULL;
}


/*!
 * Look up an entry keyed with (key1, key2) in RMCTRL cache.
 * Does not take any locks.
 */
static RmapiControlCacheEntry*
_getCacheEntry
(
    NvU64 key1,
    NvU64 key2
)
{
    // for cache get, return map find result directly
    return multimapFindItem(&RmapiControlCache.gpusControlCache, key1, key2);
}

static NvBool _isGpuGetInfoIndexCacheable(NvU32 index, NvU32 cacheGpuFlags)
{
    switch (index)
    {
        case NV2080_CTRL_GPU_INFO_INDEX_MINOR_REVISION_EXT:
        case NV2080_CTRL_GPU_INFO_INDEX_NETLIST_REV0:
        case NV2080_CTRL_GPU_INFO_INDEX_NETLIST_REV1:
        case NV2080_CTRL_GPU_INFO_INDEX_SYSMEM_ACCESS:
        case NV2080_CTRL_GPU_INFO_INDEX_GEMINI_BOARD:
        case NV2080_CTRL_GPU_INFO_INDEX_SURPRISE_REMOVAL_POSSIBLE:
        case NV2080_CTRL_GPU_INFO_INDEX_GLOBAL_POISON_FUSE_ENABLED:
        case NV2080_CTRL_GPU_INFO_INDEX_GPU_SR_SUPPORT:
        case NV2080_CTRL_GPU_INFO_INDEX_SPLIT_VAS_MGMT_SERVER_CLIENT_RM:
        case NV2080_CTRL_GPU_INFO_INDEX_GPU_SM_VERSION:
        case NV2080_CTRL_GPU_INFO_INDEX_4K_PAGE_ISOLATION_REQUIRED:
        case NV2080_CTRL_GPU_INFO_INDEX_DISPLAY_ENABLED:
        case NV2080_CTRL_GPU_INFO_INDEX_MOBILE_CONFIG_ENABLED:
        case NV2080_CTRL_GPU_INFO_INDEX_GPU_PROFILING_CAPABILITY:
        case NV2080_CTRL_GPU_INFO_INDEX_GPU_DEBUGGING_CAPABILITY:
        case NV2080_CTRL_GPU_INFO_INDEX_CMP_SKU:
        case NV2080_CTRL_GPU_INFO_INDEX_DMABUF_CAPABILITY:
        case NV2080_CTRL_GPU_INFO_INDEX_ECID_LO32:
        case NV2080_CTRL_GPU_INFO_INDEX_ECID_HI32:
        case NV2080_CTRL_GPU_INFO_INDEX_ECID_EXTENDED:
            return NV_TRUE;

        default:
            return NV_FALSE;
    }
}

static NvBool _isFifoGetInfoIndexCacheable(NvU32 index)
{
    switch (index)
    {
        case NV2080_CTRL_FIFO_INFO_INDEX_INSTANCE_TOTAL:
        case NV2080_CTRL_FIFO_INFO_INDEX_MAX_CHANNEL_GROUPS:
        case NV2080_CTRL_FIFO_INFO_INDEX_MAX_CHANNELS_PER_GROUP:
        case NV2080_CTRL_FIFO_INFO_INDEX_MAX_SUBCONTEXT_PER_GROUP:
        case NV2080_CTRL_FIFO_INFO_INDEX_BAR1_USERD_START_OFFSET:
        case NV2080_CTRL_FIFO_INFO_INDEX_DEFAULT_CHANNEL_TIMESLICE:
            return NV_TRUE;
        default:
            return NV_FALSE;
    }
}

static NvBool _isBusGetInfoIndexCacheable(NvU32 index)
{
    switch (index)
    {
        case NV2080_CTRL_BUS_INFO_INDEX_TYPE:
        case NV2080_CTRL_BUS_INFO_INDEX_INTLINE:
        case NV2080_CTRL_BUS_INFO_INDEX_CAPS:
        case NV2080_CTRL_BUS_INFO_INDEX_PCIE_DOWNSTREAM_LINK_CAPS:
        case NV2080_CTRL_BUS_INFO_INDEX_COHERENT_DMA_FLAGS:
        case NV2080_CTRL_BUS_INFO_INDEX_NONCOHERENT_DMA_FLAGS:
        case NV2080_CTRL_BUS_INFO_INDEX_BUS_NUMBER:
        case NV2080_CTRL_BUS_INFO_INDEX_DEVICE_NUMBER:
        case NV2080_CTRL_BUS_INFO_INDEX_DOMAIN_NUMBER:
        case NV2080_CTRL_BUS_INFO_INDEX_INTERFACE_TYPE:
        case NV2080_CTRL_BUS_INFO_INDEX_GPU_INTERFACE_TYPE:
            return NV_TRUE;
        default:
            return NV_FALSE;
    }
}

static NvBool _isVbiosGetInfoIndexCacheable(NvU32 index)
{
    switch (index)
    {
        case NV2080_CTRL_BIOS_INFO_INDEX_REVISION:
        case NV2080_CTRL_BIOS_INFO_INDEX_OEM_REVISION:
            return NV_TRUE;
        default:
            return NV_FALSE;
    }
}

static NvBool _isGetInfoIndexCacheable(NvU32 cmd, NvU32 index, NvU32 cacheGpuFlags)
{
    switch (cmd)
    {
        case NV2080_CTRL_CMD_GPU_GET_INFO_V2:
            return _isGpuGetInfoIndexCacheable(index, cacheGpuFlags);
        case NV2080_CTRL_CMD_FIFO_GET_INFO:
            return _isFifoGetInfoIndexCacheable(index);
        case NV2080_CTRL_CMD_BUS_GET_INFO_V2:
            return _isBusGetInfoIndexCacheable(index);
        case NV2080_CTRL_CMD_BIOS_GET_INFO_V2:
            return _isVbiosGetInfoIndexCacheable(index);
    }

    return NV_FALSE;
}

void _rmapiControlCacheRemoveMapEntry
(
    RmapiControlCacheEntry *pEntry
)
{
    multimapRemoveItem(&RmapiControlCache.gpusControlCache, pEntry);
}

//
// For GET_INFO controls, we use an array of getInfoCacheEntry to store the
// cached value.
//
// The length of the array is the max list length of each control and is
// enough to store the cached value of all indexes.
//
// The Nth item in the array, array[N], represent the cache state of the info
// whose index value is N. If the info is cached, array[N].valid is NV_TRUE
// and the cached value is stored in array[N].data.
// array[N].valid is NV_FALSE if the info is not cached.
//
typedef struct GetInfoCacheEntry {
    NvBool valid;
    NvU32 data;
} GetInfoCacheEntry;

static NV_STATUS _getInfoCacheHandler
(
    NvHandle              hClient,
    NvHandle              hObject,
    NvU32                 cmd,
    NvU32                 rmctrlFlags,
    NVXXXX_CTRL_XXX_INFO *pInfo,
    NvU32                 listSize,
    NvU32                 listSizeLimit,
    NvBool                bSet
)
{
    NV_STATUS status = NV_OK;
    NvU32 i = 0;
    NvU32 gpuInst;
    NvU32 cacheGpuFlags;
    RmapiControlCacheEntry *entry = NULL;
    GetInfoCacheEntry *cachedTable = NULL;
    const NvU32 allocSize = sizeof(GetInfoCacheEntry) * listSizeLimit;
    enum CACHE_LOCK_TYPE lockType = bSet ? LOCK_EXCLUSIVE : LOCK_SHARED;

    if (listSize <= 0 || listSize > listSizeLimit || pInfo == NULL)
    {
        return NV_ERR_INVALID_PARAMETER;
    }

    _cacheLockAcquire(lockType);

    if (_cacheIsDisabled())
    {
        // unexpected mode change.
        status = NV_ERR_INVALID_STATE;
        goto done;
    }

    status = _rmapiControlCacheGetGpuAttrForObject(hClient, hObject, &gpuInst, &cacheGpuFlags);
    if (status != NV_OK)
        goto done;

    if (bSet)
        entry = _setCacheEntry(gpuInst, cmd, allocSize, rmctrlFlags, NULL);
    else
        entry = _getCacheEntry(gpuInst, cmd);

    if (entry == NULL || entry->params == NULL)
    {
        status = bSet ? NV_ERR_NO_MEMORY : NV_ERR_OBJECT_NOT_FOUND;
        goto done;
    }

    if (!(entry->rmctrlFlags & RMCTRL_FLAGS_CACHEABLE_BY_INPUT))
    {
        status = NV_ERR_INVALID_STATE;
        goto done;
    }

    cachedTable = (GetInfoCacheEntry*)entry->params;

    for (i = 0; i < listSize; ++i)
    {
        const NvU32 index = pInfo[i].index;

        if (index >= listSizeLimit)
        {
            status = NV_ERR_INVALID_ARGUMENT;
            goto done;
        }

        if (bSet)
        {
            if (_isGetInfoIndexCacheable(cmd, index, cacheGpuFlags))
            {
                if (cachedTable[index].valid)
                {
                    NV_ASSERT(cachedTable[index].data == pInfo[i].data);
                }
                else
                {
                    cachedTable[index].valid = NV_TRUE;
                    cachedTable[index].data = pInfo[i].data;
                }
            }
        }
        else
        {
            // if any of the entry is not cacheable or not in the cache, skip the whole cmd
            if (!_isGetInfoIndexCacheable(cmd, index, cacheGpuFlags) || !cachedTable[index].valid)
            {
                status = NV_ERR_OBJECT_NOT_FOUND;
                goto done;
            }
        }
    }

    if (!bSet)
    {
        for (i = 0; i < listSize; ++i)
            pInfo[i].data = cachedTable[pInfo[i].index].data;
    }

done:

    if (status != NV_OK && bSet)
    {
        if (entry != NULL)
        {
            portMemFree(entry->params);
            _rmapiControlCacheRemoveMapEntry(entry);
        }
    }

    _cacheLockRelease(lockType);

    return status;
}

NV_STATUS _rmapiControlCacheGetByInputTemplateMethod
(
    NvHandle                    hClient,
    NvHandle                    hObject,
    NvU32                       cmd,
    NvU32                       rmctrlFlags,
    void                       *pParams,
    NvU32                       cacheEntrySize,
    RmapiCacheGetByInputHandler cacheHandler,
    NvBool                      bSet
)
{
    NvU32                   gpuInst;
    NV_STATUS               status   = NV_OK;
    RmapiControlCacheEntry *entry    = NULL;
    enum CACHE_LOCK_TYPE    lockType = (bSet) ? LOCK_EXCLUSIVE : LOCK_SHARED;
    NvBool                  bCacheEntryAllocated;

    _cacheLockAcquire(lockType);

    if (_cacheIsDisabled())
    {
        // Unexpected mode change.
        status = NV_ERR_INVALID_STATE;
        goto done;
    }

    status = _rmapiControlCacheGetGpuAttrForObject(hClient, hObject, &gpuInst, NULL);
    if (status != NV_OK)
        goto done;

    if (bSet)
        entry = _setCacheEntry(gpuInst, cmd, cacheEntrySize, rmctrlFlags, &bCacheEntryAllocated);
    else
        entry = _getCacheEntry(gpuInst, cmd);

    if (entry == NULL || entry->params == NULL)
    {
        status = (bSet) ? NV_ERR_NO_MEMORY : NV_ERR_OBJECT_NOT_FOUND;
        goto done;
    }

    status = cacheHandler(entry->params, pParams, bSet);
done:
    if (status != NV_OK && bSet)
    {
        if (entry != NULL && bCacheEntryAllocated)
        {
            if (entry->params)
                portMemFree(entry->params);
            _rmapiControlCacheRemoveMapEntry(entry);
        }
    }
    _cacheLockRelease(lockType);
    return status;
}

typedef struct GpuNameStringCacheEntry
{
    NvBool bAsciiValid;
    NvBool bUnicodeValid;
    NvU8  ascii[NV2080_GPU_MAX_NAME_STRING_LENGTH];
    NvU16 unicode[NV2080_GPU_MAX_NAME_STRING_LENGTH];
} GpuNameStringCacheEntry;

NV_STATUS _gpuNameStringGet
(
    NvHandle hClient,
    NvHandle hObject,
    NV2080_CTRL_GPU_GET_NAME_STRING_PARAMS *pParams
)
{
    NvU32 gpuInst;
    NV_STATUS status = NV_OK;
    RmapiControlCacheEntry *entry = NULL;
    GpuNameStringCacheEntry *cachedParams = NULL;

    _cacheLockAcquire(LOCK_SHARED);

    if (_cacheIsDisabled())
    {
        // unexpected mode change.
        status = NV_ERR_INVALID_STATE;
        goto done;
    }

    status = _rmapiControlCacheGetGpuAttrForObject(hClient, hObject, &gpuInst, NULL);
    if (status != NV_OK)
        goto done;

    entry = _getCacheEntry(gpuInst, NV2080_CTRL_CMD_GPU_GET_NAME_STRING);
    if (entry == NULL || entry->params == NULL)
    {
        status = NV_ERR_OBJECT_NOT_FOUND;
        goto done;
    }

    if (!(entry->rmctrlFlags & RMCTRL_FLAGS_CACHEABLE_BY_INPUT))
    {
        status = NV_ERR_INVALID_STATE;
        goto done;
    }

    cachedParams = (GpuNameStringCacheEntry *)entry->params;

    switch (pParams->gpuNameStringFlags)
    {
        case NV2080_CTRL_GPU_GET_NAME_STRING_FLAGS_TYPE_ASCII:
            if (!cachedParams->bAsciiValid)
            {
                status = NV_ERR_OBJECT_NOT_FOUND;
                goto done;
            }
            portMemCopy(pParams->gpuNameString.ascii,
                        sizeof(pParams->gpuNameString.ascii),
                        cachedParams->ascii,
                        sizeof(pParams->gpuNameString.ascii));
            break;

        case NV2080_CTRL_GPU_GET_NAME_STRING_FLAGS_TYPE_UNICODE:
            if (!cachedParams->bUnicodeValid)
            {
                status = NV_ERR_OBJECT_NOT_FOUND;
                goto done;
            }
            portMemCopy(pParams->gpuNameString.unicode,
                        sizeof(pParams->gpuNameString.unicode),
                        cachedParams->unicode,
                        sizeof(pParams->gpuNameString.unicode));
            break;

        default:
            NV_PRINTF(LEVEL_ERROR, "Unknown gpu name string flag: %u\n", pParams->gpuNameStringFlags);
            status = NV_ERR_OBJECT_NOT_FOUND;
            goto done;
    }
done:
    _cacheLockRelease(LOCK_SHARED);
    return status;
}

NV_STATUS _gpuNameStringSet
(
    NvHandle hClient,
    NvHandle hObject,
    NvU32 rmctrlFlags,
    const NV2080_CTRL_GPU_GET_NAME_STRING_PARAMS *pParams
)
{
    NvU32 gpuInst;
    NV_STATUS status;
    RmapiControlCacheEntry *entry = NULL;
    GpuNameStringCacheEntry *cachedParams = NULL;

    _cacheLockAcquire(LOCK_EXCLUSIVE);

    if (_cacheIsDisabled())
    {
        // unexpected mode change.
        status = NV_ERR_INVALID_STATE;
        goto done;
    }

    status = _rmapiControlCacheGetGpuAttrForObject(hClient, hObject, &gpuInst, NULL);
    if (status != NV_OK)
        goto done;

    entry = _setCacheEntry(gpuInst, NV2080_CTRL_CMD_GPU_GET_NAME_STRING, sizeof(GpuNameStringCacheEntry), rmctrlFlags, NULL);
    if (entry == NULL)
    {
        status = NV_ERR_NO_MEMORY;
        goto done;
    }

    cachedParams = (GpuNameStringCacheEntry *)entry->params;

    switch (pParams->gpuNameStringFlags)
    {
        case NV2080_CTRL_GPU_GET_NAME_STRING_FLAGS_TYPE_ASCII:
            if (cachedParams->bAsciiValid)
            {
                NV_ASSERT(
                    portMemCmp(pParams->gpuNameString.ascii,
                               cachedParams->ascii,
                               sizeof(pParams->gpuNameString.ascii)) == 0);
            }
            else
            {
                portMemCopy(cachedParams->ascii,
                            sizeof(pParams->gpuNameString.ascii),
                            pParams->gpuNameString.ascii,
                            sizeof(pParams->gpuNameString.ascii));
                cachedParams->bAsciiValid = NV_TRUE;
            }
            break;

        case NV2080_CTRL_GPU_GET_NAME_STRING_FLAGS_TYPE_UNICODE:
            if (cachedParams->bUnicodeValid)
            {
                NV_ASSERT(
                    portMemCmp(pParams->gpuNameString.unicode,
                               cachedParams->unicode,
                               sizeof(pParams->gpuNameString.unicode)) == 0);
            }
            else
            {
                portMemCopy(cachedParams->unicode,
                            sizeof(pParams->gpuNameString.unicode),
                            pParams->gpuNameString.unicode,
                            sizeof(pParams->gpuNameString.unicode));
                cachedParams->bUnicodeValid = NV_TRUE;
            }
            break;

        default:
            NV_PRINTF(LEVEL_ERROR, "Unknown gpu name string flag: %u\n", pParams->gpuNameStringFlags);
            status = NV_ERR_INVALID_PARAMETER;
            goto done;
    }

done:
    if (status != NV_OK)
    {
        if (entry != NULL)
        {
            if (entry->params)
                portMemFree(entry->params);
            _rmapiControlCacheRemoveMapEntry(entry);
        }
    }
    _cacheLockRelease(LOCK_EXCLUSIVE);

    return status;
}

typedef struct CePhysicalCapsCacheEntry
{
    NvBool valid;
    NvU8 capsTbl[NV2080_CTRL_CE_CAPS_TBL_SIZE];
} CePhysicalCapsCacheEntry;

static NV_STATUS _getCePhysicalCapsHandler
(
    NvHandle hClient,
    NvHandle hObject,
    NvU32 rmctrlFlags,
    NvU32 ceEngineType,
    NvU8 capsTbl[NV2080_CTRL_CE_CAPS_TBL_SIZE],
    NvBool bSet
)
{
    NV_STATUS status = NV_OK;
    NvU32 gpuInst;
    RmapiControlCacheEntry *entry = NULL;
    CePhysicalCapsCacheEntry *cachedTable = NULL;
    const NvU32 allocSize = sizeof(CePhysicalCapsCacheEntry) * NV2080_ENGINE_TYPE_COPY_SIZE;
    enum CACHE_LOCK_TYPE lockType = bSet ? LOCK_EXCLUSIVE : LOCK_SHARED;
    NvU32 ceEngineIndex;

    if (!NV2080_ENGINE_TYPE_IS_COPY(ceEngineType))
    {
        return NV_ERR_INVALID_ARGUMENT;
    }
    ceEngineIndex = NV2080_ENGINE_TYPE_COPY_IDX(ceEngineType);

    _cacheLockAcquire(lockType);

    if (_cacheIsDisabled())
    {
        // unexpected mode change.
        status = NV_ERR_INVALID_STATE;
        goto done;
    }

    status = _rmapiControlCacheGetGpuAttrForObject(hClient, hObject, &gpuInst, NULL);
    if (status != NV_OK)
        goto done;

    if (bSet)
        entry = _setCacheEntry(gpuInst, NV2080_CTRL_CMD_CE_GET_PHYSICAL_CAPS, allocSize, rmctrlFlags, NULL);
    else
        entry = _getCacheEntry(gpuInst, NV2080_CTRL_CMD_CE_GET_PHYSICAL_CAPS);

    if (entry == NULL || entry->params == NULL)
    {
        status = NV_ERR_NO_MEMORY;
        goto done;
    }

    if (!(entry->rmctrlFlags & RMCTRL_FLAGS_CACHEABLE_BY_INPUT))
    {
        status = NV_ERR_INVALID_STATE;
        goto done;
    }

    cachedTable = (CePhysicalCapsCacheEntry *)entry->params;

    if (bSet)
    {
        if (cachedTable[ceEngineIndex].valid)
        {
            NV_ASSERT(portMemCmp(capsTbl,
                                 cachedTable[ceEngineIndex].capsTbl,
                                 NV2080_CTRL_CE_CAPS_TBL_SIZE) == 0);
        }
        else
        {
            cachedTable[ceEngineIndex].valid = NV_TRUE;
            portMemCopy(cachedTable[ceEngineIndex].capsTbl,
                        NV2080_CTRL_CE_CAPS_TBL_SIZE,
                        capsTbl,
                        NV2080_CTRL_CE_CAPS_TBL_SIZE);
        }
    }
    else
    {
        if (!cachedTable[ceEngineIndex].valid)
        {
            status = NV_ERR_OBJECT_NOT_FOUND;
            goto done;
        }
        else
        {
            portMemCopy(capsTbl,
                        NV2080_CTRL_CE_CAPS_TBL_SIZE,
                        cachedTable[ceEngineIndex].capsTbl,
                        NV2080_CTRL_CE_CAPS_TBL_SIZE);
        }
    }

done:
    if (status != NV_OK && bSet)
    {
        if (entry != NULL)
        {
            if (entry->params != NULL)
                portMemFree(entry->params);
            multimapRemoveItem(&RmapiControlCache.gpusControlCache, entry);
        }
    }
    _cacheLockRelease(lockType);

    return status;
}

typedef struct CePceMaskCacheEntry
{
    NvBool valid;
    NvU32 pceMask;
} CePceMaskCacheEntry;

static NV_STATUS _getCePceMaskHandler
(
    NvHandle hClient,
    NvHandle hObject,
    NvU32 rmctrlFlags,
    NvU32 ceEngineType,
    NvU32 *pceMask,
    NvBool bSet
)
{
    NV_STATUS status = NV_OK;
    NvU32 gpuInst;
    RmapiControlCacheEntry *entry = NULL;
    CePceMaskCacheEntry *cachedTable = NULL;
    const NvU32 allocSize = sizeof(CePceMaskCacheEntry) * NV2080_ENGINE_TYPE_COPY_SIZE;
    enum CACHE_LOCK_TYPE lockType = bSet ? LOCK_EXCLUSIVE : LOCK_SHARED;
    NvU32 ceEngineIndex;

    if (!NV2080_ENGINE_TYPE_IS_COPY(ceEngineType))
    {
        return NV_ERR_INVALID_ARGUMENT;
    }
    ceEngineIndex = NV2080_ENGINE_TYPE_COPY_IDX(ceEngineType);

    _cacheLockAcquire(lockType);

    if (_cacheIsDisabled())
    {
        // unexpected mode change.
        status = NV_ERR_INVALID_STATE;
        goto done;
    }

    status = _rmapiControlCacheGetGpuAttrForObject(hClient, hObject, &gpuInst, NULL);
    if (status != NV_OK)
        goto done;

    if (bSet)
        entry = _setCacheEntry(gpuInst, NV2080_CTRL_CMD_CE_GET_CE_PCE_MASK, allocSize, rmctrlFlags, NULL);
    else
        entry = _getCacheEntry(gpuInst, NV2080_CTRL_CMD_CE_GET_CE_PCE_MASK);

    if (entry == NULL || entry->params == NULL)
    {
        status = NV_ERR_NO_MEMORY;
        goto done;
    }

    if (!(entry->rmctrlFlags & RMCTRL_FLAGS_CACHEABLE_BY_INPUT))
    {
        status = NV_ERR_INVALID_STATE;
        goto done;
    }

    cachedTable = (CePceMaskCacheEntry *)entry->params;

    if (bSet)
    {
        if (cachedTable[ceEngineIndex].valid)
        {
            NV_ASSERT(*pceMask == cachedTable[ceEngineIndex].pceMask);
        }
        else
        {
            cachedTable[ceEngineIndex].valid = NV_TRUE;
            cachedTable[ceEngineIndex].pceMask = *pceMask;
        }
    }
    else
    {
        if (!cachedTable[ceEngineIndex].valid)
        {
            status = NV_ERR_OBJECT_NOT_FOUND;
            goto done;
        }
        else
        {
            *pceMask = cachedTable[ceEngineIndex].pceMask;
        }
    }

done:
    if (status != NV_OK && bSet)
    {
        if (entry != NULL)
        {
            if (entry->params != NULL)
                portMemFree(entry->params);
            multimapRemoveItem(&RmapiControlCache.gpusControlCache, entry);
        }
    }
    _cacheLockRelease(lockType);

    return status;
}

NV_STATUS rmapiControlCacheFreeForControl
(
    NvU32 gpuInstance,
    NvU32 cmd
)
{
    RmapiControlCacheEntry *entry = NULL;

    _cacheLockAcquire(LOCK_EXCLUSIVE);

    entry = multimapFindItem(&RmapiControlCache.gpusControlCache, gpuInstance, cmd);

    if (entry == NULL)
        goto done;

    if (entry->params != NULL)
        portMemFree(entry->params);

    multimapRemoveItem(&RmapiControlCache.gpusControlCache, entry);

done:
    _cacheLockRelease(LOCK_EXCLUSIVE);

    return NV_OK;
}

static NV_STATUS _rmapiControlCacheGetByInput
(
    NvHandle hClient,
    NvHandle hObject,
    NvU32    cmd,
    void*    params,
    NvU32    paramsSize
)
{
    switch (cmd)
    {
        case NV2080_CTRL_CMD_GPU_GET_INFO_V2:
            return _getInfoCacheHandler(hClient, hObject, cmd, 0,
                                        ((NV2080_CTRL_GPU_GET_INFO_V2_PARAMS*)params)->gpuInfoList,
                                        ((NV2080_CTRL_GPU_GET_INFO_V2_PARAMS*)params)->gpuInfoListSize,
                                        NV2080_CTRL_GPU_INFO_MAX_LIST_SIZE,
                                        NV_FALSE);

        case NV2080_CTRL_CMD_FIFO_GET_INFO:
            return _getInfoCacheHandler(hClient, hObject, cmd, 0,
                                        ((NV2080_CTRL_FIFO_GET_INFO_PARAMS*)params)->fifoInfoTbl,
                                        ((NV2080_CTRL_FIFO_GET_INFO_PARAMS*)params)->fifoInfoTblSize,
                                        NV2080_CTRL_FIFO_GET_INFO_MAX_ENTRIES,
                                        NV_FALSE);

        case NV2080_CTRL_CMD_BUS_GET_INFO_V2:
            return _getInfoCacheHandler(hClient, hObject, cmd, 0,
                                        ((NV2080_CTRL_BUS_GET_INFO_V2_PARAMS*)params)->busInfoList,
                                        ((NV2080_CTRL_BUS_GET_INFO_V2_PARAMS*)params)->busInfoListSize,
                                        NV2080_CTRL_BUS_INFO_MAX_LIST_SIZE,
                                        NV_FALSE);

        case NV2080_CTRL_CMD_BIOS_GET_INFO_V2:
            return _getInfoCacheHandler(hClient, hObject, cmd, 0,
                                        ((NV2080_CTRL_BIOS_GET_INFO_V2_PARAMS*)params)->biosInfoList,
                                        ((NV2080_CTRL_BIOS_GET_INFO_V2_PARAMS*)params)->biosInfoListSize,
                                        NV2080_CTRL_BIOS_INFO_MAX_SIZE,
                                        NV_FALSE);

        case NV2080_CTRL_CMD_CE_GET_PHYSICAL_CAPS:
            return _getCePhysicalCapsHandler(hClient, hObject, 0,
                                             ((NV2080_CTRL_CE_GET_PHYSICAL_CAPS_PARAMS*)params)->ceEngineType,
                                             ((NV2080_CTRL_CE_GET_PHYSICAL_CAPS_PARAMS*)params)->capsTbl,
                                             NV_FALSE);

        case NV2080_CTRL_CMD_CE_GET_CE_PCE_MASK:
            return _getCePceMaskHandler(hClient, hObject, 0,
                                        ((NV2080_CTRL_CE_GET_CE_PCE_MASK_PARAMS*)params)->ceEngineType,
                                        &((NV2080_CTRL_CE_GET_CE_PCE_MASK_PARAMS*)params)->pceMask,
                                        NV_FALSE);

        case NV2080_CTRL_CMD_GPU_GET_NAME_STRING:
            return _gpuNameStringGet(hClient, hObject, params);

        case NV0073_CTRL_CMD_SYSTEM_GET_SUPPORTED:
            return _rmapiControlCacheGetByInputTemplateMethod(hClient, hObject, cmd, 0,
                                                              params, sizeof(DispSystemGetSupportedCacheEntry),
                                                              _dispSystemGetSupportedCacheHandler, NV_FALSE);

        case NV0073_CTRL_CMD_SYSTEM_GET_INTERNAL_DISPLAYS:
            return _rmapiControlCacheGetByInputTemplateMethod(hClient, hObject, cmd, 0,
                                                              params, sizeof(DispSystemGetInternalDisplaysCacheEntry),
                                                              _dispSystemGetInternalDisplaysCacheHandler, NV_FALSE);

        case NV0073_CTRL_CMD_SPECIFIC_GET_TYPE:
            return _rmapiControlCacheGetByInputTemplateMethod(hClient, hObject, cmd, 0,
                                                              params, sizeof(DispSpecificGetTypeCacheTable),
                                                              _dispSpecificGetTypeCacheHandler, NV_FALSE);
        case NV0073_CTRL_CMD_DP_GET_CAPS:
        return _rmapiControlCacheGetByInputTemplateMethod(hClient, hObject, cmd, 0,
                                                          params, sizeof(DispDpGetCapsCacheTable),
                                                          _dispDpGetCapsCacheHandler, NV_FALSE);
        default:
            NV_PRINTF(LEVEL_WARNING, "No implementation for cacheable by input cmd 0x%x\n", cmd);
            return NV_ERR_OBJECT_NOT_FOUND;
    }
}

NV_STATUS _rmapiControlCacheSetByInput
(
    NvHandle hClient,
    NvHandle hObject,
    NvU32 cmd,
    NvU32 rmctrlFlags,
    void* params,
    NvU32 paramsSize
)
{
    switch (cmd)
    {
        case NV2080_CTRL_CMD_GPU_GET_INFO_V2:
            return _getInfoCacheHandler(hClient, hObject, cmd, rmctrlFlags,
                                        ((NV2080_CTRL_GPU_GET_INFO_V2_PARAMS*)params)->gpuInfoList,
                                        ((NV2080_CTRL_GPU_GET_INFO_V2_PARAMS*)params)->gpuInfoListSize,
                                        NV2080_CTRL_GPU_INFO_MAX_LIST_SIZE,
                                        NV_TRUE);

        case NV2080_CTRL_CMD_FIFO_GET_INFO:
            return _getInfoCacheHandler(hClient, hObject, cmd, rmctrlFlags,
                                        ((NV2080_CTRL_FIFO_GET_INFO_PARAMS*)params)->fifoInfoTbl,
                                        ((NV2080_CTRL_FIFO_GET_INFO_PARAMS*)params)->fifoInfoTblSize,
                                        NV2080_CTRL_FIFO_GET_INFO_MAX_ENTRIES,
                                        NV_TRUE);

        case NV2080_CTRL_CMD_BUS_GET_INFO_V2:
            return _getInfoCacheHandler(hClient, hObject, cmd, rmctrlFlags,
                                        ((NV2080_CTRL_BUS_GET_INFO_V2_PARAMS*)params)->busInfoList,
                                        ((NV2080_CTRL_BUS_GET_INFO_V2_PARAMS*)params)->busInfoListSize,
                                        NV2080_CTRL_BUS_INFO_MAX_LIST_SIZE,
                                        NV_TRUE);

        case NV2080_CTRL_CMD_BIOS_GET_INFO_V2:
            return _getInfoCacheHandler(hClient, hObject, cmd, rmctrlFlags,
                                        ((NV2080_CTRL_BIOS_GET_INFO_V2_PARAMS*)params)->biosInfoList,
                                        ((NV2080_CTRL_BIOS_GET_INFO_V2_PARAMS*)params)->biosInfoListSize,
                                        NV2080_CTRL_BIOS_INFO_MAX_SIZE,
                                        NV_TRUE);

        case NV2080_CTRL_CMD_CE_GET_PHYSICAL_CAPS:
            return _getCePhysicalCapsHandler(hClient, hObject, rmctrlFlags,
                                             ((NV2080_CTRL_CE_GET_PHYSICAL_CAPS_PARAMS*)params)->ceEngineType,
                                             ((NV2080_CTRL_CE_GET_PHYSICAL_CAPS_PARAMS*)params)->capsTbl,
                                             NV_TRUE);

        case NV2080_CTRL_CMD_CE_GET_CE_PCE_MASK:
            return _getCePceMaskHandler(hClient, hObject, rmctrlFlags,
                                        ((NV2080_CTRL_CE_GET_CE_PCE_MASK_PARAMS*)params)->ceEngineType,
                                        &((NV2080_CTRL_CE_GET_CE_PCE_MASK_PARAMS*)params)->pceMask,
                                        NV_TRUE);

        case NV2080_CTRL_CMD_GPU_GET_NAME_STRING:
            return _gpuNameStringSet(hClient, hObject, rmctrlFlags, params);

        case NV0073_CTRL_CMD_SYSTEM_GET_SUPPORTED:
            return _rmapiControlCacheGetByInputTemplateMethod(hClient, hObject, cmd, rmctrlFlags,
                                                              params, sizeof(DispSystemGetSupportedCacheEntry),
                                                              _dispSystemGetSupportedCacheHandler, NV_TRUE);

        case NV0073_CTRL_CMD_SYSTEM_GET_INTERNAL_DISPLAYS:
            return _rmapiControlCacheGetByInputTemplateMethod(hClient, hObject, cmd, rmctrlFlags,
                                                              params, sizeof(DispSystemGetInternalDisplaysCacheEntry),
                                                              _dispSystemGetInternalDisplaysCacheHandler, NV_TRUE);

        case NV0073_CTRL_CMD_SPECIFIC_GET_TYPE:
            return _rmapiControlCacheGetByInputTemplateMethod(hClient, hObject, cmd, rmctrlFlags,
                                                              params, sizeof(DispSpecificGetTypeCacheTable),
                                                              _dispSpecificGetTypeCacheHandler, NV_TRUE);
        case NV0073_CTRL_CMD_DP_GET_CAPS:
            return _rmapiControlCacheGetByInputTemplateMethod(hClient, hObject, cmd, rmctrlFlags,
                                                            params, sizeof(DispDpGetCapsCacheTable),
                                                            _dispDpGetCapsCacheHandler, NV_TRUE);
        default:
            NV_PRINTF(LEVEL_WARNING, "No implementation for cacheable by input cmd 0x%x\n", cmd);
            return NV_ERR_OBJECT_NOT_FOUND;
    }
}

/*!
 * Look up cached params for a given (hClient, hObject, cmd) triple.
 * This function does not perform any checks against the RMCTRL export tables.
 *
 * @param[in]  paramsSize       size of parameters to copy out to
 * @param[out] params           returned cached RMCTRL parameter data
 */
NV_STATUS rmapiControlCacheGetUnchecked
(
    NvHandle           hClient,
    NvHandle           hObject,
    NvU32              cmd,
    void*              params,
    NvU32              paramsSize,
    API_SECURITY_INFO *pSecInfo
)
{
    NV_STATUS status = NV_OK;

    if (RmapiControlCache.mode == NV0000_CTRL_SYSTEM_RMCTRL_CACHE_MODE_CTRL_MODE_VERIFY_ONLY)
        return NV_ERR_OBJECT_NOT_FOUND;

    status = _rmapiControlCacheGetAny(hClient, hObject, cmd, params, paramsSize, pSecInfo);

    NV_PRINTF(LEVEL_INFO, "control cache get for 0x%x 0x%x 0x%x status: 0x%x\n", hClient, hObject, cmd, status);
    return status;
}

/*!
 * Look up cached params for a given (hClient, hObject, cmd) triple.
 * This function checks passed parameters against RMCTRL export tables. It treats a control
 * as CACHEABLE or CACHEABLE_BY_INPUT depending on RMCTRL flags from the tables.
 *
 * @param[in]  paramsSize       size of parameters to copy out to
 * @param[out] params           returned cached RMCTRL parameter data
 */
NV_STATUS rmapiControlCacheGet
(
    NvHandle           hClient,
    NvHandle           hObject,
    NvU32              cmd,
    void*              params,
    NvU32              paramsSize,
    API_SECURITY_INFO *pSecInfo
)
{
    NV_STATUS status = NV_OK;
    NvU32 flags;
    NvU32 ctrlParamsSize;

    if (RmapiControlCache.mode == NV0000_CTRL_SYSTEM_RMCTRL_CACHE_MODE_CTRL_MODE_VERIFY_ONLY)
        return NV_ERR_OBJECT_NOT_FOUND;

    status = rmapiutilGetControlInfo(cmd, &flags, NULL, &ctrlParamsSize);
    if (status != NV_OK)
        goto done;

    NV_CHECK_OR_ELSE(LEVEL_ERROR,
                     (params != NULL && paramsSize == ctrlParamsSize),
                     status = NV_ERR_INVALID_PARAMETER; goto done);

    status = rmControlValidateClientPrivilegeAccess(hClient, hObject, cmd, flags, pSecInfo);
    if (status != NV_OK)
        goto done;

    switch ((flags & RMCTRL_FLAGS_CACHEABLE_ANY))
    {
        case RMCTRL_FLAGS_CACHEABLE:
            status = _rmapiControlCacheGetCacheable(hClient, hObject, cmd, params, paramsSize);
            break;
        case RMCTRL_FLAGS_CACHEABLE_BY_INPUT:
            status = _rmapiControlCacheGetByInput(hClient, hObject, cmd, params, paramsSize);
            break;
        default:
            NV_PRINTF(LEVEL_ERROR, "Invalid cacheable flag 0x%x for cmd 0x%x\n", flags, cmd);
            status = NV_ERR_INVALID_PARAMETER;
            goto done;
    }

done:
    NV_PRINTF(LEVEL_INFO, "control cache get for 0x%x 0x%x 0x%x status: 0x%x\n", hClient, hObject, cmd, status);
    return status;
}

/*!
 * Try to set cached params for a (hClient, hObject, cmd) triple.
 * If there is an existing cache entry for the triple, the entry is unmodified.
 * This function checks passed parameters against RMCTRL export tables. It treats a control
 * as CACHEABLE or CACHEABLE_BY_INPUT depending on known RMCTRL flags from the tables.
 *
 * @param[in]  paramsSize       size of parameters to allocate for cache entry
 * @param[in]  params           data for the cached parameters
 */
NV_STATUS rmapiControlCacheSet
(
    NvHandle hClient,
    NvHandle hObject,
    NvU32 cmd,
    void* params,
    NvU32 paramsSize
)
{
    NvU32 flags;
    NvU32 ctrlParamsSize;

    NV_CHECK_OK_OR_RETURN(LEVEL_ERROR, rmapiutilGetControlInfo(cmd, &flags, NULL, &ctrlParamsSize));

    NV_CHECK_OR_RETURN(LEVEL_ERROR,
                       (params != NULL && paramsSize == ctrlParamsSize),
                       NV_ERR_INVALID_PARAMETER);

    return rmapiControlCacheSetUnchecked(hClient, hObject, cmd, params, paramsSize, flags);
}

/*!
 * Try to set cached params for a (hClient, hObject, cmd) triple.
 * If there is an existing cache entry for the triple, the entry is unmodified.
 * This function does not perform any checks against the RMCTRL export tables. It trusts caller
 * to pass the correct paramsSize and rmctrlFlags.
 *
 * @param[in]  paramsSize          size of parameters to allocate for cache entry
 * @param[in]  params              data for the cached parameters
 * @param[in]  rmctrlFlags RMCTRL_FLAGS_CACHEABLE_* flag
 */
NV_STATUS rmapiControlCacheSetUnchecked
(
    NvHandle hClient,
    NvHandle hObject,
    NvU32 cmd,
    void* params,
    NvU32 paramsSize,
    NvU32 rmctrlFlags
)
{
    NV_STATUS status = NV_OK;

    switch ((rmctrlFlags & RMCTRL_FLAGS_CACHEABLE_ANY))
    {
        case RMCTRL_FLAGS_CACHEABLE:
            status = _rmapiControlCacheSet(hClient, hObject, cmd, rmctrlFlags, params, paramsSize);
            break;
        case RMCTRL_FLAGS_CACHEABLE_BY_INPUT:
            status = _rmapiControlCacheSetByInput(hClient, hObject, cmd, rmctrlFlags, params, paramsSize);
            break;
        default:
            NV_PRINTF(LEVEL_ERROR, "Invalid cacheable flag 0x%x for cmd 0x%x\n", rmctrlFlags, cmd);
            status = NV_ERR_INVALID_PARAMETER;
            goto done;
    }

done:
    NV_PRINTF(LEVEL_INFO, "control cache set for 0x%x 0x%x 0x%x status: 0x%x\n", hClient, hObject, cmd, status);
    return status;
}

// Need to hold rmapi control cache write lock
static void _freeSubmap(GpusControlCache *pMap, GpusControlCacheSubmap* pSubmap, NvBool bFreePersistent)
{
    /* (Sub)map modification invalidates the iterator, so we have to restart */
    while (NV_TRUE)
    {
        GpusControlCacheIter it = multimapSubmapIterItems(pMap, pSubmap);
        NvBool bHasNext;

        for (bHasNext = multimapItemIterNext(&it); bHasNext; bHasNext = multimapItemIterNext(&it))
        {
            RmapiControlCacheEntry* entry = it.pValue;

            if ((entry->rmctrlFlags & RMCTRL_FLAGS_PERSISTENT_CACHEABLE) && !bFreePersistent)
            {
                continue;
            }

            portMemFree(entry->params);
            multimapRemoveItem(pMap, entry);

            // Restart iterating
            break;
        }

        // Reached the end of the submap without removing any items, or can't remove any items
        if (!bHasNext)
            break;
    }

    if (multimapCountSubmapItems(pMap, pSubmap) == 0)
    {
        multimapRemoveSubmap(pMap, pSubmap);
    }
}

static void _rmapiControlCacheFreeGpuCache(NvU32 gpuInst, NvBool bFreePersistent)
{
    GpusControlCacheSubmap *submap;

    submap = multimapFindSubmap(&RmapiControlCache.gpusControlCache, gpuInst);
    if (submap != NULL)
        _freeSubmap(&RmapiControlCache.gpusControlCache, submap, bFreePersistent);
}

void rmapiControlCacheFreeNonPersistentCacheForGpu
(
    NvU32 gpuInst
)
{
    _cacheLockAcquire(LOCK_EXCLUSIVE);

    _rmapiControlCacheFreeGpuCache(gpuInst, NV_FALSE);

    _cacheLockRelease(LOCK_EXCLUSIVE);
}

void rmapiControlCacheFreeAllCacheForGpu
(
    NvU32 gpuInst
)
{
    _cacheLockAcquire(LOCK_EXCLUSIVE);

    _rmapiControlCacheFreeGpuCache(gpuInst, NV_TRUE);

    _cacheLockRelease(LOCK_EXCLUSIVE);
}

void rmapiControlCacheFreeClientEntry(NvHandle hClient)
{
    _cacheLockAcquire(LOCK_EXCLUSIVE);
    _rmapiControlCacheFreeGpuAttrForClient(hClient);
    _cacheLockRelease(LOCK_EXCLUSIVE);
}

void rmapiControlCacheFreeObjectEntry(NvHandle hClient, NvHandle hObject)
{
    if (hClient == hObject)
    {
        rmapiControlCacheFreeClientEntry(hClient);
        return;
    }

    _cacheLockAcquire(LOCK_EXCLUSIVE);
    _rmapiControlCacheFreeGpuAttrForObject(hClient, hObject);
    _cacheLockRelease(LOCK_EXCLUSIVE);
}

void rmapiControlCacheFree(void)
{
    GpusControlCacheIter it;

    it = multimapItemIterAll(&RmapiControlCache.gpusControlCache);
    while (multimapItemIterNext(&it))
    {
        RmapiControlCacheEntry* entry = it.pValue;

        portMemFree(entry->params);
    }

    multimapDestroy(&RmapiControlCache.gpusControlCache);
    mapDestroy(&RmapiControlCache.objectToGpuAttrMap);
    portSyncRwLockDestroy(RmapiControlCache.pLock);
    RmapiControlCache.pLock = NULL;
}

void rmapiControlCacheSetMode(NvU32 mode)
{
    NV_PRINTF(LEVEL_INFO, "Set rmapi control cache mode to 0x%x\n", mode);

    _cacheLockAcquire(LOCK_EXCLUSIVE);
    RmapiControlCache.mode = mode;
    _cacheLockRelease(LOCK_EXCLUSIVE);
}

NvU32 rmapiControlCacheGetMode(void)
{
    return RmapiControlCache.mode;
}
