mirror of
https://github.com/torvalds/linux.git
synced 2026-04-18 14:53:58 -04:00
fwctl: Basic ioctl dispatch for the character device
Each file descriptor gets a chunk of per-FD driver specific context that allows the driver to attach a device specific struct to. The core code takes care of the memory lifetime for this structure. The ioctl dispatch and design is based on what was built for iommufd. The ioctls have a struct which has a combined in/out behavior with a typical 'zero pad' scheme for future extension and backwards compatibility. Like iommufd some shared logic does most of the ioctl marshaling and compatibility work and table dispatches to some function pointers for each unique ioctl. This approach has proven to work quite well in the iommufd and rdma subsystems. Allocate an ioctl number space for the subsystem. Link: https://patch.msgid.link/r/2-v5-642aa0c94070+4447f-fwctl_jgg@nvidia.com Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com> Reviewed-by: Dave Jiang <dave.jiang@intel.com> Reviewed-by: Shannon Nelson <shannon.nelson@amd.com> Tested-by: Dave Jiang <dave.jiang@intel.com> Tested-by: Shannon Nelson <shannon.nelson@amd.com> Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
This commit is contained in:
@@ -10,6 +10,8 @@
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <uapi/fwctl/fwctl.h>
|
||||
|
||||
enum {
|
||||
FWCTL_MAX_DEVICES = 4096,
|
||||
};
|
||||
@@ -18,20 +20,128 @@ static_assert(FWCTL_MAX_DEVICES < (1U << MINORBITS));
|
||||
static dev_t fwctl_dev;
|
||||
static DEFINE_IDA(fwctl_ida);
|
||||
|
||||
struct fwctl_ucmd {
|
||||
struct fwctl_uctx *uctx;
|
||||
void __user *ubuffer;
|
||||
void *cmd;
|
||||
u32 user_size;
|
||||
};
|
||||
|
||||
/* On stack memory for the ioctl structs */
|
||||
union fwctl_ucmd_buffer {
|
||||
};
|
||||
|
||||
struct fwctl_ioctl_op {
|
||||
unsigned int size;
|
||||
unsigned int min_size;
|
||||
unsigned int ioctl_num;
|
||||
int (*execute)(struct fwctl_ucmd *ucmd);
|
||||
};
|
||||
|
||||
#define IOCTL_OP(_ioctl, _fn, _struct, _last) \
|
||||
[_IOC_NR(_ioctl) - FWCTL_CMD_BASE] = { \
|
||||
.size = sizeof(_struct) + \
|
||||
BUILD_BUG_ON_ZERO(sizeof(union fwctl_ucmd_buffer) < \
|
||||
sizeof(_struct)), \
|
||||
.min_size = offsetofend(_struct, _last), \
|
||||
.ioctl_num = _ioctl, \
|
||||
.execute = _fn, \
|
||||
}
|
||||
static const struct fwctl_ioctl_op fwctl_ioctl_ops[] = {
|
||||
};
|
||||
|
||||
static long fwctl_fops_ioctl(struct file *filp, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct fwctl_uctx *uctx = filp->private_data;
|
||||
const struct fwctl_ioctl_op *op;
|
||||
struct fwctl_ucmd ucmd = {};
|
||||
union fwctl_ucmd_buffer buf;
|
||||
unsigned int nr;
|
||||
int ret;
|
||||
|
||||
nr = _IOC_NR(cmd);
|
||||
if ((nr - FWCTL_CMD_BASE) >= ARRAY_SIZE(fwctl_ioctl_ops))
|
||||
return -ENOIOCTLCMD;
|
||||
|
||||
op = &fwctl_ioctl_ops[nr - FWCTL_CMD_BASE];
|
||||
if (op->ioctl_num != cmd)
|
||||
return -ENOIOCTLCMD;
|
||||
|
||||
ucmd.uctx = uctx;
|
||||
ucmd.cmd = &buf;
|
||||
ucmd.ubuffer = (void __user *)arg;
|
||||
ret = get_user(ucmd.user_size, (u32 __user *)ucmd.ubuffer);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (ucmd.user_size < op->min_size)
|
||||
return -EINVAL;
|
||||
|
||||
ret = copy_struct_from_user(ucmd.cmd, op->size, ucmd.ubuffer,
|
||||
ucmd.user_size);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
guard(rwsem_read)(&uctx->fwctl->registration_lock);
|
||||
if (!uctx->fwctl->ops)
|
||||
return -ENODEV;
|
||||
return op->execute(&ucmd);
|
||||
}
|
||||
|
||||
static int fwctl_fops_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct fwctl_device *fwctl =
|
||||
container_of(inode->i_cdev, struct fwctl_device, cdev);
|
||||
int ret;
|
||||
|
||||
guard(rwsem_read)(&fwctl->registration_lock);
|
||||
if (!fwctl->ops)
|
||||
return -ENODEV;
|
||||
|
||||
struct fwctl_uctx *uctx __free(kfree) =
|
||||
kzalloc(fwctl->ops->uctx_size, GFP_KERNEL_ACCOUNT);
|
||||
if (!uctx)
|
||||
return -ENOMEM;
|
||||
|
||||
uctx->fwctl = fwctl;
|
||||
ret = fwctl->ops->open_uctx(uctx);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
scoped_guard(mutex, &fwctl->uctx_list_lock) {
|
||||
list_add_tail(&uctx->uctx_list_entry, &fwctl->uctx_list);
|
||||
}
|
||||
|
||||
get_device(&fwctl->dev);
|
||||
filp->private_data = fwctl;
|
||||
filp->private_data = no_free_ptr(uctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void fwctl_destroy_uctx(struct fwctl_uctx *uctx)
|
||||
{
|
||||
lockdep_assert_held(&uctx->fwctl->uctx_list_lock);
|
||||
list_del(&uctx->uctx_list_entry);
|
||||
uctx->fwctl->ops->close_uctx(uctx);
|
||||
}
|
||||
|
||||
static int fwctl_fops_release(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct fwctl_device *fwctl = filp->private_data;
|
||||
struct fwctl_uctx *uctx = filp->private_data;
|
||||
struct fwctl_device *fwctl = uctx->fwctl;
|
||||
|
||||
scoped_guard(rwsem_read, &fwctl->registration_lock) {
|
||||
/*
|
||||
* NULL ops means fwctl_unregister() has already removed the
|
||||
* driver and destroyed the uctx.
|
||||
*/
|
||||
if (fwctl->ops) {
|
||||
guard(mutex)(&fwctl->uctx_list_lock);
|
||||
fwctl_destroy_uctx(uctx);
|
||||
}
|
||||
}
|
||||
|
||||
kfree(uctx);
|
||||
fwctl_put(fwctl);
|
||||
return 0;
|
||||
}
|
||||
@@ -40,6 +150,7 @@ static const struct file_operations fwctl_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = fwctl_fops_open,
|
||||
.release = fwctl_fops_release,
|
||||
.unlocked_ioctl = fwctl_fops_ioctl,
|
||||
};
|
||||
|
||||
static void fwctl_device_release(struct device *device)
|
||||
@@ -48,6 +159,7 @@ static void fwctl_device_release(struct device *device)
|
||||
container_of(device, struct fwctl_device, dev);
|
||||
|
||||
ida_free(&fwctl_ida, fwctl->dev.devt - fwctl_dev);
|
||||
mutex_destroy(&fwctl->uctx_list_lock);
|
||||
kfree(fwctl);
|
||||
}
|
||||
|
||||
@@ -71,9 +183,6 @@ _alloc_device(struct device *parent, const struct fwctl_ops *ops, size_t size)
|
||||
if (!fwctl)
|
||||
return NULL;
|
||||
|
||||
fwctl->dev.class = &fwctl_class;
|
||||
fwctl->dev.parent = parent;
|
||||
|
||||
devnum = ida_alloc_max(&fwctl_ida, FWCTL_MAX_DEVICES - 1, GFP_KERNEL);
|
||||
if (devnum < 0)
|
||||
return NULL;
|
||||
@@ -82,6 +191,10 @@ _alloc_device(struct device *parent, const struct fwctl_ops *ops, size_t size)
|
||||
fwctl->dev.class = &fwctl_class;
|
||||
fwctl->dev.parent = parent;
|
||||
|
||||
init_rwsem(&fwctl->registration_lock);
|
||||
mutex_init(&fwctl->uctx_list_lock);
|
||||
INIT_LIST_HEAD(&fwctl->uctx_list);
|
||||
|
||||
device_initialize(&fwctl->dev);
|
||||
return_ptr(fwctl);
|
||||
}
|
||||
@@ -132,6 +245,10 @@ EXPORT_SYMBOL_NS_GPL(fwctl_register, "FWCTL");
|
||||
* Undoes fwctl_register(). On return no driver ops will be called. The
|
||||
* caller must still call fwctl_put() to free the fwctl.
|
||||
*
|
||||
* Unregister will return even if userspace still has file descriptors open.
|
||||
* This will call ops->close_uctx() on any open FDs and after return no driver
|
||||
* op will be called. The FDs remain open but all fops will return -ENODEV.
|
||||
*
|
||||
* The design of fwctl allows this sort of disassociation of the driver from the
|
||||
* subsystem primarily by keeping memory allocations owned by the core subsytem.
|
||||
* The fwctl_device and fwctl_uctx can both be freed without requiring a driver
|
||||
@@ -139,7 +256,23 @@ EXPORT_SYMBOL_NS_GPL(fwctl_register, "FWCTL");
|
||||
*/
|
||||
void fwctl_unregister(struct fwctl_device *fwctl)
|
||||
{
|
||||
struct fwctl_uctx *uctx;
|
||||
|
||||
cdev_device_del(&fwctl->cdev, &fwctl->dev);
|
||||
|
||||
/* Disable and free the driver's resources for any still open FDs. */
|
||||
guard(rwsem_write)(&fwctl->registration_lock);
|
||||
guard(mutex)(&fwctl->uctx_list_lock);
|
||||
while ((uctx = list_first_entry_or_null(&fwctl->uctx_list,
|
||||
struct fwctl_uctx,
|
||||
uctx_list_entry)))
|
||||
fwctl_destroy_uctx(uctx);
|
||||
|
||||
/*
|
||||
* The driver module may unload after this returns, the op pointer will
|
||||
* not be valid.
|
||||
*/
|
||||
fwctl->ops = NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(fwctl_unregister, "FWCTL");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user