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"); 174