fwctl: Add basic structure for a class subsystem with a cdev
authorJason Gunthorpe <jgg@nvidia.com>
Fri, 28 Feb 2025 00:26:29 +0000 (20:26 -0400)
committerJason Gunthorpe <jgg@nvidia.com>
Thu, 6 Mar 2025 19:11:30 +0000 (15:11 -0400)
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.

Link: https://patch.msgid.link/r/1-v5-642aa0c94070+4447f-fwctl_jgg@nvidia.com
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Reviewed-by: Dan Williams <dan.j.williams@intel.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>
MAINTAINERS
drivers/Kconfig
drivers/Makefile
drivers/fwctl/Kconfig [new file with mode: 0644]
drivers/fwctl/Makefile [new file with mode: 0644]
drivers/fwctl/main.c [new file with mode: 0644]
include/linux/fwctl.h [new file with mode: 0644]

index 896a307fa06545e2861abe46ea7029f9b4d3628e..a05b7ac4f0cb75b874e53872ad2bdd4f93fbb4c8 100644 (file)
@@ -9557,6 +9557,15 @@ F:       kernel/futex/*
 F:     tools/perf/bench/futex*
 F:     tools/testing/selftests/futex/
 
+FWCTL SUBSYSTEM
+M:     Dave Jiang <dave.jiang@intel.com>
+M:     Jason Gunthorpe <jgg@nvidia.com>
+M:     Saeed Mahameed <saeedm@nvidia.com>
+R:     Jonathan Cameron <Jonathan.Cameron@huawei.com>
+S:     Maintained
+F:     drivers/fwctl/
+F:     include/linux/fwctl.h
+
 GALAXYCORE GC0308 CAMERA SENSOR DRIVER
 M:     Sebastian Reichel <sre@kernel.org>
 L:     linux-media@vger.kernel.org
index 7bdad836fc6207727300e79c2d6f7db485baf80a..7c556c5ac4fddca16a47532d524f738e8e3f6a75 100644 (file)
@@ -21,6 +21,8 @@ source "drivers/connector/Kconfig"
 
 source "drivers/firmware/Kconfig"
 
+source "drivers/fwctl/Kconfig"
+
 source "drivers/gnss/Kconfig"
 
 source "drivers/mtd/Kconfig"
index 45d1c3e630f754672deb5baf1491e57f4ca830d3..b5749cf67044ce1963ad3314090643460f5cf2b3 100644 (file)
@@ -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 (file)
index 0000000..37147a6
--- /dev/null
@@ -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 (file)
index 0000000..1cad210
--- /dev/null
@@ -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 (file)
index 0000000..76c4ede
--- /dev/null
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2024-2025, 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;
+       fwctl->dev.class = &fwctl_class;
+       fwctl->dev.parent = parent;
+
+       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 (file)
index 0000000..39d5059
--- /dev/null
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2024-2025, 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