xref: /linux/drivers/fwctl/main.c (revision 2e4986cf2d525eed3a240b7821f89ca45cf36d78)
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