Skip to content

Commit 2e4986c

Browse files
committed
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>
1 parent 2014c95 commit 2e4986c

7 files changed

Lines changed: 267 additions & 0 deletions

File tree

MAINTAINERS

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9557,6 +9557,15 @@ F: kernel/futex/*
95579557
F: tools/perf/bench/futex*
95589558
F: tools/testing/selftests/futex/
95599559

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+
95609569
GALAXYCORE GC0308 CAMERA SENSOR DRIVER
95619570
M: Sebastian Reichel <sre@kernel.org>
95629571
L: linux-media@vger.kernel.org

drivers/Kconfig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ source "drivers/connector/Kconfig"
2121

2222
source "drivers/firmware/Kconfig"
2323

24+
source "drivers/fwctl/Kconfig"
25+
2426
source "drivers/gnss/Kconfig"
2527

2628
source "drivers/mtd/Kconfig"

drivers/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ obj-y += ufs/
135135
obj-$(CONFIG_MEMSTICK) += memstick/
136136
obj-$(CONFIG_INFINIBAND) += infiniband/
137137
obj-y += firmware/
138+
obj-$(CONFIG_FWCTL) += fwctl/
138139
obj-$(CONFIG_CRYPTO) += crypto/
139140
obj-$(CONFIG_SUPERH) += sh/
140141
obj-y += clocksource/

drivers/fwctl/Kconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
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.

drivers/fwctl/Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# SPDX-License-Identifier: GPL-2.0
2+
obj-$(CONFIG_FWCTL) += fwctl.o
3+
4+
fwctl-y += main.o

drivers/fwctl/main.c

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
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");

include/linux/fwctl.h

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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

0 commit comments

Comments
 (0)