// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB /* * Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved */ #include #include #include #include #include #include #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; }