Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

fwctl: Add basic structure for a class subsystem with a cdev

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>

+267
+9
MAINTAINERS
··· 9557 9557 F: tools/perf/bench/futex* 9558 9558 F: tools/testing/selftests/futex/ 9559 9559 9560 + FWCTL SUBSYSTEM 9561 + M: Dave Jiang <dave.jiang@intel.com> 9562 + M: Jason Gunthorpe <jgg@nvidia.com> 9563 + M: Saeed Mahameed <saeedm@nvidia.com> 9564 + R: Jonathan Cameron <Jonathan.Cameron@huawei.com> 9565 + S: Maintained 9566 + F: drivers/fwctl/ 9567 + F: include/linux/fwctl.h 9568 + 9560 9569 GALAXYCORE GC0308 CAMERA SENSOR DRIVER 9561 9570 M: Sebastian Reichel <sre@kernel.org> 9562 9571 L: linux-media@vger.kernel.org
+2
drivers/Kconfig
··· 21 21 22 22 source "drivers/firmware/Kconfig" 23 23 24 + source "drivers/fwctl/Kconfig" 25 + 24 26 source "drivers/gnss/Kconfig" 25 27 26 28 source "drivers/mtd/Kconfig"
+1
drivers/Makefile
··· 135 135 obj-$(CONFIG_MEMSTICK) += memstick/ 136 136 obj-$(CONFIG_INFINIBAND) += infiniband/ 137 137 obj-y += firmware/ 138 + obj-$(CONFIG_FWCTL) += fwctl/ 138 139 obj-$(CONFIG_CRYPTO) += crypto/ 139 140 obj-$(CONFIG_SUPERH) += sh/ 140 141 obj-y += clocksource/
+9
drivers/fwctl/Kconfig
··· 1 + # SPDX-License-Identifier: GPL-2.0-only 2 + menuconfig FWCTL 3 + tristate "fwctl device firmware access framework" 4 + help 5 + fwctl provides a userspace API for restricted access to communicate 6 + with on-device firmware. The communication channel is intended to 7 + support a wide range of lockdown compatible device behaviors including 8 + manipulating device FLASH, debugging, and other activities that don't 9 + fit neatly into an existing subsystem.
+4
drivers/fwctl/Makefile
··· 1 + # SPDX-License-Identifier: GPL-2.0 2 + obj-$(CONFIG_FWCTL) += fwctl.o 3 + 4 + fwctl-y += main.o
+173
drivers/fwctl/main.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* 3 + * Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES 4 + */ 5 + #define pr_fmt(fmt) "fwctl: " fmt 6 + #include <linux/fwctl.h> 7 + 8 + #include <linux/container_of.h> 9 + #include <linux/fs.h> 10 + #include <linux/module.h> 11 + #include <linux/slab.h> 12 + 13 + enum { 14 + FWCTL_MAX_DEVICES = 4096, 15 + }; 16 + static_assert(FWCTL_MAX_DEVICES < (1U << MINORBITS)); 17 + 18 + static dev_t fwctl_dev; 19 + static DEFINE_IDA(fwctl_ida); 20 + 21 + static int fwctl_fops_open(struct inode *inode, struct file *filp) 22 + { 23 + struct fwctl_device *fwctl = 24 + container_of(inode->i_cdev, struct fwctl_device, cdev); 25 + 26 + get_device(&fwctl->dev); 27 + filp->private_data = fwctl; 28 + return 0; 29 + } 30 + 31 + static int fwctl_fops_release(struct inode *inode, struct file *filp) 32 + { 33 + struct fwctl_device *fwctl = filp->private_data; 34 + 35 + fwctl_put(fwctl); 36 + return 0; 37 + } 38 + 39 + static const struct file_operations fwctl_fops = { 40 + .owner = THIS_MODULE, 41 + .open = fwctl_fops_open, 42 + .release = fwctl_fops_release, 43 + }; 44 + 45 + static void fwctl_device_release(struct device *device) 46 + { 47 + struct fwctl_device *fwctl = 48 + container_of(device, struct fwctl_device, dev); 49 + 50 + ida_free(&fwctl_ida, fwctl->dev.devt - fwctl_dev); 51 + kfree(fwctl); 52 + } 53 + 54 + static char *fwctl_devnode(const struct device *dev, umode_t *mode) 55 + { 56 + return kasprintf(GFP_KERNEL, "fwctl/%s", dev_name(dev)); 57 + } 58 + 59 + static struct class fwctl_class = { 60 + .name = "fwctl", 61 + .dev_release = fwctl_device_release, 62 + .devnode = fwctl_devnode, 63 + }; 64 + 65 + static struct fwctl_device * 66 + _alloc_device(struct device *parent, const struct fwctl_ops *ops, size_t size) 67 + { 68 + struct fwctl_device *fwctl __free(kfree) = kzalloc(size, GFP_KERNEL); 69 + int devnum; 70 + 71 + if (!fwctl) 72 + return NULL; 73 + 74 + fwctl->dev.class = &fwctl_class; 75 + fwctl->dev.parent = parent; 76 + 77 + devnum = ida_alloc_max(&fwctl_ida, FWCTL_MAX_DEVICES - 1, GFP_KERNEL); 78 + if (devnum < 0) 79 + return NULL; 80 + 81 + fwctl->dev.devt = fwctl_dev + devnum; 82 + fwctl->dev.class = &fwctl_class; 83 + fwctl->dev.parent = parent; 84 + 85 + device_initialize(&fwctl->dev); 86 + return_ptr(fwctl); 87 + } 88 + 89 + /* Drivers use the fwctl_alloc_device() wrapper */ 90 + struct fwctl_device *_fwctl_alloc_device(struct device *parent, 91 + const struct fwctl_ops *ops, 92 + size_t size) 93 + { 94 + struct fwctl_device *fwctl __free(fwctl) = 95 + _alloc_device(parent, ops, size); 96 + 97 + if (!fwctl) 98 + return NULL; 99 + 100 + cdev_init(&fwctl->cdev, &fwctl_fops); 101 + /* 102 + * The driver module is protected by fwctl_register/unregister(), 103 + * unregister won't complete until we are done with the driver's module. 104 + */ 105 + fwctl->cdev.owner = THIS_MODULE; 106 + 107 + if (dev_set_name(&fwctl->dev, "fwctl%d", fwctl->dev.devt - fwctl_dev)) 108 + return NULL; 109 + 110 + fwctl->ops = ops; 111 + return_ptr(fwctl); 112 + } 113 + EXPORT_SYMBOL_NS_GPL(_fwctl_alloc_device, "FWCTL"); 114 + 115 + /** 116 + * fwctl_register - Register a new device to the subsystem 117 + * @fwctl: Previously allocated fwctl_device 118 + * 119 + * On return the device is visible through sysfs and /dev, driver ops may be 120 + * called. 121 + */ 122 + int fwctl_register(struct fwctl_device *fwctl) 123 + { 124 + return cdev_device_add(&fwctl->cdev, &fwctl->dev); 125 + } 126 + EXPORT_SYMBOL_NS_GPL(fwctl_register, "FWCTL"); 127 + 128 + /** 129 + * fwctl_unregister - Unregister a device from the subsystem 130 + * @fwctl: Previously allocated and registered fwctl_device 131 + * 132 + * Undoes fwctl_register(). On return no driver ops will be called. The 133 + * caller must still call fwctl_put() to free the fwctl. 134 + * 135 + * The design of fwctl allows this sort of disassociation of the driver from the 136 + * subsystem primarily by keeping memory allocations owned by the core subsytem. 137 + * The fwctl_device and fwctl_uctx can both be freed without requiring a driver 138 + * callback. This allows the module to remain unlocked while FDs are open. 139 + */ 140 + void fwctl_unregister(struct fwctl_device *fwctl) 141 + { 142 + cdev_device_del(&fwctl->cdev, &fwctl->dev); 143 + } 144 + EXPORT_SYMBOL_NS_GPL(fwctl_unregister, "FWCTL"); 145 + 146 + static int __init fwctl_init(void) 147 + { 148 + int ret; 149 + 150 + ret = alloc_chrdev_region(&fwctl_dev, 0, FWCTL_MAX_DEVICES, "fwctl"); 151 + if (ret) 152 + return ret; 153 + 154 + ret = class_register(&fwctl_class); 155 + if (ret) 156 + goto err_chrdev; 157 + return 0; 158 + 159 + err_chrdev: 160 + unregister_chrdev_region(fwctl_dev, FWCTL_MAX_DEVICES); 161 + return ret; 162 + } 163 + 164 + static void __exit fwctl_exit(void) 165 + { 166 + class_unregister(&fwctl_class); 167 + unregister_chrdev_region(fwctl_dev, FWCTL_MAX_DEVICES); 168 + } 169 + 170 + module_init(fwctl_init); 171 + module_exit(fwctl_exit); 172 + MODULE_DESCRIPTION("fwctl device firmware access framework"); 173 + MODULE_LICENSE("GPL");
+69
include/linux/fwctl.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0-only */ 2 + /* 3 + * Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES 4 + */ 5 + #ifndef __LINUX_FWCTL_H 6 + #define __LINUX_FWCTL_H 7 + #include <linux/device.h> 8 + #include <linux/cdev.h> 9 + #include <linux/cleanup.h> 10 + 11 + struct fwctl_device; 12 + struct fwctl_uctx; 13 + 14 + struct fwctl_ops { 15 + }; 16 + 17 + /** 18 + * struct fwctl_device - Per-driver registration struct 19 + * @dev: The sysfs (class/fwctl/fwctlXX) device 20 + * 21 + * Each driver instance will have one of these structs with the driver private 22 + * data following immediately after. This struct is refcounted, it is freed by 23 + * calling fwctl_put(). 24 + */ 25 + struct fwctl_device { 26 + struct device dev; 27 + /* private: */ 28 + struct cdev cdev; 29 + const struct fwctl_ops *ops; 30 + }; 31 + 32 + struct fwctl_device *_fwctl_alloc_device(struct device *parent, 33 + const struct fwctl_ops *ops, 34 + size_t size); 35 + /** 36 + * fwctl_alloc_device - Allocate a fwctl 37 + * @parent: Physical device that provides the FW interface 38 + * @ops: Driver ops to register 39 + * @drv_struct: 'struct driver_fwctl' that holds the struct fwctl_device 40 + * @member: Name of the struct fwctl_device in @drv_struct 41 + * 42 + * This allocates and initializes the fwctl_device embedded in the drv_struct. 43 + * Upon success the pointer must be freed via fwctl_put(). Returns a 'drv_struct 44 + * \*' on success, NULL on error. 45 + */ 46 + #define fwctl_alloc_device(parent, ops, drv_struct, member) \ 47 + ({ \ 48 + static_assert(__same_type(struct fwctl_device, \ 49 + ((drv_struct *)NULL)->member)); \ 50 + static_assert(offsetof(drv_struct, member) == 0); \ 51 + (drv_struct *)_fwctl_alloc_device(parent, ops, \ 52 + sizeof(drv_struct)); \ 53 + }) 54 + 55 + static inline struct fwctl_device *fwctl_get(struct fwctl_device *fwctl) 56 + { 57 + get_device(&fwctl->dev); 58 + return fwctl; 59 + } 60 + static inline void fwctl_put(struct fwctl_device *fwctl) 61 + { 62 + put_device(&fwctl->dev); 63 + } 64 + DEFINE_FREE(fwctl, struct fwctl_device *, if (_T) fwctl_put(_T)); 65 + 66 + int fwctl_register(struct fwctl_device *fwctl); 67 + void fwctl_unregister(struct fwctl_device *fwctl); 68 + 69 + #endif