Skip to content

Commit 0e79a47

Browse files
committed
fwctl: Basic ioctl dispatch for the character device
Each file descriptor gets a chunk of per-FD driver specific context that allows the driver to attach a device specific struct to. The core code takes care of the memory lifetime for this structure. The ioctl dispatch and design is based on what was built for iommufd. The ioctls have a struct which has a combined in/out behavior with a typical 'zero pad' scheme for future extension and backwards compatibility. Like iommufd some shared logic does most of the ioctl marshaling and compatibility work and table dispatches to some function pointers for each unique ioctl. This approach has proven to work quite well in the iommufd and rdma subsystems. Allocate an ioctl number space for the subsystem. Link: https://patch.msgid.link/r/2-v5-642aa0c94070+4447f-fwctl_jgg@nvidia.com Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.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 2e4986c commit 0e79a47

5 files changed

Lines changed: 224 additions & 5 deletions

File tree

Documentation/userspace-api/ioctl/ioctl-number.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,7 @@ Code Seq# Include File Comments
331331
0x97 00-7F fs/ceph/ioctl.h Ceph file system
332332
0x99 00-0F 537-Addinboard driver
333333
<mailto:buk@buks.ipn.de>
334+
0x9A 00-0F include/uapi/fwctl/fwctl.h
334335
0xA0 all linux/sdp/sdp.h Industrial Device Project
335336
<mailto:kenji@bitgate.com>
336337
0xA1 0 linux/vtpm_proxy.h TPM Emulator Proxy Driver

MAINTAINERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9565,6 +9565,7 @@ R: Jonathan Cameron <Jonathan.Cameron@huawei.com>
95659565
S: Maintained
95669566
F: drivers/fwctl/
95679567
F: include/linux/fwctl.h
9568+
F: include/uapi/fwctl/
95689569

95699570
GALAXYCORE GC0308 CAMERA SENSOR DRIVER
95709571
M: Sebastian Reichel <sre@kernel.org>

drivers/fwctl/main.c

Lines changed: 138 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
#include <linux/module.h>
1111
#include <linux/slab.h>
1212

13+
#include <uapi/fwctl/fwctl.h>
14+
1315
enum {
1416
FWCTL_MAX_DEVICES = 4096,
1517
};
@@ -18,20 +20,128 @@ static_assert(FWCTL_MAX_DEVICES < (1U << MINORBITS));
1820
static dev_t fwctl_dev;
1921
static DEFINE_IDA(fwctl_ida);
2022

23+
struct fwctl_ucmd {
24+
struct fwctl_uctx *uctx;
25+
void __user *ubuffer;
26+
void *cmd;
27+
u32 user_size;
28+
};
29+
30+
/* On stack memory for the ioctl structs */
31+
union fwctl_ucmd_buffer {
32+
};
33+
34+
struct fwctl_ioctl_op {
35+
unsigned int size;
36+
unsigned int min_size;
37+
unsigned int ioctl_num;
38+
int (*execute)(struct fwctl_ucmd *ucmd);
39+
};
40+
41+
#define IOCTL_OP(_ioctl, _fn, _struct, _last) \
42+
[_IOC_NR(_ioctl) - FWCTL_CMD_BASE] = { \
43+
.size = sizeof(_struct) + \
44+
BUILD_BUG_ON_ZERO(sizeof(union fwctl_ucmd_buffer) < \
45+
sizeof(_struct)), \
46+
.min_size = offsetofend(_struct, _last), \
47+
.ioctl_num = _ioctl, \
48+
.execute = _fn, \
49+
}
50+
static const struct fwctl_ioctl_op fwctl_ioctl_ops[] = {
51+
};
52+
53+
static long fwctl_fops_ioctl(struct file *filp, unsigned int cmd,
54+
unsigned long arg)
55+
{
56+
struct fwctl_uctx *uctx = filp->private_data;
57+
const struct fwctl_ioctl_op *op;
58+
struct fwctl_ucmd ucmd = {};
59+
union fwctl_ucmd_buffer buf;
60+
unsigned int nr;
61+
int ret;
62+
63+
nr = _IOC_NR(cmd);
64+
if ((nr - FWCTL_CMD_BASE) >= ARRAY_SIZE(fwctl_ioctl_ops))
65+
return -ENOIOCTLCMD;
66+
67+
op = &fwctl_ioctl_ops[nr - FWCTL_CMD_BASE];
68+
if (op->ioctl_num != cmd)
69+
return -ENOIOCTLCMD;
70+
71+
ucmd.uctx = uctx;
72+
ucmd.cmd = &buf;
73+
ucmd.ubuffer = (void __user *)arg;
74+
ret = get_user(ucmd.user_size, (u32 __user *)ucmd.ubuffer);
75+
if (ret)
76+
return ret;
77+
78+
if (ucmd.user_size < op->min_size)
79+
return -EINVAL;
80+
81+
ret = copy_struct_from_user(ucmd.cmd, op->size, ucmd.ubuffer,
82+
ucmd.user_size);
83+
if (ret)
84+
return ret;
85+
86+
guard(rwsem_read)(&uctx->fwctl->registration_lock);
87+
if (!uctx->fwctl->ops)
88+
return -ENODEV;
89+
return op->execute(&ucmd);
90+
}
91+
2192
static int fwctl_fops_open(struct inode *inode, struct file *filp)
2293
{
2394
struct fwctl_device *fwctl =
2495
container_of(inode->i_cdev, struct fwctl_device, cdev);
96+
int ret;
97+
98+
guard(rwsem_read)(&fwctl->registration_lock);
99+
if (!fwctl->ops)
100+
return -ENODEV;
101+
102+
struct fwctl_uctx *uctx __free(kfree) =
103+
kzalloc(fwctl->ops->uctx_size, GFP_KERNEL_ACCOUNT);
104+
if (!uctx)
105+
return -ENOMEM;
106+
107+
uctx->fwctl = fwctl;
108+
ret = fwctl->ops->open_uctx(uctx);
109+
if (ret)
110+
return ret;
111+
112+
scoped_guard(mutex, &fwctl->uctx_list_lock) {
113+
list_add_tail(&uctx->uctx_list_entry, &fwctl->uctx_list);
114+
}
25115

26116
get_device(&fwctl->dev);
27-
filp->private_data = fwctl;
117+
filp->private_data = no_free_ptr(uctx);
28118
return 0;
29119
}
30120

121+
static void fwctl_destroy_uctx(struct fwctl_uctx *uctx)
122+
{
123+
lockdep_assert_held(&uctx->fwctl->uctx_list_lock);
124+
list_del(&uctx->uctx_list_entry);
125+
uctx->fwctl->ops->close_uctx(uctx);
126+
}
127+
31128
static int fwctl_fops_release(struct inode *inode, struct file *filp)
32129
{
33-
struct fwctl_device *fwctl = filp->private_data;
130+
struct fwctl_uctx *uctx = filp->private_data;
131+
struct fwctl_device *fwctl = uctx->fwctl;
34132

133+
scoped_guard(rwsem_read, &fwctl->registration_lock) {
134+
/*
135+
* NULL ops means fwctl_unregister() has already removed the
136+
* driver and destroyed the uctx.
137+
*/
138+
if (fwctl->ops) {
139+
guard(mutex)(&fwctl->uctx_list_lock);
140+
fwctl_destroy_uctx(uctx);
141+
}
142+
}
143+
144+
kfree(uctx);
35145
fwctl_put(fwctl);
36146
return 0;
37147
}
@@ -40,6 +150,7 @@ static const struct file_operations fwctl_fops = {
40150
.owner = THIS_MODULE,
41151
.open = fwctl_fops_open,
42152
.release = fwctl_fops_release,
153+
.unlocked_ioctl = fwctl_fops_ioctl,
43154
};
44155

45156
static void fwctl_device_release(struct device *device)
@@ -48,6 +159,7 @@ static void fwctl_device_release(struct device *device)
48159
container_of(device, struct fwctl_device, dev);
49160

50161
ida_free(&fwctl_ida, fwctl->dev.devt - fwctl_dev);
162+
mutex_destroy(&fwctl->uctx_list_lock);
51163
kfree(fwctl);
52164
}
53165

@@ -71,9 +183,6 @@ _alloc_device(struct device *parent, const struct fwctl_ops *ops, size_t size)
71183
if (!fwctl)
72184
return NULL;
73185

74-
fwctl->dev.class = &fwctl_class;
75-
fwctl->dev.parent = parent;
76-
77186
devnum = ida_alloc_max(&fwctl_ida, FWCTL_MAX_DEVICES - 1, GFP_KERNEL);
78187
if (devnum < 0)
79188
return NULL;
@@ -82,6 +191,10 @@ _alloc_device(struct device *parent, const struct fwctl_ops *ops, size_t size)
82191
fwctl->dev.class = &fwctl_class;
83192
fwctl->dev.parent = parent;
84193

194+
init_rwsem(&fwctl->registration_lock);
195+
mutex_init(&fwctl->uctx_list_lock);
196+
INIT_LIST_HEAD(&fwctl->uctx_list);
197+
85198
device_initialize(&fwctl->dev);
86199
return_ptr(fwctl);
87200
}
@@ -132,14 +245,34 @@ EXPORT_SYMBOL_NS_GPL(fwctl_register, "FWCTL");
132245
* Undoes fwctl_register(). On return no driver ops will be called. The
133246
* caller must still call fwctl_put() to free the fwctl.
134247
*
248+
* Unregister will return even if userspace still has file descriptors open.
249+
* This will call ops->close_uctx() on any open FDs and after return no driver
250+
* op will be called. The FDs remain open but all fops will return -ENODEV.
251+
*
135252
* The design of fwctl allows this sort of disassociation of the driver from the
136253
* subsystem primarily by keeping memory allocations owned by the core subsytem.
137254
* The fwctl_device and fwctl_uctx can both be freed without requiring a driver
138255
* callback. This allows the module to remain unlocked while FDs are open.
139256
*/
140257
void fwctl_unregister(struct fwctl_device *fwctl)
141258
{
259+
struct fwctl_uctx *uctx;
260+
142261
cdev_device_del(&fwctl->cdev, &fwctl->dev);
262+
263+
/* Disable and free the driver's resources for any still open FDs. */
264+
guard(rwsem_write)(&fwctl->registration_lock);
265+
guard(mutex)(&fwctl->uctx_list_lock);
266+
while ((uctx = list_first_entry_or_null(&fwctl->uctx_list,
267+
struct fwctl_uctx,
268+
uctx_list_entry)))
269+
fwctl_destroy_uctx(uctx);
270+
271+
/*
272+
* The driver module may unload after this returns, the op pointer will
273+
* not be valid.
274+
*/
275+
fwctl->ops = NULL;
143276
}
144277
EXPORT_SYMBOL_NS_GPL(fwctl_unregister, "FWCTL");
145278

include/linux/fwctl.h

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,30 @@
1111
struct fwctl_device;
1212
struct fwctl_uctx;
1313

14+
/**
15+
* struct fwctl_ops - Driver provided operations
16+
*
17+
* fwctl_unregister() will wait until all excuting ops are completed before it
18+
* returns. Drivers should be mindful to not let their ops run for too long as
19+
* it will block device hot unplug and module unloading.
20+
*/
1421
struct fwctl_ops {
22+
/**
23+
* @uctx_size: The size of the fwctl_uctx struct to allocate. The first
24+
* bytes of this memory will be a fwctl_uctx. The driver can use the
25+
* remaining bytes as its private memory.
26+
*/
27+
size_t uctx_size;
28+
/**
29+
* @open_uctx: Called when a file descriptor is opened before the uctx
30+
* is ever used.
31+
*/
32+
int (*open_uctx)(struct fwctl_uctx *uctx);
33+
/**
34+
* @close_uctx: Called when the uctx is destroyed, usually when the FD
35+
* is closed.
36+
*/
37+
void (*close_uctx)(struct fwctl_uctx *uctx);
1538
};
1639

1740
/**
@@ -26,6 +49,15 @@ struct fwctl_device {
2649
struct device dev;
2750
/* private: */
2851
struct cdev cdev;
52+
53+
/* Protect uctx_list */
54+
struct mutex uctx_list_lock;
55+
struct list_head uctx_list;
56+
/*
57+
* Protect ops, held for write when ops becomes NULL during unregister,
58+
* held for read whenever ops is loaded or an ops function is running.
59+
*/
60+
struct rw_semaphore registration_lock;
2961
const struct fwctl_ops *ops;
3062
};
3163

@@ -66,4 +98,18 @@ DEFINE_FREE(fwctl, struct fwctl_device *, if (_T) fwctl_put(_T));
6698
int fwctl_register(struct fwctl_device *fwctl);
6799
void fwctl_unregister(struct fwctl_device *fwctl);
68100

101+
/**
102+
* struct fwctl_uctx - Per user FD context
103+
* @fwctl: fwctl instance that owns the context
104+
*
105+
* Every FD opened by userspace will get a unique context allocation. Any driver
106+
* private data will follow immediately after.
107+
*/
108+
struct fwctl_uctx {
109+
struct fwctl_device *fwctl;
110+
/* private: */
111+
/* Head at fwctl_device::uctx_list */
112+
struct list_head uctx_list_entry;
113+
};
114+
69115
#endif

include/uapi/fwctl/fwctl.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
2+
/* Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES.
3+
*/
4+
#ifndef _UAPI_FWCTL_H
5+
#define _UAPI_FWCTL_H
6+
7+
#define FWCTL_TYPE 0x9A
8+
9+
/**
10+
* DOC: General ioctl format
11+
*
12+
* The ioctl interface follows a general format to allow for extensibility. Each
13+
* ioctl is passed a structure pointer as the argument providing the size of
14+
* the structure in the first u32. The kernel checks that any structure space
15+
* beyond what it understands is 0. This allows userspace to use the backward
16+
* compatible portion while consistently using the newer, larger, structures.
17+
*
18+
* ioctls use a standard meaning for common errnos:
19+
*
20+
* - ENOTTY: The IOCTL number itself is not supported at all
21+
* - E2BIG: The IOCTL number is supported, but the provided structure has
22+
* non-zero in a part the kernel does not understand.
23+
* - EOPNOTSUPP: The IOCTL number is supported, and the structure is
24+
* understood, however a known field has a value the kernel does not
25+
* understand or support.
26+
* - EINVAL: Everything about the IOCTL was understood, but a field is not
27+
* correct.
28+
* - ENOMEM: Out of memory.
29+
* - ENODEV: The underlying device has been hot-unplugged and the FD is
30+
* orphaned.
31+
*
32+
* As well as additional errnos, within specific ioctls.
33+
*/
34+
enum {
35+
FWCTL_CMD_BASE = 0,
36+
};
37+
38+
#endif

0 commit comments

Comments
 (0)