Files
linux-stable-mirror/drivers/infiniband/core/ucaps.c
T
Linus Torvalds 9e7e663345 Merge tag 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/rdma/rdma
Pull rdma updates from Jason Gunthorpe:
 "Many AI driven bug fixes, and several big driver API cleanups

   - Driver bug fixes and minor cleanups in mlx5, hns, rxe, efa, siw,
     rtrs, mana, irdma, mlx4. Commonly error path flows, integer
     arithmetic overflows on unsafe data, out of bounds access, and use
     after free issues under races.

   - Second half of the new udata API for drivers focusing on uAPI
     response

   - bnxt_re supports more options for QP creation that will allow a dv
     path in rdma-core

   - Untangle the module dependencies so drivers don't link to
     ib_uverbs.ko as was originall intended

   - Provide a new way to handle umems with a consistent simplified uAPI
     and update several drivers to use it. This brings dmabuf support to
     more places and more drivers

   - Support for mlx5 rate limit and packet pacing for UD and UC

   - A batch of fixes for the new shared FRMR pools infrastructure"

* tag 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/rdma/rdma: (148 commits)
  RDMA/irdma: Replace waitqueue and flag with completion
  RDMA/hns: Fix memory leak of bonding resources
  RDMA/rtrs-srv: Bound RDMA-Write length to chunk size in rdma_write_sg
  docs: infiniband: correct name of option to enable the ib_uverbs module
  RDMA/bnxt_re: Reject GET_TOGGLE_MEM when toggle page was not allocated
  RDMA/bnxt_re: Fail DBR related page allocation UAPIs if the feature is disabled
  RDMA/bnxt_re: Avoid repeated requests to allocate WC pages
  RDMA/bnxt_re: Proper rollback if the ioremap fails
  RDMA/bnxt_re: Add a max slot check for SQ
  RDMA/bnxt_re: Avoid displaying the kernel pointer
  RDMA/bnxt_re: Free CQ toggle page after firmware teardown
  RDMA/bnxt_re: Free SRQ toggle page after firmware teardown
  RDMA/bnxt_re: Initialize dpi variable to zero
  ABI: sysfs-class-infiniband: minor cleanup
  RDMA/mlx5: Release the HW‑provided UAR index rather than the SW one
  RDMA/mlx5: Fix undefined shift of user RQ WQE size
  RDMA/mlx5: Remove raw RSS QP restrack tracking
  RDMA/mlx5: Remove DCT restrack tracking
  RDMA/mlx5: Drop FRMR pool handle on UMR revoke failure
  RDMA/core: Add ib_frmr_pool_drop for unrecoverable handles
  ...
2026-06-18 08:16:21 -07:00

264 lines
5.6 KiB
C

// 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,
};
static __exit 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)
{
CLASS(fd, f)(fd);
if (fd_empty(f) || fd_file(f)->f_op != &ucaps_cdev_fops)
return -EBADF;
*ret_dev = file_inode(fd_file(f))->i_rdev;
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_obj(*ucap);
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, "%s", 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;
}
EXPORT_SYMBOL_NS_GPL(ib_get_ucaps, "rdma_core");
module_exit(ib_cleanup_ucaps);