/*
 * SPDX-FileCopyrightText: Copyright (c) 1993-2021 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.
 */

/******************************************************************************
*
*   Description:
*       This is a device resource implementation.
*
******************************************************************************/



#include "resserv/resserv.h"
#include "resserv/rs_server.h"
#include "resserv/rs_client.h"
#include "resserv/rs_resource.h"
#include "gpu/device/device.h"
#include "gpu/subdevice/subdevice.h"

#include "class/cl0080.h"
#include "core/locks.h"
#include "vgpu/rpc.h"
#include "mem_mgr/mem.h"

#include "rmapi/rs_utils.h"
#include "nvsecurityinfo.h"

static NV_STATUS _deviceTeardown(Device *pDevice, CALL_CONTEXT *pCallContext);
static NV_STATUS _deviceTeardownRef(Device *pDevice, CALL_CONTEXT *pCallContext);

NV_STATUS
deviceConstruct_IMPL
(
    Device *pDevice,
    CALL_CONTEXT *pCallContext,
    RS_RES_ALLOC_PARAMS_INTERNAL *pParams
)
{
    NV0080_ALLOC_PARAMETERS         *pNv0080AllocParams = pParams->pAllocParams;
    NvU32                            deviceInst, flags, vaMode;
    NvU32                            deviceClass        = pParams->externalClassId;
    NvHandle                         hClientShare;
    NvHandle                         hTargetClient      = NV01_NULL_OBJECT;
    NvHandle                         hTargetDevice      = NV01_NULL_OBJECT;
    NvU64                            vaSize             = 0;
    NV_STATUS                        rmStatus           = NV_OK;
    OBJSYS                          *pSys               = SYS_GET_INSTANCE();
    OBJOS                           *pOS                = SYS_GET_OS(pSys);
    OBJGPU                          *pGpu;
    NvU64                            vaStartInternal     = 0;
    NvU64                            vaLimitInternal     = 0;
    NvU32                            physicalAllocFlags;

    if (pNv0080AllocParams == NULL)
    {
        deviceInst   = pParams->externalClassId - NV01_DEVICE_0;
        hClientShare = NV01_NULL_OBJECT;
        flags        = 0;
        vaSize       = 0;
        vaMode       = 0;
    }
    else
    {
        deviceInst      = pNv0080AllocParams->deviceId;
        hClientShare    = pNv0080AllocParams->hClientShare;
        hTargetClient   = pNv0080AllocParams->hTargetClient;
        hTargetDevice   = pNv0080AllocParams->hTargetDevice;
        flags           = pNv0080AllocParams->flags;
        vaSize          = pNv0080AllocParams->vaSpaceSize;
        vaMode          = pNv0080AllocParams->vaMode;

        // valid only if NV_DEVICE_ALLOCATION_FLAGS_RESTRICT_RESERVED_VALIMITS is flagged.
        if (flags & NV_DEVICE_ALLOCATION_FLAGS_RESTRICT_RESERVED_VALIMITS)
        {
            vaStartInternal = pNv0080AllocParams->vaStartInternal;
            vaLimitInternal = pNv0080AllocParams->vaLimitInternal;

            if ((vaLimitInternal < vaStartInternal)  || (vaLimitInternal == 0))
            {
                return NV_ERR_INVALID_ARGUMENT;
            }
        }
    }

    // validate device instance
    if (gpumgrIsDeviceInstanceValid(deviceInst) != NV_OK)
    {
        return NV_ERR_INVALID_CLASS;
    }

    // Make sure this device has not been disabled
    if (gpumgrIsDeviceEnabled(deviceInst) == NV_FALSE)
    {
        return NV_ERR_INVALID_ARGUMENT;
    }

    // add new device to client and set the device context
    rmStatus = deviceInit(pDevice, pCallContext, pParams->hClient, pParams->hResource, deviceInst,
                          hClientShare, hTargetClient, hTargetDevice, vaSize, vaStartInternal, vaLimitInternal,
                          flags, vaMode);
    if (rmStatus != NV_OK)
        return rmStatus;

    pGpu = GPU_RES_GET_GPU(pDevice);

    if (pCallContext->secInfo.privLevel < RS_PRIV_LEVEL_KERNEL)
    {
        if (!osIsGpuAccessible(pGpu))
        {
            // Delete the device from the client since we should not be allocating it
            _deviceTeardownRef(pDevice, pCallContext);
            _deviceTeardown(pDevice, pCallContext);
            return NV_ERR_INSUFFICIENT_PERMISSIONS;
        }
    }

    //
    // Make sure this device is not in fullchip reset on OSes where it is
    // restricted.
    //
    if (pOS->getProperty(pOS, PDB_PROP_OS_LIMIT_GPU_RESET) &&
        pGpu->getProperty(pGpu, PDB_PROP_GPU_IN_FULLCHIP_RESET))
    {
        // Delete the device from the client since we should not be allocating it
        _deviceTeardownRef(pDevice, pCallContext);
        _deviceTeardown(pDevice, pCallContext);
        return NV_ERR_GPU_IN_FULLCHIP_RESET;
    }

    if (IS_VIRTUAL(pGpu) || IS_GSP_CLIENT(pGpu))
    {
        physicalAllocFlags = flags & ~(NV_DEVICE_ALLOCATION_FLAGS_PLUGIN_CONTEXT
            | NV_DEVICE_ALLOCATION_FLAGS_HOST_VGPU_DEVICE);

        NV_RM_RPC_ALLOC_SHARE_DEVICE(pGpu, pParams->hParent, pParams->hResource, pDevice->hClientShare,
                                     hTargetClient, hTargetDevice, deviceClass, physicalAllocFlags, vaSize, vaMode, rmStatus);
        if (rmStatus != NV_OK)
        {
            return rmStatus;
        }
    }

    return rmStatus;
} // end of deviceConstruct_IMPL

void
deviceDestruct_IMPL
(
    Device *pDevice
)
{
    CALL_CONTEXT           *pCallContext;
    RS_RES_FREE_PARAMS_INTERNAL *pParams;
    NV_STATUS               rmStatus = NV_OK;
    NV_STATUS               tmpStatus;
    NvHandle                hClient;

    resGetFreeParams(staticCast(pDevice, RsResource), &pCallContext, &pParams);

    hClient = pCallContext->pClient->hClient;

    NV_PRINTF(LEVEL_INFO, "    type: device\n");

    LOCK_METER_DATA(FREE_DEVICE, 0, 0, 0);

    // free the device
    if (_deviceTeardownRef(pDevice, pCallContext) != NV_OK ||
        _deviceTeardown(pDevice, pCallContext) != NV_OK)
    {
        tmpStatus = NV_ERR_INVALID_OBJECT_HANDLE;
        if (tmpStatus != NV_OK && rmStatus == NV_OK)
            rmStatus = tmpStatus;
    }

    //
    // If the client was created, but never had any devices successfully
    // attached, we'll get here.  The client's device structure will have
    // been created, but pGpu will be NULL if the device was later found
    // to be non-existent
    //
    if (GPU_RES_GET_GPU(pDevice))
    {
        OBJGPU *pGpu = GPU_RES_GET_GPU(pDevice);
        // vGpu support
        if (IS_VIRTUAL(pGpu) || IS_GSP_CLIENT(pGpu))
        {
            RsResourceRef *pResourceRef = pCallContext->pResourceRef;
            NvHandle       hDevice = pResourceRef->hResource;

            if (rmStatus == NV_OK)
            {
                NV_RM_RPC_FREE(pGpu, hClient, hClient, hDevice, rmStatus);
            }

            if (rmStatus != NV_OK)
            {
                pParams->status = rmStatus;
                return;
            }

            NV_RM_RPC_FREE(pGpu, hClient, NV01_NULL_OBJECT, hClient, rmStatus);
        }
    }
} // end of deviceDestruct_IMPL

NV_STATUS
deviceControl_IMPL
(
    Device *pDevice,
    CALL_CONTEXT *pCallContext,
    RS_RES_CONTROL_PARAMS_INTERNAL *pParams
)
{

    //
    // Some assertions to make RMCTRL to NVOC migration smooth
    // Those will be removed at the end of ctrl0080.def migration
    //
    NV_ASSERT_OR_RETURN(pParams->hClient == RES_GET_CLIENT_HANDLE(pDevice), NV_ERR_INVALID_STATE);
    NV_ASSERT_OR_RETURN(pParams->hObject == RES_GET_HANDLE(pDevice), NV_ERR_INVALID_STATE);
    NV_ASSERT_OR_RETURN(pParams->hParent == RES_GET_PARENT_HANDLE(pDevice), NV_ERR_INVALID_STATE);

    pParams->pGpuGrp      = GPU_RES_GET_GPUGRP(pDevice);
    return gpuresControl_IMPL(staticCast(pDevice, GpuResource),
            pCallContext, pParams);
}

NV_STATUS
deviceInternalControlForward_IMPL
(
    Device *pDevice,
    NvU32 command,
    void *pParams,
    NvU32 size
)
{
    return gpuresInternalControlForward_IMPL(staticCast(pDevice, GpuResource), command, pParams, size);
}

//
// add a device with specified handle, instance num, within a specified client
// (hClientShare also specified)
//
NV_STATUS
deviceInit_IMPL
(
    Device  *pDevice,
    CALL_CONTEXT *pCallContext,
    NvHandle hClient,
    NvHandle hDevice,
    NvU32    deviceInst,
    NvHandle hClientShare,
    NvHandle hTargetClient,
    NvHandle hTargetDevice,
    NvU64    vaSize,
    NvU64    vaStartInternal,
    NvU64    vaLimitInternal,
    NvU32    allocFlags,
    NvU32    vaMode
)
{
    OBJGPU      *pGpu;
    NV_STATUS    status;
    GpuResource *pGpuResource = staticCast(pDevice, GpuResource);
    Device      *pExistingDevice;
    NvU32        gpuInst;

    if (deviceInst >= NV_MAX_DEVICES)
        return NV_ERR_INVALID_ARGUMENT;

    // Check if device inst already allocated, fail if this call succeeds
    status = deviceGetByInstance(pCallContext->pClient, deviceInst, &pExistingDevice);
    if (status == NV_OK)
    {
        //
        // RS-TODO: Status code should be NV_ERR_STATE_IN_USE, however keeping
        // existing code from CliAllocElement (for now)
        //
        return NV_ERR_INSUFFICIENT_RESOURCES;
    }

    // Look up GPU and GPU Group
    gpuInst = gpumgrGetPrimaryForDevice(deviceInst);

    if ((pGpu = gpumgrGetGpu(gpuInst)) == NULL)
    {
        return NV_ERR_INVALID_STATE;
    }

    pDevice->hTargetClient  = hTargetClient;
    pDevice->hTargetDevice  = hTargetDevice;
    pDevice->pKernelHostVgpuDevice = NULL;

    pDevice->deviceInst = deviceInst;

    // Update VA Mode
    pDevice->vaMode = vaMode;

    gpuresSetGpu(pGpuResource, pGpu, NV_TRUE);

    status = deviceSetClientShare(pDevice, hClientShare, vaSize,
                                  vaStartInternal, vaLimitInternal, allocFlags);
    if (NV_OK != status)
        goto done;

    if (allocFlags & NV_DEVICE_ALLOCATION_FLAGS_PLUGIN_CONTEXT)
    {
        NV_ASSERT_OR_RETURN(allocFlags & NV_DEVICE_ALLOCATION_FLAGS_HOST_VGPU_DEVICE,
            NV_ERR_INVALID_ARGUMENT);
    }

done:
    if (status != NV_OK)
    {
        deviceRemoveFromClientShare(pDevice);
    }

    return status;
} // end of deviceInit_IMPL()

//
// delete a device with a specified handle within a client
//
static NV_STATUS
_deviceTeardown
(
    Device  *pDevice,
    CALL_CONTEXT *pCallContext
)
{
    OBJGPU    *pGpu     = GPU_RES_GET_GPU(pDevice);
    PORT_UNREFERENCED_VARIABLE(pGpu);

    deviceRemoveFromClientShare(pDevice);

    return NV_OK;
}

static NV_STATUS _deviceTeardownRef
(
    Device *pDevice,
    CALL_CONTEXT *pCallContext
)
{

    return NV_OK;
}

NV_STATUS
deviceGetByHandle_IMPL
(
    RsClient         *pClient,
    NvHandle          hDevice,
    Device          **ppDevice
)
{
    RsResourceRef  *pResourceRef;
    NV_STATUS       status;

    *ppDevice = NULL;

    status = clientGetResourceRef(pClient, hDevice, &pResourceRef);
    if (status != NV_OK)
        return status;

    *ppDevice = dynamicCast(pResourceRef->pResource, Device);

    return (*ppDevice) ? NV_OK : NV_ERR_INVALID_OBJECT_HANDLE;
}

NV_STATUS
deviceGetByInstance_IMPL
(
    RsClient         *pClient,
    NvU32             deviceInstance,
    Device          **ppDevice
)
{
    RS_ITERATOR  it;
    Device      *pDevice;

    *ppDevice = NULL;

    it = clientRefIter(pClient, NULL, classId(Device), RS_ITERATE_CHILDREN, NV_TRUE);

    while (clientRefIterNext(it.pClient, &it))
    {
        pDevice = dynamicCast(it.pResourceRef->pResource, Device);

        if ((pDevice != NULL) && (deviceInstance == pDevice->deviceInst))
        {
            *ppDevice = pDevice;
            return NV_OK;
        }
    }

    return NV_ERR_OBJECT_NOT_FOUND;
}

NV_STATUS
deviceGetByGpu_IMPL
(
    RsClient         *pClient,
    OBJGPU           *pGpu,
    NvBool            bAnyInGroup,
    Device          **ppDevice
)
{
    NvU32     deviceInstance = gpuGetDeviceInstance(pGpu);
    NV_STATUS status;

    status = deviceGetByInstance(pClient, deviceInstance, ppDevice);
    if (status != NV_OK)
        return status;

    // If pGpu is not the primary GPU return failure
    if (!bAnyInGroup && pGpu != GPU_RES_GET_GPU(*ppDevice))
    {
        *ppDevice = NULL;
        return NV_ERR_OBJECT_NOT_FOUND;
    }

    return NV_OK;
}

// ****************************************************************************
//                            Deprecated Functions
// ****************************************************************************

/**
 * WARNING: This function is deprecated and use is *strongly* discouraged
 * (especially for new code!)
 *
 * From the function name (CliSetGpuContext) it appears as a simple accessor but
 * violates expectations by modifying the SLI BC threadstate (calls to
 * GPU_RES_SET_THREAD_BC_STATE). This can be dangerous if not carefully managed
 * by the caller.
 *
 * Instead of using this routine, please use deviceGetByHandle then call
 * GPU_RES_GET_GPU, GPU_RES_GET_GPUGRP, GPU_RES_SET_THREAD_BC_STATE as needed.
 *
 * Note that GPU_RES_GET_GPU supports returning a pGpu for both pDevice,
 * pSubdevice, the base pResource type, and any resource that inherits from
 * GpuResource. That is, instead of using CliSetGpuContext or
 * CliSetSubDeviceContext, please use following pattern to look up the pGpu:
 *
 * OBJGPU *pGpu = GPU_RES_GET_GPU(pResource or pResourceRef->pResource)
 *
 * To set the threadstate, please use:
 *
 * GPU_RES_SET_THREAD_BC_STATE(pResource or pResourceRef->pResource);
 */
NV_STATUS
CliSetGpuContext
(
    NvHandle    hClient,
    NvHandle    hDevice,
    OBJGPU    **ppGpu,
    OBJGPUGRP **ppGpuGrp
)
{
    Device    *pDevice;
    RsClient  *pClient;
    NV_STATUS  status;

    if (ppGpuGrp != NULL)
        *ppGpuGrp = NULL;

    if (ppGpu != NULL)
        *ppGpu = NULL;

    status = serverGetClientUnderLock(&g_resServ, hClient, &pClient);
    if (status != NV_OK)
        return status;

    status = deviceGetByHandle(pClient, hDevice, &pDevice);
    if (status != NV_OK)
        return status;

    if (ppGpu != NULL)
        *ppGpu = GPU_RES_GET_GPU(pDevice);

    if (ppGpuGrp != NULL)
        *ppGpuGrp = GPU_RES_GET_GPUGRP(pDevice);

    GPU_RES_SET_THREAD_BC_STATE(pDevice);

    return NV_OK;
}

/**
 * WARNING: This function is deprecated! Please use gpuGetByRef()
 */
POBJGPU
CliGetGpuFromContext
(
    RsResourceRef *pContextRef,
    NvBool        *pbBroadcast
)
{
    NV_STATUS status;
    OBJGPU   *pGpu;

    status = gpuGetByRef(pContextRef, pbBroadcast, &pGpu);

    return (status == NV_OK) ? pGpu : NULL;
}

/**
 * WARNING: This function is deprecated! Please use gpuGetByHandle()
 */
POBJGPU
CliGetGpuFromHandle
(
    NvHandle hClient,
    NvHandle hResource,
    NvBool *pbBroadcast
)
{
    RsClient    *pClient;
    NV_STATUS    status;
    OBJGPU      *pGpu;

    status = serverGetClientUnderLock(&g_resServ, hClient, &pClient);
    if (status != NV_OK)
        return NULL;

    status = gpuGetByHandle(pClient, hResource, pbBroadcast, &pGpu);

    return (status == NV_OK) ? pGpu : NULL;
}
