mirror of
git://git.yoctoproject.org/linux-yocto.git
synced 2025-10-22 23:13:01 +02:00
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>
This commit is contained in:
parent
2014c95afe
commit
2e4986cf2d
|
@ -9557,6 +9557,15 @@ F: kernel/futex/*
|
||||||
F: tools/perf/bench/futex*
|
F: tools/perf/bench/futex*
|
||||||
F: tools/testing/selftests/futex/
|
F: tools/testing/selftests/futex/
|
||||||
|
|
||||||
|
FWCTL SUBSYSTEM
|
||||||
|
M: Dave Jiang <dave.jiang@intel.com>
|
||||||
|
M: Jason Gunthorpe <jgg@nvidia.com>
|
||||||
|
M: Saeed Mahameed <saeedm@nvidia.com>
|
||||||
|
R: Jonathan Cameron <Jonathan.Cameron@huawei.com>
|
||||||
|
S: Maintained
|
||||||
|
F: drivers/fwctl/
|
||||||
|
F: include/linux/fwctl.h
|
||||||
|
|
||||||
GALAXYCORE GC0308 CAMERA SENSOR DRIVER
|
GALAXYCORE GC0308 CAMERA SENSOR DRIVER
|
||||||
M: Sebastian Reichel <sre@kernel.org>
|
M: Sebastian Reichel <sre@kernel.org>
|
||||||
L: linux-media@vger.kernel.org
|
L: linux-media@vger.kernel.org
|
||||||
|
|
|
@ -21,6 +21,8 @@ source "drivers/connector/Kconfig"
|
||||||
|
|
||||||
source "drivers/firmware/Kconfig"
|
source "drivers/firmware/Kconfig"
|
||||||
|
|
||||||
|
source "drivers/fwctl/Kconfig"
|
||||||
|
|
||||||
source "drivers/gnss/Kconfig"
|
source "drivers/gnss/Kconfig"
|
||||||
|
|
||||||
source "drivers/mtd/Kconfig"
|
source "drivers/mtd/Kconfig"
|
||||||
|
|
|
@ -135,6 +135,7 @@ obj-y += ufs/
|
||||||
obj-$(CONFIG_MEMSTICK) += memstick/
|
obj-$(CONFIG_MEMSTICK) += memstick/
|
||||||
obj-$(CONFIG_INFINIBAND) += infiniband/
|
obj-$(CONFIG_INFINIBAND) += infiniband/
|
||||||
obj-y += firmware/
|
obj-y += firmware/
|
||||||
|
obj-$(CONFIG_FWCTL) += fwctl/
|
||||||
obj-$(CONFIG_CRYPTO) += crypto/
|
obj-$(CONFIG_CRYPTO) += crypto/
|
||||||
obj-$(CONFIG_SUPERH) += sh/
|
obj-$(CONFIG_SUPERH) += sh/
|
||||||
obj-y += clocksource/
|
obj-y += clocksource/
|
||||||
|
|
9
drivers/fwctl/Kconfig
Normal file
9
drivers/fwctl/Kconfig
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
menuconfig FWCTL
|
||||||
|
tristate "fwctl device firmware access framework"
|
||||||
|
help
|
||||||
|
fwctl provides a userspace API for restricted access to communicate
|
||||||
|
with on-device firmware. The communication channel is intended to
|
||||||
|
support a wide range of lockdown compatible device behaviors including
|
||||||
|
manipulating device FLASH, debugging, and other activities that don't
|
||||||
|
fit neatly into an existing subsystem.
|
4
drivers/fwctl/Makefile
Normal file
4
drivers/fwctl/Makefile
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
obj-$(CONFIG_FWCTL) += fwctl.o
|
||||||
|
|
||||||
|
fwctl-y += main.o
|
173
drivers/fwctl/main.c
Normal file
173
drivers/fwctl/main.c
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES
|
||||||
|
*/
|
||||||
|
#define pr_fmt(fmt) "fwctl: " fmt
|
||||||
|
#include <linux/fwctl.h>
|
||||||
|
|
||||||
|
#include <linux/container_of.h>
|
||||||
|
#include <linux/fs.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
|
||||||
|
enum {
|
||||||
|
FWCTL_MAX_DEVICES = 4096,
|
||||||
|
};
|
||||||
|
static_assert(FWCTL_MAX_DEVICES < (1U << MINORBITS));
|
||||||
|
|
||||||
|
static dev_t fwctl_dev;
|
||||||
|
static DEFINE_IDA(fwctl_ida);
|
||||||
|
|
||||||
|
static int fwctl_fops_open(struct inode *inode, struct file *filp)
|
||||||
|
{
|
||||||
|
struct fwctl_device *fwctl =
|
||||||
|
container_of(inode->i_cdev, struct fwctl_device, cdev);
|
||||||
|
|
||||||
|
get_device(&fwctl->dev);
|
||||||
|
filp->private_data = fwctl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fwctl_fops_release(struct inode *inode, struct file *filp)
|
||||||
|
{
|
||||||
|
struct fwctl_device *fwctl = filp->private_data;
|
||||||
|
|
||||||
|
fwctl_put(fwctl);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct file_operations fwctl_fops = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.open = fwctl_fops_open,
|
||||||
|
.release = fwctl_fops_release,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void fwctl_device_release(struct device *device)
|
||||||
|
{
|
||||||
|
struct fwctl_device *fwctl =
|
||||||
|
container_of(device, struct fwctl_device, dev);
|
||||||
|
|
||||||
|
ida_free(&fwctl_ida, fwctl->dev.devt - fwctl_dev);
|
||||||
|
kfree(fwctl);
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *fwctl_devnode(const struct device *dev, umode_t *mode)
|
||||||
|
{
|
||||||
|
return kasprintf(GFP_KERNEL, "fwctl/%s", dev_name(dev));
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct class fwctl_class = {
|
||||||
|
.name = "fwctl",
|
||||||
|
.dev_release = fwctl_device_release,
|
||||||
|
.devnode = fwctl_devnode,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct fwctl_device *
|
||||||
|
_alloc_device(struct device *parent, const struct fwctl_ops *ops, size_t size)
|
||||||
|
{
|
||||||
|
struct fwctl_device *fwctl __free(kfree) = kzalloc(size, GFP_KERNEL);
|
||||||
|
int devnum;
|
||||||
|
|
||||||
|
if (!fwctl)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
fwctl->dev.class = &fwctl_class;
|
||||||
|
fwctl->dev.parent = parent;
|
||||||
|
|
||||||
|
devnum = ida_alloc_max(&fwctl_ida, FWCTL_MAX_DEVICES - 1, GFP_KERNEL);
|
||||||
|
if (devnum < 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
fwctl->dev.devt = fwctl_dev + devnum;
|
||||||
|
fwctl->dev.class = &fwctl_class;
|
||||||
|
fwctl->dev.parent = parent;
|
||||||
|
|
||||||
|
device_initialize(&fwctl->dev);
|
||||||
|
return_ptr(fwctl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Drivers use the fwctl_alloc_device() wrapper */
|
||||||
|
struct fwctl_device *_fwctl_alloc_device(struct device *parent,
|
||||||
|
const struct fwctl_ops *ops,
|
||||||
|
size_t size)
|
||||||
|
{
|
||||||
|
struct fwctl_device *fwctl __free(fwctl) =
|
||||||
|
_alloc_device(parent, ops, size);
|
||||||
|
|
||||||
|
if (!fwctl)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
cdev_init(&fwctl->cdev, &fwctl_fops);
|
||||||
|
/*
|
||||||
|
* The driver module is protected by fwctl_register/unregister(),
|
||||||
|
* unregister won't complete until we are done with the driver's module.
|
||||||
|
*/
|
||||||
|
fwctl->cdev.owner = THIS_MODULE;
|
||||||
|
|
||||||
|
if (dev_set_name(&fwctl->dev, "fwctl%d", fwctl->dev.devt - fwctl_dev))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
fwctl->ops = ops;
|
||||||
|
return_ptr(fwctl);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_NS_GPL(_fwctl_alloc_device, "FWCTL");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fwctl_register - Register a new device to the subsystem
|
||||||
|
* @fwctl: Previously allocated fwctl_device
|
||||||
|
*
|
||||||
|
* On return the device is visible through sysfs and /dev, driver ops may be
|
||||||
|
* called.
|
||||||
|
*/
|
||||||
|
int fwctl_register(struct fwctl_device *fwctl)
|
||||||
|
{
|
||||||
|
return cdev_device_add(&fwctl->cdev, &fwctl->dev);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_NS_GPL(fwctl_register, "FWCTL");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fwctl_unregister - Unregister a device from the subsystem
|
||||||
|
* @fwctl: Previously allocated and registered fwctl_device
|
||||||
|
*
|
||||||
|
* Undoes fwctl_register(). On return no driver ops will be called. The
|
||||||
|
* caller must still call fwctl_put() to free the fwctl.
|
||||||
|
*
|
||||||
|
* The design of fwctl allows this sort of disassociation of the driver from the
|
||||||
|
* subsystem primarily by keeping memory allocations owned by the core subsytem.
|
||||||
|
* The fwctl_device and fwctl_uctx can both be freed without requiring a driver
|
||||||
|
* callback. This allows the module to remain unlocked while FDs are open.
|
||||||
|
*/
|
||||||
|
void fwctl_unregister(struct fwctl_device *fwctl)
|
||||||
|
{
|
||||||
|
cdev_device_del(&fwctl->cdev, &fwctl->dev);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_NS_GPL(fwctl_unregister, "FWCTL");
|
||||||
|
|
||||||
|
static int __init fwctl_init(void)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = alloc_chrdev_region(&fwctl_dev, 0, FWCTL_MAX_DEVICES, "fwctl");
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = class_register(&fwctl_class);
|
||||||
|
if (ret)
|
||||||
|
goto err_chrdev;
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err_chrdev:
|
||||||
|
unregister_chrdev_region(fwctl_dev, FWCTL_MAX_DEVICES);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __exit fwctl_exit(void)
|
||||||
|
{
|
||||||
|
class_unregister(&fwctl_class);
|
||||||
|
unregister_chrdev_region(fwctl_dev, FWCTL_MAX_DEVICES);
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(fwctl_init);
|
||||||
|
module_exit(fwctl_exit);
|
||||||
|
MODULE_DESCRIPTION("fwctl device firmware access framework");
|
||||||
|
MODULE_LICENSE("GPL");
|
69
include/linux/fwctl.h
Normal file
69
include/linux/fwctl.h
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES
|
||||||
|
*/
|
||||||
|
#ifndef __LINUX_FWCTL_H
|
||||||
|
#define __LINUX_FWCTL_H
|
||||||
|
#include <linux/device.h>
|
||||||
|
#include <linux/cdev.h>
|
||||||
|
#include <linux/cleanup.h>
|
||||||
|
|
||||||
|
struct fwctl_device;
|
||||||
|
struct fwctl_uctx;
|
||||||
|
|
||||||
|
struct fwctl_ops {
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct fwctl_device - Per-driver registration struct
|
||||||
|
* @dev: The sysfs (class/fwctl/fwctlXX) device
|
||||||
|
*
|
||||||
|
* Each driver instance will have one of these structs with the driver private
|
||||||
|
* data following immediately after. This struct is refcounted, it is freed by
|
||||||
|
* calling fwctl_put().
|
||||||
|
*/
|
||||||
|
struct fwctl_device {
|
||||||
|
struct device dev;
|
||||||
|
/* private: */
|
||||||
|
struct cdev cdev;
|
||||||
|
const struct fwctl_ops *ops;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct fwctl_device *_fwctl_alloc_device(struct device *parent,
|
||||||
|
const struct fwctl_ops *ops,
|
||||||
|
size_t size);
|
||||||
|
/**
|
||||||
|
* fwctl_alloc_device - Allocate a fwctl
|
||||||
|
* @parent: Physical device that provides the FW interface
|
||||||
|
* @ops: Driver ops to register
|
||||||
|
* @drv_struct: 'struct driver_fwctl' that holds the struct fwctl_device
|
||||||
|
* @member: Name of the struct fwctl_device in @drv_struct
|
||||||
|
*
|
||||||
|
* This allocates and initializes the fwctl_device embedded in the drv_struct.
|
||||||
|
* Upon success the pointer must be freed via fwctl_put(). Returns a 'drv_struct
|
||||||
|
* \*' on success, NULL on error.
|
||||||
|
*/
|
||||||
|
#define fwctl_alloc_device(parent, ops, drv_struct, member) \
|
||||||
|
({ \
|
||||||
|
static_assert(__same_type(struct fwctl_device, \
|
||||||
|
((drv_struct *)NULL)->member)); \
|
||||||
|
static_assert(offsetof(drv_struct, member) == 0); \
|
||||||
|
(drv_struct *)_fwctl_alloc_device(parent, ops, \
|
||||||
|
sizeof(drv_struct)); \
|
||||||
|
})
|
||||||
|
|
||||||
|
static inline struct fwctl_device *fwctl_get(struct fwctl_device *fwctl)
|
||||||
|
{
|
||||||
|
get_device(&fwctl->dev);
|
||||||
|
return fwctl;
|
||||||
|
}
|
||||||
|
static inline void fwctl_put(struct fwctl_device *fwctl)
|
||||||
|
{
|
||||||
|
put_device(&fwctl->dev);
|
||||||
|
}
|
||||||
|
DEFINE_FREE(fwctl, struct fwctl_device *, if (_T) fwctl_put(_T));
|
||||||
|
|
||||||
|
int fwctl_register(struct fwctl_device *fwctl);
|
||||||
|
void fwctl_unregister(struct fwctl_device *fwctl);
|
||||||
|
|
||||||
|
#endif
|
Loading…
Reference in New Issue
Block a user