/*******************************************************************************
    Copyright (c) 2015 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.

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

#include <linux/module.h>
#include <linux/major.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/string.h>
#include <linux/slab.h>

#include "nvlink_common.h"
#include "nvlink_linux.h"
#include "nvlink_proto.h"

#define MAX_ERROR_STRING           512

static int nvlink_major_devnum;

static void dbg_breakpoint(void)
{
#if defined(DEBUG)
  #if defined(CONFIG_X86_REMOTE_DEBUG) || defined(CONFIG_KGDB) || defined(CONFIG_XMON)
    #if defined(NVCPU_X86) || defined(NVCPU_X86_64)
        __asm__ __volatile__ ("int $3");
    #elif defined(NVCPU_ARM)
        __asm__ __volatile__ (".word %c0" :: "i" (KGDB_COMPILED_BREAK));
    #elif defined(NVCPU_AARCH64)
        # warning "Need to implement dbg_breakpoint() for aarch64"
    #elif defined(NVCPU_PPC64LE)
        __asm__ __volatile__ ("trap");
    #endif // NVCPU_X86 || NVCPU_X86_64
  #elif defined(CONFIG_KDB)
      KDB_ENTER();
  #endif // CONFIG_X86_REMOTE_DEBUG || CONFIG_KGDB || CONFIG_XMON
#endif // DEBUG
}

static int nvlink_fops_open(struct inode *inode, struct file *filp)
{
    return 0;
}

static int nvlink_fops_release(struct inode *inode, struct file *filp)
{
    return 0;
}

static long nvlink_fops_unlocked_ioctl(struct file *filp,
                                       unsigned int cmd,
                                       unsigned long arg)
{
    return -ENOTSUPP;
}

static const struct file_operations nvlink_fops = {
    .owner           = THIS_MODULE,
    .open            = nvlink_fops_open,
    .release         = nvlink_fops_release,
#if defined(NV_FILE_OPERATIONS_HAS_UNLOCKED_IOCTL)
    .unlocked_ioctl  = nvlink_fops_unlocked_ioctl,
#endif
};

int __init nvlink_core_init(void)
{
    dev_t dev;
    int ret;

    ret = alloc_chrdev_region(&dev, 0, NVLINK_NUM_MINOR_DEVICES,
                              NVLINK_DEVICE_NAME);
    nvlink_major_devnum = MAJOR(dev);
    if (ret < 0)
    {
        nvl_print(NVL_DBG_ERRORS, "alloc_chrdev_region failed: %d\n",
               ret);
        return ret;
    }

    //
    // TODO: Create char dev node and associate it with nvlink_fops
    //
 
    nvl_print(NVL_DBG_INFO, "Nvlink Core is being initialized, "
              "major device number %d\n", nvlink_major_devnum);

    return ret;
}

void __exit nvlink_core_exit(void)
{
    if (nvlink_major_devnum != 0)
    {
        unregister_chrdev_region(MKDEV(nvlink_major_devnum, 0),
                                 NVLINK_NUM_MINOR_DEVICES);
    }

    nvl_print(NVL_DBG_INFO, "Unregistered the Nvlink Core, "
              "major device number %d\n", nvlink_major_devnum);
}


void  nvl_print(int log_level, const char *fmt, ...)
{
    va_list arglist;
    char    nv_string[MAX_ERROR_STRING];
    char   *sys_log_level;

    switch (log_level) {
    case NVL_DBG_INFO:
        sys_log_level = KERN_INFO;
        break;
    case NVL_DBG_SETUP:
        sys_log_level = KERN_DEBUG;
        break;
    case NVL_DBG_USERERRORS:
        sys_log_level = KERN_NOTICE;
        break;
    case NVL_DBG_WARNINGS:
        sys_log_level = KERN_WARNING;
        break;
    case NVL_DBG_ERRORS:
        sys_log_level = KERN_ERR;
        break;
    default:
        sys_log_level = KERN_INFO;
        break;
    }

    va_start(arglist, fmt);
    vsnprintf(nv_string, sizeof(nv_string), fmt, arglist);
    va_end(arglist);

    nv_string[sizeof(nv_string) - 1] = '\0';
    printk("%snvidia-nvlink: %s", sys_log_level, nv_string);
}

void *nvl_malloc(NvLength size)
{
   return kmalloc(size, GFP_KERNEL);
}

void nvl_free(void *ptr)
{
    return kfree(ptr);
}

char *nvl_strcpy(char *dest, const char *src)
{
    return strcpy(dest, src);
}

int nvl_strcmp(const char *dest, const char *src)
{
    return strcmp(dest, src);
}

NvLength nvl_strlen(const char *s)
{
    return strlen(s);
}

int nvl_snprintf(char *dest, NvLength size, const char *fmt, ...)
{
    va_list arglist;
    int chars_written;

    va_start(arglist, fmt);
    chars_written = vsnprintf(dest, size, fmt, arglist);
    va_end(arglist);

    return chars_written;
}

int nvlMemRd32(const volatile void * address)
{
    return (*(const volatile unsigned int*)(address));
}

void nvlMemWr32(volatile void *address, unsigned int data)
{
    (*(volatile unsigned int *)(address)) = data;
}

int nvlMemRd64(const volatile void * address)
{
    return (*(const volatile unsigned long long *)(address));
}

void nvlMemWr64(volatile void *address, unsigned long long data)
{
    (*(volatile unsigned long long *)(address)) = data;
}

void *nvl_memset(void *dest, int value, NvLength size)
{
     return memset(dest, value, size);
}

void *nvl_memcpy(void *dest, void *src, NvLength size)
{
    return memcpy(dest, src, size);
}

/*
 * Sleep for specified milliseconds. Yields the CPU to scheduler.
 */
void nvl_sleep(unsigned int ms)
{
    return;
}

void nvl_assert(int cond)
{
    if ((cond) == 0x0)
    {
        nvl_print(NVL_DBG_ERRORS, "Assertion failed!\n");
        dbg_breakpoint();
    }
}
