From: Jiri Pirko <jiri@resnulli.us>
To: linux-coco@lists.linux.dev
Cc: linux-kernel@vger.kernel.org, linux-api@vger.kernel.org,
x86@kernel.org, kvm@vger.kernel.org, linux-doc@vger.kernel.org,
jgg@ziepe.ca, david.quigley@intel.com, lukas@wunner.de,
ksinyuk@kernel.org, rodrigo.vivi@intel.com,
cedric.xing@intel.com, sameo@meta.com, drdeeglaze@gmail.com,
James.Bottomley@HansenPartnership.com, aik@amd.com,
jpiotrowski@nvidia.com, qinkun@google.com, pgonda@google.com,
jic23@kernel.org, alistair.francis@wdc.com, ankita@nvidia.com,
suzuki.poulose@arm.com, kas@kernel.org,
rick.p.edgecombe@intel.com, elena.reshetova@intel.com,
dave.hansen@linux.intel.com,
sathyanarayanan.kuppuswamy@linux.intel.com, peter.fang@intel.com,
jarkko@kernel.org, zohar@linux.ibm.com, yeoreum.yun@arm.com,
aneesh.kumar@kernel.org, dave.jiang@intel.com,
nik.borisov@suse.com, sergii.dmytruk@3mdeb.com
Subject: [PATCH RFC v2 12/15] ctlv: Add character device nodes
Date: Fri, 13 Nov 2026 16:55:11 +0100 [thread overview]
Message-ID: <20261113155515.2510194-13-jiri@resnulli.us> (raw)
In-Reply-To: <20261113155515.2510194-1-jiri@resnulli.us>
From: Jiri Pirko <jiri@nvidia.com>
Give a subsystem's device a node of the subsystem's own class, whose
file operations are the CTLV ones. The subsystem registers the class
with CTLV, which takes its device numbers, and embeds a struct
ctlv_device beside the instance.
Assisted-by: Cursor: GPT-5.6 Sol
Assisted-by: Cursor: Claude Opus 5.5
Signed-off-by: Jiri Pirko <jiri@nvidia.com>
---
RFC->RFCv2:
- split out of the device and operation dispatch patch
- made a library each subsystem's class builds its nodes with, and
dropped the ctlv class
---
include/linux/ctlv_device.h | 57 ++++++++++++
lib/ctlv/Makefile | 2 +-
lib/ctlv/core.c | 17 ++++
lib/ctlv/device.c | 179 ++++++++++++++++++++++++++++++++++++
lib/ctlv/internal.h | 2 +
5 files changed, 256 insertions(+), 1 deletion(-)
create mode 100644 include/linux/ctlv_device.h
create mode 100644 lib/ctlv/device.c
diff --git a/include/linux/ctlv_device.h b/include/linux/ctlv_device.h
new file mode 100644
index 000000000000..9ff10ce7368f
--- /dev/null
+++ b/include/linux/ctlv_device.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES
+ *
+ * Character device nodes of CTLV instances, in a subsystem's own class.
+ */
+#ifndef _LINUX_CTLV_DEVICE_H
+#define _LINUX_CTLV_DEVICE_H
+
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/idr.h>
+#include <linux/types.h>
+
+struct ctlv;
+struct module;
+
+/**
+ * struct ctlv_device_class - The class of a subsystem's nodes.
+ * @class: the class, whose dev_release frees a node's structure; CTLV sets
+ * its dev_uevent
+ * @max_devices: how many nodes the class may have at once
+ */
+struct ctlv_device_class {
+ struct class class;
+ unsigned int max_devices;
+ /* private: */
+ struct module *owner;
+ dev_t devt;
+ struct ida ids;
+};
+
+/**
+ * struct ctlv_device - The node of one instance.
+ * @dev: the node's device, named for its class and a number of its own
+ *
+ * Embedded beside the instance in the subsystem's device structure.
+ */
+struct ctlv_device {
+ struct device dev;
+ /* private: */
+ struct cdev cdev;
+ struct ctlv *ctlv;
+ struct ctlv_device_class *cls;
+};
+
+int __ctlv_device_class_register(struct ctlv_device_class *cls, struct module *owner);
+#define ctlv_device_class_register(cls) __ctlv_device_class_register(cls, THIS_MODULE)
+void ctlv_device_class_unregister(struct ctlv_device_class *cls);
+
+int ctlv_device_init(struct ctlv_device *ctlvdev, struct ctlv_device_class *cls,
+ struct device *parent, struct ctlv *ctlv);
+void ctlv_device_fini(struct ctlv_device *ctlvdev);
+int ctlv_device_add(struct ctlv_device *ctlvdev);
+void ctlv_device_del(struct ctlv_device *ctlvdev);
+
+#endif /* _LINUX_CTLV_DEVICE_H */
diff --git a/lib/ctlv/Makefile b/lib/ctlv/Makefile
index 73acdbdf387d..e783ad450c12 100644
--- a/lib/ctlv/Makefile
+++ b/lib/ctlv/Makefile
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_CTLV) += ctlv.o
-ctlv-y += blob.o blob_desc.o builder.o core.o introspection.o \
+ctlv-y += blob.o blob_desc.o builder.o core.o device.o introspection.o \
message.o schema.o ctlv_gen.o
diff --git a/lib/ctlv/core.c b/lib/ctlv/core.c
index 605f191de77b..eb493104da62 100644
--- a/lib/ctlv/core.c
+++ b/lib/ctlv/core.c
@@ -1034,6 +1034,23 @@ static int ctlv_uevent_check(const struct ctlv_family *family)
return ret;
}
+/**
+ * ctlv_uevent - Add the family variables to a device uevent.
+ * @ctlv: instance
+ * @env: uevent environment
+ *
+ * Return: 0, or a negative error.
+ */
+int ctlv_uevent(const struct ctlv *ctlv, struct kobj_uevent_env *env)
+{
+ /* Read unlocked: a uevent may be asked for under the registration lock. */
+ const struct ctlv_family *family = READ_ONCE(ctlv->family);
+
+ if (!family)
+ return 0;
+ return ctlv_uevent_families(env, family);
+}
+
/**
* ctlv_init - Prepare an embedded instance for one family.
* @ctlv: zeroed instance
diff --git a/lib/ctlv/device.c b/lib/ctlv/device.c
new file mode 100644
index 000000000000..b761739e5442
--- /dev/null
+++ b/lib/ctlv/device.c
@@ -0,0 +1,179 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES
+ *
+ * Character device nodes of CTLV instances.
+ */
+#include <linux/bug.h>
+#include <linux/cdev.h>
+#include <linux/cleanup.h>
+#include <linux/container_of.h>
+#include <linux/ctlv.h>
+#include <linux/ctlv_device.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/idr.h>
+#include <linux/kdev_t.h>
+#include <linux/module.h>
+#include <linux/rwsem.h>
+
+#include "internal.h"
+
+static int ctlv_device_open(struct inode *inode, struct file *filp)
+{
+ struct ctlv_device *ctlvdev = container_of(inode->i_cdev, struct ctlv_device, cdev);
+ struct ctlv_file *cfile;
+
+ cfile = ctlv_file_open(ctlvdev->ctlv, filp);
+ if (IS_ERR(cfile))
+ return PTR_ERR(cfile);
+ filp->private_data = cfile;
+ return 0;
+}
+
+static int ctlv_device_release(struct inode *inode, struct file *filp)
+{
+ ctlv_file_close(filp->private_data);
+ return 0;
+}
+
+static long ctlv_device_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ return ctlv_file_ioctl(filp->private_data, cmd, arg);
+}
+
+static const struct file_operations ctlv_device_operations = {
+ .owner = THIS_MODULE,
+ .open = ctlv_device_open,
+ .release = ctlv_device_release,
+ .unlocked_ioctl = ctlv_device_ioctl,
+ /* Every ioctl field is fixed width, so one entry point serves both. */
+ .compat_ioctl = compat_ptr_ioctl,
+};
+
+static int ctlv_device_uevent(const struct device *dev, struct kobj_uevent_env *env)
+{
+ const struct ctlv_device *ctlvdev = container_of_const(dev, struct ctlv_device, dev);
+
+ return ctlv_uevent(ctlvdev->ctlv, env);
+}
+
+/**
+ * __ctlv_device_class_register - Register a class of nodes.
+ * @cls: class, its name, dev_release and max_devices set
+ * @owner: module of the dev_release, held while a node is open
+ *
+ * Called as ctlv_device_class_register(), which passes the calling module.
+ *
+ * Return: 0, or a negative error.
+ */
+int __ctlv_device_class_register(struct ctlv_device_class *cls, struct module *owner)
+{
+ int ret;
+
+ if (WARN_ON(!cls->max_devices))
+ return -EINVAL;
+ ret = alloc_chrdev_region(&cls->devt, 0, cls->max_devices, cls->class.name);
+ if (ret)
+ return ret;
+ cls->owner = owner;
+ cls->class.dev_uevent = ctlv_device_uevent;
+ ida_init(&cls->ids);
+ ret = class_register(&cls->class);
+ if (ret)
+ unregister_chrdev_region(cls->devt, cls->max_devices);
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(__ctlv_device_class_register, "CTLV");
+
+/**
+ * ctlv_device_class_unregister - Unregister a class of nodes.
+ * @cls: class whose nodes are all released
+ */
+void ctlv_device_class_unregister(struct ctlv_device_class *cls)
+{
+ class_unregister(&cls->class);
+ unregister_chrdev_region(cls->devt, cls->max_devices);
+ ida_destroy(&cls->ids);
+}
+EXPORT_SYMBOL_NS_GPL(ctlv_device_class_unregister, "CTLV");
+
+/**
+ * ctlv_device_init - Prepare the node of an instance.
+ * @ctlvdev: zeroed node, in the structure the class's dev_release frees
+ * @cls: registered class
+ * @parent: parent device, or NULL
+ * @ctlv: instance, for ctlv_init() with the node's device
+ *
+ * From here on, put_device() unwinds, whether or not this succeeds.
+ *
+ * Return: 0, or a negative error.
+ */
+int ctlv_device_init(struct ctlv_device *ctlvdev, struct ctlv_device_class *cls,
+ struct device *parent, struct ctlv *ctlv)
+{
+ int devnum;
+
+ device_initialize(&ctlvdev->dev);
+ ctlvdev->dev.class = &cls->class;
+ ctlvdev->dev.parent = parent;
+ ctlvdev->ctlv = ctlv;
+ cdev_init(&ctlvdev->cdev, &ctlv_device_operations);
+ ctlvdev->cdev.owner = cls->owner;
+
+ devnum = ida_alloc_max(&cls->ids, cls->max_devices - 1, GFP_KERNEL);
+ if (devnum < 0)
+ return devnum;
+ ctlvdev->cls = cls;
+ ctlvdev->dev.devt = MKDEV(MAJOR(cls->devt), devnum);
+ return dev_set_name(&ctlvdev->dev, "%s%d", cls->class.name, devnum);
+}
+EXPORT_SYMBOL_NS_GPL(ctlv_device_init, "CTLV");
+
+/**
+ * ctlv_device_fini - Free the node's number.
+ * @ctlvdev: node
+ *
+ * For the class's dev_release, before it frees the node.
+ */
+void ctlv_device_fini(struct ctlv_device *ctlvdev)
+{
+ if (ctlvdev->cls)
+ ida_free(&ctlvdev->cls->ids, MINOR(ctlvdev->dev.devt));
+}
+EXPORT_SYMBOL_NS_GPL(ctlv_device_fini, "CTLV");
+
+/**
+ * ctlv_device_add - Add the node.
+ * @ctlvdev: node, whose instance was initialized with its device and published
+ *
+ * The node may be opened as soon as it appears, so its instance is published
+ * before and unpublished after ctlv_device_del().
+ *
+ * Return: 0, or a negative error.
+ */
+int ctlv_device_add(struct ctlv_device *ctlvdev)
+{
+ struct ctlv *ctlv = ctlvdev->ctlv;
+ bool published;
+
+ scoped_guard(rwsem_read, &ctlv->registration_lock)
+ published = ctlv->registered;
+ if (WARN_ON(ctlv->dev != &ctlvdev->dev || !published))
+ return -EINVAL;
+ return cdev_device_add(&ctlvdev->cdev, &ctlvdev->dev);
+}
+EXPORT_SYMBOL_NS_GPL(ctlv_device_add, "CTLV");
+
+/**
+ * ctlv_device_del - Remove the node.
+ * @ctlvdev: added node
+ *
+ * A file still open keeps the node's device until it is closed.
+ */
+void ctlv_device_del(struct ctlv_device *ctlvdev)
+{
+ cdev_device_del(&ctlvdev->cdev, &ctlvdev->dev);
+}
+EXPORT_SYMBOL_NS_GPL(ctlv_device_del, "CTLV");
diff --git a/lib/ctlv/internal.h b/lib/ctlv/internal.h
index 2e1e36799a17..8fb5f0abdc47 100644
--- a/lib/ctlv/internal.h
+++ b/lib/ctlv/internal.h
@@ -23,6 +23,7 @@
#define CTLV_ASSERT(cond) BUILD_BUG_ON_INVALID(cond)
#endif
+struct kobj_uevent_env;
struct task_struct;
#define CTLV_ERROR_MESSAGE_LEN 256
@@ -81,5 +82,6 @@ struct ctlv_op_ctx {
};
struct ctlv_op_entry *ctlv_op_find(struct ctlv *ctlv, u64 op_id);
+int ctlv_uevent(const struct ctlv *ctlv, struct kobj_uevent_env *env);
#endif /* _CTLV_INTERNAL_H */
--
2.56.0
next prev parent reply other threads:[~2026-11-13 15:56 UTC|newest]
Thread overview: 16+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-13 15:54 [PATCH RFC v2 00/15] ctlv: Add a schema-driven TLV uAPI for character devices Jiri Pirko
2026-11-13 15:55 ` [PATCH RFC v2 01/15] ctlv: Add the schema language and generator Jiri Pirko
2026-11-13 15:55 ` [PATCH RFC v2 02/15] ctlv: Add the UAPI message format and family API Jiri Pirko
2026-11-13 15:55 ` [PATCH RFC v2 03/15] ctlv: Add the root family specification and generated files Jiri Pirko
2026-11-13 15:55 ` [PATCH RFC v2 04/15] Documentation: ctlv: Add userspace ABI Jiri Pirko
2026-11-13 15:55 ` [PATCH RFC v2 05/15] ctlv: Prepare message input handling Jiri Pirko
2026-11-13 15:55 ` [PATCH RFC v2 06/15] ctlv: Prepare blob descriptor validation Jiri Pirko
2026-11-13 15:55 ` [PATCH RFC v2 07/15] ctlv: Prepare schema validation Jiri Pirko
2026-11-13 15:55 ` [PATCH RFC v2 08/15] ctlv: Prepare message output handling Jiri Pirko
2026-11-13 15:55 ` [PATCH RFC v2 09/15] ctlv: Prepare VA blob handling Jiri Pirko
2026-11-13 15:55 ` [PATCH RFC v2 10/15] ctlv: Prepare runtime introspection Jiri Pirko
2026-11-13 15:55 ` [PATCH RFC v2 11/15] ctlv: Add operation dispatch Jiri Pirko
2026-11-13 15:55 ` Jiri Pirko [this message]
2026-11-13 15:55 ` [PATCH RFC v2 13/15] Documentation: ctlv: Describe how to write a family Jiri Pirko
2026-11-13 15:55 ` [PATCH RFC v2 14/15] tools/ctlv: Add the userspace library Jiri Pirko
2026-11-13 15:55 ` [PATCH RFC v2 15/15] tools/ctlv: Add ctlvctl Jiri Pirko
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261113155515.2510194-13-jiri@resnulli.us \
--to=jiri@resnulli.us \
--cc=James.Bottomley@HansenPartnership.com \
--cc=aik@amd.com \
--cc=alistair.francis@wdc.com \
--cc=aneesh.kumar@kernel.org \
--cc=ankita@nvidia.com \
--cc=cedric.xing@intel.com \
--cc=dave.hansen@linux.intel.com \
--cc=dave.jiang@intel.com \
--cc=david.quigley@intel.com \
--cc=drdeeglaze@gmail.com \
--cc=elena.reshetova@intel.com \
--cc=jarkko@kernel.org \
--cc=jgg@ziepe.ca \
--cc=jic23@kernel.org \
--cc=jpiotrowski@nvidia.com \
--cc=kas@kernel.org \
--cc=ksinyuk@kernel.org \
--cc=kvm@vger.kernel.org \
--cc=linux-api@vger.kernel.org \
--cc=linux-coco@lists.linux.dev \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=lukas@wunner.de \
--cc=nik.borisov@suse.com \
--cc=peter.fang@intel.com \
--cc=pgonda@google.com \
--cc=qinkun@google.com \
--cc=rick.p.edgecombe@intel.com \
--cc=rodrigo.vivi@intel.com \
--cc=sameo@meta.com \
--cc=sathyanarayanan.kuppuswamy@linux.intel.com \
--cc=sergii.dmytruk@3mdeb.com \
--cc=suzuki.poulose@arm.com \
--cc=x86@kernel.org \
--cc=yeoreum.yun@arm.com \
--cc=zohar@linux.ibm.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox