From: Pavol Sakac <sakacpav@amazon.de>
To: Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
Tejun Heo <tj@kernel.org>,
"Rafael J . Wysocki" <rafael@kernel.org>,
Danilo Krummrich <dakr@kernel.org>
Cc: <driver-core@lists.linux.dev>, <linux-kernel@vger.kernel.org>,
"Andy Shevchenko" <andriy.shevchenko@linux.intel.com>,
Xu Yang <xu.yang_2@nxp.com>,
Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>,
Bjorn Helgaas <bhelgaas@google.com>, <linux-pci@vger.kernel.org>,
Alex Williamson <alex@shazbot.org>, <kvm@vger.kernel.org>,
<nh-open-source@amazon.com>
Subject: [RFC PATCH 5/8] drivers: base: test: Add KUnit suite for staged sysfs registration
Date: Fri, 11 Sep 2026 19:43:37 +0200 [thread overview]
Message-ID: <20260911174414.97060-5-sakacpav@amazon.de> (raw)
In-Reply-To: <20260911-vfopt-s5-v1-0-fa4cacdb6ca8@amazon.de>
Add a staged_device KUnit suite under its own
CONFIG_STAGED_DEVICE_KUNIT_TEST symbol exercising the staged mechanism.
kobject_add() on a kobject with ->sd_staged builds the directory staged
but does not publish it, since only device_add() does, so the tests
drive the window directly: stage, then populate and observe, then
sysfs_publish_dir(). The cases cover publication of plain, named-group
and merged-group content, the failure modes of publication itself, abort
without publication, foreign-thread access, concurrent windows including
two that share one hashed staged mutex, glue-directory sharing, and
bus-klist membership by the time device_add() returns. The suite also
covers the KERNFS_STAGED_TOP/KERNFS_HAS_MMAP aliasing on a staged
mmap-capable bin file.
The publication-misuse cases intentionally trigger the publication
guards' WARN backtraces, and the duplicate-name cases trigger
sysfs_warn_dup() splats: those backtraces are expected output of a
passing run, not failures. The suite is therefore unsuitable for
runners that set panic_on_warn, which turns the first intentional splat
into a panic, and does not follow KUNIT_ALL_TESTS: it runs only when its
symbol is enabled explicitly.
Assisted-by: LLM
Signed-off-by: Pavol Sakac <sakacpav@amazon.de>
---
drivers/base/test/.kunitconfig | 1 +
drivers/base/test/Kconfig | 12 +
drivers/base/test/Makefile | 1 +
drivers/base/test/staged-device-test.c | 1042 ++++++++++++++++++++++++
4 files changed, 1056 insertions(+)
create mode 100644 drivers/base/test/staged-device-test.c
diff --git a/drivers/base/test/.kunitconfig b/drivers/base/test/.kunitconfig
index 28322bad39a7..3766972a2e84 100644
--- a/drivers/base/test/.kunitconfig
+++ b/drivers/base/test/.kunitconfig
@@ -1,3 +1,4 @@
CONFIG_KUNIT=y
CONFIG_DM_KUNIT_TEST=y
CONFIG_GLUE_DIR_KUNIT_TEST=y
+CONFIG_STAGED_DEVICE_KUNIT_TEST=y
diff --git a/drivers/base/test/Kconfig b/drivers/base/test/Kconfig
index 253b5bd96aff..0b32e6ca2e12 100644
--- a/drivers/base/test/Kconfig
+++ b/drivers/base/test/Kconfig
@@ -36,3 +36,15 @@ config GLUE_DIR_KUNIT_TEST
not a glue directory is never mistaken for one.
If unsure say N.
+
+config STAGED_DEVICE_KUNIT_TEST
+ tristate "KUnit Tests for staged device registration" if !KUNIT_ALL_TESTS
+ depends on KUNIT && SYSFS
+ help
+ Enable this option to test staged sysfs device registration and
+ publication.
+
+ Some cases intentionally exercise warning and duplicate-name paths;
+ do not run this suite with panic_on_warn.
+
+ If unsure, say N.
diff --git a/drivers/base/test/Makefile b/drivers/base/test/Makefile
index f13f0c399bea..2e6dbb56fb7b 100644
--- a/drivers/base/test/Makefile
+++ b/drivers/base/test/Makefile
@@ -3,6 +3,7 @@ obj-$(CONFIG_TEST_ASYNC_DRIVER_PROBE) += test_async_driver_probe.o
obj-$(CONFIG_DM_KUNIT_TEST) += root-device-test.o
obj-$(CONFIG_DM_KUNIT_TEST) += platform-device-test.o
+obj-$(CONFIG_STAGED_DEVICE_KUNIT_TEST) += staged-device-test.o
obj-$(CONFIG_DRIVER_PE_KUNIT_TEST) += property-entry-test.o
CFLAGS_property-entry-test.o += $(DISABLE_STRUCTLEAK_PLUGIN)
diff --git a/drivers/base/test/staged-device-test.c b/drivers/base/test/staged-device-test.c
new file mode 100644
index 000000000000..d32f00214a8b
--- /dev/null
+++ b/drivers/base/test/staged-device-test.c
@@ -0,0 +1,1042 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * KUnit tests for staged sysfs directory registration.
+ *
+ * The mechanism lives below the sysfs population layer: a kobject opted in
+ * with ->sd_staged has its directory built staged (invisible, off the sysfs
+ * root lock) and published in one step. kobject_add() creates the staged
+ * directory but does NOT publish it (only device_add() does), which gives
+ * these tests direct control over the window: stage -> populate/observe ->
+ * sysfs_publish_dir().
+ */
+
+#include <kunit/resource.h>
+#include <kunit/test.h>
+
+#include <linux/device.h>
+#include <linux/hash.h>
+#include <linux/kobject.h>
+#include <linux/kernfs.h>
+#include <linux/kthread.h>
+#include <linux/completion.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+
+MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
+
+/* A minimal kobject type usable as a staged directory. */
+
+struct staged_kobj {
+ struct kobject kobj;
+};
+
+static ssize_t staged_attr_show(struct kobject *kobj, struct attribute *attr,
+ char *buf)
+{
+ return 0;
+}
+
+static const struct sysfs_ops staged_sysfs_ops = {
+ .show = staged_attr_show,
+};
+
+static void staged_kobj_release(struct kobject *kobj)
+{
+ /* frees the container; no caller frees a kobject-embedding struct */
+ kfree(container_of(kobj, struct staged_kobj, kobj));
+}
+
+static const struct kobj_type staged_ktype = {
+ .sysfs_ops = &staged_sysfs_ops,
+ .release = staged_kobj_release,
+};
+
+struct staged_test_priv {
+ struct kobject *parent; /* published parent for staged kids */
+};
+
+/* Is @parent's staged bit set on its kernfs node? */
+static bool sd_is_staged(struct kobject *kobj)
+{
+ return kobj->sd && (kobj->sd->flags & KERNFS_STAGED);
+}
+
+/* subdir count of a kobject's kernfs directory (parent accounting) */
+static unsigned long sd_subdirs(struct kobject *kobj)
+{
+ return kobj->sd ? kobj->sd->dir.subdirs : 0;
+}
+
+/* Does a visible child @name exist under @parent (goes through the funnel)? */
+static bool child_visible(struct kobject *parent, const char *name)
+{
+ struct kernfs_node *kn = kernfs_find_and_get(parent->sd, name);
+ bool found = !!kn;
+
+ kernfs_put(kn);
+ return found;
+}
+
+/*
+ * Allocate a staged child kobject. It is freed by staged_kobj_release() when
+ * its last reference is dropped (kobject_put()); nothing frees it directly, so
+ * the deferred release under CONFIG_DEBUG_KOBJECT_RELEASE is safe.
+ */
+static struct kobject *staged_child_alloc(struct kunit *test)
+{
+ struct staged_kobj *sk;
+
+ sk = kzalloc_obj(*sk);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sk);
+
+ kobject_init(&sk->kobj, &staged_ktype);
+ kobject_set_sd_staged(&sk->kobj, true);
+ return &sk->kobj;
+}
+
+/* Add @n plain attribute files to a (staged) kobject; names attr0..attrN-1. */
+static int staged_add_files(struct kunit *test, struct kobject *kobj, int n)
+{
+ int i, ret;
+
+ for (i = 0; i < n; i++) {
+ struct attribute *a = kunit_kzalloc(test, sizeof(*a), GFP_KERNEL);
+ char *nm;
+
+ if (!a)
+ return -ENOMEM;
+ sysfs_attr_init(a);
+ nm = kunit_kmalloc(test, 16, GFP_KERNEL);
+ if (!nm)
+ return -ENOMEM;
+ snprintf(nm, 16, "attr%d", i);
+ a->name = nm;
+ a->mode = 0644;
+ ret = sysfs_create_file(kobj, a);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+static int staged_test_init(struct kunit *test)
+{
+ struct staged_test_priv *priv;
+
+ priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv);
+
+ priv->parent = kobject_create_and_add("staged_kunit", kernel_kobj);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->parent);
+
+ test->priv = priv;
+ return 0;
+}
+
+static void staged_test_exit(struct kunit *test)
+{
+ struct staged_test_priv *priv = test->priv;
+
+ if (priv && priv->parent)
+ kobject_put(priv->parent);
+}
+
+/*
+ * End-to-end publish; the merged group is the sysfs_merge_group() case,
+ * which dereferences kobj->sd while the directory is still staged.
+ */
+static struct attribute grp_attr = { .name = "grp_attr", .mode = 0644 };
+static struct attribute *named_grp_attrs[] = { &grp_attr, NULL };
+static const struct attribute_group named_grp = {
+ .name = "ngroup",
+ .attrs = named_grp_attrs,
+};
+
+static struct attribute merge_attr = { .name = "merged", .mode = 0644 };
+static struct attribute *merge_attrs[] = { &merge_attr, NULL };
+static const struct attribute_group merge_grp = {
+ .name = "ngroup",
+ .attrs = merge_attrs,
+};
+
+static void staged_test_publish_end_to_end(struct kunit *test)
+{
+ struct staged_test_priv *priv = test->priv;
+ struct kobject *kobj = staged_child_alloc(test);
+ unsigned long parent_subdirs = sd_subdirs(priv->parent);
+ struct kernfs_node *ngroup_kn, *merged_kn;
+ int ret;
+
+ ret = kobject_add(kobj, priv->parent, "end_to_end");
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ KUNIT_EXPECT_TRUE(test, sd_is_staged(kobj));
+ KUNIT_EXPECT_FALSE(test, child_visible(priv->parent, "end_to_end"));
+ KUNIT_EXPECT_EQ(test, sd_subdirs(priv->parent), parent_subdirs);
+
+ ret = sysfs_create_file(kobj, &grp_attr);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ ret = sysfs_create_group(kobj, &named_grp);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ ret = sysfs_merge_group(kobj, &merge_grp);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+
+ ret = sysfs_publish_dir(kobj);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ KUNIT_EXPECT_FALSE(test, sd_is_staged(kobj));
+ KUNIT_EXPECT_TRUE(test, child_visible(priv->parent, "end_to_end"));
+ KUNIT_EXPECT_EQ(test, sd_subdirs(priv->parent), parent_subdirs + 1);
+ KUNIT_EXPECT_TRUE(test, child_visible(kobj, "grp_attr"));
+ KUNIT_EXPECT_TRUE(test, child_visible(kobj, "ngroup"));
+
+ /* the merged attr lives under ngroup/, not at the top level */
+ ngroup_kn = kernfs_find_and_get(kobj->sd, "ngroup");
+ KUNIT_ASSERT_NOT_NULL(test, ngroup_kn);
+ merged_kn = kernfs_find_and_get(ngroup_kn, "merged");
+ KUNIT_EXPECT_NOT_NULL(test, merged_kn);
+ kernfs_put(merged_kn);
+ kernfs_put(ngroup_kn);
+
+ sysfs_unmerge_group(kobj, &merge_grp);
+ sysfs_remove_group(kobj, &named_grp);
+ sysfs_remove_file(kobj, &grp_attr);
+ kobject_del(kobj);
+ kobject_put(kobj);
+ KUNIT_EXPECT_EQ(test, sd_subdirs(priv->parent), parent_subdirs);
+}
+
+/*
+ * Duplicate name: two staged children with the same name. The first
+ * publishes; the second fails -EEXIST at publication and tears down cleanly,
+ * leaving the winner and the parent undisturbed.
+ */
+static void staged_test_duplicate_name(struct kunit *test)
+{
+ struct staged_test_priv *priv = test->priv;
+ struct kobject *win = staged_child_alloc(test);
+ struct kobject *lose = staged_child_alloc(test);
+ unsigned long base = sd_subdirs(priv->parent);
+ int ret;
+
+ KUNIT_ASSERT_EQ(test, kobject_add(win, priv->parent, "dup"), 0);
+ KUNIT_ASSERT_EQ(test, sysfs_publish_dir(win), 0);
+ KUNIT_EXPECT_EQ(test, sd_subdirs(priv->parent), base + 1);
+
+ /* second staged dir, same name, only fails at publish */
+ KUNIT_ASSERT_EQ(test, kobject_add(lose, priv->parent, "dup"), 0);
+ ret = sysfs_publish_dir(lose);
+ KUNIT_EXPECT_EQ(test, ret, -EEXIST);
+
+ /* winner undisturbed; parent gained exactly one child */
+ KUNIT_EXPECT_TRUE(test, child_visible(priv->parent, "dup"));
+ KUNIT_EXPECT_EQ(test, sd_subdirs(priv->parent), base + 1);
+
+ kobject_del(lose); /* tear down the staged loser */
+ kobject_put(lose);
+ KUNIT_EXPECT_EQ(test, sd_subdirs(priv->parent), base + 1);
+
+ kobject_del(win);
+ kobject_put(win);
+ KUNIT_EXPECT_EQ(test, sd_subdirs(priv->parent), base);
+}
+
+/*
+ * kernfs_publish() rejects (WARN, -EINVAL) a staged top whose parent is
+ * itself still staged: a staged parent's children are serialized by a
+ * different subtree mutex, so publication into it is misuse. Outside-in
+ * order -- the parent first, then the child -- publishes both.
+ */
+static void staged_test_publish_staged_parent(struct kunit *test)
+{
+ struct staged_test_priv *priv = test->priv;
+ struct kobject *outer = staged_child_alloc(test);
+ struct kobject *inner = staged_child_alloc(test);
+
+ KUNIT_ASSERT_EQ(test, kobject_add(outer, priv->parent, "outer"), 0);
+
+ /* a staged top of its own, under the still-staged outer */
+ KUNIT_ASSERT_EQ(test, kobject_add(inner, outer, "inner"), 0);
+
+ KUNIT_EXPECT_EQ(test, sysfs_publish_dir(inner), -EINVAL);
+
+ /* outside-in publication order works */
+ KUNIT_EXPECT_EQ(test, sysfs_publish_dir(outer), 0);
+ KUNIT_EXPECT_EQ(test, sysfs_publish_dir(inner), 0);
+ KUNIT_EXPECT_TRUE(test, child_visible(outer, "inner"));
+
+ kobject_del(inner);
+ kobject_put(inner);
+ kobject_del(outer);
+ kobject_put(outer);
+}
+
+/*
+ * kernfs_publish() rejects (WARN, -EINVAL) a node that is no longer staged:
+ * a second publication of an already-published directory fails and leaves
+ * the published directory undisturbed.
+ */
+static void staged_test_publish_twice(struct kunit *test)
+{
+ struct staged_test_priv *priv = test->priv;
+ struct kobject *kobj = staged_child_alloc(test);
+
+ KUNIT_ASSERT_EQ(test, kobject_add(kobj, priv->parent, "twice"), 0);
+ KUNIT_ASSERT_EQ(test, staged_add_files(test, kobj, 2), 0);
+ KUNIT_ASSERT_EQ(test, sysfs_publish_dir(kobj), 0);
+
+ /* no longer staged: a second publication is misuse */
+ KUNIT_EXPECT_EQ(test, sysfs_publish_dir(kobj), -EINVAL);
+
+ /* the published directory is undisturbed */
+ KUNIT_EXPECT_FALSE(test, sd_is_staged(kobj));
+ KUNIT_EXPECT_TRUE(test, child_visible(priv->parent, "twice"));
+ KUNIT_EXPECT_TRUE(test, child_visible(kobj, "attr0"));
+
+ kobject_del(kobj);
+ kobject_put(kobj);
+}
+
+/*
+ * Never called: a staged file cannot be opened, and the case never mmaps
+ * the file after publication either.
+ */
+static int staged_mmap_stub(struct file *file, struct kobject *kobj,
+ const struct bin_attribute *attr,
+ struct vm_area_struct *vma)
+{
+ return -ENODEV;
+}
+
+static const struct bin_attribute mmap_file_attr = {
+ .attr = { .name = "mmap_file", .mode = 0444 },
+ .size = PAGE_SIZE,
+ .mmap = staged_mmap_stub,
+};
+
+/*
+ * KERNFS_STAGED_TOP aliases KERNFS_HAS_MMAP in one flag bit, and publication
+ * and teardown clear KERNFS_STAGED_TOP only on directories -- so a FILE's
+ * aliased HAS_MMAP must never be disturbed. A staged mmap-capable bin file
+ * carries KERNFS_HAS_MMAP from creation, and publication's DIR-gated clear
+ * mask strips KERNFS_STAGED from it while leaving KERNFS_HAS_MMAP intact.
+ */
+static void staged_test_mmap_file_alias(struct kunit *test)
+{
+ struct staged_test_priv *priv = test->priv;
+ struct kobject *kobj = staged_child_alloc(test);
+ struct kernfs_node *kn, *fresh;
+
+ KUNIT_ASSERT_EQ(test, kobject_add(kobj, priv->parent, "mmap_alias"), 0);
+ KUNIT_ASSERT_EQ(test, sysfs_create_bin_file(kobj, &mmap_file_attr), 0);
+
+ /* in the window: HAS_MMAP from creation, STAGED from the subtree */
+ kn = kernfs_find_and_get(kobj->sd, "mmap_file");
+ KUNIT_ASSERT_NOT_NULL(test, kn);
+ KUNIT_EXPECT_EQ(test, kernfs_type(kn), KERNFS_FILE);
+ KUNIT_EXPECT_TRUE(test, kn->flags & KERNFS_HAS_MMAP);
+ KUNIT_EXPECT_TRUE(test, kn->flags & KERNFS_STAGED);
+
+ KUNIT_ASSERT_EQ(test, sysfs_publish_dir(kobj), 0);
+
+ /* same node: STAGED stripped, aliased HAS_MMAP survived the clear */
+ KUNIT_EXPECT_TRUE(test, kn->flags & KERNFS_HAS_MMAP);
+ KUNIT_EXPECT_FALSE(test, kn->flags & KERNFS_STAGED);
+
+ /* and the file is reachable post-publish */
+ fresh = kernfs_find_and_get(kobj->sd, "mmap_file");
+ KUNIT_EXPECT_PTR_EQ(test, fresh, kn);
+ kernfs_put(fresh);
+ kernfs_put(kn);
+
+ sysfs_remove_bin_file(kobj, &mmap_file_attr);
+ kobject_del(kobj);
+ kobject_put(kobj);
+}
+
+/* A foreign task acts on the staged directory. */
+
+struct foreign_ctx {
+ struct kobject *kobj;
+ struct attribute *new_attr; /* file the foreign task creates */
+ struct attribute *rm_attr; /* file (we created) it removes */
+ const char *notify_name; /* a live staged attr to notify */
+ int create_ret; /* sysfs_create_file() return */
+ bool notify_target_present; /* the notified attr was findable */
+ struct completion done;
+};
+
+static int foreign_fn(void *data)
+{
+ struct foreign_ctx *c = data;
+ struct kernfs_node *kn;
+
+ c->create_ret = sysfs_create_file(c->kobj, c->new_attr);
+ sysfs_remove_file(c->kobj, c->rm_attr);
+
+ /*
+ * Notify a LIVE staged attribute (the one just created) so
+ * kernfs_notify actually runs against a staged node; record that it
+ * was findable.
+ */
+ kn = kernfs_find_and_get(c->kobj->sd, c->notify_name);
+ c->notify_target_present = !!kn;
+ kernfs_put(kn);
+ sysfs_notify(c->kobj, NULL, c->notify_name);
+
+ complete(&c->done);
+ return 0;
+}
+
+static void staged_test_foreign_task(struct kunit *test)
+{
+ struct staged_test_priv *priv = test->priv;
+ struct kobject *kobj = staged_child_alloc(test);
+ struct task_struct *t;
+ struct foreign_ctx c = {};
+ long rc;
+
+ c.kobj = kobj;
+ c.new_attr = kunit_kzalloc(test, sizeof(*c.new_attr), GFP_KERNEL);
+ c.rm_attr = kunit_kzalloc(test, sizeof(*c.rm_attr), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, c.new_attr);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, c.rm_attr);
+ sysfs_attr_init(c.new_attr);
+ sysfs_attr_init(c.rm_attr);
+ c.new_attr->name = "foreign_new";
+ c.new_attr->mode = 0644;
+ c.rm_attr->name = "to_remove";
+ c.rm_attr->mode = 0644;
+ c.notify_name = "foreign_new";
+ init_completion(&c.done);
+
+ KUNIT_ASSERT_EQ(test, kobject_add(kobj, priv->parent, "foreign"), 0);
+ /* we create the file the foreign task will remove */
+ KUNIT_ASSERT_EQ(test, sysfs_create_file(kobj, c.rm_attr), 0);
+
+ t = kthread_run(foreign_fn, &c, "staged_foreign");
+ KUNIT_ASSERT_FALSE(test, IS_ERR(t));
+
+ rc = wait_for_completion_timeout(&c.done, msecs_to_jiffies(10000));
+ if (!rc)
+ wait_for_completion(&c.done); /* join before reading ctx */
+ KUNIT_EXPECT_GT(test, rc, 0);
+
+ /* foreign create succeeded; the notified attribute was a live node */
+ KUNIT_EXPECT_EQ(test, c.create_ret, 0);
+ KUNIT_EXPECT_TRUE(test, c.notify_target_present);
+
+ KUNIT_ASSERT_EQ(test, sysfs_publish_dir(kobj), 0);
+
+ /* after publish: created file present, removed file absent */
+ KUNIT_EXPECT_TRUE(test, child_visible(kobj, "foreign_new"));
+ KUNIT_EXPECT_FALSE(test, child_visible(kobj, "to_remove"));
+
+ sysfs_remove_file(kobj, c.new_attr);
+ kobject_del(kobj);
+ kobject_put(kobj);
+}
+
+/*
+ * Abort without publication, twice in a row. Leak-observable: the parent
+ * kernfs node's subdir count and its base refcount return to their
+ * pre-registration values after each aborted, torn-down window.
+ */
+static void staged_test_abort_twice(struct kunit *test)
+{
+ struct staged_test_priv *priv = test->priv;
+ unsigned long base_subdirs = sd_subdirs(priv->parent);
+ int base_count = atomic_read(&priv->parent->sd->count);
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ struct kobject *kobj = staged_child_alloc(test);
+
+ KUNIT_ASSERT_EQ(test, kobject_add(kobj, priv->parent, "abort"), 0);
+ KUNIT_ASSERT_EQ(test, staged_add_files(test, kobj, 10), 0);
+ KUNIT_EXPECT_TRUE(test, sd_is_staged(kobj));
+
+ /* abort: tear down without publishing */
+ kobject_del(kobj);
+ kobject_put(kobj);
+
+ /* leak-observable: parent fully restored */
+ KUNIT_EXPECT_EQ(test, sd_subdirs(priv->parent), base_subdirs);
+ KUNIT_EXPECT_EQ(test, atomic_read(&priv->parent->sd->count),
+ base_count);
+ }
+}
+
+/*
+ * Partial abort: create then remove a named group inside the window; publish;
+ * the removed group is absent, other content present.
+ */
+static void staged_test_partial_group_abort(struct kunit *test)
+{
+ struct staged_test_priv *priv = test->priv;
+ struct kobject *kobj = staged_child_alloc(test);
+
+ KUNIT_ASSERT_EQ(test, kobject_add(kobj, priv->parent, "partial"), 0);
+ KUNIT_ASSERT_EQ(test, sysfs_create_group(kobj, &named_grp), 0);
+ KUNIT_ASSERT_EQ(test, sysfs_create_file(kobj, &grp_attr), 0);
+
+ /* remove the group again, still in the window */
+ sysfs_remove_group(kobj, &named_grp);
+
+ KUNIT_ASSERT_EQ(test, sysfs_publish_dir(kobj), 0);
+ KUNIT_EXPECT_FALSE(test, child_visible(kobj, "ngroup"));
+ KUNIT_EXPECT_TRUE(test, child_visible(kobj, "grp_attr"));
+
+ sysfs_remove_file(kobj, &grp_attr);
+ kobject_del(kobj);
+ kobject_put(kobj);
+}
+
+/*
+ * Parent removed during the window: publication fails -ENOENT and the orphan
+ * tears down cleanly.
+ */
+static void staged_test_parent_removed(struct kunit *test)
+{
+ struct staged_test_priv *priv = test->priv;
+ struct kobject *mid, *kobj;
+ int ret;
+
+ /* an intermediate published parent we can remove mid-window */
+ mid = kobject_create_and_add("mid", priv->parent);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, mid);
+
+ kobj = staged_child_alloc(test);
+ KUNIT_ASSERT_EQ(test, kobject_add(kobj, mid, "orphan"), 0);
+ KUNIT_ASSERT_EQ(test, staged_add_files(test, kobj, 4), 0);
+
+ /* remove the parent while the child is still staged */
+ kobject_del(mid);
+
+ ret = sysfs_publish_dir(kobj);
+ KUNIT_EXPECT_EQ(test, ret, -ENOENT);
+
+ /* orphan tears down cleanly */
+ kobject_del(kobj);
+ kobject_put(kobj);
+ kobject_put(mid);
+}
+
+/*
+ * O(1) shared-parent cost: a staged directory with >= 20 nodes is built
+ * without a single link into the parent's children collection: the parent's
+ * subdir count is unchanged across the whole population and rises by exactly
+ * one at publish, independent of node count.
+ */
+static void staged_test_o1_root_cost(struct kunit *test)
+{
+ struct staged_test_priv *priv = test->priv;
+ struct kobject *kobj = staged_child_alloc(test);
+ unsigned long base = sd_subdirs(priv->parent);
+
+ KUNIT_ASSERT_EQ(test, kobject_add(kobj, priv->parent, "o1cost"), 0);
+ KUNIT_ASSERT_EQ(test, staged_add_files(test, kobj, 25), 0);
+
+ /* 25 nodes added, zero links into the shared parent */
+ KUNIT_EXPECT_EQ(test, sd_subdirs(priv->parent), base);
+ KUNIT_EXPECT_FALSE(test, child_visible(priv->parent, "o1cost"));
+
+ KUNIT_ASSERT_EQ(test, sysfs_publish_dir(kobj), 0);
+ /* exactly one link at publish, regardless of the 25 nodes */
+ KUNIT_EXPECT_EQ(test, sd_subdirs(priv->parent), base + 1);
+ KUNIT_EXPECT_TRUE(test, child_visible(kobj, "attr0"));
+ KUNIT_EXPECT_TRUE(test, child_visible(kobj, "attr24"));
+
+ kobject_del(kobj);
+ kobject_put(kobj);
+ KUNIT_EXPECT_EQ(test, sd_subdirs(priv->parent), base);
+}
+
+/* Two concurrent windows. */
+
+#define WINDOW_NODES 50
+
+struct window_ctx {
+ struct kobject *parent;
+ const char *name;
+ int add_ret;
+ int publish_ret;
+ struct completion done;
+};
+
+static int window_fn(void *data)
+{
+ struct window_ctx *w = data;
+ struct attribute *attrs[WINDOW_NODES] = {};
+ struct staged_kobj *sk;
+ struct kobject *kobj;
+ int i;
+
+ sk = kzalloc_obj(*sk);
+ if (!sk) {
+ w->add_ret = -ENOMEM;
+ complete(&w->done);
+ return 0;
+ }
+ kobject_init(&sk->kobj, &staged_ktype);
+ kobject_set_sd_staged(&sk->kobj, true);
+ kobj = &sk->kobj;
+
+ w->add_ret = kobject_add(kobj, w->parent, "%s", w->name);
+ if (w->add_ret) {
+ kobject_put(kobj); /* release frees the container */
+ complete(&w->done);
+ return 0;
+ }
+
+ for (i = 0; i < WINDOW_NODES; i++) {
+ struct attribute *a = kzalloc_obj(*a);
+
+ if (!a)
+ break;
+ sysfs_attr_init(a);
+ a->name = kasprintf(GFP_KERNEL, "attr%d", i);
+ a->mode = 0644;
+ if (sysfs_create_file(kobj, a)) {
+ kfree((void *)a->name);
+ kfree(a);
+ break;
+ }
+ attrs[i] = a;
+ }
+
+ w->publish_ret = sysfs_publish_dir(kobj);
+
+ kobject_del(kobj);
+ kobject_put(kobj); /* release frees the container */
+
+ /* attributes outlive their files; free them after teardown */
+ for (i = 0; i < WINDOW_NODES; i++) {
+ if (!attrs[i])
+ continue;
+ kfree((void *)attrs[i]->name);
+ kfree(attrs[i]);
+ }
+
+ complete(&w->done);
+ return 0;
+}
+
+static void staged_test_two_windows(struct kunit *test)
+{
+ struct staged_test_priv *priv = test->priv;
+ struct window_ctx w1 = { .parent = priv->parent, .name = "winA" };
+ struct window_ctx w2 = { .parent = priv->parent, .name = "winB" };
+ struct task_struct *t1, *t2;
+ long r1, r2;
+
+ init_completion(&w1.done);
+ init_completion(&w2.done);
+
+ t1 = kthread_run(window_fn, &w1, "staged_winA");
+ /* nothing spawned yet if this aborts */
+ KUNIT_ASSERT_FALSE(test, IS_ERR(t1));
+ t2 = kthread_run(window_fn, &w2, "staged_winB");
+ KUNIT_EXPECT_FALSE(test, IS_ERR(t2));
+ if (IS_ERR(t2)) {
+ /* t1 writes on-stack w1: join it before returning */
+ wait_for_completion(&w1.done);
+ return;
+ }
+
+ /* no deadlock within the timeout */
+ r1 = wait_for_completion_timeout(&w1.done, msecs_to_jiffies(10000));
+ r2 = wait_for_completion_timeout(&w2.done, msecs_to_jiffies(10000));
+ /* join both before touching the on-stack contexts they write to */
+ if (!r1)
+ wait_for_completion(&w1.done);
+ if (!r2)
+ wait_for_completion(&w2.done);
+ KUNIT_EXPECT_GT(test, r1, 0);
+ KUNIT_EXPECT_GT(test, r2, 0);
+
+ KUNIT_EXPECT_EQ(test, w1.add_ret, 0);
+ KUNIT_EXPECT_EQ(test, w2.add_ret, 0);
+ KUNIT_EXPECT_EQ(test, w1.publish_ret, 0);
+ KUNIT_EXPECT_EQ(test, w2.publish_ret, 0);
+}
+
+/* Staged registration through device_add(). */
+
+static void staged_dev_release(struct device *dev)
+{
+ kfree(dev);
+}
+
+static void staged_dev_unregister(void *data)
+{
+ device_unregister(data);
+}
+
+static void staged_root_unregister(void *data)
+{
+ root_device_unregister(data);
+}
+
+/*
+ * Allocate and initialize a device for device_add(); @staged opts it in to the
+ * staged path. Freed by staged_dev_release() when the last reference drops;
+ * nothing frees it directly.
+ */
+static struct device *staged_dev_alloc(struct kunit *test,
+ struct device *parent,
+ const struct bus_type *bus,
+ const char *name, bool staged)
+{
+ struct device *dev;
+ int ret;
+
+ dev = kzalloc_obj(*dev);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+
+ device_initialize(dev);
+ dev->parent = parent;
+ dev->bus = bus;
+ dev->release = staged_dev_release;
+ if (staged)
+ dev_set_sysfs_staged(dev);
+
+ ret = dev_set_name(dev, "%s", name);
+ if (ret)
+ put_device(dev);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ return dev;
+}
+
+/* Staged registration under a class glue directory. */
+
+static void staged_class_destroy(void *data)
+{
+ class_destroy(data);
+}
+
+/*
+ * Register a staged class device with a NULL parent; it lands under the
+ * class glue directory in /sys/devices/virtual/. Unregistered by a deferred
+ * kunit action (released early where a test removes it mid-flight).
+ */
+static struct device *staged_class_dev_add(struct kunit *test,
+ const struct class *class,
+ const char *name)
+{
+ struct device *dev;
+ int ret;
+
+ dev = staged_dev_alloc(test, NULL, NULL, name, true);
+ dev->class = class;
+
+ ret = device_add(dev);
+ if (ret)
+ put_device(dev);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test,
+ staged_dev_unregister,
+ dev), 0);
+ return dev;
+}
+
+/*
+ * Staged registration under a class glue directory. Two staged class
+ * devices share one glue dir; removing one sibling must not reap the
+ * shared glue dir under the other (cleanup_glue_dir() sees the survivor's
+ * glue-dir reference, kref >= 2), and once the last child is gone the
+ * reaped glue dir must be recreatable by a further staged registration.
+ */
+static struct attribute glue_attr = { .name = "glue_attr", .mode = 0644 };
+
+static void staged_test_glue_dir(struct kunit *test)
+{
+ struct device *dev_a, *dev_b, *dev_a2;
+ struct class *class;
+
+ class = class_create("staged_kunit_class");
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, class);
+ KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test,
+ staged_class_destroy,
+ class), 0);
+
+ /* two staged children of the same (new) glue directory */
+ dev_a = staged_class_dev_add(test, class, "glueA");
+ dev_b = staged_class_dev_add(test, class, "glueB");
+ KUNIT_ASSERT_NOT_NULL(test, dev_a->kobj.sd);
+ KUNIT_ASSERT_NOT_NULL(test, dev_b->kobj.sd);
+ KUNIT_EXPECT_PTR_EQ(test, dev_a->kobj.parent, dev_b->kobj.parent);
+
+ /*
+ * The glue dir itself is not staged: glue dirs are created eagerly.
+ * This case cannot observe the inside of the window -- device_add()
+ * opens and closes it internally; the kobject-level cases above
+ * cover the window itself.
+ */
+ KUNIT_EXPECT_TRUE(test, kobject_sd_staged(&dev_a->kobj));
+ KUNIT_EXPECT_TRUE(test, kobject_sd_staged(&dev_b->kobj));
+ KUNIT_EXPECT_FALSE(test, sd_is_staged(&dev_a->kobj));
+ KUNIT_EXPECT_FALSE(test, sd_is_staged(&dev_b->kobj));
+ KUNIT_EXPECT_FALSE(test, sd_is_staged(dev_a->kobj.parent));
+ KUNIT_EXPECT_TRUE(test, child_visible(dev_a->kobj.parent, "glueA"));
+ KUNIT_EXPECT_TRUE(test, child_visible(dev_b->kobj.parent, "glueB"));
+
+ /* sibling removal must not reap the glue dir under dev_b */
+ kunit_release_action(test, staged_dev_unregister, dev_a);
+ KUNIT_ASSERT_NOT_NULL(test, dev_b->kobj.sd);
+ KUNIT_EXPECT_TRUE(test, child_visible(dev_b->kobj.parent, "glueB"));
+ KUNIT_EXPECT_EQ(test, sysfs_create_file(&dev_b->kobj, &glue_attr), 0);
+ KUNIT_EXPECT_TRUE(test, child_visible(&dev_b->kobj, "glue_attr"));
+ sysfs_remove_file(&dev_b->kobj, &glue_attr);
+
+ /* last child gone: the glue dir is reaped ... */
+ kunit_release_action(test, staged_dev_unregister, dev_b);
+
+ /* ... and a staged re-registration under A's name recreates it */
+ dev_a2 = staged_class_dev_add(test, class, "glueA");
+ KUNIT_EXPECT_NOT_NULL(test, dev_a2->kobj.sd);
+ KUNIT_EXPECT_FALSE(test, sd_is_staged(&dev_a2->kobj));
+}
+
+/*
+ * device_add() staged bracket, error unwind. A staged device whose name
+ * collides with a live sibling only fails at publication, deep inside
+ * device_add(). The failure must unwind the whole bracket: the caller sees the
+ * error, the failed device keeps no kernfs node, the collision winner and the
+ * parent are undisturbed, the last reference frees the device, and a fresh
+ * staged registration under a free name still succeeds afterwards.
+ */
+static void staged_test_device_add_unwind(struct kunit *test)
+{
+ struct device *root, *eager, *staged, *retry;
+ struct kernfs_node *kn;
+ unsigned long base;
+ int ret;
+
+ root = root_device_register("staged_kunit_unwind");
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, root);
+ KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test,
+ staged_root_unregister,
+ root), 0);
+
+ /* the collision winner, registered eagerly */
+ eager = staged_dev_alloc(test, root, NULL, "collide", false);
+ KUNIT_ASSERT_EQ(test, device_add(eager), 0);
+ KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test,
+ staged_dev_unregister,
+ eager), 0);
+ base = sd_subdirs(&root->kobj);
+
+ /* same name, staged: the directory is built, publication collides */
+ staged = staged_dev_alloc(test, root, NULL, "collide", true);
+ ret = device_add(staged);
+ KUNIT_ASSERT_EQ(test, ret, -EEXIST);
+
+ /* unwound: no kernfs node left on the failed device */
+ KUNIT_EXPECT_NULL(test, staged->kobj.sd);
+
+ /* no residue under the parent; the winner is what remains */
+ KUNIT_EXPECT_EQ(test, sd_subdirs(&root->kobj), base);
+ kn = kernfs_find_and_get(root->kobj.sd, "collide");
+ KUNIT_EXPECT_PTR_EQ(test, kn, eager->kobj.sd);
+ kernfs_put(kn);
+
+ /* the failed device's last reference frees it */
+ put_device(staged);
+
+ /* a fresh staged registration under a free name still succeeds */
+ retry = staged_dev_alloc(test, root, NULL, "retry", true);
+ KUNIT_ASSERT_EQ(test, device_add(retry), 0);
+ KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test,
+ staged_dev_unregister,
+ retry), 0);
+ KUNIT_EXPECT_FALSE(test, sd_is_staged(&retry->kobj));
+ KUNIT_EXPECT_TRUE(test, child_visible(&root->kobj, "retry"));
+ KUNIT_EXPECT_EQ(test, sd_subdirs(&root->kobj), base + 1);
+}
+
+/* Deferred bus-klist insertion, on a real bus with a real driver. */
+
+static const struct bus_type staged_test_bus = {
+ .name = "staged_kunit_bus",
+};
+
+static int staged_test_driver_probe(struct device *dev)
+{
+ return 0;
+}
+
+static struct device_driver staged_test_driver = {
+ .name = "staged_kunit_drv",
+ .bus = &staged_test_bus,
+ .owner = THIS_MODULE,
+ .probe = staged_test_driver_probe,
+};
+
+static void staged_bus_unregister(void *data)
+{
+ bus_unregister(data);
+}
+
+static void staged_driver_unregister(void *data)
+{
+ driver_unregister(data);
+}
+
+struct staged_bus_scan {
+ struct device *want;
+ unsigned int seen;
+ bool found;
+};
+
+static int staged_bus_scan_fn(struct device *dev, void *data)
+{
+ struct staged_bus_scan *scan = data;
+
+ scan->seen++;
+ if (dev == scan->want)
+ scan->found = true;
+ return 0;
+}
+
+/*
+ * bus_add_device() keeps a staged device off the bus klist -- the one
+ * driver_attach() walks -- until publication, and device_add() inserts it via
+ * bus_add_device_publish() before anything can observe the device. Assert the
+ * outcome that a regression in that gating would destroy: once device_add()
+ * returns, the device is on the klist that bus_for_each_dev() walks, and the
+ * driver has actually bound to it.
+ */
+static void staged_test_bus_klist(struct kunit *test)
+{
+ struct staged_bus_scan scan = {};
+ struct device *dev;
+
+ KUNIT_ASSERT_EQ(test, bus_register(&staged_test_bus), 0);
+ KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test,
+ staged_bus_unregister,
+ (void *)&staged_test_bus), 0);
+
+ KUNIT_ASSERT_EQ(test, driver_register(&staged_test_driver), 0);
+ KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test,
+ staged_driver_unregister,
+ &staged_test_driver), 0);
+
+ dev = staged_dev_alloc(test, NULL, &staged_test_bus, "busdev", true);
+ KUNIT_ASSERT_EQ(test, device_add(dev), 0);
+ KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test,
+ staged_dev_unregister,
+ dev), 0);
+
+ /* opted in, and published by the time device_add() returned */
+ KUNIT_EXPECT_TRUE(test, kobject_sd_staged(&dev->kobj));
+ KUNIT_EXPECT_FALSE(test, sd_is_staged(&dev->kobj));
+
+ /* the deferred klist insertion happened: the bus walkers see it */
+ scan.want = dev;
+ KUNIT_ASSERT_EQ(test, bus_for_each_dev(&staged_test_bus, NULL, &scan,
+ staged_bus_scan_fn), 0);
+ KUNIT_EXPECT_TRUE(test, scan.found);
+ KUNIT_EXPECT_EQ(test, scan.seen, 1u);
+
+ /* ... and driver binding actually ran against it */
+ KUNIT_EXPECT_PTR_EQ(test, dev->driver, &staged_test_driver);
+}
+
+/*
+ * Hashed staged-mutex slot aliasing. Open one more coexisting staged window
+ * than the hashed mutex array has slots, so by pigeonhole at least two staged
+ * tops share one mutex. Aliasing must not confuse kernfs_staged_lock()'s
+ * owning-subtree identification: every window must populate, publish and come
+ * out complete. The collision is asserted rather than assumed -- the slot
+ * index is the same pure function of the node address that
+ * kernfs_staged_mutex_ptr() uses.
+ */
+#define STAGED_SLOT_WINDOWS (NR_KERNFS_LOCKS + 1)
+
+static void staged_test_hashed_slot_collision(struct kunit *test)
+{
+ struct staged_test_priv *priv = test->priv;
+ unsigned long base = sd_subdirs(priv->parent);
+ struct kobject **kids;
+ unsigned int *slots;
+ bool collided = false;
+ int i, j;
+
+ kids = kunit_kcalloc(test, STAGED_SLOT_WINDOWS, sizeof(*kids), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, kids);
+ slots = kunit_kcalloc(test, STAGED_SLOT_WINDOWS, sizeof(*slots), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, slots);
+
+ for (i = 0; i < STAGED_SLOT_WINDOWS; i++) {
+ kids[i] = staged_child_alloc(test);
+ KUNIT_ASSERT_EQ(test, kobject_add(kids[i], priv->parent,
+ "slot%d", i), 0);
+ KUNIT_ASSERT_TRUE(test, sd_is_staged(kids[i]));
+ KUNIT_ASSERT_EQ(test, staged_add_files(test, kids[i], 2), 0);
+ slots[i] = hash_ptr(kids[i]->sd, NR_KERNFS_LOCK_BITS);
+ }
+ KUNIT_EXPECT_EQ(test, sd_subdirs(priv->parent), base);
+
+ /* pigeonhole: NR_KERNFS_LOCKS + 1 tops over NR_KERNFS_LOCKS slots */
+ for (i = 0; i < STAGED_SLOT_WINDOWS && !collided; i++)
+ for (j = i + 1; j < STAGED_SLOT_WINDOWS; j++)
+ if (slots[i] == slots[j]) {
+ collided = true;
+ break;
+ }
+ KUNIT_EXPECT_TRUE(test, collided);
+
+ for (i = 0; i < STAGED_SLOT_WINDOWS; i++)
+ KUNIT_ASSERT_EQ(test, sysfs_publish_dir(kids[i]), 0);
+
+ KUNIT_EXPECT_EQ(test, sd_subdirs(priv->parent),
+ base + STAGED_SLOT_WINDOWS);
+ for (i = 0; i < STAGED_SLOT_WINDOWS; i++) {
+ char name[16];
+
+ snprintf(name, sizeof(name), "slot%d", i);
+ KUNIT_EXPECT_FALSE(test, sd_is_staged(kids[i]));
+ KUNIT_EXPECT_TRUE(test, child_visible(priv->parent, name));
+ KUNIT_EXPECT_TRUE(test, child_visible(kids[i], "attr0"));
+ KUNIT_EXPECT_TRUE(test, child_visible(kids[i], "attr1"));
+ }
+
+ for (i = 0; i < STAGED_SLOT_WINDOWS; i++) {
+ kobject_del(kids[i]);
+ kobject_put(kids[i]);
+ }
+ KUNIT_EXPECT_EQ(test, sd_subdirs(priv->parent), base);
+}
+
+static struct kunit_case staged_device_tests[] = {
+ KUNIT_CASE(staged_test_publish_end_to_end),
+ KUNIT_CASE(staged_test_duplicate_name),
+ KUNIT_CASE(staged_test_publish_staged_parent),
+ KUNIT_CASE(staged_test_publish_twice),
+ KUNIT_CASE(staged_test_mmap_file_alias),
+ KUNIT_CASE(staged_test_foreign_task),
+ KUNIT_CASE(staged_test_abort_twice),
+ KUNIT_CASE(staged_test_partial_group_abort),
+ KUNIT_CASE(staged_test_parent_removed),
+ KUNIT_CASE(staged_test_o1_root_cost),
+ KUNIT_CASE(staged_test_two_windows),
+ KUNIT_CASE(staged_test_glue_dir),
+ KUNIT_CASE(staged_test_device_add_unwind),
+ KUNIT_CASE(staged_test_bus_klist),
+ KUNIT_CASE(staged_test_hashed_slot_collision),
+ {}
+};
+
+static struct kunit_suite staged_device_test_suite = {
+ .name = "staged_device",
+ .init = staged_test_init,
+ .exit = staged_test_exit,
+ .test_cases = staged_device_tests,
+};
+
+kunit_test_suite(staged_device_test_suite);
+
+MODULE_DESCRIPTION("KUnit tests for staged sysfs directory registration");
+MODULE_LICENSE("GPL");
--
2.47.3
next prev parent reply other threads:[~2026-09-11 17:46 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-11 17:43 [RFC PATCH 0/8] kernfs, driver core: " Pavol Sakac
2026-09-11 17:43 ` [RFC PATCH 1/8] kernfs: factor out reusable directory helpers Pavol Sakac
2026-09-11 17:43 ` [RFC PATCH 2/8] kernfs: add staged directory creation and publication Pavol Sakac
2026-09-11 17:43 ` [RFC PATCH 3/8] sysfs: add opt-in " Pavol Sakac
2026-09-11 17:43 ` [RFC PATCH 4/8] driver core: Register opted-in devices through the staged sysfs path Pavol Sakac
2026-09-11 17:43 ` Pavol Sakac [this message]
2026-09-12 13:14 ` [RFC PATCH 5/8] drivers: base: test: Add KUnit suite for staged sysfs registration Andy Shevchenko
2026-09-11 17:43 ` [RFC PATCH 6/8] driver core: Defer uevents for devices registered in a staged window Pavol Sakac
2026-09-12 13:20 ` Andy Shevchenko
2026-09-11 17:43 ` [RFC PATCH 7/8] PCI/IOV: Register virtual functions through the staged sysfs path Pavol Sakac
2026-09-11 17:43 ` [RFC PATCH 8/8] vfio: Opt the group and vfio-dev class devices into staged sysfs Pavol Sakac
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=20260911174414.97060-5-sakacpav@amazon.de \
--to=sakacpav@amazon.de \
--cc=alex@shazbot.org \
--cc=andriy.shevchenko@linux.intel.com \
--cc=bartosz.golaszewski@oss.qualcomm.com \
--cc=bhelgaas@google.com \
--cc=dakr@kernel.org \
--cc=driver-core@lists.linux.dev \
--cc=gregkh@linuxfoundation.org \
--cc=kvm@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-pci@vger.kernel.org \
--cc=nh-open-source@amazon.com \
--cc=rafael@kernel.org \
--cc=tj@kernel.org \
--cc=xu.yang_2@nxp.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
all inboxes | Powered by JetHome®