/*******************************************************************************
    Copyright (c) 2019-2021 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 "nv-linux.h"
#include "nv-caps.h"
#include "nv-procfs.h"
#include "nv-hash.h"

extern int NVreg_ModifyDeviceFiles;

/* sys_close() or __close_fd() */
#include <linux/syscalls.h>

#define NV_CAP_DRV_MINOR_COUNT 8192

/* Hash table with 512 buckets */
#define NV_CAP_HASH_BITS 9
NV_DECLARE_HASHTABLE(g_nv_cap_hash_table, NV_CAP_HASH_BITS);

#define NV_CAP_HASH_SIZE NV_HASH_SIZE(g_nv_cap_hash_table)

#define nv_cap_hash_key(path) (nv_string_hash(path) % NV_CAP_HASH_SIZE)

typedef struct nv_cap_table_entry
{
    /* name must be the first element */
    const char *name;
    int minor;
    struct hlist_node hlist;
} nv_cap_table_entry_t;

#define NV_CAP_NUM_ENTRIES(_table) (sizeof(_table) / sizeof(_table[0]))

static nv_cap_table_entry_t g_nv_cap_nvlink_table[] =
{
    {"/driver/nvidia-nvlink/capabilities/fabric-mgmt"}
};

static nv_cap_table_entry_t g_nv_cap_mig_table[] =
{
    {"/driver/nvidia/capabilities/mig/config"},
    {"/driver/nvidia/capabilities/mig/monitor"}
};

#define NV_CAP_MIG_CI_ENTRIES(_gi)  \
    {_gi "/ci0/access"},            \
    {_gi "/ci1/access"},            \
    {_gi "/ci2/access"},            \
    {_gi "/ci3/access"},            \
    {_gi "/ci4/access"},            \
    {_gi "/ci5/access"},            \
    {_gi "/ci6/access"},            \
    {_gi "/ci7/access"}

#define NV_CAP_MIG_GI_ENTRIES(_gpu)       \
    {_gpu "/gi0/access"},                 \
    NV_CAP_MIG_CI_ENTRIES(_gpu "/gi0"),   \
    {_gpu "/gi1/access"},                 \
    NV_CAP_MIG_CI_ENTRIES(_gpu "/gi1"),   \
    {_gpu "/gi2/access"},                 \
    NV_CAP_MIG_CI_ENTRIES(_gpu "/gi2"),   \
    {_gpu "/gi3/access"},                 \
    NV_CAP_MIG_CI_ENTRIES(_gpu "/gi3"),   \
    {_gpu "/gi4/access"},                 \
    NV_CAP_MIG_CI_ENTRIES(_gpu "/gi4"),   \
    {_gpu "/gi5/access"},                 \
    NV_CAP_MIG_CI_ENTRIES(_gpu "/gi5"),   \
    {_gpu "/gi6/access"},                 \
    NV_CAP_MIG_CI_ENTRIES(_gpu "/gi6"),   \
    {_gpu "/gi7/access"},                 \
    NV_CAP_MIG_CI_ENTRIES(_gpu "/gi7"),   \
    {_gpu "/gi8/access"},                 \
    NV_CAP_MIG_CI_ENTRIES(_gpu "/gi8"),   \
    {_gpu "/gi9/access"},                 \
    NV_CAP_MIG_CI_ENTRIES(_gpu "/gi9"),   \
    {_gpu "/gi10/access"},                \
    NV_CAP_MIG_CI_ENTRIES(_gpu "/gi10"),  \
    {_gpu "/gi11/access"},                \
    NV_CAP_MIG_CI_ENTRIES(_gpu "/gi11"),  \
    {_gpu "/gi12/access"},                \
    NV_CAP_MIG_CI_ENTRIES(_gpu "/gi12"),  \
    {_gpu "/gi13/access"},                \
    NV_CAP_MIG_CI_ENTRIES(_gpu "/gi13"),  \
    {_gpu "/gi14/access"},                \
    NV_CAP_MIG_CI_ENTRIES(_gpu "/gi14")

static nv_cap_table_entry_t g_nv_cap_mig_gpu_table[] =
{
    NV_CAP_MIG_GI_ENTRIES("/driver/nvidia/capabilities/gpu0/mig"),
    NV_CAP_MIG_GI_ENTRIES("/driver/nvidia/capabilities/gpu1/mig"),
    NV_CAP_MIG_GI_ENTRIES("/driver/nvidia/capabilities/gpu2/mig"),
    NV_CAP_MIG_GI_ENTRIES("/driver/nvidia/capabilities/gpu3/mig"),
    NV_CAP_MIG_GI_ENTRIES("/driver/nvidia/capabilities/gpu4/mig"),
    NV_CAP_MIG_GI_ENTRIES("/driver/nvidia/capabilities/gpu5/mig"),
    NV_CAP_MIG_GI_ENTRIES("/driver/nvidia/capabilities/gpu6/mig"),
    NV_CAP_MIG_GI_ENTRIES("/driver/nvidia/capabilities/gpu7/mig"),
    NV_CAP_MIG_GI_ENTRIES("/driver/nvidia/capabilities/gpu8/mig"),
    NV_CAP_MIG_GI_ENTRIES("/driver/nvidia/capabilities/gpu9/mig"),
    NV_CAP_MIG_GI_ENTRIES("/driver/nvidia/capabilities/gpu10/mig"),
    NV_CAP_MIG_GI_ENTRIES("/driver/nvidia/capabilities/gpu11/mig"),
    NV_CAP_MIG_GI_ENTRIES("/driver/nvidia/capabilities/gpu12/mig"),
    NV_CAP_MIG_GI_ENTRIES("/driver/nvidia/capabilities/gpu13/mig"),
    NV_CAP_MIG_GI_ENTRIES("/driver/nvidia/capabilities/gpu14/mig"),
    NV_CAP_MIG_GI_ENTRIES("/driver/nvidia/capabilities/gpu15/mig"),
    NV_CAP_MIG_GI_ENTRIES("/driver/nvidia/capabilities/gpu16/mig"),
    NV_CAP_MIG_GI_ENTRIES("/driver/nvidia/capabilities/gpu17/mig"),
    NV_CAP_MIG_GI_ENTRIES("/driver/nvidia/capabilities/gpu18/mig"),
    NV_CAP_MIG_GI_ENTRIES("/driver/nvidia/capabilities/gpu19/mig"),
    NV_CAP_MIG_GI_ENTRIES("/driver/nvidia/capabilities/gpu20/mig"),
    NV_CAP_MIG_GI_ENTRIES("/driver/nvidia/capabilities/gpu21/mig"),
    NV_CAP_MIG_GI_ENTRIES("/driver/nvidia/capabilities/gpu22/mig"),
    NV_CAP_MIG_GI_ENTRIES("/driver/nvidia/capabilities/gpu23/mig"),
    NV_CAP_MIG_GI_ENTRIES("/driver/nvidia/capabilities/gpu24/mig"),
    NV_CAP_MIG_GI_ENTRIES("/driver/nvidia/capabilities/gpu25/mig"),
    NV_CAP_MIG_GI_ENTRIES("/driver/nvidia/capabilities/gpu26/mig"),
    NV_CAP_MIG_GI_ENTRIES("/driver/nvidia/capabilities/gpu27/mig"),
    NV_CAP_MIG_GI_ENTRIES("/driver/nvidia/capabilities/gpu28/mig"),
    NV_CAP_MIG_GI_ENTRIES("/driver/nvidia/capabilities/gpu29/mig"),
    NV_CAP_MIG_GI_ENTRIES("/driver/nvidia/capabilities/gpu30/mig"),
    NV_CAP_MIG_GI_ENTRIES("/driver/nvidia/capabilities/gpu31/mig")
};

struct nv_cap
{
    char *path;
    char *name;
    int minor;
    int permissions;
    int modify;
    struct proc_dir_entry *parent;
    struct proc_dir_entry *entry;
};

#define NV_CAP_PROCFS_WRITE_BUF_SIZE 128

typedef struct nv_cap_file_private
{
    int minor;
    int permissions;
    int modify;
    char buffer[NV_CAP_PROCFS_WRITE_BUF_SIZE];
    off_t offset;
} nv_cap_file_private_t;

struct
{
    NvBool initialized;
    struct cdev cdev;
    dev_t devno;
} g_nv_cap_drv;

#define NV_CAP_PROCFS_DIR "driver/nvidia-caps"
#define NV_CAP_NAME_BUF_SIZE 128

static struct proc_dir_entry *nv_cap_procfs_dir;
static struct proc_dir_entry *nv_cap_procfs_nvlink_minors;
static struct proc_dir_entry *nv_cap_procfs_mig_minors;

static int nv_procfs_read_nvlink_minors(struct seq_file *s, void *v)
{
    int i, count;
    char name[NV_CAP_NAME_BUF_SIZE];

    count = NV_CAP_NUM_ENTRIES(g_nv_cap_nvlink_table);
    for (i = 0; i < count; i++)
    {
        if (sscanf(g_nv_cap_nvlink_table[i].name,
                   "/driver/nvidia-nvlink/capabilities/%s", name) == 1)
        {
            name[sizeof(name) - 1] = '\0';
            seq_printf(s, "%s %d\n", name, g_nv_cap_nvlink_table[i].minor);
        }
    }

    return 0;
}

static int nv_procfs_read_mig_minors(struct seq_file *s, void *v)
{
    int i, count, gpu;
    char name[NV_CAP_NAME_BUF_SIZE];

    count = NV_CAP_NUM_ENTRIES(g_nv_cap_mig_table);
    for (i = 0; i < count; i++)
    {
        if (sscanf(g_nv_cap_mig_table[i].name,
                   "/driver/nvidia/capabilities/mig/%s", name) == 1)
        {
            name[sizeof(name) - 1] = '\0';
            seq_printf(s, "%s %d\n", name, g_nv_cap_mig_table[i].minor);
        }
    }

    count = NV_CAP_NUM_ENTRIES(g_nv_cap_mig_gpu_table);
    for (i = 0; i < count; i++)
    {
        if (sscanf(g_nv_cap_mig_gpu_table[i].name,
                   "/driver/nvidia/capabilities/gpu%d/mig/%s", &gpu, name) == 2)
        {
            name[sizeof(name) - 1] = '\0';
            seq_printf(s, "gpu%d/%s %d\n",
                       gpu, name, g_nv_cap_mig_gpu_table[i].minor);
        }
    }

    return 0;
}

NV_DEFINE_SINGLE_PROCFS_FILE_READ_ONLY(nvlink_minors,
                                       NV_READ_LOCK_SYSTEM_PM_LOCK_INTERRUPTIBLE,
                                       NV_READ_UNLOCK_SYSTEM_PM_LOCK);

NV_DEFINE_SINGLE_PROCFS_FILE_READ_ONLY(mig_minors,
                                       NV_READ_LOCK_SYSTEM_PM_LOCK_INTERRUPTIBLE,
                                       NV_READ_UNLOCK_SYSTEM_PM_LOCK);

static void nv_cap_procfs_exit(void)
{
    if (!nv_cap_procfs_dir)
    {
        return;
    }

    nv_procfs_unregister_all(nv_cap_procfs_dir, nv_cap_procfs_dir);
    nv_cap_procfs_dir = NULL;
}

int nv_cap_procfs_init(void)
{
    nv_cap_procfs_dir = NV_CREATE_PROC_DIR(NV_CAP_PROCFS_DIR, NULL);
    if (nv_cap_procfs_dir == NULL)
    {
        return -EACCES;
    }

    nv_cap_procfs_mig_minors = NV_CREATE_PROC_FILE("mig-minors",
                                                   nv_cap_procfs_dir,
                                                   mig_minors,
                                                   NULL);
    if (nv_cap_procfs_mig_minors == NULL)
    {
        goto cleanup;
    }

    nv_cap_procfs_nvlink_minors = NV_CREATE_PROC_FILE("nvlink-minors",
                                                      nv_cap_procfs_dir,
                                                      nvlink_minors,
                                                      NULL);
    if (nv_cap_procfs_nvlink_minors == NULL)
    {
        goto cleanup;
    }

    return 0;

cleanup:
    nv_cap_procfs_exit();

    return -EACCES;
}

static int nv_cap_find_minor(char *path)
{
    unsigned int key = nv_cap_hash_key(path);
    nv_cap_table_entry_t *entry;

    nv_hash_for_each_possible(g_nv_cap_hash_table, entry, hlist, key)
    {
        if (strcmp(path, entry->name) == 0)
        {
            return entry->minor;
        }
    }

    return -1;
}

static void _nv_cap_table_init(nv_cap_table_entry_t *table, int count)
{
    int i;
    unsigned int key;
    static int minor = 0;

    for (i = 0; i < count; i++)
    {
        table[i].minor = minor++;
        INIT_HLIST_NODE(&table[i].hlist);
        key = nv_cap_hash_key(table[i].name);
        nv_hash_add(g_nv_cap_hash_table, &table[i].hlist, key);
    }

    WARN_ON(minor > NV_CAP_DRV_MINOR_COUNT);
}

#define nv_cap_table_init(table) \
    _nv_cap_table_init(table, NV_CAP_NUM_ENTRIES(table))

static void nv_cap_tables_init(void)
{
    BUILD_BUG_ON(offsetof(nv_cap_table_entry_t, name) != 0);

    nv_hash_init(g_nv_cap_hash_table);

    nv_cap_table_init(g_nv_cap_nvlink_table);
    nv_cap_table_init(g_nv_cap_mig_table);
    nv_cap_table_init(g_nv_cap_mig_gpu_table);
}

static ssize_t nv_cap_procfs_write(struct file *file,
                                    const char __user *buffer,
                                    size_t count, loff_t *pos)
{
    nv_cap_file_private_t *private = NULL;
    unsigned long bytes_left;
    char *proc_buffer;

    private = ((struct seq_file *)file->private_data)->private;
    bytes_left = (sizeof(private->buffer) - private->offset - 1);

    if (count == 0)
    {
        return -EINVAL;
    }

    if ((bytes_left == 0) || (count > bytes_left))
    {
        return -ENOSPC;
    }

    proc_buffer = &private->buffer[private->offset];

    if (copy_from_user(proc_buffer, buffer, count))
    {
        nv_printf(NV_DBG_ERRORS, "nv-caps: failed to copy in proc data!\n");
        return -EFAULT;
    }

    private->offset += count;
    proc_buffer[count] = '\0';

    *pos = private->offset;

    return count;
}

static int nv_cap_procfs_read(struct seq_file *s, void *v)
{
    nv_cap_file_private_t *private = s->private;

    seq_printf(s, "%s: %d\n", "DeviceFileMinor", private->minor);
    seq_printf(s, "%s: %d\n", "DeviceFileMode", private->permissions);
    seq_printf(s, "%s: %d\n", "DeviceFileModify", private->modify);

    return 0;
}

static int nv_cap_procfs_open(struct inode *inode, struct file *file)
{
    nv_cap_file_private_t *private = NULL;
    int rc;
    nv_cap_t *cap = NV_PDE_DATA(inode);

    NV_KMALLOC(private, sizeof(nv_cap_file_private_t));
    if (private == NULL)
    {
        return -ENOMEM;
    }

    private->minor = cap->minor;
    private->permissions = cap->permissions;
    private->offset = 0;
    private->modify = cap->modify;

    rc = single_open(file, nv_cap_procfs_read, private);
    if (rc < 0)
    {
        NV_KFREE(private, sizeof(nv_cap_file_private_t));
        return rc;
    }

    rc = NV_READ_LOCK_SYSTEM_PM_LOCK_INTERRUPTIBLE();
    if (rc < 0)
    {
        single_release(inode, file);
        NV_KFREE(private, sizeof(nv_cap_file_private_t));
    }

    return rc;
}

static int nv_cap_procfs_release(struct inode *inode, struct file *file)
{
    struct seq_file *s = file->private_data;
    nv_cap_file_private_t *private = NULL;
    char *buffer;
    int modify;
    nv_cap_t *cap = NV_PDE_DATA(inode);

    if (s != NULL)
    {
        private = s->private;
    }

    NV_READ_UNLOCK_SYSTEM_PM_LOCK();

    single_release(inode, file);

    if (private != NULL)
    {
        buffer = private->buffer;

        if (private->offset != 0)
        {
            if (sscanf(buffer, "DeviceFileModify: %d", &modify) == 1)
            {
                cap->modify = modify;
            }
        }

        NV_KFREE(private, sizeof(nv_cap_file_private_t));
    }

    /*
     * All open files using the proc entry will be invalidated
     * if the entry is removed.
     */
    file->private_data = NULL;

    return 0;
}

static nv_proc_ops_t g_nv_cap_procfs_fops = {
    NV_PROC_OPS_SET_OWNER()
    .NV_PROC_OPS_OPEN    = nv_cap_procfs_open,
    .NV_PROC_OPS_RELEASE = nv_cap_procfs_release,
    .NV_PROC_OPS_WRITE   = nv_cap_procfs_write,
    .NV_PROC_OPS_READ    = seq_read,
    .NV_PROC_OPS_LSEEK   = seq_lseek,
};

/* forward declaration of g_nv_cap_drv_fops */
static struct file_operations g_nv_cap_drv_fops;

int NV_API_CALL nv_cap_validate_and_dup_fd(const nv_cap_t *cap, int fd)
{
    struct file *file;
    int dup_fd;
    struct inode *inode = NULL;
    dev_t rdev = 0;
    struct files_struct *files = current->files;
    struct fdtable *fdt;

    if (cap == NULL)
    {
        return -1;
    }

    file = fget(fd);
    if (file == NULL)
    {
        return -1;
    }

    inode = NV_FILE_INODE(file);
    if (inode == NULL)
    {
        goto err;
    }

    /* Make sure the fd belongs to the nv-cap-drv */
    if (file->f_op != &g_nv_cap_drv_fops)
    {
        goto err;
    }

    /* Make sure the fd has the expected capability */
    rdev = inode->i_rdev;
    if (MINOR(rdev) != cap->minor)
    {
        goto err;
    }

    dup_fd = NV_GET_UNUSED_FD_FLAGS(O_CLOEXEC);
    if (dup_fd < 0)
    {
        dup_fd = NV_GET_UNUSED_FD();
        if (dup_fd < 0)
        {
            goto err;
        }

        /*
         * Set CLOEXEC before installing the FD.
         *
         * If fork() happens in between, the opened unused FD will have
         * a NULL struct file associated with it, which is okay.
         *
         * The only well known bug here is the race with dup(2), which is
         * already documented in the kernel, see fd_install()'s description.
         */

        spin_lock(&files->file_lock);
        fdt = files_fdtable(files);
        NV_SET_CLOSE_ON_EXEC(dup_fd, fdt);
        spin_unlock(&files->file_lock);
    }

    fd_install(dup_fd, file);
    return dup_fd;

err:
    fput(file);
    return -1;
}

void NV_API_CALL nv_cap_close_fd(int fd)
{
    if (fd == -1)
    {
        return;
    }

    /*
     * Acquire task_lock as we access current->files explicitly (__close_fd)
     * and implicitly (sys_close), and it will race with the exit path.
     */
    task_lock(current);

    /* Nothing to do, we are in exit path */
    if (current->files == NULL)
    {
        task_unlock(current);
        return;
    }

/*
 * From v4.17-rc1 (to v5.10.8) kernels have stopped exporting sys_close(fd)
 * and started exporting __close_fd, as of this commit:
 * 2018-04-02 2ca2a09d6215 ("fs: add ksys_close() wrapper; remove in-kernel
 * calls to sys_close()")
 * Kernels v5.11-rc1 onwards have stopped exporting __close_fd, and started
 * exporting close_fd, as of this commit:
 * 2020-12-20 8760c909f54a ("file: Rename __close_fd to close_fd and remove
 * the files parameter")
 */
#if NV_IS_EXPORT_SYMBOL_PRESENT_close_fd
    close_fd(fd);
#elif NV_IS_EXPORT_SYMBOL_PRESENT___close_fd
    __close_fd(current->files, fd);
#else
    sys_close(fd);
#endif

    task_unlock(current);
}

static nv_cap_t* nv_cap_alloc(nv_cap_t *parent_cap, const char *name)
{
    nv_cap_t *cap;
    int len;

    if (parent_cap == NULL || name == NULL)
    {
        return NULL;
    }

    NV_KMALLOC(cap, sizeof(nv_cap_t));
    if (cap == NULL)
    {
        return NULL;
    }

    len = strlen(name) + strlen(parent_cap->path) + 2;
    NV_KMALLOC(cap->path, len);
    if (cap->path == NULL)
    {
        NV_KFREE(cap, sizeof(nv_cap_t));
        return NULL;
    }

    strcpy(cap->path, parent_cap->path);
    strcat(cap->path, "/");
    strcat(cap->path, name);

    len = strlen(name) + 1;
    NV_KMALLOC(cap->name, len);
    if (cap->name == NULL)
    {
        NV_KFREE(cap->path, strlen(cap->path) + 1);
        NV_KFREE(cap, sizeof(nv_cap_t));
        return NULL;
    }

    strcpy(cap->name, name);

    cap->minor = -1;
    cap->modify = NVreg_ModifyDeviceFiles;

    return cap;
}

static void nv_cap_free(nv_cap_t *cap)
{
    if (cap == NULL)
    {
        return;
    }

    NV_KFREE(cap->path, strlen(cap->path) + 1);
    NV_KFREE(cap->name, strlen(cap->name) + 1);
    NV_KFREE(cap, sizeof(nv_cap_t));
}

nv_cap_t* NV_API_CALL nv_cap_create_file_entry(nv_cap_t *parent_cap,
                                               const char *name, int mode)
{
    nv_cap_t *cap = NULL;
    int minor;

    cap = nv_cap_alloc(parent_cap, name);
    if (cap == NULL)
    {
        return NULL;
    }

    cap->parent = parent_cap->entry;
    cap->permissions = mode;

    mode = (S_IFREG | S_IRUGO);

    minor = nv_cap_find_minor(cap->path);
    if (minor < 0)
    {
        nv_cap_free(cap);
        return NULL;
    }

    cap->minor = minor;

    cap->entry = proc_create_data(name, mode, parent_cap->entry,
                                  &g_nv_cap_procfs_fops, (void*)cap);
    if (cap->entry == NULL)
    {
        nv_cap_free(cap);
        return NULL;
    }

    return cap;
}

nv_cap_t* NV_API_CALL nv_cap_create_dir_entry(nv_cap_t *parent_cap,
                                              const char *name, int mode)
{
    nv_cap_t *cap = NULL;

    cap = nv_cap_alloc(parent_cap, name);
    if (cap == NULL)
    {
        return NULL;
    }

    cap->parent = parent_cap->entry;
    cap->permissions = mode;
    cap->minor = -1;

    mode = (S_IFDIR | S_IRUGO | S_IXUGO);

    cap->entry = NV_PROC_MKDIR_MODE(name, mode, parent_cap->entry);
    if (cap->entry == NULL)
    {
        nv_cap_free(cap);
        return NULL;
    }

    return cap;
}

nv_cap_t* NV_API_CALL nv_cap_init(const char *path)
{
    nv_cap_t parent_cap;
    nv_cap_t *cap;
    int mode;
    char *name = NULL;
    char dir[] = "/capabilities";

    if (path == NULL)
    {
        return NULL;
    }

    NV_KMALLOC(name, (strlen(path) + strlen(dir)) + 1);
    if (name == NULL)
    {
        return NULL;
    }

    strcpy(name, path);
    strcat(name, dir);
    parent_cap.entry = NULL;
    parent_cap.path = "";
    parent_cap.name = "";
    mode =  S_IRUGO | S_IXUGO;
    cap = nv_cap_create_dir_entry(&parent_cap, name, mode);

    NV_KFREE(name, strlen(name) + 1);
    return cap;
}

void NV_API_CALL nv_cap_destroy_entry(nv_cap_t *cap)
{
    if (WARN_ON(cap == NULL))
    {
        return;
    }

    remove_proc_entry(cap->name, cap->parent);
    nv_cap_free(cap);
}

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

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

static struct file_operations g_nv_cap_drv_fops =
{
    .owner = THIS_MODULE,
    .open    = nv_cap_drv_open,
    .release = nv_cap_drv_release
};

int NV_API_CALL nv_cap_drv_init(void)
{
    int rc;

    nv_cap_tables_init();

    if (g_nv_cap_drv.initialized)
    {
        nv_printf(NV_DBG_ERRORS, "nv-caps-drv is already initialized.\n");
        return -EBUSY;
    }

    rc = alloc_chrdev_region(&g_nv_cap_drv.devno,
                             0,
                             NV_CAP_DRV_MINOR_COUNT,
                             "nvidia-caps");
    if (rc < 0)
    {
        nv_printf(NV_DBG_ERRORS, "nv-caps-drv failed to create cdev region.\n");
        return rc;
    }

    cdev_init(&g_nv_cap_drv.cdev, &g_nv_cap_drv_fops);

    g_nv_cap_drv.cdev.owner = THIS_MODULE;

    rc = cdev_add(&g_nv_cap_drv.cdev, g_nv_cap_drv.devno,
                  NV_CAP_DRV_MINOR_COUNT);
    if (rc < 0)
    {
        nv_printf(NV_DBG_ERRORS, "nv-caps-drv failed to create cdev.\n");
        goto cdev_add_fail;
    }

    rc = nv_cap_procfs_init();
    if (rc < 0)
    {
        nv_printf(NV_DBG_ERRORS, "nv-caps-drv: unable to init proc\n");
        goto proc_init_fail;
    }

    g_nv_cap_drv.initialized = NV_TRUE;

    return 0;

proc_init_fail:
    cdev_del(&g_nv_cap_drv.cdev);

cdev_add_fail:
    unregister_chrdev_region(g_nv_cap_drv.devno, NV_CAP_DRV_MINOR_COUNT);

    return rc;
}

void NV_API_CALL nv_cap_drv_exit(void)
{
    if (!g_nv_cap_drv.initialized)
    {
        return;
    }

    nv_cap_procfs_exit();

    cdev_del(&g_nv_cap_drv.cdev);

    unregister_chrdev_region(g_nv_cap_drv.devno, NV_CAP_DRV_MINOR_COUNT);

    g_nv_cap_drv.initialized = NV_FALSE;
}
