open-gpu-kernel-modules/kernel-open/nvidia/nvlink_linux.c
2022-11-22 10:04:21 -08:00

647 lines
14 KiB
C

/*
* SPDX-FileCopyrightText: Copyright (c) 2015-2022 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 "conftest.h"
#include "nvlink_os.h"
#include "nvlink_linux.h"
#include "nvlink_errors.h"
#include "nvlink_export.h"
#include "nv-linux.h"
#include "nv-procfs.h"
#include "nv-time.h"
#include "nvlink_caps.h"
#include <linux/module.h>
#include <linux/major.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/string.h>
#include <linux/mutex.h>
#define MAX_ERROR_STRING 512
typedef struct nvlink_file_private
{
struct
{
/* A duped file descriptor for fabric_mgmt capability */
int fabric_mgmt;
} capability_fds;
} nvlink_file_private_t;
#define NVLINK_SET_FILE_PRIVATE(filp, data) ((filp)->private_data = (data))
#define NVLINK_GET_FILE_PRIVATE(filp) ((nvlink_file_private_t *)(filp)->private_data)
typedef struct
{
struct mutex lock;
NvBool initialized;
struct cdev cdev;
dev_t devno;
int opened;
int major_devnum;
} _nvlink_drvctx;
// nvlink driver local state
static _nvlink_drvctx nvlink_drvctx;
#if defined(CONFIG_PROC_FS)
#define NV_DEFINE_SINGLE_NVLINK_PROCFS_FILE(name) \
NV_DEFINE_SINGLE_PROCFS_FILE_READ_ONLY(name, nv_system_pm_lock)
#endif
#define NVLINK_PROCFS_DIR "driver/nvidia-nvlink"
static struct proc_dir_entry *nvlink_procfs_dir = NULL;
#if defined(CONFIG_PROC_FS)
static int nvlink_is_procfs_available = 1;
#else
static int nvlink_is_procfs_available = 0;
#endif
static struct proc_dir_entry *nvlink_permissions = NULL;
static int nv_procfs_read_permissions(struct seq_file *s, void *v)
{
// Restrict device node permissions - 0666.
seq_printf(s, "%s: %u\n", "DeviceFileMode", 438);
return 0;
}
NV_DEFINE_SINGLE_NVLINK_PROCFS_FILE(permissions);
static void nvlink_permissions_exit(void)
{
if (!nvlink_permissions)
{
return;
}
NV_REMOVE_PROC_ENTRY(nvlink_permissions);
nvlink_permissions = NULL;
}
static int nvlink_permissions_init(void)
{
if (!nvlink_procfs_dir)
{
return -EACCES;
}
nvlink_permissions = NV_CREATE_PROC_FILE("permissions",
nvlink_procfs_dir,
permissions,
NULL);
if (!nvlink_permissions)
{
return -EACCES;
}
return 0;
}
static void nvlink_procfs_exit(void)
{
nvlink_permissions_exit();
if (!nvlink_procfs_dir)
{
return;
}
NV_REMOVE_PROC_ENTRY(nvlink_procfs_dir);
nvlink_procfs_dir = NULL;
}
static int nvlink_procfs_init(void)
{
int rc = 0;
if (!nvlink_is_procfs_available)
{
return -EACCES;
}
nvlink_procfs_dir = NV_CREATE_PROC_DIR(NVLINK_PROCFS_DIR, NULL);
if (!nvlink_procfs_dir)
{
return -EACCES;
}
rc = nvlink_permissions_init();
if (rc < 0)
{
goto cleanup;
}
return 0;
cleanup:
nvlink_procfs_exit();
return rc;
}
static int nvlink_fops_open(struct inode *inode, struct file *filp)
{
int rc = 0;
nvlink_file_private_t *private = NULL;
nvlink_print(NVLINK_DBG_INFO, "nvlink driver open\n");
mutex_lock(&nvlink_drvctx.lock);
// nvlink lib driver is currently exclusive open.
if (nvlink_drvctx.opened)
{
rc = -EBUSY;
goto open_error;
}
private = (nvlink_file_private_t *)nvlink_malloc(sizeof(*private));
if (private == NULL)
{
rc = -ENOMEM;
goto open_error;
}
private->capability_fds.fabric_mgmt = -1;
NVLINK_SET_FILE_PRIVATE(filp, private);
// mark our state as opened
nvlink_drvctx.opened = NV_TRUE;
open_error:
mutex_unlock(&nvlink_drvctx.lock);
return rc;
}
static int nvlink_fops_release(struct inode *inode, struct file *filp)
{
nvlink_file_private_t *private = NVLINK_GET_FILE_PRIVATE(filp);
nvlink_print(NVLINK_DBG_INFO, "nvlink driver close\n");
if (private == NULL)
return -ENOMEM;
mutex_lock(&nvlink_drvctx.lock);
if (private->capability_fds.fabric_mgmt > 0)
{
nvlink_cap_release(private->capability_fds.fabric_mgmt);
private->capability_fds.fabric_mgmt = -1;
}
nvlink_free(filp->private_data);
NVLINK_SET_FILE_PRIVATE(filp, NULL);
// mark the device as not opened
nvlink_drvctx.opened = NV_FALSE;
mutex_unlock(&nvlink_drvctx.lock);
return 0;
}
static int nvlink_fops_ioctl(struct inode *inode,
struct file *filp,
unsigned int cmd,
unsigned long arg)
{
nvlink_ioctrl_params ctrl_params = {0};
int param_size = _IOC_SIZE(cmd);
void *param_buf = NULL;
NvlStatus ret_val = 0;
int rc = 0;
// no buffer for simple _IO types
if (param_size)
{
// allocate a buffer to hold user input
param_buf = kzalloc(param_size, GFP_KERNEL);
if (NULL == param_buf)
{
rc = -ENOMEM;
goto nvlink_ioctl_fail;
}
// copy user input to kernel buffers. Simple _IOR() ioctls can skip this step.
if (_IOC_DIR(cmd) & _IOC_WRITE)
{
// copy user input to local buffer
if (copy_from_user(param_buf, (const void *)arg, param_size))
{
rc = -EFAULT;
goto nvlink_ioctl_fail;
}
}
}
ctrl_params.osPrivate = filp->private_data;
ctrl_params.cmd = _IOC_NR(cmd);
ctrl_params.buf = param_buf;
ctrl_params.size = param_size;
ret_val = nvlink_lib_ioctl_ctrl(&ctrl_params);
if (NVL_SUCCESS != ret_val)
{
rc = -EINVAL;
goto nvlink_ioctl_fail;
}
// no copy for write-only ioctl
if ((param_size) && (_IOC_DIR(cmd) & _IOC_READ))
{
if (copy_to_user((void *)arg, ctrl_params.buf, ctrl_params.size))
{
rc = -EFAULT;
goto nvlink_ioctl_fail;
}
}
nvlink_ioctl_fail:
if (param_buf)
{
kfree(param_buf);
}
return rc;
}
#define NV_FILE_INODE(file) (file)->f_inode
static long nvlink_fops_unlocked_ioctl(struct file *file,
unsigned int cmd,
unsigned long arg)
{
return nvlink_fops_ioctl(NV_FILE_INODE(file), file, cmd, arg);
}
static const struct file_operations nvlink_fops = {
.owner = THIS_MODULE,
.open = nvlink_fops_open,
.release = nvlink_fops_release,
#if defined(NV_FILE_OPERATIONS_HAS_IOCTL)
.ioctl = nvlink_fops_ioctl,
#endif
.unlocked_ioctl = nvlink_fops_unlocked_ioctl,
};
int __init nvlink_core_init(void)
{
NvlStatus ret_val;
int rc;
if (NV_TRUE == nvlink_drvctx.initialized)
{
nvlink_print(NVLINK_DBG_ERRORS, "nvlink core interface already initialized\n");
return -EBUSY;
}
mutex_init(&nvlink_drvctx.lock);
ret_val = nvlink_lib_initialize();
if (NVL_SUCCESS != ret_val)
{
nvlink_print(NVLINK_DBG_ERRORS, "Failed to initialize driver : %d\n", ret_val);
rc = -ENODEV;
goto nvlink_lib_initialize_fail;
}
rc = alloc_chrdev_region(&nvlink_drvctx.devno, 0, NVLINK_NUM_MINOR_DEVICES,
NVLINK_DEVICE_NAME);
if (rc < 0)
{
nvlink_print(NVLINK_DBG_ERRORS, "alloc_chrdev_region failed: %d\n", rc);
goto alloc_chrdev_region_fail;
}
nvlink_drvctx.major_devnum = MAJOR(nvlink_drvctx.devno);
nvlink_print(NVLINK_DBG_INFO, "Nvlink Core is being initialized, major device number %d\n",
nvlink_drvctx.major_devnum);
cdev_init(&nvlink_drvctx.cdev, &nvlink_fops);
nvlink_drvctx.cdev.owner = THIS_MODULE;
rc = cdev_add(&nvlink_drvctx.cdev, nvlink_drvctx.devno, NVLINK_NUM_MINOR_DEVICES);
if (rc < 0)
{
nvlink_print(NVLINK_DBG_ERRORS, " Unable to create cdev\n");
goto cdev_add_fail;
}
rc = nvlink_procfs_init();
if (rc < 0)
{
goto procfs_init_fail;
}
rc = nvlink_cap_init(NVLINK_PROCFS_DIR);
if (rc < 0)
{
nvlink_print(NVLINK_DBG_ERRORS, " Unable to create capability\n");
goto cap_init_fail;
}
nvlink_drvctx.initialized = NV_TRUE;
return 0;
cap_init_fail:
nvlink_procfs_exit();
procfs_init_fail:
cdev_del(&nvlink_drvctx.cdev);
cdev_add_fail:
unregister_chrdev_region(nvlink_drvctx.devno, NVLINK_NUM_MINOR_DEVICES);
alloc_chrdev_region_fail:
nvlink_lib_unload();
nvlink_lib_initialize_fail:
nv_mutex_destroy(&nvlink_drvctx.lock);
return rc;
}
void nvlink_core_exit(void)
{
if (NV_FALSE == nvlink_drvctx.initialized)
{
return;
}
nvlink_cap_exit();
nvlink_procfs_exit();
cdev_del(&nvlink_drvctx.cdev);
unregister_chrdev_region(nvlink_drvctx.devno, NVLINK_NUM_MINOR_DEVICES);
nvlink_lib_unload();
nv_mutex_destroy(&nvlink_drvctx.lock);
nvlink_print(NVLINK_DBG_INFO, "Unregistered Nvlink Core, major device number %d\n",
nvlink_drvctx.major_devnum);
}
void
nvlink_print
(
const char *file,
int line,
const char *function,
int log_level,
const char *fmt,
...
)
{
va_list arglist;
char nv_string[MAX_ERROR_STRING];
char *sys_log_level;
switch (log_level) {
case NVLINK_DBG_LEVEL_INFO:
sys_log_level = KERN_INFO;
break;
case NVLINK_DBG_LEVEL_SETUP:
sys_log_level = KERN_DEBUG;
break;
case NVLINK_DBG_LEVEL_USERERRORS:
sys_log_level = KERN_NOTICE;
break;
case NVLINK_DBG_LEVEL_WARNINGS:
sys_log_level = KERN_WARNING;
break;
case NVLINK_DBG_LEVEL_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 * nvlink_malloc(NvLength size)
{
return kmalloc(size, GFP_KERNEL);
}
void nvlink_free(void *ptr)
{
return kfree(ptr);
}
char * nvlink_strcpy(char *dest, const char *src)
{
return strcpy(dest, src);
}
int nvlink_strcmp(const char *dest, const char *src)
{
return strcmp(dest, src);
}
NvLength nvlink_strlen(const char *s)
{
return strlen(s);
}
int nvlink_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;
}
NvU32 nvlink_memRd32(const volatile void * address)
{
return (*(const volatile NvU32*)(address));
}
void nvlink_memWr32(volatile void *address, NvU32 data)
{
(*(volatile NvU32 *)(address)) = data;
}
NvU64 nvlink_memRd64(const volatile void * address)
{
return (*(const volatile NvU64 *)(address));
}
void nvlink_memWr64(volatile void *address, NvU64 data)
{
(*(volatile NvU64 *)(address)) = data;
}
void * nvlink_memset(void *dest, int value, NvLength size)
{
return memset(dest, value, size);
}
void * nvlink_memcpy(void *dest, const void *src, NvLength size)
{
return memcpy(dest, src, size);
}
int nvlink_memcmp(const void *s1, const void *s2, NvLength size)
{
return memcmp(s1, s2, size);
}
/*
* Sleep for specified milliseconds. Yields the CPU to scheduler.
*/
void nvlink_sleep(unsigned int ms)
{
NV_STATUS status;
status = nv_sleep_ms(ms);
if (status != NV_OK)
{
if (printk_ratelimit())
{
nvlink_print(NVLINK_DBG_ERRORS, "NVLink: requested sleep duration"
" %d msec exceeded %d msec\n",
ms, NV_MAX_ISR_DELAY_MS);
WARN_ON(1);
}
}
}
void nvlink_assert(int cond)
{
if ((cond) == 0x0)
{
if (printk_ratelimit())
{
nvlink_print(NVLINK_DBG_ERRORS, "NVLink: Assertion failed!\n");
WARN_ON(1);
}
dbg_breakpoint();
}
}
void * nvlink_allocLock()
{
struct semaphore *sema;
sema = nvlink_malloc(sizeof(*sema));
if (sema == NULL)
{
nvlink_print(NVLINK_DBG_ERRORS, "Failed to allocate sema!\n");
return NULL;
}
sema_init(sema, 1);
return sema;
}
void nvlink_acquireLock(void *hLock)
{
down(hLock);
}
void nvlink_releaseLock(void *hLock)
{
up(hLock);
}
void nvlink_freeLock(void *hLock)
{
if (NULL == hLock)
{
return;
}
NVLINK_FREE(hLock);
}
NvBool nvlink_isLockOwner(void *hLock)
{
return NV_TRUE;
}
NvlStatus nvlink_acquire_fabric_mgmt_cap(void *osPrivate, NvU64 capDescriptor)
{
int dup_fd = -1;
nvlink_file_private_t *private_data = (nvlink_file_private_t *)osPrivate;
if (private_data == NULL)
{
return NVL_BAD_ARGS;
}
dup_fd = nvlink_cap_acquire((int)capDescriptor,
NVLINK_CAP_FABRIC_MANAGEMENT);
if (dup_fd < 0)
{
return NVL_ERR_OPERATING_SYSTEM;
}
private_data->capability_fds.fabric_mgmt = dup_fd;
return NVL_SUCCESS;
}
int nvlink_is_fabric_manager(void *osPrivate)
{
nvlink_file_private_t *private_data = (nvlink_file_private_t *)osPrivate;
/* Make sure that fabric mgmt capbaility fd is valid */
if ((private_data == NULL) ||
(private_data->capability_fds.fabric_mgmt < 0))
{
return 0;
}
return 1;
}
int nvlink_is_admin(void)
{
return NV_IS_SUSER();
}