On 2/6/25 5:13 PM, Jason Gunthorpe wrote: > Create the class, character device and functions for a fwctl driver to > un/register to the subsystem. > > A typical fwctl driver has a sysfs presence like: > > $ ls -l /dev/fwctl/fwctl0 > crw------- 1 root root 250, 0 Apr 25 19:16 /dev/fwctl/fwctl0 > > $ ls /sys/class/fwctl/fwctl0 > dev device power subsystem uevent > > $ ls /sys/class/fwctl/fwctl0/device/infiniband/ > ibp0s10f0 > > $ ls /sys/class/infiniband/ibp0s10f0/device/fwctl/ > fwctl0/ > > $ ls /sys/devices/pci0000:00/0000:00:0a.0/fwctl/fwctl0 > dev device power subsystem uevent > > Which allows userspace to link all the multi-subsystem driver components > together and learn the subsystem specific names for the device's > components. > > Reviewed-by: Jonathan Cameron <Jonathan.Cameron@xxxxxxxxxx> > Signed-off-by: Jason Gunthorpe <jgg@xxxxxxxxxx> Reviewed-by: Dave Jiang <dave.jiang@xxxxxxxxx> > --- > MAINTAINERS | 8 ++ > drivers/Kconfig | 2 + > drivers/Makefile | 1 + > drivers/fwctl/Kconfig | 9 +++ > drivers/fwctl/Makefile | 4 + > drivers/fwctl/main.c | 170 +++++++++++++++++++++++++++++++++++++++++ > include/linux/fwctl.h | 69 +++++++++++++++++ > 7 files changed, 263 insertions(+) > create mode 100644 drivers/fwctl/Kconfig > create mode 100644 drivers/fwctl/Makefile > create mode 100644 drivers/fwctl/main.c > create mode 100644 include/linux/fwctl.h > > diff --git a/MAINTAINERS b/MAINTAINERS > index 896a307fa06545..ff418a77f39e4d 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -9557,6 +9557,14 @@ F: kernel/futex/* > F: tools/perf/bench/futex* > F: tools/testing/selftests/futex/ > > +FWCTL SUBSYSTEM > +M: Jason Gunthorpe <jgg@xxxxxxxxxx> > +M: Saeed Mahameed <saeedm@xxxxxxxxxx> > +S: Maintained > +F: Documentation/userspace-api/fwctl.rst > +F: drivers/fwctl/ > +F: include/linux/fwctl.h > + > GALAXYCORE GC0308 CAMERA SENSOR DRIVER > M: Sebastian Reichel <sre@xxxxxxxxxx> > L: linux-media@xxxxxxxxxxxxxxx > diff --git a/drivers/Kconfig b/drivers/Kconfig > index 7bdad836fc6207..7c556c5ac4fddc 100644 > --- a/drivers/Kconfig > +++ b/drivers/Kconfig > @@ -21,6 +21,8 @@ source "drivers/connector/Kconfig" > > source "drivers/firmware/Kconfig" > > +source "drivers/fwctl/Kconfig" > + > source "drivers/gnss/Kconfig" > > source "drivers/mtd/Kconfig" > diff --git a/drivers/Makefile b/drivers/Makefile > index 45d1c3e630f754..b5749cf67044ce 100644 > --- a/drivers/Makefile > +++ b/drivers/Makefile > @@ -135,6 +135,7 @@ obj-y += ufs/ > obj-$(CONFIG_MEMSTICK) += memstick/ > obj-$(CONFIG_INFINIBAND) += infiniband/ > obj-y += firmware/ > +obj-$(CONFIG_FWCTL) += fwctl/ > obj-$(CONFIG_CRYPTO) += crypto/ > obj-$(CONFIG_SUPERH) += sh/ > obj-y += clocksource/ > diff --git a/drivers/fwctl/Kconfig b/drivers/fwctl/Kconfig > new file mode 100644 > index 00000000000000..37147a695add9a > --- /dev/null > +++ b/drivers/fwctl/Kconfig > @@ -0,0 +1,9 @@ > +# SPDX-License-Identifier: GPL-2.0-only > +menuconfig FWCTL > + tristate "fwctl device firmware access framework" > + help > + fwctl provides a userspace API for restricted access to communicate > + with on-device firmware. The communication channel is intended to > + support a wide range of lockdown compatible device behaviors including > + manipulating device FLASH, debugging, and other activities that don't > + fit neatly into an existing subsystem. > diff --git a/drivers/fwctl/Makefile b/drivers/fwctl/Makefile > new file mode 100644 > index 00000000000000..1cad210f6ba580 > --- /dev/null > +++ b/drivers/fwctl/Makefile > @@ -0,0 +1,4 @@ > +# SPDX-License-Identifier: GPL-2.0 > +obj-$(CONFIG_FWCTL) += fwctl.o > + > +fwctl-y += main.o > diff --git a/drivers/fwctl/main.c b/drivers/fwctl/main.c > new file mode 100644 > index 00000000000000..34946bdc3bf3d7 > --- /dev/null > +++ b/drivers/fwctl/main.c > @@ -0,0 +1,170 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Copyright (c) 2024, NVIDIA CORPORATION & AFFILIATES > + */ > +#define pr_fmt(fmt) "fwctl: " fmt > +#include <linux/fwctl.h> > + > +#include <linux/container_of.h> > +#include <linux/fs.h> > +#include <linux/module.h> > +#include <linux/slab.h> > + > +enum { > + FWCTL_MAX_DEVICES = 4096, > +}; > +static_assert(FWCTL_MAX_DEVICES < (1U << MINORBITS)); > + > +static dev_t fwctl_dev; > +static DEFINE_IDA(fwctl_ida); > + > +static int fwctl_fops_open(struct inode *inode, struct file *filp) > +{ > + struct fwctl_device *fwctl = > + container_of(inode->i_cdev, struct fwctl_device, cdev); > + > + get_device(&fwctl->dev); > + filp->private_data = fwctl; > + return 0; > +} > + > +static int fwctl_fops_release(struct inode *inode, struct file *filp) > +{ > + struct fwctl_device *fwctl = filp->private_data; > + > + fwctl_put(fwctl); > + return 0; > +} > + > +static const struct file_operations fwctl_fops = { > + .owner = THIS_MODULE, > + .open = fwctl_fops_open, > + .release = fwctl_fops_release, > +}; > + > +static void fwctl_device_release(struct device *device) > +{ > + struct fwctl_device *fwctl = > + container_of(device, struct fwctl_device, dev); > + > + ida_free(&fwctl_ida, fwctl->dev.devt - fwctl_dev); > + kfree(fwctl); > +} > + > +static char *fwctl_devnode(const struct device *dev, umode_t *mode) > +{ > + return kasprintf(GFP_KERNEL, "fwctl/%s", dev_name(dev)); > +} > + > +static struct class fwctl_class = { > + .name = "fwctl", > + .dev_release = fwctl_device_release, > + .devnode = fwctl_devnode, > +}; > + > +static struct fwctl_device * > +_alloc_device(struct device *parent, const struct fwctl_ops *ops, size_t size) > +{ > + struct fwctl_device *fwctl __free(kfree) = kzalloc(size, GFP_KERNEL); > + int devnum; > + > + 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; > + fwctl->dev.devt = fwctl_dev + devnum; > + > + device_initialize(&fwctl->dev); > + return_ptr(fwctl); > +} > + > +/* Drivers use the fwctl_alloc_device() wrapper */ > +struct fwctl_device *_fwctl_alloc_device(struct device *parent, > + const struct fwctl_ops *ops, > + size_t size) > +{ > + struct fwctl_device *fwctl __free(fwctl) = > + _alloc_device(parent, ops, size); > + > + if (!fwctl) > + return NULL; > + > + cdev_init(&fwctl->cdev, &fwctl_fops); > + /* > + * The driver module is protected by fwctl_register/unregister(), > + * unregister won't complete until we are done with the driver's module. > + */ > + fwctl->cdev.owner = THIS_MODULE; > + > + if (dev_set_name(&fwctl->dev, "fwctl%d", fwctl->dev.devt - fwctl_dev)) > + return NULL; > + > + fwctl->ops = ops; > + return_ptr(fwctl); > +} > +EXPORT_SYMBOL_NS_GPL(_fwctl_alloc_device, "FWCTL"); > + > +/** > + * fwctl_register - Register a new device to the subsystem > + * @fwctl: Previously allocated fwctl_device > + * > + * On return the device is visible through sysfs and /dev, driver ops may be > + * called. > + */ > +int fwctl_register(struct fwctl_device *fwctl) > +{ > + return cdev_device_add(&fwctl->cdev, &fwctl->dev); > +} > +EXPORT_SYMBOL_NS_GPL(fwctl_register, "FWCTL"); > + > +/** > + * fwctl_unregister - Unregister a device from the subsystem > + * @fwctl: Previously allocated and registered fwctl_device > + * > + * Undoes fwctl_register(). On return no driver ops will be called. The > + * caller must still call fwctl_put() to free the fwctl. > + * > + * 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 > + * callback. This allows the module to remain unlocked while FDs are open. > + */ > +void fwctl_unregister(struct fwctl_device *fwctl) > +{ > + cdev_device_del(&fwctl->cdev, &fwctl->dev); > +} > +EXPORT_SYMBOL_NS_GPL(fwctl_unregister, "FWCTL"); > + > +static int __init fwctl_init(void) > +{ > + int ret; > + > + ret = alloc_chrdev_region(&fwctl_dev, 0, FWCTL_MAX_DEVICES, "fwctl"); > + if (ret) > + return ret; > + > + ret = class_register(&fwctl_class); > + if (ret) > + goto err_chrdev; > + return 0; > + > +err_chrdev: > + unregister_chrdev_region(fwctl_dev, FWCTL_MAX_DEVICES); > + return ret; > +} > + > +static void __exit fwctl_exit(void) > +{ > + class_unregister(&fwctl_class); > + unregister_chrdev_region(fwctl_dev, FWCTL_MAX_DEVICES); > +} > + > +module_init(fwctl_init); > +module_exit(fwctl_exit); > +MODULE_DESCRIPTION("fwctl device firmware access framework"); > +MODULE_LICENSE("GPL"); > diff --git a/include/linux/fwctl.h b/include/linux/fwctl.h > new file mode 100644 > index 00000000000000..68ac2d5ab87481 > --- /dev/null > +++ b/include/linux/fwctl.h > @@ -0,0 +1,69 @@ > +/* SPDX-License-Identifier: GPL-2.0-only */ > +/* > + * Copyright (c) 2024, NVIDIA CORPORATION & AFFILIATES > + */ > +#ifndef __LINUX_FWCTL_H > +#define __LINUX_FWCTL_H > +#include <linux/device.h> > +#include <linux/cdev.h> > +#include <linux/cleanup.h> > + > +struct fwctl_device; > +struct fwctl_uctx; > + > +struct fwctl_ops { > +}; > + > +/** > + * struct fwctl_device - Per-driver registration struct > + * @dev: The sysfs (class/fwctl/fwctlXX) device > + * > + * Each driver instance will have one of these structs with the driver private > + * data following immediately after. This struct is refcounted, it is freed by > + * calling fwctl_put(). > + */ > +struct fwctl_device { > + struct device dev; > + /* private: */ > + struct cdev cdev; > + const struct fwctl_ops *ops; > +}; > + > +struct fwctl_device *_fwctl_alloc_device(struct device *parent, > + const struct fwctl_ops *ops, > + size_t size); > +/** > + * fwctl_alloc_device - Allocate a fwctl > + * @parent: Physical device that provides the FW interface > + * @ops: Driver ops to register > + * @drv_struct: 'struct driver_fwctl' that holds the struct fwctl_device > + * @member: Name of the struct fwctl_device in @drv_struct > + * > + * This allocates and initializes the fwctl_device embedded in the drv_struct. > + * Upon success the pointer must be freed via fwctl_put(). Returns a 'drv_struct > + * \*' on success, NULL on error. > + */ > +#define fwctl_alloc_device(parent, ops, drv_struct, member) \ > + ({ \ > + static_assert(__same_type(struct fwctl_device, \ > + ((drv_struct *)NULL)->member)); \ > + static_assert(offsetof(drv_struct, member) == 0); \ > + (drv_struct *)_fwctl_alloc_device(parent, ops, \ > + sizeof(drv_struct)); \ > + }) > + > +static inline struct fwctl_device *fwctl_get(struct fwctl_device *fwctl) > +{ > + get_device(&fwctl->dev); > + return fwctl; > +} > +static inline void fwctl_put(struct fwctl_device *fwctl) > +{ > + put_device(&fwctl->dev); > +} > +DEFINE_FREE(fwctl, struct fwctl_device *, if (_T) fwctl_put(_T)); > + > +int fwctl_register(struct fwctl_device *fwctl); > +void fwctl_unregister(struct fwctl_device *fwctl); > + > +#endif