blob: 6853c6d078f91e540adf0943b76d3550af57b1e3 [file] [log] [blame]
// 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;
ret = dev_set_name(&ucap->dev, ucap_names[type]);
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;
}