2025-03-06 13:51:26 +02:00
|
|
|
// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
|
|
|
|
/*
|
|
|
|
* Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/kref.h>
|
|
|
|
#include <linux/cdev.h>
|
|
|
|
#include <linux/mutex.h>
|
|
|
|
#include <linux/file.h>
|
|
|
|
#include <linux/fs.h>
|
|
|
|
#include <rdma/ib_ucaps.h>
|
|
|
|
|
|
|
|
#define RDMA_UCAP_FIRST RDMA_UCAP_MLX5_CTRL_LOCAL
|
|
|
|
|
|
|
|
static DEFINE_MUTEX(ucaps_mutex);
|
|
|
|
static struct ib_ucap *ucaps_list[RDMA_UCAP_MAX];
|
|
|
|
static bool ucaps_class_is_registered;
|
|
|
|
static dev_t ucaps_base_dev;
|
|
|
|
|
|
|
|
struct ib_ucap {
|
|
|
|
struct cdev cdev;
|
|
|
|
struct device dev;
|
|
|
|
struct kref ref;
|
|
|
|
};
|
|
|
|
|
|
|
|
static const char *ucap_names[RDMA_UCAP_MAX] = {
|
|
|
|
[RDMA_UCAP_MLX5_CTRL_LOCAL] = "mlx5_perm_ctrl_local",
|
|
|
|
[RDMA_UCAP_MLX5_CTRL_OTHER_VHCA] = "mlx5_perm_ctrl_other_vhca"
|
|
|
|
};
|
|
|
|
|
|
|
|
static char *ucaps_devnode(const struct device *dev, umode_t *mode)
|
|
|
|
{
|
|
|
|
if (mode)
|
|
|
|
*mode = 0600;
|
|
|
|
|
|
|
|
return kasprintf(GFP_KERNEL, "infiniband/%s", dev_name(dev));
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct class ucaps_class = {
|
|
|
|
.name = "infiniband_ucaps",
|
|
|
|
.devnode = ucaps_devnode,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct file_operations ucaps_cdev_fops = {
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.open = simple_open,
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* ib_cleanup_ucaps - cleanup all API resources and class.
|
|
|
|
*
|
|
|
|
* This is called once, when removing the ib_uverbs module.
|
|
|
|
*/
|
|
|
|
void ib_cleanup_ucaps(void)
|
|
|
|
{
|
|
|
|
mutex_lock(&ucaps_mutex);
|
|
|
|
if (!ucaps_class_is_registered) {
|
|
|
|
mutex_unlock(&ucaps_mutex);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = RDMA_UCAP_FIRST; i < RDMA_UCAP_MAX; i++)
|
|
|
|
WARN_ON(ucaps_list[i]);
|
|
|
|
|
|
|
|
class_unregister(&ucaps_class);
|
|
|
|
ucaps_class_is_registered = false;
|
|
|
|
unregister_chrdev_region(ucaps_base_dev, RDMA_UCAP_MAX);
|
|
|
|
mutex_unlock(&ucaps_mutex);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int get_ucap_from_devt(dev_t devt, u64 *idx_mask)
|
|
|
|
{
|
|
|
|
for (int type = RDMA_UCAP_FIRST; type < RDMA_UCAP_MAX; type++) {
|
|
|
|
if (ucaps_list[type] && ucaps_list[type]->dev.devt == devt) {
|
|
|
|
*idx_mask |= 1 << type;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int get_devt_from_fd(unsigned int fd, dev_t *ret_dev)
|
|
|
|
{
|
|
|
|
struct file *file;
|
|
|
|
|
|
|
|
file = fget(fd);
|
|
|
|
if (!file)
|
|
|
|
return -EBADF;
|
|
|
|
|
|
|
|
*ret_dev = file_inode(file)->i_rdev;
|
|
|
|
fput(file);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* ib_ucaps_init - Initialization required before ucap creation.
|
|
|
|
*
|
|
|
|
* Return: 0 on success, or a negative errno value on failure
|
|
|
|
*/
|
|
|
|
static int ib_ucaps_init(void)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
if (ucaps_class_is_registered)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
ret = class_register(&ucaps_class);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
ret = alloc_chrdev_region(&ucaps_base_dev, 0, RDMA_UCAP_MAX,
|
|
|
|
ucaps_class.name);
|
|
|
|
if (ret < 0) {
|
|
|
|
class_unregister(&ucaps_class);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
ucaps_class_is_registered = true;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ucap_dev_release(struct device *device)
|
|
|
|
{
|
|
|
|
struct ib_ucap *ucap = container_of(device, struct ib_ucap, dev);
|
|
|
|
|
|
|
|
kfree(ucap);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* ib_create_ucap - Add a ucap character device
|
|
|
|
* @type: UCAP type
|
|
|
|
*
|
|
|
|
* Creates a ucap character device in the /dev/infiniband directory. By default,
|
|
|
|
* the device has root-only read-write access.
|
|
|
|
*
|
|
|
|
* A driver may call this multiple times with the same UCAP type. A reference
|
|
|
|
* count tracks creations and deletions.
|
|
|
|
*
|
|
|
|
* Return: 0 on success, or a negative errno value on failure
|
|
|
|
*/
|
|
|
|
int ib_create_ucap(enum rdma_user_cap type)
|
|
|
|
{
|
|
|
|
struct ib_ucap *ucap;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (type >= RDMA_UCAP_MAX)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
mutex_lock(&ucaps_mutex);
|
|
|
|
ret = ib_ucaps_init();
|
|
|
|
if (ret)
|
|
|
|
goto unlock;
|
|
|
|
|
|
|
|
ucap = ucaps_list[type];
|
|
|
|
if (ucap) {
|
|
|
|
kref_get(&ucap->ref);
|
|
|
|
mutex_unlock(&ucaps_mutex);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
ucap = kzalloc(sizeof(*ucap), GFP_KERNEL);
|
|
|
|
if (!ucap) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto unlock;
|
|
|
|
}
|
|
|
|
|
|
|
|
device_initialize(&ucap->dev);
|
|
|
|
ucap->dev.class = &ucaps_class;
|
|
|
|
ucap->dev.devt = MKDEV(MAJOR(ucaps_base_dev), type);
|
|
|
|
ucap->dev.release = ucap_dev_release;
|
2025-03-14 16:57:15 +01:00
|
|
|
ret = dev_set_name(&ucap->dev, "%s", ucap_names[type]);
|
2025-03-06 13:51:26 +02:00
|
|
|
if (ret)
|
|
|
|
goto err_device;
|
|
|
|
|
|
|
|
cdev_init(&ucap->cdev, &ucaps_cdev_fops);
|
|
|
|
ucap->cdev.owner = THIS_MODULE;
|
|
|
|
|
|
|
|
ret = cdev_device_add(&ucap->cdev, &ucap->dev);
|
|
|
|
if (ret)
|
|
|
|
goto err_device;
|
|
|
|
|
|
|
|
kref_init(&ucap->ref);
|
|
|
|
ucaps_list[type] = ucap;
|
|
|
|
mutex_unlock(&ucaps_mutex);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_device:
|
|
|
|
put_device(&ucap->dev);
|
|
|
|
unlock:
|
|
|
|
mutex_unlock(&ucaps_mutex);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(ib_create_ucap);
|
|
|
|
|
|
|
|
static void ib_release_ucap(struct kref *ref)
|
|
|
|
{
|
|
|
|
struct ib_ucap *ucap = container_of(ref, struct ib_ucap, ref);
|
|
|
|
enum rdma_user_cap type;
|
|
|
|
|
|
|
|
for (type = RDMA_UCAP_FIRST; type < RDMA_UCAP_MAX; type++) {
|
|
|
|
if (ucaps_list[type] == ucap)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
WARN_ON(type == RDMA_UCAP_MAX);
|
|
|
|
|
|
|
|
ucaps_list[type] = NULL;
|
|
|
|
cdev_device_del(&ucap->cdev, &ucap->dev);
|
|
|
|
put_device(&ucap->dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* ib_remove_ucap - Remove a ucap character device
|
|
|
|
* @type: User cap type
|
|
|
|
*
|
|
|
|
* Removes the ucap character device according to type. The device is completely
|
|
|
|
* removed from the filesystem when its reference count reaches 0.
|
|
|
|
*/
|
|
|
|
void ib_remove_ucap(enum rdma_user_cap type)
|
|
|
|
{
|
|
|
|
struct ib_ucap *ucap;
|
|
|
|
|
|
|
|
mutex_lock(&ucaps_mutex);
|
|
|
|
ucap = ucaps_list[type];
|
|
|
|
if (WARN_ON(!ucap))
|
|
|
|
goto end;
|
|
|
|
|
|
|
|
kref_put(&ucap->ref, ib_release_ucap);
|
|
|
|
end:
|
|
|
|
mutex_unlock(&ucaps_mutex);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(ib_remove_ucap);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* ib_get_ucaps - Get bitmask of ucap types from file descriptors
|
|
|
|
* @fds: Array of file descriptors
|
|
|
|
* @fd_count: Number of file descriptors in the array
|
|
|
|
* @idx_mask: Bitmask to be updated based on the ucaps in the fd list
|
|
|
|
*
|
|
|
|
* Given an array of file descriptors, this function returns a bitmask of
|
|
|
|
* the ucaps where a bit is set if an FD for that ucap type was in the array.
|
|
|
|
*
|
|
|
|
* Return: 0 on success, or a negative errno value on failure
|
|
|
|
*/
|
|
|
|
int ib_get_ucaps(int *fds, int fd_count, uint64_t *idx_mask)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
dev_t dev;
|
|
|
|
|
|
|
|
*idx_mask = 0;
|
|
|
|
mutex_lock(&ucaps_mutex);
|
|
|
|
for (int i = 0; i < fd_count; i++) {
|
|
|
|
ret = get_devt_from_fd(fds[i], &dev);
|
|
|
|
if (ret)
|
|
|
|
goto end;
|
|
|
|
|
|
|
|
ret = get_ucap_from_devt(dev, idx_mask);
|
|
|
|
if (ret)
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
end:
|
|
|
|
mutex_unlock(&ucaps_mutex);
|
|
|
|
return ret;
|
|
|
|
}
|