mirror of
https://github.com/NVIDIA/open-gpu-kernel-modules.git
synced 2024-12-14 08:23:57 +01:00
822 lines
21 KiB
C
822 lines
21 KiB
C
|
/*
|
||
|
* SPDX-FileCopyrightText: Copyright (c) 2019-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.
|
||
|
*/
|
||
|
|
||
|
#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_system_pm_lock);
|
||
|
|
||
|
NV_DEFINE_SINGLE_PROCFS_FILE_READ_ONLY(mig_minors, nv_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_down_read_interruptible(&nv_system_pm_lock);
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
up_read(&nv_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;
|
||
|
}
|