/*******************************************************************************
    Copyright (c) 2013 NVIDIA Corporation

    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.

*******************************************************************************/

/*
 * uvm_common.c
 *
 * This file contains code that is common to all variants of the (Linux) UVM
 * kernel module.
 *
 * Initially, UVM-Lite and Uvm-Next work is proceeding somewhat
 * independently, but the long- and short-term plan is for them to share the
 * same kernel module (this one). That's why, at least initially, you'll find
 * files for UVM-Lite, for Uvm-Next, and for common code. We'll refactor as
 * necessary, to achieve the right balance of sharing code and decoupling the
 * features.
 *
 * --jhubbard
 *
 */

#include "uvm_common.h"
#include "uvm_linux.h"

// TODO: Remove this when the GPU event stubs are no longer needed
#include "nv_uvm_interface.h"
#include "uvm_lite.h"

#if defined(NVIDIA_UVM_RM_ENABLED)
#include "uvm_channel_mgmt.h"
#endif

#if defined(NVIDIA_UVM_NEXT_ENABLED)
#include "uvm_next.h"
#else
static int uvmnext_init(dev_t uvmBaseDev)
{
    return 0;
}
static void uvmnext_exit(void)
{

}
static NV_STATUS uvmnext_isr_top_half(void)
{
    return NV_ERR_NO_INTR_PENDING;
}
static NV_STATUS uvmnext_gpu_event_start_device(UvmGpuUuid *gpuUuidStruct)
{
    return NV_OK;
}
static NV_STATUS uvmnext_gpu_event_stop_device(UvmGpuUuid *gpuUuidStruct)
{
    return NV_OK;
}
NvBool uvmnext_activated(void)
{
    return NV_FALSE;
}
#endif // NVIDIA_UVM_NEXT_ENABLED

static dev_t g_uvmBaseDev;
struct UvmOpsUvmEvents g_exportedUvmOps;

// TODO: This would be easier if RM allowed for multiple registrations, since we
//       could register UVM-Lite and UVM-Next separately (bug 1372835).
static NV_STATUS uvm_gpu_event_start_device(UvmGpuUuid *gpuUuidStruct)
{
    NV_STATUS nvStatus = NV_OK;

    if (uvmnext_activated())
        nvStatus = uvmnext_gpu_event_start_device(gpuUuidStruct);
    else
        nvStatus = uvmlite_gpu_event_start_device(gpuUuidStruct);

    return nvStatus;
}

static NV_STATUS uvm_gpu_event_stop_device(UvmGpuUuid *gpuUuidStruct)
{
    NV_STATUS nvStatus = NV_OK;

    if (uvmnext_activated())
        nvStatus = uvmnext_gpu_event_stop_device(gpuUuidStruct);
    else
        nvStatus = uvmlite_gpu_event_stop_device(gpuUuidStruct);

    return nvStatus;
}

static NV_STATUS uvmSetupGpuProvider(void)
{
    NV_STATUS status = NV_OK;

#ifdef NVIDIA_UVM_RM_ENABLED
    g_exportedUvmOps.startDevice = uvm_gpu_event_start_device;
    g_exportedUvmOps.stopDevice  = uvm_gpu_event_stop_device;
    g_exportedUvmOps.isrTopHalf  = uvmnext_isr_top_half;

    // call RM to exchange the function pointers.
    status = nvUvmInterfaceRegisterUvmCallbacks(&g_exportedUvmOps);
#endif
    return status;
}

static int __init uvm_init(void)
{
    int ret = 0;
    int devAlloced = 0;
    int uvmliteInitialized = 0;
    int uvmnextInitialized = 0;
    int registered = 0;
    NV_STATUS status;

    // The various helper init routines will create their own minor devices, so
    // we only need to create space for them here.
    ret = alloc_chrdev_region(&g_uvmBaseDev,
                              0,
                              NVIDIA_UVM_NUM_MINOR_DEVICES,
                              NVIDIA_UVM_DEVICE_NAME);
    if (ret)
    {
        UVM_ERR_PRINT("alloc_chrdev_region failed: %d\n", ret);
        goto error;
    }
    devAlloced = 1;

#ifdef NVIDIA_UVM_RM_ENABLED
    status = uvm_initialize_channel_mgmt_api();
    if (NV_OK != status)
    {
        UVM_ERR_PRINT_NV_STATUS("Could not initialize channel mgmt api.\n",
                                status);
        // TODO: Need a helper function to convert from NV_STATUS to errno
        ret = -ENOMEM;
        goto error;
    }
#endif

    if (uvmnext_activated())
    {
        ret = uvmnext_init(g_uvmBaseDev);
        if (ret)
            goto error;
        uvmnextInitialized = 1;
    }
    else
    {
        ret = uvmlite_init(g_uvmBaseDev);
        if (ret)
            goto error;
        uvmliteInitialized = 1;
    }

    // Register with RM
    //
    // TODO: This should probably happen before calling uvmlite_init and
    //       uvmnext_init, so those routines can make RM calls as part of their
    //       setup. But doing that means we could get event callbacks before
    //       those init routines are called, which is scary. Ideally this could
    //       be solved by having RM handle multiple registrations (bug 1372835).
    if (rm_enabled())
    {
        status = uvmSetupGpuProvider();
        if (NV_OK != status)
        {
            UVM_ERR_PRINT("Cannot register ops with NVIDIA driver. UVM error: 0x%x\n",
                          status);
            // TODO: Need a helper function to convert from NV_STATUS to errno
            ret = -EINVAL;
            goto error;
        }
        registered = 1;
    }

    // Query GPU list from RM
    //
    // TODO: This should be called within uvmlite_init but the RM calls aren't
    //       enabled until after the uvmSetupGpuProvider call above. This can be
    //       moved once multiple RM registrations are allowed (bug 1372835).
    //       Until then we need a dummy symbol.
    if(uvmliteInitialized && registered)
    {
        ret = uvmlite_setup_gpu_list();
        if (ret)
        {
            UVM_ERR_PRINT("uvm_lite_setup_gpu_list failed\n");
            goto error;
        }
    }

    pr_info("Loaded the UVM driver, major device number %d\n",
            MAJOR(g_uvmBaseDev));

    if (uvm_enable_builtin_tests)
        pr_info("Built-in UVM tests are enabled. This is a security risk.\n");

    return 0;

error:
#ifdef NVIDIA_UVM_RM_ENABLED
    if (registered)
        nvUvmInterfaceDeRegisterUvmOps();

        uvm_deinitialize_channel_mgmt_api();
#endif

    if (uvmliteInitialized)
        uvmlite_exit();

    if (uvmnextInitialized)
        uvmnext_exit();

    if (devAlloced)
        unregister_chrdev_region(g_uvmBaseDev, NVIDIA_UVM_NUM_MINOR_DEVICES);

    return ret;
}

static void __exit uvm_exit(void)
{
#ifdef NVIDIA_UVM_RM_ENABLED
    // this will cleanup registered interfaces
    nvUvmInterfaceDeRegisterUvmOps();

    uvm_deinitialize_channel_mgmt_api();
#endif

    if (uvmnext_activated())
        uvmnext_exit();
    else
        uvmlite_exit();

    unregister_chrdev_region(g_uvmBaseDev, NVIDIA_UVM_NUM_MINOR_DEVICES);

    pr_info("Unregistered the UVM driver\n");
}

//
// Convert kernel errno codes to corresponding NV_STATUS
//
NV_STATUS errno_to_nv_status(int errnoCode)
{
    if (errnoCode < 0)
        errnoCode = -errnoCode;

    switch (errnoCode)
    {
        case 0:
            return NV_OK;

        case E2BIG:
        case EINVAL:
            return NV_ERR_INVALID_ARGUMENT;

        case EACCES:
            return NV_ERR_INSUFFICIENT_PERMISSIONS;

        case EADDRINUSE:
        case EADDRNOTAVAIL:
            return NV_ERR_UVM_ADDRESS_IN_USE;

        case EFAULT:
            return NV_ERR_INVALID_ADDRESS;

        case EINTR:
            return NV_ERR_BUSY_RETRY;

        case ENODEV:
            return NV_ERR_MODULE_LOAD_FAILED;

        case ENOMEM:
            return NV_ERR_NO_MEMORY;

        case EPERM:
            return NV_ERR_INSUFFICIENT_PERMISSIONS;

        default:
            return NV_ERR_GENERIC;
    };
}

//
// This routine retrieves the process ID of current, but makes no attempt to
// refcount or lock the pid in place, because that capability is only available
// to GPL-licenses device drivers.
//
// TODO: use the GPL-protected routines if and when we are able to change over
// to a dual MIT/GPL license (bug 1483843).
//
unsigned uvm_get_stale_process_id(void)
{
    return (unsigned) current->tgid;
}

unsigned uvm_get_stale_thread_id(void)
{
    return (unsigned) current->pid;
}

//
// A simple security rule for allowing access to UVM user space memory: if you
// are the same user as the owner of the memory, or if you are root, then you
// are granted access. The idea is to allow debuggers and profilers to work, but
// without opening up any security holes.
//
NvBool uvm_user_id_security_check(uid_t euidTarget)
{
    return (NV_CURRENT_EUID() == euidTarget) ||
           (UVM_ROOT_UID == euidTarget);
}

// Locking: you must hold mm->mmap_sem write lock before calling this function
NV_STATUS uvm_map_page(struct vm_area_struct *vma, struct page *page,
                       unsigned long addr)
{
    if (vm_insert_page(vma, addr, page) == 0)
        return NV_OK;

    return NV_ERR_INVALID_ARGUMENT;
}


module_init(uvm_init);
module_exit(uvm_exit);

// This parameter allows a program in user mode to call the kernel tests
// defined in this module. This parameter should only be used for testing and
// must not be set to true otherwise since it breaks security when it is
// enabled. By default and for safety reasons this parameter is set to false.
int uvm_enable_builtin_tests = 0;
module_param(uvm_enable_builtin_tests , int, S_IRUGO|S_IWUSR);
MODULE_PARM_DESC(uvm_enable_builtin_tests,
                 "Enable the UVM built-in tests. (This is a security risk)");

MODULE_LICENSE("MIT");
MODULE_INFO(supported, "external");
