* [RFC PATCH 00/14] driver core: defer per-VF sysfs creation for fast SR-IOV bring-up
@ 2026-07-02 17:40 Pavol Sakac
2026-07-02 17:40 ` [RFC PATCH 01/14] kernfs: add populate callbacks and KERNFS_LAZY flag for lazy dir population Pavol Sakac
` (5 more replies)
0 siblings, 6 replies; 15+ messages in thread
From: Pavol Sakac @ 2026-07-02 17:40 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rafael J . Wysocki, Danilo Krummrich,
Tejun Heo
Cc: linux-kernel, driver-core, David Woodhouse, Pasha Tatashin,
Mike Rapoport, Pratyush Yadav, David Matlack, Samiullah Khawaja,
Alexander Graf, linux-mm, kexec, nh-open-source
Virtualization hardware keeps increasing in CPU-core and VF density.
Kernel is trending towards preserving VFs using LUO across a kexec
which will put SR-IOV in live-update hotpath. VFs must be re-added to
the sysfs tree, along with its supporting devices (e.g. vfio,
iommu_groups). On production hardware a single VF can consume ~80 kernfs
nodes. With thousands of VFs on modern hardware, hundreds of thousands
of kernfs nodes need to be created, which can add 100ms+ to the boot
time and to guest downtime.
Proposed here is a PoC of deferred materialization of sysfs files until
first access to avoid cost of kernfs node creation in hot path. In the
reproducer below using a synthetic VF to isolate the sysfs overhead,
the per-device sysfs-creation time is reduced by ~74%. It exploits the
expected access pattern of hypervisors, where only a subset of device
files need to be accessed to attach a VF to a VM on the hot path.
To allow lazy init scheme, a minimal set of files and directories is
built per device: a device directory resolves directly to the device's
kobject and is used to materialize attributes at access time. The
access is trapped in kernfs at the dir-read or path-walk stage and
depending on the trigger, either the full directory or a single file is
materialized.
The changes are two logically distinct parts:
1. Refactor of hardcoded device_add / iommu_group sysfs attribute
creation to a table-driven form to allow walkability of all
attributes and single point of definition for attributes
regardless of lazy opt-in to avoid duplicate definitions and
associated maintainability issues with it.
2. Lazy-init infra to front-run access to device files at lookup/
readdir time to trigger materialization. Opt-in per device: PCI VFs
and VFIO devices opt in via device_set_sysfs_lazy(); iommu_group
opts in at the kobject level.
As a PoC, the code does not yet follow this logical structure in the
commit sequence and likely does not yet cover all corner cases.
Synthetic reproducer measurements
---------------------------------
devices per-dev time total time kernfs nodes (base -> lazy)
------- ------------ -------------- ----------------------------
500 31.8->8.3 us 15.9-> 4.1 ms 36,417 -> 3,922 (72.8->7.8/dev)
1000 33.2->8.7 us 33.2-> 8.7 ms 73,427 -> 8,432 (73.4->8.4/dev)
2500 32.8->9.1 us 82.0->22.7 ms 184,421 -> 21,925 (73.8->8.8/dev)
5000 33.0->8.7 us 165.1->43.5 ms 369,401 -> 44,406 (73.9->8.9/dev)
The reproducer is synthetic: an in-kernel module registers N unbound
platform devices (60 attributes / 4 groups, ~74 kernfs nodes each; no
config space, BARs, or probe) in one timed burst that models SR-IOV
enablement. It runs as a two-kernel A/B from one vanilla 7.1-rc2 tree
(eager baseline vs patched, plus a patched/opt-in-OFF arm), 20 runs per
point. Measures only sysfs-creation slice.
Deferral removes ~74% of the sysfs-creation time and ~88% of the kernfs
nodes. At 5000 devices that is 121.6 ms removed (165.1 -> 43.5 ms) and
~325,000 fewer kernfs nodes (369,401 -> 44,406). The table-driven rework
adds a +7..+11% eager-path time overhead to non-opted devices.
Available as a docker image that orchestrates builds in qemu guest and
prints results as a table (x86_64 Linux host with /dev/kvm assumed):
docker run --device /dev/kvm ghcr.io/pavsa/linux-lazy-sysfs-vf-bench:7.1-rc2
Assumptions / Trade-offs
------------------------
The saving depends on the access pattern of hypervisors, and on no
component walking the whole device tree on the hot path to avoid full
materialization. Both are expected to hold on optimized hypervisors.
An actual production hypervisor was observed in local testing to
materialize an additional 5-10% of total nodes on average on the hot
path.
Overhead: +7..+11% eager-path time overhead applies to every non-opted
device going through device_add() as all struct devices traverse the
walker and can't be gated under a config option.
The lazy opt-in infra can be gated under config option to have zero
impact if disabled.
Final words
-----------
This is an RFC to first get feedback on the decisions taken at driver
core + kernfs level before engaging maintainers of other subsystems
if we want to pursue this further or pivot to something else.
The code is at a proof-of-concept quality written with AI assistance
with some known limitations with main target to gather potential
savings and initiate conversation.
Feedback appreciated for:
- table-driven attribute refactor targeting common device add path /
suggestions for more efficient/less invasive approach
- mechanism of interception in kernfs, materialization, locking scheme
- fit into context of LUO - complementary or is there some other
mechanism already considered to mitigate the sysfs impact
Pavol Sakac (14):
kernfs: add populate callbacks and KERNFS_LAZY flag for lazy dir
population
sysfs: add existence-check helpers for lazy populate races
sysfs: introduce sysfs_kf_syscall_ops dispatching to kobj_type
driver core: add struct sysfs_lazy_state and device_set_sysfs_lazy()
driver core: add struct device_sysfs_entry and walker
driver core: wire device_ktype populate to walker
driver core: migrate device sysfs to device_sysfs_entry table
PCI/sysfs: migrate to device_sysfs_entry, defer physfn symlink
iommu: lazy-populate iommu_group reserved_regions/type attrs
PCI/IOV: opt SR-IOV VFs into sysfs_lazy
vfio: opt vfio-dev and VFIO group devices into sysfs_lazy
driver core: test: add KUnit tests for device_sysfs_apply
Documentation: add lazy sysfs initialisation and device_sysfs_entry
docs
selftests: sysfs-lazy: add tests for lazy sysfs initialization
Documentation/ABI/testing/sysfs-lazy | 77 +
Documentation/driver-api/index.rst | 1 +
Documentation/driver-api/sysfs-lazy.rst | 146 ++
MAINTAINERS | 4 +
drivers/base/bus.c | 41 +-
drivers/base/core.c | 1047 +++++++++--
drivers/base/test/.kunitconfig | 7 +
drivers/base/test/Kconfig | 13 +
drivers/base/test/Makefile | 2 +
drivers/base/test/device_sysfs_apply_test.c | 1601 +++++++++++++++++
drivers/iommu/iommu.c | 132 +-
drivers/pci/iov.c | 24 +-
drivers/pci/pci-sysfs.c | 158 +-
drivers/pci/pci.h | 5 +
drivers/pci/probe.c | 4 +-
drivers/vfio/group.c | 5 +
drivers/vfio/vfio_main.c | 5 +
fs/kernfs/dir.c | 43 +-
fs/sysfs/dir.c | 43 +
fs/sysfs/file.c | 39 +
fs/sysfs/group.c | 70 +
fs/sysfs/mount.c | 60 +-
fs/sysfs/sysfs.h | 7 +-
include/linux/device.h | 90 +-
include/linux/kernfs.h | 28 +
include/linux/kobject.h | 15 +
include/linux/sysfs.h | 39 +
tools/testing/selftests/Makefile | 1 +
tools/testing/selftests/sysfs-lazy/.gitignore | 3 +
tools/testing/selftests/sysfs-lazy/Makefile | 24 +
tools/testing/selftests/sysfs-lazy/README.rst | 52 +
tools/testing/selftests/sysfs-lazy/config | 6 +
.../selftests/sysfs-lazy/iommu_groups.c | 162 ++
.../selftests/sysfs-lazy/kmsg_cursor.h | 167 ++
.../selftests/sysfs-lazy/pci_resource.c | 284 +++
tools/testing/selftests/sysfs-lazy/settings | 1 +
.../testing/selftests/sysfs-lazy/sysfs-lazy.c | 777 ++++++++
.../selftests/sysfs-lazy/test_mod/Makefile | 28 +
.../sysfs-lazy/test_mod/sysfs_lazy_test_mod.c | 319 ++++
39 files changed, 5341 insertions(+), 189 deletions(-)
create mode 100644 Documentation/ABI/testing/sysfs-lazy
create mode 100644 Documentation/driver-api/sysfs-lazy.rst
create mode 100644 drivers/base/test/device_sysfs_apply_test.c
create mode 100644 tools/testing/selftests/sysfs-lazy/.gitignore
create mode 100644 tools/testing/selftests/sysfs-lazy/Makefile
create mode 100644 tools/testing/selftests/sysfs-lazy/README.rst
create mode 100644 tools/testing/selftests/sysfs-lazy/config
create mode 100644 tools/testing/selftests/sysfs-lazy/iommu_groups.c
create mode 100644 tools/testing/selftests/sysfs-lazy/kmsg_cursor.h
create mode 100644 tools/testing/selftests/sysfs-lazy/pci_resource.c
create mode 100644 tools/testing/selftests/sysfs-lazy/settings
create mode 100644 tools/testing/selftests/sysfs-lazy/sysfs-lazy.c
create mode 100644 tools/testing/selftests/sysfs-lazy/test_mod/Makefile
create mode 100644 tools/testing/selftests/sysfs-lazy/test_mod/sysfs_lazy_test_mod.c
base-commit: ec89572766744e844df24c27d31c97b4c00f4e07
--
2.47.3
Amazon Web Services Development Center Germany GmbH
Tamara-Danz-Str. 13
10243 Berlin
Geschaeftsfuehrung: Christof Hellmis, Andreas Stieger
Eingetragen am Amtsgericht Charlottenburg unter HRB 257764 B
Sitz: Berlin
Ust-ID: DE 365 538 597
^ permalink raw reply [flat|nested] 15+ messages in thread
* [RFC PATCH 01/14] kernfs: add populate callbacks and KERNFS_LAZY flag for lazy dir population
2026-07-02 17:40 [RFC PATCH 00/14] driver core: defer per-VF sysfs creation for fast SR-IOV bring-up Pavol Sakac
@ 2026-07-02 17:40 ` Pavol Sakac
2026-07-02 17:40 ` [RFC PATCH 02/14] sysfs: add existence-check helpers for lazy populate races Pavol Sakac
` (4 subsequent siblings)
5 siblings, 0 replies; 15+ messages in thread
From: Pavol Sakac @ 2026-07-02 17:40 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rafael J . Wysocki, Danilo Krummrich,
Tejun Heo
Cc: linux-kernel, driver-core, David Woodhouse, Pasha Tatashin,
Mike Rapoport, Pratyush Yadav, David Matlack, Samiullah Khawaja,
Alexander Graf, linux-mm, kexec, nh-open-source
Add populate() and populate_all() callbacks to struct
kernfs_syscall_ops:
- populate() runs on a KERNFS_LAZY parent from
kernfs_iop_lookup() before kernfs_rwsem is taken; the locked
lookup then proceeds and its result is authoritative. The
subsystem may materialize the named child during the call.
- populate_all() runs from kernfs_fop_readdir() before
kernfs_rwsem is taken. The subsystem may materialize all
deferred children.
Both are invoked with the parent's active reference held by the
kernfs lookup path, so parent->priv stays live across the
(sleeping) callback. The callback must not drop this pin.
Add KERNFS_LAZY (0x8000) to enum kernfs_node_flag; non-lazy
directories skip the indirect call entirely.
Add kernfs_set_lazy() to mark a directory lazy between linking
into the parent tree and kernfs_activate(). It returns -EINVAL
for namespaced and non-directory nodes: populate dispatch does
not propagate the namespace tag, so a namespaced lazy node would
silently fail tagged lookups. The error code lets callers detect
contract violations without polluting dmesg with a stack trace
and lets KUnit cover the rejection paths via return-value
assertions rather than triggering WARN_ON_ONCE.
Add matching populate/populate_all fields to struct kobj_type.
These are sysfs-only; non-sysfs kernfs roots do not invoke them.
No behavior change: no subsystem sets these callbacks yet.
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Rafael J. Wysocki <rafael@kernel.org>
Cc: Tejun Heo <tj@kernel.org>
Cc: linux-kernel@vger.kernel.org
Cc: linux-api@vger.kernel.org
Cc: Danilo Krummrich <dakr@kernel.org>
Cc: driver-core@lists.linux.dev
Assisted-by: Claude:claude-opus-4.7
Signed-off-by: Pavol Sakac <sakacpav@amazon.de>
---
fs/kernfs/dir.c | 43 +++++++++++++++++++++++++++++++++++++++--
include/linux/kernfs.h | 28 +++++++++++++++++++++++++++
include/linux/kobject.h | 10 ++++++++++
3 files changed, 79 insertions(+), 2 deletions(-)
diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c
index 4f9ade82b08ab..9c5ec8b82d940 100644
--- a/fs/kernfs/dir.c
+++ b/fs/kernfs/dir.c
@@ -975,6 +975,20 @@ struct kernfs_node *kernfs_find_and_get_ns(struct kernfs_node *parent,
}
EXPORT_SYMBOL_GPL(kernfs_find_and_get_ns);
+int kernfs_set_lazy(struct kernfs_node *kn)
+{
+ struct kernfs_root *root = kernfs_root(kn);
+
+ if (kn->ns || (kn->flags & KERNFS_NS) ||
+ kernfs_type(kn) != KERNFS_DIR)
+ return -EINVAL;
+
+ down_write(&root->kernfs_rwsem);
+ kn->flags |= KERNFS_LAZY;
+ up_write(&root->kernfs_rwsem);
+ return 0;
+}
+
/**
* kernfs_walk_and_get_ns - find and get kernfs_node with the given path
* @parent: kernfs_node to search under
@@ -1255,6 +1269,19 @@ static struct dentry *kernfs_iop_lookup(struct inode *dir,
struct kernfs_root *root;
struct inode *inode = NULL;
const struct ns_common *ns = NULL;
+ struct kernfs_syscall_ops *scops = kernfs_root(parent)->syscall_ops;
+ int populate_ret = 0;
+
+ /* Dispatch populate before taking kernfs_rwsem; kernfs_get_active()
+ * pins @parent so @parent->priv stays live across the sleepable call.
+ */
+ if (scops && scops->populate && (READ_ONCE(parent->flags) & KERNFS_LAZY)) {
+ if (kernfs_get_active(parent)) {
+ populate_ret = scops->populate(parent,
+ dentry->d_name.name);
+ kernfs_put_active(parent);
+ }
+ }
root = kernfs_root(parent);
down_read(&root->kernfs_rwsem);
@@ -1285,6 +1312,10 @@ static struct dentry *kernfs_iop_lookup(struct inode *dir,
kernfs_set_rev(parent, dentry);
up_read(&root->kernfs_rwsem);
+ /* Transient populate error: do not cache a negative dentry. */
+ if (!kn && populate_ret && populate_ret != -ENOENT)
+ return ERR_PTR(populate_ret);
+
/* instantiate and hash (possibly negative) dentry */
return d_splice_alias(inode, dentry);
}
@@ -1973,13 +2004,21 @@ static int kernfs_fop_readdir(struct file *file, struct dir_context *ctx)
struct dentry *dentry = file->f_path.dentry;
struct kernfs_node *parent = kernfs_dentry_node(dentry);
struct kernfs_node *pos = file->private_data;
- struct kernfs_root *root;
+ struct kernfs_root *root = kernfs_root(parent);
+ struct kernfs_syscall_ops *scops = root->syscall_ops;
const struct ns_common *ns = NULL;
if (!dir_emit_dots(file, ctx))
return 0;
- root = kernfs_root(parent);
+ /* Same pinning invariant as kernfs_iop_lookup. */
+ if (scops && scops->populate_all && (READ_ONCE(parent->flags) & KERNFS_LAZY)) {
+ if (kernfs_get_active(parent)) {
+ scops->populate_all(parent);
+ kernfs_put_active(parent);
+ }
+ }
+
down_read(&root->kernfs_rwsem);
if (kernfs_ns_enabled(parent))
diff --git a/include/linux/kernfs.h b/include/linux/kernfs.h
index e21b2f7f4159f..cd529fd843831 100644
--- a/include/linux/kernfs.h
+++ b/include/linux/kernfs.h
@@ -113,6 +113,7 @@ enum kernfs_node_flag {
KERNFS_EMPTY_DIR = 0x1000,
KERNFS_HAS_RELEASE = 0x2000,
KERNFS_REMOVING = 0x4000,
+ KERNFS_LAZY = 0x8000, /* subsystem defers child creation */
};
/* @flags for kernfs_create_root() */
@@ -239,6 +240,7 @@ struct kernfs_node {
* kernfs_node parameter.
*/
struct kernfs_syscall_ops {
+ /* Syscall-reactive callbacks */
int (*show_options)(struct seq_file *sf, struct kernfs_root *root);
int (*mkdir)(struct kernfs_node *parent, const char *name,
@@ -248,6 +250,12 @@ struct kernfs_syscall_ops {
const char *new_name);
int (*show_path)(struct seq_file *sf, struct kernfs_node *kn,
struct kernfs_root *root);
+
+ /* Lazy dispatch for KERNFS_LAZY directories; @parent is pinned
+ * via kernfs_get_active() across the call. Both may sleep.
+ */
+ int (*populate)(struct kernfs_node *parent, const char *name);
+ void (*populate_all)(struct kernfs_node *parent);
};
struct kernfs_node *kernfs_root_to_node(struct kernfs_root *root);
@@ -398,6 +406,24 @@ static inline bool kernfs_ns_enabled(struct kernfs_node *kn)
return kn->flags & KERNFS_NS;
}
+/**
+ * kernfs_set_lazy - mark a kernfs directory for lazy population
+ * @kn: directory kernfs_node (must not be namespaced)
+ *
+ * Sets KERNFS_LAZY under kernfs_rwsem to serialize with other
+ * kn->flags writers. The flag is set once and never cleared.
+ *
+ * Populate dispatch does not propagate the namespace tag, so a
+ * namespaced lazy node would silently fail tagged lookups. The
+ * function therefore rejects namespaced and non-directory nodes.
+ *
+ * Return:
+ * * %0 - on success.
+ * * %-EINVAL - @kn is namespaced (@kn->ns non-NULL or KERNFS_NS
+ * flag set) or is not a directory.
+ */
+int kernfs_set_lazy(struct kernfs_node *kn);
+
int kernfs_name(struct kernfs_node *kn, char *buf, size_t buflen);
int kernfs_path_from_node(struct kernfs_node *kn_to, struct kernfs_node *kn_from,
char *buf, size_t buflen);
@@ -481,6 +507,8 @@ static inline void kernfs_enable_ns(struct kernfs_node *kn) { }
static inline bool kernfs_ns_enabled(struct kernfs_node *kn)
{ return false; }
+static inline int kernfs_set_lazy(struct kernfs_node *kn) { return 0; }
+
static inline int kernfs_name(struct kernfs_node *kn, char *buf, size_t buflen)
{ return -ENOSYS; }
diff --git a/include/linux/kobject.h b/include/linux/kobject.h
index bcb5d4e320015..cec6de1720076 100644
--- a/include/linux/kobject.h
+++ b/include/linux/kobject.h
@@ -120,6 +120,16 @@ struct kobj_type {
const struct kobj_ns_type_operations *(*child_ns_type)(const struct kobject *kobj);
const struct ns_common *(*namespace)(const struct kobject *kobj);
void (*get_ownership)(const struct kobject *kobj, kuid_t *uid, kgid_t *gid);
+
+ /*
+ * Lazy sysfs population. Invoked from kernfs lookup / readdir
+ * paths only when the directory is flagged KERNFS_LAZY. May
+ * sleep; @kobj is pinned across the call. populate() returns
+ * 0 / -ENOENT / -errno (kernfs propagates non-ENOENT errors
+ * to userspace). populate_all() is best-effort.
+ */
+ int (*populate)(struct kobject *kobj, const char *name);
+ void (*populate_all)(struct kobject *kobj);
};
struct kobj_uevent_env {
--
2.47.3
Amazon Web Services Development Center Germany GmbH
Tamara-Danz-Str. 13
10243 Berlin
Geschaeftsfuehrung: Christof Hellmis, Andreas Stieger
Eingetragen am Amtsgericht Charlottenburg unter HRB 257764 B
Sitz: Berlin
Ust-ID: DE 365 538 597
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 02/14] sysfs: add existence-check helpers for lazy populate races
2026-07-02 17:40 [RFC PATCH 00/14] driver core: defer per-VF sysfs creation for fast SR-IOV bring-up Pavol Sakac
2026-07-02 17:40 ` [RFC PATCH 01/14] kernfs: add populate callbacks and KERNFS_LAZY flag for lazy dir population Pavol Sakac
@ 2026-07-02 17:40 ` Pavol Sakac
2026-07-02 17:40 ` [RFC PATCH 03/14] sysfs: introduce sysfs_kf_syscall_ops dispatching to kobj_type Pavol Sakac
` (3 subsequent siblings)
5 siblings, 0 replies; 15+ messages in thread
From: Pavol Sakac @ 2026-07-02 17:40 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rafael J . Wysocki, Danilo Krummrich,
Tejun Heo
Cc: linux-kernel, driver-core, David Woodhouse, Pasha Tatashin,
Mike Rapoport, Pratyush Yadav, David Matlack, Samiullah Khawaja,
Alexander Graf, linux-mm, kexec, nh-open-source
Lazy populate paths (introduced in subsequent commits) acquire a
per-device lock and may walk a table of create callbacks.
A populate_one("X") and populate_all() racing on the same device
serialize through that lock, but each create callback must verify
the entry doesn't already exist before calling sysfs_create_*().
Otherwise the second caller hits __kernfs_create_* which fires
sysfs_warn_dup() before the create callback can absorb -EEXIST.
Add two read-only helpers that wrap kernfs_find_and_get() so
create callbacks can perform the existence check under the same
lock that excludes other lazy create paths. Pairing exists() with
create() under lock makes the check-and-act atomic with respect
to all lazy populate paths on the device, and eager paths are
temporally separated (eager creates run during device_add, before
lazy is enabled).
- sysfs_kn_exists(kobj, name): true iff @kobj has a child kernfs
node named @name (any type).
- sysfs_group_exists(kobj, grp):
* grp->name != NULL: true iff a KERNFS_DIR child named
grp->name exists under @kobj->sd (per-attribute presence is
not inspected; mirrors how internal_create_group()
short-circuits on -EEXIST).
* grp->name == NULL: true iff every visible attribute in
grp->attrs and grp->bin_attrs is present; a partially
populated unnamed group returns false so a follow-up create
pass can fill the missing entries.
Both take kernfs_rwsem(read) briefly via kernfs_find_and_get and
return false on bad input or non-existent name; sysfs_group_exists
additionally returns false on a node-type mismatch.
This commit only adds the helpers; subsequent commits convert
create callbacks to use them.
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Rafael J. Wysocki <rafael@kernel.org>
Cc: Tejun Heo <tj@kernel.org>
Cc: linux-kernel@vger.kernel.org
Cc: linux-api@vger.kernel.org
Cc: Danilo Krummrich <dakr@kernel.org>
Cc: driver-core@lists.linux.dev
Assisted-by: Claude:claude-opus-4.7
Signed-off-by: Pavol Sakac <sakacpav@amazon.de>
---
fs/sysfs/dir.c | 25 ++++++++++++++++
fs/sysfs/file.c | 2 ++
fs/sysfs/group.c | 70 +++++++++++++++++++++++++++++++++++++++++++
include/linux/sysfs.h | 15 ++++++++++
4 files changed, 112 insertions(+)
diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c
index ffdcd4153c584..ae97ab7e41939 100644
--- a/fs/sysfs/dir.c
+++ b/fs/sysfs/dir.c
@@ -159,3 +159,28 @@ void sysfs_remove_mount_point(struct kobject *parent_kobj, const char *name)
kernfs_remove_by_name_ns(parent, name, NULL);
}
EXPORT_SYMBOL_GPL(sysfs_remove_mount_point);
+
+
+/**
+ * sysfs_kn_exists - check whether any sysfs entry with @name exists
+ * @kobj: kobject under which to look
+ * @name: name to look up
+ *
+ * Existence probe under per-device sysfs_lazy_state.lock; lets a
+ * caller skip a redundant sysfs_create_*() and the resulting
+ * sysfs_warn_dup() WARN.
+ *
+ * Return: true if a kernfs node of any type with @name exists.
+ */
+bool sysfs_kn_exists(struct kobject *kobj, const char *name)
+{
+ struct kernfs_node *kn;
+
+ if (!kobj || !kobj->sd || !name)
+ return false;
+ kn = kernfs_find_and_get(kobj->sd, name);
+ if (!kn)
+ return false;
+ kernfs_put(kn);
+ return true;
+}
diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c
index 5709cede1d756..5f3144e52ab72 100644
--- a/fs/sysfs/file.c
+++ b/fs/sysfs/file.c
@@ -816,3 +816,5 @@ ssize_t sysfs_bin_attr_simple_read(struct file *file, struct kobject *kobj,
return count;
}
EXPORT_SYMBOL_GPL(sysfs_bin_attr_simple_read);
+
+
diff --git a/fs/sysfs/group.c b/fs/sysfs/group.c
index 182e54e575ee9..d6b2034cc22c9 100644
--- a/fs/sysfs/group.c
+++ b/fs/sysfs/group.c
@@ -636,3 +636,73 @@ int sysfs_groups_change_owner(struct kobject *kobj,
return error;
}
EXPORT_SYMBOL_GPL(sysfs_groups_change_owner);
+
+
+/**
+ * sysfs_group_exists - check whether an attribute_group is materialised
+ * @kobj: kobject under which to look
+ * @grp: attribute_group descriptor (may NOT be NULL)
+ *
+ * Read-only existence probe used by lazy populate paths.
+ *
+ * Two shapes:
+ * - @grp->name != NULL: the group owns its own subdirectory.
+ * Returns true iff a child kernfs_node of type KERNFS_DIR named
+ * @grp->name exists under @kobj->sd. Per-attribute presence inside
+ * the subdirectory is NOT inspected - once the subdir exists the
+ * create path treats the named group as already materialised
+ * (consistent with how internal_create_group() short-circuits its
+ * create on -EEXIST).
+ * - @grp->name == NULL: the group's attributes live directly under
+ * @kobj->sd. Returns true iff every visible attribute in
+ * @grp->attrs and @grp->bin_attrs is already present. A partially-
+ * populated unnamed group returns false so a follow-up create
+ * pass can fill the missing entries.
+ *
+ * Locking expectations: see sysfs_kn_exists().
+ *
+ * Return: true if the group is fully present per the rules above,
+ * false otherwise (including @grp == NULL).
+ */
+bool sysfs_group_exists(struct kobject *kobj, const struct attribute_group *grp)
+{
+ struct attribute *const *a;
+ const struct bin_attribute *const *ba;
+ struct kernfs_node *kn;
+ bool exists;
+ int i;
+
+ if (!kobj || !kobj->sd || !grp)
+ return false;
+
+ if (grp->name) {
+ kn = kernfs_find_and_get(kobj->sd, grp->name);
+ if (!kn)
+ return false;
+ exists = kernfs_type(kn) == KERNFS_DIR;
+ kernfs_put(kn);
+ return exists;
+ }
+
+ if (grp->attrs) {
+ for (i = 0, a = grp->attrs; *a; i++, a++) {
+ if (grp->is_visible && !grp->is_visible(kobj, *a, i))
+ continue;
+ kn = kernfs_find_and_get(kobj->sd, (*a)->name);
+ if (!kn)
+ return false;
+ kernfs_put(kn);
+ }
+ }
+ if (grp->bin_attrs) {
+ for (i = 0, ba = grp->bin_attrs; *ba; i++, ba++) {
+ if (grp->is_bin_visible && !grp->is_bin_visible(kobj, *ba, i))
+ continue;
+ kn = kernfs_find_and_get(kobj->sd, (*ba)->attr.name);
+ if (!kn)
+ return false;
+ kernfs_put(kn);
+ }
+ }
+ return true;
+}
diff --git a/include/linux/sysfs.h b/include/linux/sysfs.h
index b1a3a1e6ad09c..de211563f3dec 100644
--- a/include/linux/sysfs.h
+++ b/include/linux/sysfs.h
@@ -428,6 +428,8 @@ int __must_check sysfs_create_bin_file(struct kobject *kobj,
void sysfs_remove_bin_file(struct kobject *kobj,
const struct bin_attribute *attr);
+bool sysfs_kn_exists(struct kobject *kobj, const char *name);
+
int __must_check sysfs_create_link(struct kobject *kobj, struct kobject *target,
const char *name);
int __must_check sysfs_create_link_nowarn(struct kobject *kobj,
@@ -454,6 +456,8 @@ void sysfs_remove_group(struct kobject *kobj,
const struct attribute_group *grp);
void sysfs_remove_groups(struct kobject *kobj,
const struct attribute_group *const *groups);
+bool sysfs_group_exists(struct kobject *kobj,
+ const struct attribute_group *grp);
int sysfs_add_file_to_group(struct kobject *kobj,
const struct attribute *attr, const char *group);
void sysfs_remove_file_from_group(struct kobject *kobj,
@@ -593,6 +597,11 @@ static inline void sysfs_remove_bin_file(struct kobject *kobj,
{
}
+static inline bool sysfs_kn_exists(struct kobject *kobj, const char *name)
+{
+ return false;
+}
+
static inline int sysfs_create_link(struct kobject *kobj,
struct kobject *target, const char *name)
{
@@ -656,6 +665,12 @@ static inline void sysfs_remove_groups(struct kobject *kobj,
{
}
+static inline bool sysfs_group_exists(struct kobject *kobj,
+ const struct attribute_group *grp)
+{
+ return false;
+}
+
static inline int sysfs_add_file_to_group(struct kobject *kobj,
const struct attribute *attr, const char *group)
{
--
2.47.3
Amazon Web Services Development Center Germany GmbH
Tamara-Danz-Str. 13
10243 Berlin
Geschaeftsfuehrung: Christof Hellmis, Andreas Stieger
Eingetragen am Amtsgericht Charlottenburg unter HRB 257764 B
Sitz: Berlin
Ust-ID: DE 365 538 597
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 03/14] sysfs: introduce sysfs_kf_syscall_ops dispatching to kobj_type
2026-07-02 17:40 [RFC PATCH 00/14] driver core: defer per-VF sysfs creation for fast SR-IOV bring-up Pavol Sakac
2026-07-02 17:40 ` [RFC PATCH 01/14] kernfs: add populate callbacks and KERNFS_LAZY flag for lazy dir population Pavol Sakac
2026-07-02 17:40 ` [RFC PATCH 02/14] sysfs: add existence-check helpers for lazy populate races Pavol Sakac
@ 2026-07-02 17:40 ` Pavol Sakac
2026-07-02 17:40 ` [RFC PATCH 04/14] driver core: add struct sysfs_lazy_state and device_set_sysfs_lazy() Pavol Sakac
` (2 subsequent siblings)
5 siblings, 0 replies; 15+ messages in thread
From: Pavol Sakac @ 2026-07-02 17:40 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rafael J . Wysocki, Danilo Krummrich,
Tejun Heo
Cc: linux-kernel, driver-core, David Woodhouse, Pasha Tatashin,
Mike Rapoport, Pratyush Yadav, David Matlack, Samiullah Khawaja,
Alexander Graf, linux-mm, kexec, nh-open-source
Wire the sysfs side of the lazy populate chain. sysfs_init()
previously passed NULL as syscall_ops to kernfs_create_root();
install sysfs_kf_syscall_ops with populate and populate_all
that dispatch to the owning kobject's kobj_type.
The kernfs lookup path pins the parent's active reference, so
parent->priv is safe to read; sysfs_populate() additionally
pins the dereferenced kobject with kobject_get_unless_zero()
across the sleeping ktype->populate call. sysfs_populate_all()
does the same for ktype->populate_all on readdir.
Both callbacks refuse dispatch in namespaced directories: the
initial consumers (PCI, VFIO) are namespace-agnostic, and tag
propagation is out of scope. Transient errors propagate without
negative-dentry caching so the next lookup retries populate.
No behavior change: no kobj_type sets populate/populate_all yet.
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Rafael J. Wysocki <rafael@kernel.org>
Cc: linux-kernel@vger.kernel.org
Cc: linux-api@vger.kernel.org
Cc: Danilo Krummrich <dakr@kernel.org>
Cc: driver-core@lists.linux.dev
Cc: Tejun Heo <tj@kernel.org>
Assisted-by: Claude:claude-opus-4.7
Signed-off-by: Pavol Sakac <sakacpav@amazon.de>
---
fs/sysfs/mount.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 59 insertions(+), 1 deletion(-)
diff --git a/fs/sysfs/mount.c b/fs/sysfs/mount.c
index b199e8ff79b1f..9e0a37b60d6e7 100644
--- a/fs/sysfs/mount.c
+++ b/fs/sysfs/mount.c
@@ -10,6 +10,7 @@
*/
#include <linux/fs.h>
+#include <linux/kobject.h>
#include <linux/magic.h>
#include <linux/mount.h>
#include <linux/init.h>
@@ -96,11 +97,68 @@ static struct file_system_type sysfs_fs_type = {
.fs_flags = FS_USERNS_MOUNT,
};
+/*
+ * sysfs_populate - dispatch a lookup miss to the owning kobj_type.
+ *
+ * kernfs pins @parent across the call; we pin @kobj while the
+ * (sleepable) ktype->populate callback runs.
+ */
+static int sysfs_populate(struct kernfs_node *parent, const char *name)
+{
+ struct kobject *kobj;
+ const struct kobj_type *ktype;
+ int ret;
+
+ if (kernfs_ns_enabled(parent))
+ return -ENOENT;
+
+ kobj = parent->priv;
+ ktype = kobj ? kobj->ktype : NULL;
+ if (!ktype || !ktype->populate)
+ return -ENOENT;
+
+ if (!kobject_get_unless_zero(kobj))
+ return -ENOENT;
+
+ ret = ktype->populate(kobj, name);
+
+ kobject_put(kobj);
+ return ret;
+}
+
+/* sysfs_populate_all - readdir-time variant; best-effort, retry-safe. */
+static void sysfs_populate_all(struct kernfs_node *parent)
+{
+ struct kobject *kobj;
+ const struct kobj_type *ktype;
+
+ if (kernfs_ns_enabled(parent))
+ return;
+
+ kobj = parent->priv;
+ ktype = kobj ? kobj->ktype : NULL;
+ if (!ktype || !ktype->populate_all)
+ return;
+
+ if (!kobject_get_unless_zero(kobj))
+ return;
+
+ ktype->populate_all(kobj);
+
+ kobject_put(kobj);
+}
+
+static struct kernfs_syscall_ops sysfs_kf_syscall_ops = {
+ .populate = sysfs_populate,
+ .populate_all = sysfs_populate_all,
+};
+
int __init sysfs_init(void)
{
int err;
- sysfs_root = kernfs_create_root(NULL, KERNFS_ROOT_EXTRA_OPEN_PERM_CHECK,
+ sysfs_root = kernfs_create_root(&sysfs_kf_syscall_ops,
+ KERNFS_ROOT_EXTRA_OPEN_PERM_CHECK,
NULL);
if (IS_ERR(sysfs_root))
return PTR_ERR(sysfs_root);
--
2.47.3
Amazon Web Services Development Center Germany GmbH
Tamara-Danz-Str. 13
10243 Berlin
Geschaeftsfuehrung: Christof Hellmis, Andreas Stieger
Eingetragen am Amtsgericht Charlottenburg unter HRB 257764 B
Sitz: Berlin
Ust-ID: DE 365 538 597
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 04/14] driver core: add struct sysfs_lazy_state and device_set_sysfs_lazy()
2026-07-02 17:40 [RFC PATCH 00/14] driver core: defer per-VF sysfs creation for fast SR-IOV bring-up Pavol Sakac
` (2 preceding siblings ...)
2026-07-02 17:40 ` [RFC PATCH 03/14] sysfs: introduce sysfs_kf_syscall_ops dispatching to kobj_type Pavol Sakac
@ 2026-07-02 17:40 ` Pavol Sakac
2026-07-02 17:51 ` [RFC PATCH 05/14] driver core: add struct device_sysfs_entry and walker Pavol Sakac
2026-07-10 14:16 ` [RFC PATCH 00/14] driver core: defer per-VF sysfs creation for fast SR-IOV bring-up Greg Kroah-Hartman
5 siblings, 0 replies; 15+ messages in thread
From: Pavol Sakac @ 2026-07-02 17:40 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rafael J . Wysocki, Danilo Krummrich,
Tejun Heo
Cc: linux-kernel, driver-core, David Woodhouse, Pasha Tatashin,
Mike Rapoport, Pratyush Yadav, David Matlack, Samiullah Khawaja,
Alexander Graf, linux-mm, kexec, nh-open-source
Add the per-device lazy-sysfs bookkeeping struct and accessors that
subsystems use to opt their devices into lazy population:
- struct sysfs_lazy_state { mutex lock; bool populated;
bool power_added; } in include/linux/device.h.
- dev->sysfs_lazy pointer field on struct device. Non-NULL is the
opt-in signal device_add() consults to mark the kobj's kernfs
directory KERNFS_LAZY.
- Public accessors device_is_sysfs_lazy(), device_sysfs_populated(),
device_sysfs_set_populated().
device_sysfs_populated() uses smp_load_acquire() to pair with
device_sysfs_set_populated()'s smp_store_release(); this lets
the lockless fast-path in device_ktype_populate_all() (added in
the next commit) skip the mutex while still observing all
kernfs_create_*() side-effects performed by populate_all under
@lock. The canonical write is under @lock.
- device_set_sysfs_lazy() allocates the bookkeeping; idempotent and
must be called BEFORE device_add(). Freed by device_release().
device_add() consults dev->sysfs_lazy after kobject_add(); if set,
kernfs_set_lazy(dev->kobj.sd) gates kernfs lookups and readdirs to
device_ktype.populate / populate_all. No driver-core attribute
content is yet routed through the walker - that lands in the next
commit. No existing caller opts in here; the plumbing is in place
for subsystems that follow in this series.
The "blob whose pointer doubles as the opt-in signal" pattern
mirrors dev_iommu_get() in drivers/iommu/iommu.c.
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Rafael J. Wysocki <rafael@kernel.org>
Cc: Tejun Heo <tj@kernel.org>
Cc: Danilo Krummrich <dakr@kernel.org>
Cc: linux-kernel@vger.kernel.org
Cc: driver-core@lists.linux.dev
Cc: linux-api@vger.kernel.org
Assisted-by: Claude:claude-opus-4.7
Signed-off-by: Pavol Sakac <sakacpav@amazon.de>
---
drivers/base/core.c | 74 ++++++++++++++++++++++++++++++++++++++++++
include/linux/device.h | 28 ++++++++++++++++
2 files changed, 102 insertions(+)
diff --git a/drivers/base/core.c b/drivers/base/core.c
index bd2ddf2aab505..6d0d917d4b1ff 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -2555,6 +2555,10 @@ static void device_release(struct kobject *kobj)
*/
devres_release_all(dev);
+ if (dev->sysfs_lazy)
+ mutex_destroy(&dev->sysfs_lazy->lock);
+ kfree(dev->sysfs_lazy);
+
kfree(dev->dma_range_map);
kfree(dev->driver_override.name);
@@ -2588,6 +2592,67 @@ static void device_get_ownership(const struct kobject *kobj, kuid_t *uid, kgid_t
dev->class->get_ownership(dev, uid, gid);
}
+/*
+ * True once populate_all has completed; lockless. False for non-lazy.
+ *
+ * Memory-ordering: pairs with device_sysfs_set_populated()'s
+ * smp_store_release(). smp_load_acquire ensures any kernfs_create_*()
+ * side-effects performed by populate_all under sysfs_lazy.lock become
+ * visible to readers that observe @populated == true. The kernfs
+ * lookup path also takes kernfs_rwsem(read) which provides an
+ * independent memory-barrier; the explicit acquire here is belt-and-
+ * braces for callers that may bypass kernfs (e.g. internal sysfs_*
+ * helpers and the KUnit suite).
+ */
+bool device_sysfs_populated(const struct device *dev)
+{
+ return dev->sysfs_lazy &&
+ smp_load_acquire(&dev->sysfs_lazy->populated);
+}
+
+/*
+ * Latch @dev as fully populated. Caller holds @dev->sysfs_lazy->lock.
+ *
+ * Memory-ordering: smp_store_release() publishes all kernfs_create_*()
+ * side-effects performed under sysfs_lazy.lock to lockless readers
+ * via device_sysfs_populated()'s smp_load_acquire().
+ */
+void device_sysfs_set_populated(struct device *dev)
+{
+ smp_store_release(&dev->sysfs_lazy->populated, true);
+}
+
+/**
+ * device_set_sysfs_lazy - opt @dev into lazy sysfs (call before device_add())
+ * @dev: device to opt in
+ *
+ * Allocates @dev->sysfs_lazy. Idempotent: a second call on a
+ * device that already opted in is a no-op. Must be called before
+ * device_add(). After device_add() returns, eager attribute creation
+ * is skipped and walker dispatch lazily materializes attributes on
+ * first sysfs lookup or readdir.
+ *
+ * Return:
+ * * %0 - on success (or if already opted in).
+ * * %-ENOMEM - allocation failure.
+ */
+int device_set_sysfs_lazy(struct device *dev)
+{
+ struct sysfs_lazy_state *lazy;
+
+ if (dev->sysfs_lazy)
+ return 0;
+
+ lazy = kzalloc_obj(*lazy);
+ if (!lazy)
+ return -ENOMEM;
+
+ mutex_init(&lazy->lock);
+ dev->sysfs_lazy = lazy;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(device_set_sysfs_lazy);
+
static const struct kobj_type device_ktype = {
.release = device_release,
.sysfs_ops = &dev_sysfs_ops,
@@ -3642,6 +3707,15 @@ int device_add(struct device *dev)
error = device_add_class_symlinks(dev);
if (error)
goto SymlinkError;
+ /*
+ * device_set_sysfs_lazy() only allocates ->sysfs_lazy_state for
+ * non-namespaced devices, and device_is_sysfs_lazy() guards opt-in
+ * here, so kernfs_set_lazy() should always succeed. WARN_ON catches
+ * a regression in those preconditions.
+ */
+ if (device_is_sysfs_lazy(dev))
+ WARN_ON(kernfs_set_lazy(dev->kobj.sd));
+
error = device_add_attrs(dev);
if (error)
goto AttrsError;
diff --git a/include/linux/device.h b/include/linux/device.h
index 9c8fde6a3d866..39e08e8c950c6 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -45,6 +45,18 @@ struct fwnode_handle;
struct iommu_group;
struct dev_pin_info;
struct dev_iommu;
+/**
+ * struct sysfs_lazy_state - per-device lazy sysfs population state
+ * @lock: serialises populate callbacks (held across device_sysfs_apply())
+ * @populated: full-populate latch
+ * @power_added: dpm_sysfs_add() latch
+ */
+struct sysfs_lazy_state {
+ struct mutex lock;
+ bool populated;
+ bool power_added;
+};
+
struct msi_device_data;
/**
@@ -739,9 +751,25 @@ struct device {
bool dma_iommu:1;
#endif
+ /* Lazy-sysfs opt-in (NULL = eager). Set via device_set_sysfs_lazy(). */
+ struct sysfs_lazy_state *sysfs_lazy;
+
DECLARE_BITMAP(flags, DEV_FLAG_COUNT);
};
+/**
+ * device_is_sysfs_lazy - true if @dev opted into lazy sysfs
+ * @dev: device to query
+ */
+static inline bool device_is_sysfs_lazy(const struct device *dev)
+{
+ return !!dev->sysfs_lazy;
+}
+
+bool device_sysfs_populated(const struct device *dev);
+void device_sysfs_set_populated(struct device *dev);
+int device_set_sysfs_lazy(struct device *dev);
+
#define __create_dev_flag_accessors(accessor_name, flag_name) \
static inline bool dev_##accessor_name(const struct device *dev) \
{ \
--
2.47.3
Amazon Web Services Development Center Germany GmbH
Tamara-Danz-Str. 13
10243 Berlin
Geschaeftsfuehrung: Christof Hellmis, Andreas Stieger
Eingetragen am Amtsgericht Charlottenburg unter HRB 257764 B
Sitz: Berlin
Ust-ID: DE 365 538 597
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 05/14] driver core: add struct device_sysfs_entry and walker
2026-07-02 17:40 [RFC PATCH 00/14] driver core: defer per-VF sysfs creation for fast SR-IOV bring-up Pavol Sakac
` (3 preceding siblings ...)
2026-07-02 17:40 ` [RFC PATCH 04/14] driver core: add struct sysfs_lazy_state and device_set_sysfs_lazy() Pavol Sakac
@ 2026-07-02 17:51 ` Pavol Sakac
2026-07-02 17:51 ` [RFC PATCH 06/14] driver core: wire device_ktype populate to walker Pavol Sakac
` (6 more replies)
2026-07-10 14:16 ` [RFC PATCH 00/14] driver core: defer per-VF sysfs creation for fast SR-IOV bring-up Greg Kroah-Hartman
5 siblings, 7 replies; 15+ messages in thread
From: Pavol Sakac @ 2026-07-02 17:51 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rafael J . Wysocki, Danilo Krummrich,
Tejun Heo
Cc: linux-kernel, driver-core, David Woodhouse, Pasha Tatashin,
Mike Rapoport, Pratyush Yadav, David Matlack, Samiullah Khawaja,
Alexander Graf, linux-mm, kexec, nh-open-source
Introduce the primitive for declaring per-device sysfs content as
table rows and a single walker that dispatches across eager,
lazy-populate, and teardown paths.
struct device_sysfs_entry { name, applies_to, create, remove }
- name == NULL: wildcard row (create() does internal name
dispatch; used for attribute_group-source rows).
- applies_to: NULL or a pure, cheap, non-sleeping predicate.
- create: implements ADD_ONE (name != NULL) and ADD_ALL
(name == NULL) semantics; absorbs -EEXIST from concurrent
racers to 0.
- remove: reverse-order teardown.
device_sysfs_apply(dev, entries, action, name) dispatches
DEV_SYSFS_ADD_ONE / DEV_SYSFS_ADD_ALL / DEV_SYSFS_REMOVE_ALL across
the table. -ENOENT from a wildcard row's create() is treated as
"not my name" and the walker continues; any other error propagates
to the caller on ADD_ONE and is absorbed on ADD_ALL (best-effort).
REMOVE_ALL reverses the table and never aborts. The full contract
is documented in Documentation/driver-api/sysfs-lazy.rst (added
later in this series).
Shape follows cftype + cgroup_addrm_files (kernel/cgroup/cgroup.c)
as the in-tree precedent for table-driven attribute creation.
struct kobj_type grows a trailing .entries pointer; ktypes opt in
by setting it and wiring their populate / populate_all callbacks
to the walker. The field is at end of struct for source-
compatibility with positional initialisers of out-of-tree ktypes.
sysfs_add_file_mode_ns() and sysfs_add_bin_file_mode_ns() are
exported (kerneldoc added) so the walker can absorb -EEXIST from
concurrent create racers without going through sysfs_warn_dup().
Two static helpers - create_attr_in_groups() and
create_all_in_groups() - are added for use by attribute-group
source rows that the migration commit later wires up; they search
@groups by @name and walk every visible attribute respectively.
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Rafael J. Wysocki <rafael@kernel.org>
Cc: Tejun Heo <tj@kernel.org>
Cc: Jonathan Corbet <corbet@lwn.net>
Cc: Bjorn Helgaas <bhelgaas@google.com>
Cc: Danilo Krummrich <dakr@kernel.org>
Cc: linux-kernel@vger.kernel.org
Cc: linux-api@vger.kernel.org
Cc: driver-core@lists.linux.dev
Assisted-by: Claude:claude-opus-4.7
Signed-off-by: Pavol Sakac <sakacpav@amazon.de>
---
drivers/base/core.c | 319 ++++++++++++++++++++++++++++++++++++++++
fs/sysfs/file.c | 41 +++++-
fs/sysfs/sysfs.h | 6 -
include/linux/device.h | 56 +++++++
include/linux/kobject.h | 5 +
include/linux/sysfs.h | 24 +++
6 files changed, 443 insertions(+), 8 deletions(-)
diff --git a/drivers/base/core.c b/drivers/base/core.c
index 6d0d917d4b1ff..aeb4985eb8838 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -2574,6 +2574,325 @@ static void device_release(struct kobject *kobj)
kfree(p);
}
+/*
+ * device_sysfs_apply() - declarative per-device sysfs dispatch
+ *
+ * Single walker over a sentinel-terminated struct device_sysfs_entry
+ * table. Intended to be invoked on every dispatch path (device_add
+ * eager, lazy populate_one, lazy populate_all, device_del teardown)
+ * so adding a new per-device file is one row, not four open-coded
+ * branches. Shape follows cftype + cgroup_addrm_files
+ * (kernel/cgroup/cgroup.c) and pci_sysfs_entries[]
+ * (drivers/pci/pci-sysfs.c).
+ *
+ * ADD_ONE: first row whose applies_to passes and whose name matches
+ * (or a wildcard row whose create() does not return
+ * -ENOENT) terminates the walk. -ENOENT from a wildcard
+ * row's create() signals "not my name"; the walker
+ * continues. Realize callbacks MUST check existence (via
+ * sysfs_*_exists()) before calling sysfs_create_*() and
+ * return 0 if the entry already exists. Reaching
+ * __kernfs_create_*() for a name that another lazy path
+ * just created is forbidden under lock
+ * serialization. If sysfs_warn_dup() ever fires from a
+ * lazy path, it indicates a lock invariant
+ * violation or a non-lazy path creating lazy attrs (bug).
+ * ADD_ALL: every applicable row's create() fires with name = NULL;
+ * per-row errors are best-effort - the walker discards
+ * return values and rows log via their own diagnostics.
+ * REMOVE_ALL: reverse row order; remove() fires for every row whose
+ * applies_to passes. Teardown never aborts.
+ *
+ * The full walker contract (error-absorption matrix, interaction
+ * with negative-dentry caching, lifecycle caveats) is documented in
+ * Documentation/driver-api/sysfs-lazy.rst, added later in this
+ * series.
+ */
+/*
+ * Create one named attr inside @grp; honours grp->is_visible.
+ * Returns 0 on success/hidden/already-present, -ENOENT on no-match.
+ */
+static int create_attr_in_group(struct device *dev,
+ const struct attribute_group *grp,
+ const char *name)
+{
+ struct attribute **a;
+ const struct bin_attribute *const *ba;
+ kuid_t uid;
+ kgid_t gid;
+ umode_t mode;
+ int i;
+
+ if (device_is_sysfs_lazy(dev))
+ lockdep_assert_held(&dev->sysfs_lazy->lock);
+
+ kobject_get_ownership(&dev->kobj, &uid, &gid);
+
+ if (grp->name) {
+ if (!strcmp(grp->name, name)) {
+ if (sysfs_group_exists(&dev->kobj, grp))
+ return 0;
+ return sysfs_create_group(&dev->kobj, grp);
+ }
+ return -ENOENT;
+ }
+
+ if (grp->attrs) {
+ for (i = 0, a = grp->attrs; *a; i++, a++) {
+ if (strcmp((*a)->name, name))
+ continue;
+
+ mode = (*a)->mode;
+ if (grp->is_visible || grp->is_visible_const) {
+ if (grp->is_visible)
+ mode = grp->is_visible(&dev->kobj, *a, i);
+ else
+ mode = grp->is_visible_const(&dev->kobj, *a, i);
+ mode &= ~SYSFS_GROUP_INVISIBLE;
+ if (!mode)
+ return 0; /* hidden */
+ }
+ mode &= SYSFS_PREALLOC | 0664;
+
+ if (sysfs_kn_exists(&dev->kobj, (*a)->name))
+ return 0;
+
+ return sysfs_add_file_mode_ns(dev->kobj.sd, *a,
+ mode, uid, gid, NULL);
+ }
+ }
+ if (grp->bin_attrs) {
+ for (i = 0, ba = grp->bin_attrs; *ba; i++, ba++) {
+ if (strcmp((*ba)->attr.name, name))
+ continue;
+ mode = (*ba)->attr.mode;
+ if (grp->is_bin_visible) {
+ mode = grp->is_bin_visible(&dev->kobj, *ba, i);
+ mode &= ~SYSFS_GROUP_INVISIBLE;
+ if (!mode)
+ return 0;
+ }
+ mode &= SYSFS_PREALLOC | 0664;
+
+ if (sysfs_kn_exists(&dev->kobj, (*ba)->attr.name))
+ return 0;
+
+ return sysfs_add_bin_file_mode_ns(dev->kobj.sd,
+ *ba, mode,
+ (*ba)->size,
+ uid, gid, NULL);
+ }
+ }
+ return -ENOENT;
+}
+
+/*
+ * Per-group incremental creation. Tolerates partial state from
+ * concurrent populate_one. Best-effort: absorbs per-attr failures.
+ */
+static int create_group_incremental(struct device *dev,
+ const struct attribute_group *grp)
+{
+ struct attribute **a;
+ const struct bin_attribute *const *ba;
+ int ret;
+
+ if (device_is_sysfs_lazy(dev))
+ lockdep_assert_held(&dev->sysfs_lazy->lock);
+
+ if (grp->name) {
+ struct kernfs_node *subdir;
+ kuid_t uid;
+ kgid_t gid;
+ umode_t mode;
+ int i;
+
+ subdir = kernfs_find_and_get(dev->kobj.sd, grp->name);
+ if (!subdir)
+ return sysfs_create_group(&dev->kobj, grp);
+
+ kobject_get_ownership(&dev->kobj, &uid, &gid);
+
+ if (grp->attrs) {
+ for (i = 0, a = grp->attrs; *a; i++, a++) {
+ struct kernfs_node *kn;
+
+ mode = (*a)->mode;
+ if (grp->is_visible || grp->is_visible_const) {
+ if (grp->is_visible)
+ mode = grp->is_visible(&dev->kobj,
+ *a, i);
+ else
+ mode = grp->is_visible_const(&dev->kobj,
+ *a, i);
+ mode &= ~SYSFS_GROUP_INVISIBLE;
+ if (!mode)
+ continue;
+ }
+ mode &= SYSFS_PREALLOC | 0664;
+
+ /* Existence check inside named @subdir (not a kobject). */
+ kn = kernfs_find_and_get(subdir, (*a)->name);
+ if (kn) {
+ kernfs_put(kn);
+ continue;
+ }
+
+ ret = sysfs_add_file_mode_ns(subdir, *a,
+ mode, uid,
+ gid, NULL);
+ if (ret)
+ dev_dbg(dev,
+ "lazy %s/%s: %d\n",
+ grp->name, (*a)->name, ret);
+ }
+ }
+ if (grp->bin_attrs) {
+ for (i = 0, ba = grp->bin_attrs; *ba; i++, ba++) {
+ struct kernfs_node *kn;
+
+ mode = (*ba)->attr.mode;
+ if (grp->is_bin_visible) {
+ mode = grp->is_bin_visible(&dev->kobj,
+ *ba, i);
+ mode &= ~SYSFS_GROUP_INVISIBLE;
+ if (!mode)
+ continue;
+ }
+ mode &= SYSFS_PREALLOC | 0664;
+
+ kn = kernfs_find_and_get(subdir, (*ba)->attr.name);
+ if (kn) {
+ kernfs_put(kn);
+ continue;
+ }
+
+ ret = sysfs_add_bin_file_mode_ns(subdir,
+ *ba,
+ mode,
+ (*ba)->size,
+ uid,
+ gid,
+ NULL);
+ if (ret)
+ dev_dbg(dev,
+ "lazy %s/%s: %d\n",
+ grp->name,
+ (*ba)->attr.name, ret);
+ }
+ }
+
+ kernfs_put(subdir);
+ return 0;
+ }
+
+ if (grp->attrs) {
+ for (a = grp->attrs; *a; a++)
+ (void)create_attr_in_group(dev, grp, (*a)->name);
+ }
+ if (grp->bin_attrs) {
+ for (ba = grp->bin_attrs; *ba; ba++)
+ (void)create_attr_in_group(dev, grp,
+ (*ba)->attr.name);
+ }
+ return 0;
+}
+
+/* Search @groups for @name. -ENOENT = not in this source. */
+static int __maybe_unused create_attr_in_groups(struct device *dev,
+ const struct attribute_group *const *groups,
+ const char *name)
+{
+ const struct attribute_group *const *g;
+ int ret;
+
+ if (!groups)
+ return -ENOENT;
+
+ for (g = groups; *g; g++) {
+ ret = create_attr_in_group(dev, *g, name);
+ if (ret != -ENOENT)
+ return ret;
+ }
+ return -ENOENT;
+}
+
+/* Walk @groups creating every visible attribute. Best-effort. */
+static int __maybe_unused create_all_in_groups(struct device *dev,
+ const struct attribute_group *const *groups)
+{
+ const struct attribute_group *const *g;
+
+ if (!groups)
+ return 0;
+
+ for (g = groups; *g; g++)
+ (void)create_group_incremental(dev, *g);
+ return 0;
+}
+
+static const struct device_sysfs_entry *
+device_sysfs_entries_end(const struct device_sysfs_entry *entries)
+{
+ const struct device_sysfs_entry *e = entries;
+
+ while (e->create)
+ e++;
+ return e;
+}
+
+static __maybe_unused int
+device_sysfs_apply(struct device *dev,
+ const struct device_sysfs_entry *entries,
+ enum dev_sysfs_action action, const char *name)
+{
+ const struct device_sysfs_entry *e;
+
+ if (!entries)
+ return action == DEV_SYSFS_ADD_ONE ? -ENOENT : 0;
+
+ if (action == DEV_SYSFS_REMOVE_ALL) {
+ for (e = device_sysfs_entries_end(entries) - 1;
+ e >= entries; e--) {
+ if (e->applies_to && !e->applies_to(dev))
+ continue;
+ if (e->remove)
+ e->remove(dev);
+ }
+ return 0;
+ }
+
+ for (e = entries; e->create; e++) {
+ int ret;
+
+ if (e->applies_to && !e->applies_to(dev))
+ continue;
+ if (action == DEV_SYSFS_ADD_ONE && e->name &&
+ strcmp(e->name, name))
+ continue;
+
+ ret = e->create(dev, action == DEV_SYSFS_ADD_ONE
+ ? name : NULL);
+ if (action == DEV_SYSFS_ADD_ONE) {
+ /*
+ * A named row is the authoritative handler for its
+ * name: return its result even on -ENOENT. A wildcard
+ * row (e->name == NULL) may decline with -ENOENT, so
+ * keep scanning for another handler in that case.
+ */
+ if (e->name || ret != -ENOENT)
+ return ret;
+ }
+ /*
+ * FIXME: ADD_ALL is best-effort and never returns the first create() failure to
+ * the caller, so device_ktype_populate_all() latches populated=true after a
+ * transient failure - the missing attribute is negative-cached forever (a later
+ * lookup returns -ENOENT and never retries), contradicting the sysfs-lazy ABI.
+ */
+ }
+ return action == DEV_SYSFS_ADD_ONE ? -ENOENT : 0;
+}
+
static const struct ns_common *device_namespace(const struct kobject *kobj)
{
const struct device *dev = kobj_to_dev(kobj);
diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c
index 5f3144e52ab72..8d0b8c2c2ee4d 100644
--- a/fs/sysfs/file.c
+++ b/fs/sysfs/file.c
@@ -270,6 +270,25 @@ static const struct kernfs_ops sysfs_bin_kfops_mmap = {
.llseek = sysfs_kf_bin_llseek,
};
+/**
+ * sysfs_add_file_mode_ns - create a sysfs attribute file under @parent
+ * @parent: kernfs node for the parent directory (typically @kobj->sd)
+ * @attr: attribute descriptor; @attr->name is the file name
+ * @mode: file mode (low bits) optionally OR'd with %SYSFS_PREALLOC
+ * @uid: file owner UID
+ * @gid: file owner GID
+ * @ns: namespace tag (may be %NULL)
+ *
+ * Picks a kernfs_ops dispatch row from (prealloc?, has show, has store)
+ * routing read()/write() through @parent->priv->ktype->sysfs_ops.
+ *
+ * Return: 0 on success, or -errno. -EEXIST means the name already
+ * exists and is treated as a caller error (it triggers sysfs_warn_dup()).
+ * The lazy populate path must not reach this: it checks sysfs_kn_exists()/
+ * sysfs_group_exists() under the populate lock before creating, so a
+ * concurrent ADD_ONE/ADD_ALL race resolves to a no-op rather than a
+ * duplicate create.
+ */
int sysfs_add_file_mode_ns(struct kernfs_node *parent,
const struct attribute *attr, umode_t mode, kuid_t uid,
kgid_t gid, const struct ns_common *ns)
@@ -320,6 +339,26 @@ int sysfs_add_file_mode_ns(struct kernfs_node *parent,
return 0;
}
+/**
+ * sysfs_add_bin_file_mode_ns - create a sysfs binary-attribute file
+ * @parent: kernfs node for the parent directory
+ * @battr: binary attribute descriptor
+ * @mode: file mode (low bits)
+ * @size: in-core size of the binary attribute (passed to __kernfs_create_file())
+ * @uid: file owner UID
+ * @gid: file owner GID
+ * @ns: namespace tag (may be %NULL)
+ *
+ * Picks a kernfs_ops dispatch row from a 5-row matrix on @battr's
+ * callbacks (mmap > rw > read > write > empty).
+ *
+ * Return: 0 on success, or -errno. -EEXIST means the name already
+ * exists and is treated as a caller error (it triggers sysfs_warn_dup()).
+ * The lazy populate path must not reach this: it checks sysfs_kn_exists()/
+ * sysfs_group_exists() under the populate lock before creating, so a
+ * concurrent ADD_ONE/ADD_ALL race resolves to a no-op rather than a
+ * duplicate create.
+ */
int sysfs_add_bin_file_mode_ns(struct kernfs_node *parent,
const struct bin_attribute *battr, umode_t mode, size_t size,
kuid_t uid, kgid_t gid, const struct ns_common *ns)
@@ -816,5 +855,3 @@ ssize_t sysfs_bin_attr_simple_read(struct file *file, struct kobject *kobj,
return count;
}
EXPORT_SYMBOL_GPL(sysfs_bin_attr_simple_read);
-
-
diff --git a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h
index f4583dcafcd1e..94b8aca20bffc 100644
--- a/fs/sysfs/sysfs.h
+++ b/fs/sysfs/sysfs.h
@@ -27,12 +27,6 @@ void sysfs_warn_dup(struct kernfs_node *parent, const char *name);
/*
* file.c
*/
-int sysfs_add_file_mode_ns(struct kernfs_node *parent,
- const struct attribute *attr, umode_t amode, kuid_t uid,
- kgid_t gid, const struct ns_common *ns);
-int sysfs_add_bin_file_mode_ns(struct kernfs_node *parent,
- const struct bin_attribute *battr, umode_t mode, size_t size,
- kuid_t uid, kgid_t gid, const struct ns_common *ns);
/*
* symlink.c
diff --git a/include/linux/device.h b/include/linux/device.h
index 39e08e8c950c6..e5485bfbc6c84 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -88,6 +88,62 @@ int subsys_system_register(const struct bus_type *subsys,
int subsys_virtual_register(const struct bus_type *subsys,
const struct attribute_group **groups);
+/**
+ * enum dev_sysfs_action - verb selector for device_sysfs_apply()
+ * @DEV_SYSFS_ADD_ONE: realize a single named entry (lazy populate_one
+ * miss path); walker stops at the first row whose
+ * @applies_to passes and whose create() returns
+ * anything other than -ENOENT.
+ * @DEV_SYSFS_ADD_ALL: realize every applicable entry (eager device_add
+ * or lazy populate_all); best-effort, per-row errors
+ * do not abort the walk.
+ * @DEV_SYSFS_REMOVE_ALL: tear down every applicable entry in reverse
+ * row order (device_del teardown).
+ */
+enum dev_sysfs_action {
+ DEV_SYSFS_ADD_ONE,
+ DEV_SYSFS_ADD_ALL,
+ DEV_SYSFS_REMOVE_ALL,
+};
+
+/**
+ * struct device_sysfs_entry - declarative per-device sysfs content row
+ * @name: Attribute or symlink name at the top level of the device's
+ * sysfs directory, or %NULL for a wildcard row whose
+ * create() performs internal name dispatch (typically a
+ * group-source row iterating an attribute_group array).
+ * @applies_to: Optional predicate gating row eligibility for this
+ * device. Must be cheap, non-sleeping, side-effect-free, and
+ * monotonic for a given device state. MUST NOT acquire any
+ * lock the walker's caller may already hold (notably
+ * device_lock()). %NULL means unconditional.
+ * @create: Required. Creates the row's sysfs content. For a named
+ * row (@name != NULL) the @name argument equals @e->name on
+ * ADD_ONE and %NULL on ADD_ALL. For a wildcard row the @name
+ * argument is the ADD_ONE target (or %NULL on ADD_ALL) and
+ * the row's create() matches internally, returning -ENOENT
+ * to signal "not my name" so the walker continues. Must
+ * convert -EEXIST from concurrent racers to 0.
+ * @remove: Optional reverse-of-realize teardown. Called on
+ * REMOVE_ALL in reverse row order. MUST NOT fail the walk.
+ *
+ * Rows are declared in file-static, sentinel-terminated tables whose
+ * base pointer is published to the walker via struct kobj_type.entries
+ * (device ktype shares a single driver_core table and dispatches to
+ * dev->type->entries as well). The row shape mirrors cftype +
+ * cgroup_addrm_files (kernel/cgroup/cgroup.c) and the
+ * pci_sysfs_entries[] at drivers/pci/pci-sysfs.c (added later in
+ * the series); see Documentation/driver-api/sysfs-lazy.rst (also
+ * added later in the series) for the full walker contract and
+ * error-handling matrix.
+ */
+struct device_sysfs_entry {
+ const char *name;
+ bool (*applies_to)(struct device *dev);
+ int (*create)(struct device *dev, const char *name);
+ void (*remove)(struct device *dev);
+};
+
/*
* The type of device, "struct device" is embedded in. A class
* or bus can contain devices of different types
diff --git a/include/linux/kobject.h b/include/linux/kobject.h
index cec6de1720076..c724d86e1a049 100644
--- a/include/linux/kobject.h
+++ b/include/linux/kobject.h
@@ -61,6 +61,8 @@ enum kobject_action {
KOBJ_UNBIND,
};
+struct device_sysfs_entry;
+
struct kobject {
const char *name;
struct list_head entry;
@@ -130,6 +132,9 @@ struct kobj_type {
*/
int (*populate)(struct kobject *kobj, const char *name);
void (*populate_all)(struct kobject *kobj);
+
+ /* Optional table of per-attribute entries (see device_sysfs_apply()). */
+ const struct device_sysfs_entry *entries;
};
struct kobj_uevent_env {
diff --git a/include/linux/sysfs.h b/include/linux/sysfs.h
index de211563f3dec..aac2f773d5378 100644
--- a/include/linux/sysfs.h
+++ b/include/linux/sysfs.h
@@ -415,6 +415,12 @@ int __must_check sysfs_create_files(struct kobject *kobj,
const struct attribute * const *attr);
int __must_check sysfs_chmod_file(struct kobject *kobj,
const struct attribute *attr, umode_t mode);
+int __must_check sysfs_add_file_mode_ns(struct kernfs_node *parent,
+ const struct attribute *attr, umode_t amode, kuid_t uid,
+ kgid_t gid, const struct ns_common *ns);
+int __must_check sysfs_add_bin_file_mode_ns(struct kernfs_node *parent,
+ const struct bin_attribute *battr, umode_t mode, size_t size,
+ kuid_t uid, kgid_t gid, const struct ns_common *ns);
struct kernfs_node *sysfs_break_active_protection(struct kobject *kobj,
const struct attribute *attr);
void sysfs_unbreak_active_protection(struct kernfs_node *kn);
@@ -558,6 +564,24 @@ static inline int sysfs_chmod_file(struct kobject *kobj,
return 0;
}
+static inline int sysfs_add_file_mode_ns(struct kernfs_node *parent,
+ const struct attribute *attr,
+ umode_t amode, kuid_t uid,
+ kgid_t gid,
+ const struct ns_common *ns)
+{
+ return 0;
+}
+
+static inline int sysfs_add_bin_file_mode_ns(struct kernfs_node *parent,
+ const struct bin_attribute *battr,
+ umode_t mode, size_t size,
+ kuid_t uid, kgid_t gid,
+ const struct ns_common *ns)
+{
+ return 0;
+}
+
static inline struct kernfs_node *
sysfs_break_active_protection(struct kobject *kobj,
const struct attribute *attr)
--
2.47.3
Amazon Web Services Development Center Germany GmbH
Tamara-Danz-Str. 13
10243 Berlin
Geschaeftsfuehrung: Christof Hellmis, Andreas Stieger
Eingetragen am Amtsgericht Charlottenburg unter HRB 257764 B
Sitz: Berlin
Ust-ID: DE 365 538 597
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 06/14] driver core: wire device_ktype populate to walker
2026-07-02 17:51 ` [RFC PATCH 05/14] driver core: add struct device_sysfs_entry and walker Pavol Sakac
@ 2026-07-02 17:51 ` Pavol Sakac
2026-07-02 17:51 ` [RFC PATCH 07/14] driver core: migrate device sysfs to device_sysfs_entry table Pavol Sakac
` (5 subsequent siblings)
6 siblings, 0 replies; 15+ messages in thread
From: Pavol Sakac @ 2026-07-02 17:51 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rafael J . Wysocki, Danilo Krummrich,
Tejun Heo
Cc: linux-kernel, driver-core, David Woodhouse, Pasha Tatashin,
Mike Rapoport, Pratyush Yadav, David Matlack, Samiullah Khawaja,
Alexander Graf, linux-mm, kexec, nh-open-source
Wire the kernfs syscall callback shape (kobject *, const char *)
to the device-level walker. Add device_ktype_populate_one() and
device_ktype_populate_all() adapters that dispatch through
device_sysfs_apply() over dev->kobj.ktype->entries (currently
NULL; the migration commit installs driver_core_sysfs_entries[]).
The adapters early-return when dev->p->dead is set so a populate
racing with device_del() is a no-op.
No behaviour change: the table is empty until the migration commit.
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Rafael J. Wysocki <rafael@kernel.org>
Cc: Tejun Heo <tj@kernel.org>
Cc: Danilo Krummrich <dakr@kernel.org>
Cc: linux-kernel@vger.kernel.org
Cc: driver-core@lists.linux.dev
Cc: linux-api@vger.kernel.org
Assisted-by: Claude:claude-opus-4.7
Signed-off-by: Pavol Sakac <sakacpav@amazon.de>
---
drivers/base/core.c | 50 +++++++++++++++++++++++++++++++++++++++++----
1 file changed, 46 insertions(+), 4 deletions(-)
diff --git a/drivers/base/core.c b/drivers/base/core.c
index aeb4985eb8838..975b6e0c4dabd 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -33,6 +33,7 @@
#include <linux/swiotlb.h>
#include <linux/sysfs.h>
+
#include "base.h"
#include "physical_location.h"
#include "power/power.h"
@@ -2841,10 +2842,9 @@ device_sysfs_entries_end(const struct device_sysfs_entry *entries)
return e;
}
-static __maybe_unused int
-device_sysfs_apply(struct device *dev,
- const struct device_sysfs_entry *entries,
- enum dev_sysfs_action action, const char *name)
+static int device_sysfs_apply(struct device *dev,
+ const struct device_sysfs_entry *entries,
+ enum dev_sysfs_action action, const char *name)
{
const struct device_sysfs_entry *e;
@@ -2972,11 +2972,53 @@ int device_set_sysfs_lazy(struct device *dev)
}
EXPORT_SYMBOL_GPL(device_set_sysfs_lazy);
+/*
+ * kobj_type populate adapters - thin wrappers from the kobject
+ * callback shape (kobject *, const char *) to the device-level
+ * walker. Both read dev->kobj.ktype->entries (which is currently
+ * NULL until driver_core_sysfs_entries[] is populated below) and
+ * call device_sysfs_apply() with the matching action.
+ *
+ * The walker traverses an empty table today, so these adapters are
+ * no-ops in behavioural terms. They are wired now so that the
+ * ktype plumbing lands as one reviewable unit; subsequent commits
+ * flip rows on without touching dispatch wiring.
+ */
+static int device_ktype_populate_one(struct kobject *kobj, const char *name)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ const struct kobj_type *ktype = dev->kobj.ktype;
+ int ret;
+
+ /* Device is being torn down; do not populate. */
+ if (dev->p->dead)
+ return -ENOENT;
+
+ ret = device_sysfs_apply(dev, ktype ? ktype->entries : NULL,
+ DEV_SYSFS_ADD_ONE, name);
+ return ret;
+}
+
+static void device_ktype_populate_all(struct kobject *kobj)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ const struct kobj_type *ktype = dev->kobj.ktype;
+
+ /* Device is being torn down; do not populate. */
+ if (dev->p->dead)
+ return;
+
+ device_sysfs_apply(dev, ktype ? ktype->entries : NULL,
+ DEV_SYSFS_ADD_ALL, NULL);
+}
+
static const struct kobj_type device_ktype = {
.release = device_release,
.sysfs_ops = &dev_sysfs_ops,
.namespace = device_namespace,
.get_ownership = device_get_ownership,
+ .populate = device_ktype_populate_one,
+ .populate_all = device_ktype_populate_all,
};
--
2.47.3
Amazon Web Services Development Center Germany GmbH
Tamara-Danz-Str. 13
10243 Berlin
Geschaeftsfuehrung: Christof Hellmis, Andreas Stieger
Eingetragen am Amtsgericht Charlottenburg unter HRB 257764 B
Sitz: Berlin
Ust-ID: DE 365 538 597
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 07/14] driver core: migrate device sysfs to device_sysfs_entry table
2026-07-02 17:51 ` [RFC PATCH 05/14] driver core: add struct device_sysfs_entry and walker Pavol Sakac
2026-07-02 17:51 ` [RFC PATCH 06/14] driver core: wire device_ktype populate to walker Pavol Sakac
@ 2026-07-02 17:51 ` Pavol Sakac
2026-07-02 17:51 ` [RFC PATCH 08/14] PCI/sysfs: migrate to device_sysfs_entry, defer physfn symlink Pavol Sakac
` (4 subsequent siblings)
6 siblings, 0 replies; 15+ messages in thread
From: Pavol Sakac @ 2026-07-02 17:51 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rafael J . Wysocki, Danilo Krummrich,
Tejun Heo
Cc: linux-kernel, driver-core, David Woodhouse, Pasha Tatashin,
Mike Rapoport, Pratyush Yadav, David Matlack, Samiullah Khawaja,
Alexander Graf, linux-mm, kexec, nh-open-source
Migrate driver-core-owned per-device sysfs to the declarative table
introduced earlier in this series. Every standalone file, synthetic
symlink, and attribute_group source visible at the top of /sys/<dev>/
becomes one row in driver_core_sysfs_entries[]. Row order matches
the eager device_add() readdir(3) order (ABI). The walker runs
ADD_ALL forward in device_add() and REMOVE_ALL reverse in
device_del().
power/ stays eager. dpm_sysfs_add() runs unconditionally from
device_add(), for lazy devices too. In-kernel callers
(wakeup_sysfs_add(), dev_pm_qos_*()) sysfs_merge_group() into an
existing power/ directory at driver bind, before any userspace
access, so deferring power/ would break those merges. device_add()
sets ->power_added on lazy devices; the power/ table row then no-ops
in create_power() (the latch is already set) and remove_power() is
its reverse-order teardown pair, calling dpm_sysfs_remove() only
when power/ was actually added. Eager devices (->sysfs_lazy == NULL)
also get power/ at device_add() time, and create_power() returns
early for them.
device_del REMOVE_ALL. device_del() and the SysEntryError unwind
path drop the imperative dpm_sysfs_remove() call and reverse the
table via REMOVE_ALL. As a consequence the power/ subtree is now
torn down LAST (it is declared FIRST in the table and the walker
runs in reverse on REMOVE_ALL); the device is already dead
(dev->p->dead under device_lock) before the walker runs, so no
caller can observe a partially-torn-down state.
bus_add_device / bus_remove_device. The bus's dev_groups and the
dev->kobj/subsystem symlink are now materialised by walker rows
(create_bus_groups, create_bus_subsystem_link).
bus_add_device() and bus_remove_device() keep only the bus-
directory back-link (sp->devices_kset->kobj/<devname>) which
targets a kobject other than dev->kobj and therefore cannot be a
per-device row.
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Rafael J. Wysocki <rafael@kernel.org>
Cc: Tejun Heo <tj@kernel.org>
Cc: Danilo Krummrich <dakr@kernel.org>
Cc: linux-kernel@vger.kernel.org
Cc: driver-core@lists.linux.dev
Cc: linux-api@vger.kernel.org
Assisted-by: Claude:claude-opus-4.7
Signed-off-by: Pavol Sakac <sakacpav@amazon.de>
---
drivers/base/bus.c | 41 ++-
drivers/base/core.c | 708 +++++++++++++++++++++++++++++++----------
fs/sysfs/sysfs.h | 1 +
include/linux/device.h | 50 +--
4 files changed, 578 insertions(+), 222 deletions(-)
diff --git a/drivers/base/bus.c b/drivers/base/bus.c
index 8b6722ff8590d..4ca1bd0cf4904 100644
--- a/drivers/base/bus.c
+++ b/drivers/base/bus.c
@@ -563,34 +563,35 @@ int bus_add_device(struct device *dev)
pr_debug("bus: '%s': add device %s\n", sp->bus->name, dev_name(dev));
- error = device_add_groups(dev, sp->bus->dev_groups);
+ error = sysfs_create_link(&sp->devices_kset->kobj, &dev->kobj, dev_name(dev));
if (error)
goto out_put;
if (dev->bus->driver_override) {
error = device_add_group(dev, &driver_override_dev_group);
if (error)
- goto out_groups;
+ goto out_kset;
}
- error = sysfs_create_link(&sp->devices_kset->kobj, &dev->kobj, dev_name(dev));
- if (error)
- goto out_override;
-
- error = sysfs_create_link(&dev->kobj, &sp->subsys.kobj, "subsystem");
- if (error)
- goto out_subsys;
+ /*
+ * The bus's dev_groups and the dev->kobj/subsystem symlink
+ * are created by the driver-core walker through the
+ * bus-groups wildcard row and the bus-bound "subsystem" row
+ * of driver_core_sysfs_entries[] (see create_bus_groups()
+ * and create_bus_subsystem_link() in drivers/base/core.c);
+ * teardown runs through the REMOVE_ALL walker call in
+ * device_del(). Only the bus-directory back-link
+ * (sp->devices_kset->kobj -> dev->kobj, named after the
+ * device) stays here - it targets a kobject other than
+ * dev->kobj and therefore does not belong on the per-device
+ * walker.
+ */
klist_add_tail(&dev->p->knode_bus, &sp->klist_devices);
return 0;
-out_subsys:
+out_kset:
sysfs_remove_link(&sp->devices_kset->kobj, dev_name(dev));
-out_override:
- if (dev->bus->driver_override)
- device_remove_group(dev, &driver_override_dev_group);
-out_groups:
- device_remove_groups(dev, sp->bus->dev_groups);
out_put:
subsys_put(sp);
return error;
@@ -644,11 +645,17 @@ void bus_remove_device(struct device *dev)
sif->remove_dev(dev, sif);
mutex_unlock(&sp->mutex);
- sysfs_remove_link(&dev->kobj, "subsystem");
+ /*
+ * dev->kobj/subsystem and the bus's dev_groups are torn
+ * down by the driver-core REMOVE_ALL walker call in
+ * device_del() (reverse order over
+ * driver_core_sysfs_entries[]); only the bus-directory
+ * back-link sp->devices_kset->kobj/<devname> is removed
+ * here because it targets a kobject other than dev->kobj.
+ */
sysfs_remove_link(&sp->devices_kset->kobj, dev_name(dev));
if (dev->bus->driver_override)
device_remove_group(dev, &driver_override_dev_group);
- device_remove_groups(dev, dev->bus->dev_groups);
if (klist_node_attached(&dev->p->knode_bus))
klist_del(&dev->p->knode_bus);
diff --git a/drivers/base/core.c b/drivers/base/core.c
index 975b6e0c4dabd..15a2dcf922d4b 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -2556,12 +2556,15 @@ static void device_release(struct kobject *kobj)
*/
devres_release_all(dev);
+ kfree(dev->dma_range_map);
+ kfree(dev->physical_location);
+ kfree(dev->driver_override.name);
+
+ /* Free per-device lazy state; kfree(NULL) handles never-opted-in. */
if (dev->sysfs_lazy)
mutex_destroy(&dev->sysfs_lazy->lock);
kfree(dev->sysfs_lazy);
-
- kfree(dev->dma_range_map);
- kfree(dev->driver_override.name);
+ dev->sysfs_lazy = NULL;
if (dev->release)
dev->release(dev);
@@ -2575,40 +2578,6 @@ static void device_release(struct kobject *kobj)
kfree(p);
}
-/*
- * device_sysfs_apply() - declarative per-device sysfs dispatch
- *
- * Single walker over a sentinel-terminated struct device_sysfs_entry
- * table. Intended to be invoked on every dispatch path (device_add
- * eager, lazy populate_one, lazy populate_all, device_del teardown)
- * so adding a new per-device file is one row, not four open-coded
- * branches. Shape follows cftype + cgroup_addrm_files
- * (kernel/cgroup/cgroup.c) and pci_sysfs_entries[]
- * (drivers/pci/pci-sysfs.c).
- *
- * ADD_ONE: first row whose applies_to passes and whose name matches
- * (or a wildcard row whose create() does not return
- * -ENOENT) terminates the walk. -ENOENT from a wildcard
- * row's create() signals "not my name"; the walker
- * continues. Realize callbacks MUST check existence (via
- * sysfs_*_exists()) before calling sysfs_create_*() and
- * return 0 if the entry already exists. Reaching
- * __kernfs_create_*() for a name that another lazy path
- * just created is forbidden under lock
- * serialization. If sysfs_warn_dup() ever fires from a
- * lazy path, it indicates a lock invariant
- * violation or a non-lazy path creating lazy attrs (bug).
- * ADD_ALL: every applicable row's create() fires with name = NULL;
- * per-row errors are best-effort - the walker discards
- * return values and rows log via their own diagnostics.
- * REMOVE_ALL: reverse row order; remove() fires for every row whose
- * applies_to passes. Teardown never aborts.
- *
- * The full walker contract (error-absorption matrix, interaction
- * with negative-dentry caching, lifecycle caveats) is documented in
- * Documentation/driver-api/sysfs-lazy.rst, added later in this
- * series.
- */
/*
* Create one named attr inside @grp; honours grp->is_visible.
* Returns 0 on success/hidden/already-present, -ENOENT on no-match.
@@ -2990,12 +2959,32 @@ static int device_ktype_populate_one(struct kobject *kobj, const char *name)
const struct kobj_type *ktype = dev->kobj.ktype;
int ret;
- /* Device is being torn down; do not populate. */
- if (dev->p->dead)
+ /* KERNFS_LAZY implies dev->sysfs_lazy was allocated by device_set_sysfs_lazy(). */
+ if (WARN_ON_ONCE(!dev->sysfs_lazy))
+ return -ENOENT;
+
+ /* Fast path: directory fully walked, kernfs has authoritative state. */
+ if (device_sysfs_populated(dev))
return -ENOENT;
+ mutex_lock(&dev->sysfs_lazy->lock);
+ /* Re-check under the lock against a concurrent populate_all. */
+ if (device_sysfs_populated(dev)) {
+ ret = -ENOENT;
+ goto out;
+ }
+ /*
+ * FIXME: dev->p->dead is device_lock-protected and a bitfield (so not
+ * READ_ONCE()-able); this lockless re-check is a benign KCSAN data race.
+ */
+ if (dev->p->dead) {
+ ret = -ENOENT;
+ goto out;
+ }
ret = device_sysfs_apply(dev, ktype ? ktype->entries : NULL,
DEV_SYSFS_ADD_ONE, name);
+out:
+ mutex_unlock(&dev->sysfs_lazy->lock);
return ret;
}
@@ -3004,14 +2993,33 @@ static void device_ktype_populate_all(struct kobject *kobj)
struct device *dev = kobj_to_dev(kobj);
const struct kobj_type *ktype = dev->kobj.ktype;
- /* Device is being torn down; do not populate. */
- if (dev->p->dead)
+ /* See device_ktype_populate_one() for the invariant. */
+ if (WARN_ON_ONCE(!dev->sysfs_lazy))
+ return;
+
+ /* Fast path: directory already fully populated. */
+ if (device_sysfs_populated(dev))
return;
+ mutex_lock(&dev->sysfs_lazy->lock);
+ /* Re-check under the lock. */
+ if (device_sysfs_populated(dev))
+ goto out;
+ /* FIXME: same racy dead read; see device_ktype_populate_one(). */
+ if (dev->p->dead)
+ goto out;
+
device_sysfs_apply(dev, ktype ? ktype->entries : NULL,
DEV_SYSFS_ADD_ALL, NULL);
+
+
+ device_sysfs_set_populated(dev);
+out:
+ mutex_unlock(&dev->sysfs_lazy->lock);
}
+static const struct device_sysfs_entry driver_core_sysfs_entries[];
+
static const struct kobj_type device_ktype = {
.release = device_release,
.sysfs_ops = &dev_sysfs_ops,
@@ -3019,6 +3027,7 @@ static const struct kobj_type device_ktype = {
.get_ownership = device_get_ownership,
.populate = device_ktype_populate_one,
.populate_all = device_ktype_populate_all,
+ .entries = driver_core_sysfs_entries,
};
@@ -3271,6 +3280,25 @@ void device_remove_groups(struct device *dev,
}
EXPORT_SYMBOL_GPL(device_remove_groups);
+/* Like device_remove_groups() but skips uncreated named subdirs (lazy). */
+static void device_remove_groups_if_present(struct device *dev,
+ const struct attribute_group *const *groups)
+{
+ const struct attribute_group *const *g;
+
+ if (!groups)
+ return;
+
+ if (device_is_sysfs_lazy(dev))
+ lockdep_assert_held(&dev->sysfs_lazy->lock);
+
+ for (g = groups; *g; g++) {
+ if ((*g)->name && !sysfs_group_exists(&dev->kobj, *g))
+ continue;
+ sysfs_remove_group(&dev->kobj, *g);
+ }
+}
+
union device_attr_group_devres {
const struct attribute_group *group;
const struct attribute_group **groups;
@@ -3317,101 +3345,447 @@ int devm_device_add_group(struct device *dev, const struct attribute_group *grp)
}
EXPORT_SYMBOL_GPL(devm_device_add_group);
-static int device_add_attrs(struct device *dev)
+static ssize_t dev_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
{
- const struct class *class = dev->class;
- const struct device_type *type = dev->type;
- int error;
+ return print_dev_t(buf, dev->devt);
+}
+static DEVICE_ATTR_RO(dev);
- if (class) {
- error = device_add_groups(dev, class->dev_groups);
- if (error)
- return error;
- }
+/* Per-device sysfs catalogue (creation order; reversed on teardown). */
+#define N_DRIVER_CORE_SYSFS_ENTRIES 14
- if (type) {
- error = device_add_groups(dev, type->groups);
- if (error)
- goto err_remove_class_groups;
- }
+/* applies_to() predicates: cheap, non-sleeping, lock-free. */
+static bool dev_supports_offline_enabled(struct device *dev)
+{
+ return device_supports_offline(dev) && !dev->offline_disabled;
+}
- error = device_add_groups(dev, dev->groups);
- if (error)
- goto err_remove_type_groups;
+static bool dev_has_fwnode_devlink(struct device *dev)
+{
+ return fw_devlink_flags && !fw_devlink_is_permissive() &&
+ dev->fwnode;
+}
- if (device_supports_offline(dev) && !dev->offline_disabled) {
- error = device_create_file(dev, &dev_attr_online);
- if (error)
- goto err_remove_dev_groups;
- }
+static bool dev_removable_valid(struct device *dev)
+{
+ return dev_removable_is_valid(dev);
+}
- if (fw_devlink_flags && !fw_devlink_is_permissive() && dev->fwnode) {
- error = device_create_file(dev, &dev_attr_waiting_for_supplier);
- if (error)
- goto err_remove_dev_online;
- }
+static bool dev_has_physical_location(struct device *dev)
+{
+ return !!dev->physical_location;
+}
- if (dev_removable_is_valid(dev)) {
- error = device_create_file(dev, &dev_attr_removable);
- if (error)
- goto err_remove_dev_waiting_for_supplier;
- }
+static bool dev_has_devt_major(struct device *dev)
+{
+ return MAJOR(dev->devt) != 0;
+}
- if (dev_add_physical_location(dev)) {
- error = device_add_group(dev,
- &dev_attr_physical_location_group);
- if (error)
- goto err_remove_dev_removable;
- }
+static bool dev_has_class(struct device *dev)
+{
+ return !!dev->class;
+}
- return 0;
+static bool dev_has_bus_no_class(struct device *dev)
+{
+ return !dev->class && dev->bus;
+}
- err_remove_dev_removable:
- device_remove_file(dev, &dev_attr_removable);
- err_remove_dev_waiting_for_supplier:
- device_remove_file(dev, &dev_attr_waiting_for_supplier);
- err_remove_dev_online:
+static bool dev_is_class_child(struct device *dev)
+{
+ return dev->class && dev->parent && device_is_not_partition(dev);
+}
+
+static bool dev_has_class_dev_groups(struct device *dev)
+{
+ return dev->class && dev->class->dev_groups;
+}
+
+static bool dev_has_type_groups(struct device *dev)
+{
+ return dev->type && dev->type->groups;
+}
+
+static bool dev_has_own_groups(struct device *dev)
+{
+ return !!dev->groups;
+}
+
+static bool dev_has_bus_dev_groups(struct device *dev)
+{
+ return dev->bus && dev->bus->dev_groups;
+}
+
+static bool dev_needs_pm(struct device *dev)
+{
+ return !device_pm_not_required(dev);
+}
+
+/* Per-row create()/remove() callbacks. */
+static int create_power(struct device *dev, const char *name)
+{
+ int ret;
+
+ /* dpm_sysfs_add() already ran from device_add() on the eager path. */
+ if (!device_is_sysfs_lazy(dev))
+ return 0;
+
+ lockdep_assert_held(&dev->sysfs_lazy->lock);
+
+ if (dev->sysfs_lazy->power_added)
+ return 0;
+
+ /*
+ * FIXME: unreachable today - device_add() adds power/ eagerly and latches
+ * power_added for lazy devices too, so the power_added check above wins.
+ */
+ ret = dpm_sysfs_add(dev);
+ if (!ret)
+ dev->sysfs_lazy->power_added = true;
+ return ret;
+}
+
+static void remove_power(struct device *dev)
+{
+ /* Skip dpm_sysfs_remove() on lazy devices that never materialised power/. */
+ if (device_is_sysfs_lazy(dev) && !dev->sysfs_lazy->power_added)
+ return;
+ dpm_sysfs_remove(dev);
+}
+
+static int create_class_subsystem_link(struct device *dev, const char *name)
+{
+ struct subsys_private *sp;
+ int ret;
+
+ if (device_is_sysfs_lazy(dev))
+ lockdep_assert_held(&dev->sysfs_lazy->lock);
+
+ if (sysfs_kn_exists(&dev->kobj, "subsystem"))
+ return 0;
+
+ sp = class_to_subsys(dev->class);
+ if (!sp)
+ return 0;
+
+ ret = sysfs_create_link(&dev->kobj, &sp->subsys.kobj, "subsystem");
+ subsys_put(sp);
+ return ret;
+}
+
+static void remove_class_subsystem_link(struct device *dev)
+{
+ sysfs_remove_link(&dev->kobj, "subsystem");
+}
+
+static int create_bus_subsystem_link(struct device *dev, const char *name)
+{
+ struct subsys_private *sp;
+ int ret;
+
+ if (device_is_sysfs_lazy(dev))
+ lockdep_assert_held(&dev->sysfs_lazy->lock);
+
+ if (sysfs_kn_exists(&dev->kobj, "subsystem"))
+ return 0;
+
+ sp = bus_to_subsys(dev->bus);
+ if (!sp)
+ return 0;
+
+ ret = sysfs_create_link(&dev->kobj, &sp->subsys.kobj, "subsystem");
+ subsys_put(sp);
+ return ret;
+}
+
+static void remove_bus_subsystem_link(struct device *dev)
+{
+ sysfs_remove_link(&dev->kobj, "subsystem");
+}
+
+static int create_device_backlink(struct device *dev, const char *name)
+{
+ if (device_is_sysfs_lazy(dev))
+ lockdep_assert_held(&dev->sysfs_lazy->lock);
+
+ if (sysfs_kn_exists(&dev->kobj, "device"))
+ return 0;
+
+ return sysfs_create_link(&dev->kobj, &dev->parent->kobj, "device");
+}
+
+static void remove_device_backlink(struct device *dev)
+{
+ sysfs_remove_link(&dev->kobj, "device");
+}
+
+static int create_uevent(struct device *dev, const char *name)
+{
+ if (device_is_sysfs_lazy(dev))
+ lockdep_assert_held(&dev->sysfs_lazy->lock);
+
+ if (sysfs_kn_exists(&dev->kobj, dev_attr_uevent.attr.name))
+ return 0;
+
+ return sysfs_create_file_ns(&dev->kobj, &dev_attr_uevent.attr, NULL);
+}
+
+static void remove_uevent(struct device *dev)
+{
+ device_remove_file(dev, &dev_attr_uevent);
+}
+
+static int create_online(struct device *dev, const char *name)
+{
+ if (device_is_sysfs_lazy(dev))
+ lockdep_assert_held(&dev->sysfs_lazy->lock);
+
+ if (sysfs_kn_exists(&dev->kobj, dev_attr_online.attr.name))
+ return 0;
+
+ return sysfs_create_file_ns(&dev->kobj, &dev_attr_online.attr, NULL);
+}
+
+static void remove_online(struct device *dev)
+{
device_remove_file(dev, &dev_attr_online);
- err_remove_dev_groups:
- device_remove_groups(dev, dev->groups);
- err_remove_type_groups:
- if (type)
- device_remove_groups(dev, type->groups);
- err_remove_class_groups:
- if (class)
- device_remove_groups(dev, class->dev_groups);
+}
- return error;
+static int create_waiting_for_supplier(struct device *dev, const char *name)
+{
+ if (device_is_sysfs_lazy(dev))
+ lockdep_assert_held(&dev->sysfs_lazy->lock);
+
+ if (sysfs_kn_exists(&dev->kobj, dev_attr_waiting_for_supplier.attr.name))
+ return 0;
+
+ return sysfs_create_file_ns(&dev->kobj,
+ &dev_attr_waiting_for_supplier.attr,
+ NULL);
}
-static void device_remove_attrs(struct device *dev)
+static void remove_waiting_for_supplier(struct device *dev)
{
- const struct class *class = dev->class;
- const struct device_type *type = dev->type;
+ device_remove_file(dev, &dev_attr_waiting_for_supplier);
+}
- if (dev->physical_location) {
- device_remove_group(dev, &dev_attr_physical_location_group);
- kfree(dev->physical_location);
- }
+static int create_removable(struct device *dev, const char *name)
+{
+ if (device_is_sysfs_lazy(dev))
+ lockdep_assert_held(&dev->sysfs_lazy->lock);
+ if (sysfs_kn_exists(&dev->kobj, dev_attr_removable.attr.name))
+ return 0;
+
+ return sysfs_create_file_ns(&dev->kobj, &dev_attr_removable.attr, NULL);
+}
+
+static void remove_removable(struct device *dev)
+{
device_remove_file(dev, &dev_attr_removable);
- device_remove_file(dev, &dev_attr_waiting_for_supplier);
- device_remove_file(dev, &dev_attr_online);
- device_remove_groups(dev, dev->groups);
+}
- if (type)
- device_remove_groups(dev, type->groups);
+static int create_physical_location(struct device *dev, const char *name)
+{
+ if (device_is_sysfs_lazy(dev))
+ lockdep_assert_held(&dev->sysfs_lazy->lock);
+
+ /* NULL on non-ACPI nodes; safe under kobj reference. */
+ if (!dev->physical_location)
+ return -ENOENT;
- if (class)
- device_remove_groups(dev, class->dev_groups);
+ if (sysfs_group_exists(&dev->kobj, &dev_attr_physical_location_group))
+ return 0;
+
+ return sysfs_create_group(&dev->kobj, &dev_attr_physical_location_group);
}
-static ssize_t dev_show(struct device *dev, struct device_attribute *attr,
- char *buf)
+static void remove_physical_location(struct device *dev)
{
- return print_dev_t(buf, dev->devt);
+ /* dev->physical_location is freed in device_release(), not here. */
+ device_remove_group(dev, &dev_attr_physical_location_group);
+}
+
+static int create_dev_attr(struct device *dev, const char *name)
+{
+ if (device_is_sysfs_lazy(dev))
+ lockdep_assert_held(&dev->sysfs_lazy->lock);
+
+ if (sysfs_kn_exists(&dev->kobj, dev_attr_dev.attr.name))
+ return 0;
+
+ return sysfs_create_file_ns(&dev->kobj, &dev_attr_dev.attr, NULL);
+}
+
+static void remove_dev_attr(struct device *dev)
+{
+ device_remove_file(dev, &dev_attr_dev);
+}
+
+/* Wildcard rows for the four attribute_group sources. */
+static int create_class_groups(struct device *dev, const char *name)
+{
+ if (!dev->class)
+ return -ENOENT;
+ return name ? create_attr_in_groups(dev, dev->class->dev_groups, name)
+ : create_all_in_groups(dev, dev->class->dev_groups);
+}
+
+static void remove_class_groups(struct device *dev)
+{
+ if (!dev->class)
+ return;
+ if (device_is_sysfs_lazy(dev))
+ device_remove_groups_if_present(dev, dev->class->dev_groups);
+ else
+ device_remove_groups(dev, dev->class->dev_groups);
+}
+
+static int create_type_groups(struct device *dev, const char *name)
+{
+ if (!dev->type)
+ return -ENOENT;
+ return name ? create_attr_in_groups(dev, dev->type->groups, name)
+ : create_all_in_groups(dev, dev->type->groups);
+}
+
+static void remove_type_groups(struct device *dev)
+{
+ if (!dev->type)
+ return;
+ if (device_is_sysfs_lazy(dev))
+ device_remove_groups_if_present(dev, dev->type->groups);
+ else
+ device_remove_groups(dev, dev->type->groups);
+}
+
+static int create_dev_own_groups(struct device *dev, const char *name)
+{
+ return name ? create_attr_in_groups(dev, dev->groups, name)
+ : create_all_in_groups(dev, dev->groups);
+}
+
+static void remove_dev_own_groups(struct device *dev)
+{
+ if (device_is_sysfs_lazy(dev))
+ device_remove_groups_if_present(dev, dev->groups);
+ else
+ device_remove_groups(dev, dev->groups);
+}
+
+static int create_bus_groups(struct device *dev, const char *name)
+{
+ if (!dev->bus)
+ return -ENOENT;
+ return name ? create_attr_in_groups(dev, dev->bus->dev_groups, name)
+ : create_all_in_groups(dev, dev->bus->dev_groups);
}
-static DEVICE_ATTR_RO(dev);
+
+static void remove_bus_groups(struct device *dev)
+{
+ if (!dev->bus)
+ return;
+ if (device_is_sysfs_lazy(dev))
+ device_remove_groups_if_present(dev, dev->bus->dev_groups);
+ else
+ device_remove_groups(dev, dev->bus->dev_groups);
+}
+
+static const struct device_sysfs_entry driver_core_sysfs_entries[] = {
+ /*
+ * Rows are in eager device_add() creation order: ADD_ALL creates
+ * forward, REMOVE_ALL tears down in reverse, matching the eager
+ * path. The lazy tree exposes the same set of names and file
+ * contents as an eager device; readdir(3) order is kernfs name-hash
+ * order (kernfs_sd_compare), not table order, and is not ABI.
+ */
+ {
+ /* Created early on the eager path - keep at table top. */
+ .name = "power",
+ .applies_to = dev_needs_pm,
+ .create = create_power,
+ .remove = remove_power,
+ },
+ {
+ .name = "uevent",
+ .create = create_uevent,
+ .remove = remove_uevent,
+ },
+ /* Class-side rows. */
+ {
+ .name = "subsystem",
+ .applies_to = dev_has_class,
+ .create = create_class_subsystem_link,
+ .remove = remove_class_subsystem_link,
+ },
+ {
+ .name = "device",
+ .applies_to = dev_is_class_child,
+ .create = create_device_backlink,
+ .remove = remove_device_backlink,
+ },
+ {
+ .applies_to = dev_has_class_dev_groups,
+ .create = create_class_groups,
+ .remove = remove_class_groups,
+ },
+ /* Groups + standalones in eager device_attrs_create() order. */
+ {
+ .applies_to = dev_has_type_groups,
+ .create = create_type_groups,
+ .remove = remove_type_groups,
+ },
+ {
+ .applies_to = dev_has_own_groups,
+ .create = create_dev_own_groups,
+ .remove = remove_dev_own_groups,
+ },
+ {
+ .name = "online",
+ .applies_to = dev_supports_offline_enabled,
+ .create = create_online,
+ .remove = remove_online,
+ },
+ {
+ .name = "waiting_for_supplier",
+ .applies_to = dev_has_fwnode_devlink,
+ .create = create_waiting_for_supplier,
+ .remove = remove_waiting_for_supplier,
+ },
+ {
+ .name = "removable",
+ .applies_to = dev_removable_valid,
+ .create = create_removable,
+ .remove = remove_removable,
+ },
+ {
+ .name = "physical_location",
+ .applies_to = dev_has_physical_location,
+ .create = create_physical_location,
+ .remove = remove_physical_location,
+ },
+ /* Bus-side rows. */
+ {
+ .name = "subsystem",
+ .applies_to = dev_has_bus_no_class,
+ .create = create_bus_subsystem_link,
+ .remove = remove_bus_subsystem_link,
+ },
+ {
+ .applies_to = dev_has_bus_dev_groups,
+ .create = create_bus_groups,
+ .remove = remove_bus_groups,
+ },
+ /* Late rows. */
+ {
+ .name = "dev",
+ .applies_to = dev_has_devt_major,
+ .create = create_dev_attr,
+ .remove = remove_dev_attr,
+ },
+ { } /* sentinel */
+};
/* /sys/devices/ */
struct kset *devices_kset;
@@ -3861,30 +4235,17 @@ static int device_add_class_symlinks(struct device *dev)
if (!sp)
return 0;
- error = sysfs_create_link(&dev->kobj, &sp->subsys.kobj, "subsystem");
- if (error)
- goto out_devnode;
-
- if (dev->parent && device_is_not_partition(dev)) {
- error = sysfs_create_link(&dev->kobj, &dev->parent->kobj,
- "device");
- if (error)
- goto out_subsys;
- }
/* link in the class directory pointing to the device */
error = sysfs_create_link(&sp->subsys.kobj, &dev->kobj, dev_name(dev));
if (error)
- goto out_device;
- goto exit;
+ goto out_devnode;
+
+ subsys_put(sp);
+ return 0;
-out_device:
- sysfs_remove_link(&dev->kobj, "device");
-out_subsys:
- sysfs_remove_link(&dev->kobj, "subsystem");
out_devnode:
sysfs_remove_link(&dev->kobj, "of_node");
-exit:
subsys_put(sp);
return error;
}
@@ -3899,9 +4260,6 @@ static void device_remove_class_symlinks(struct device *dev)
if (!sp)
return;
- if (dev->parent && device_is_not_partition(dev))
- sysfs_remove_link(&dev->kobj, "device");
- sysfs_remove_link(&dev->kobj, "subsystem");
sysfs_delete_link(&sp->subsys.kobj, &dev->kobj, dev_name(dev));
subsys_put(sp);
}
@@ -4061,38 +4419,44 @@ int device_add(struct device *dev)
/* notify platform of device entry */
device_platform_notify(dev);
- error = device_create_file(dev, &dev_attr_uevent);
- if (error)
- goto attrError;
-
error = device_add_class_symlinks(dev);
if (error)
goto SymlinkError;
- /*
- * device_set_sysfs_lazy() only allocates ->sysfs_lazy_state for
- * non-namespaced devices, and device_is_sysfs_lazy() guards opt-in
- * here, so kernfs_set_lazy() should always succeed. WARN_ON catches
- * a regression in those preconditions.
- */
- if (device_is_sysfs_lazy(dev))
- WARN_ON(kernfs_set_lazy(dev->kobj.sd));
-
- error = device_add_attrs(dev);
- if (error)
- goto AttrsError;
error = bus_add_device(dev);
if (error)
goto BusError;
+ /*
+ * Create power/ eagerly even for lazy devices: in-kernel callers
+ * (wakeup_sysfs_add(), dev_pm_qos_*()) sysfs_merge_group() into an
+ * existing power/ dir at driver-bind, before any userspace access.
+ * Mark it added so the lazy create/remove path treats it as done.
+ */
error = dpm_sysfs_add(dev);
if (error)
goto DPMError;
+ if (device_is_sysfs_lazy(dev))
+ dev->sysfs_lazy->power_added = true;
device_pm_add(dev);
- if (MAJOR(dev->devt)) {
- error = device_create_file(dev, &dev_attr_dev);
- if (error)
- goto DevAttrError;
+ (void)dev_add_physical_location(dev);
+ /*
+ * Subsystems that opt a device into lazy sysfs are responsible for
+ * ensuring the device's kobj.sd is a non-namespaced directory: the
+ * device_set_sysfs_lazy() contract states the call must precede
+ * device_add(), and device_is_sysfs_lazy() gates opt-in here.
+ * kernfs_set_lazy() should therefore always succeed; WARN_ON catches
+ * a contract violation by an opted-in subsystem.
+ */
+ if (device_is_sysfs_lazy(dev))
+ WARN_ON(kernfs_set_lazy(dev->kobj.sd));
+
+ /* FIXME: eager ADD_ALL return is dropped; a failed create won't fail device_add(). */
+ if (!device_is_sysfs_lazy(dev))
+ device_sysfs_apply(dev, driver_core_sysfs_entries,
+ DEV_SYSFS_ADD_ALL, NULL);
+
+ if (MAJOR(dev->devt)) {
error = device_create_sys_dev_entry(dev);
if (error)
goto SysEntryError;
@@ -4169,21 +4533,23 @@ int device_add(struct device *dev)
put_device(dev);
return error;
SysEntryError:
- if (MAJOR(dev->devt))
- device_remove_file(dev, &dev_attr_dev);
- DevAttrError:
+ device_lock(dev);
+ kill_device(dev);
+ device_unlock(dev);
+ /* REMOVE_ALL: the remove_power() row is the sole power-group teardown. */
+ if (device_is_sysfs_lazy(dev))
+ mutex_lock(&dev->sysfs_lazy->lock);
+ device_sysfs_apply(dev, driver_core_sysfs_entries,
+ DEV_SYSFS_REMOVE_ALL, NULL);
+ if (device_is_sysfs_lazy(dev))
+ mutex_unlock(&dev->sysfs_lazy->lock);
device_pm_remove(dev);
- dpm_sysfs_remove(dev);
DPMError:
device_set_driver(dev, NULL);
bus_remove_device(dev);
BusError:
- device_remove_attrs(dev);
- AttrsError:
device_remove_class_symlinks(dev);
SymlinkError:
- device_remove_file(dev, &dev_attr_uevent);
- attrError:
device_platform_notify_remove(dev);
kobject_uevent(&dev->kobj, KOBJ_REMOVE);
glue_dir = get_glue_dir(dev);
@@ -4296,19 +4662,25 @@ void device_del(struct device *dev)
if (dev->fwnode && dev->fwnode->dev == dev)
dev->fwnode->dev = NULL;
- /* Notify clients of device removal. This call must come
- * before dpm_sysfs_remove().
- */
+ /* Must come before REMOVE_ALL tears down power/. */
noio_flag = memalloc_noio_save();
bus_notify(dev, BUS_NOTIFY_DEL_DEVICE);
- dpm_sysfs_remove(dev);
+ /* Serialize against lazy populate callbacks. dev->p->dead is
+ * already set so any populate taking this lock after us bails.
+ */
+ if (device_is_sysfs_lazy(dev))
+ mutex_lock(&dev->sysfs_lazy->lock);
+ device_sysfs_apply(dev, driver_core_sysfs_entries,
+ DEV_SYSFS_REMOVE_ALL, NULL);
+ if (device_is_sysfs_lazy(dev))
+ mutex_unlock(&dev->sysfs_lazy->lock);
+
if (parent)
klist_del(&dev->p->knode_parent);
if (MAJOR(dev->devt)) {
devtmpfs_delete_node(dev);
device_remove_sys_dev_entry(dev);
- device_remove_file(dev, &dev_attr_dev);
}
sp = class_to_subsys(dev->class);
@@ -4325,8 +4697,6 @@ void device_del(struct device *dev)
mutex_unlock(&sp->mutex);
subsys_put(sp);
}
- device_remove_file(dev, &dev_attr_uevent);
- device_remove_attrs(dev);
bus_remove_device(dev);
device_pm_remove(dev);
driver_deferred_probe_del(dev);
@@ -4578,6 +4948,10 @@ EXPORT_SYMBOL_GPL(device_find_child);
int __init devices_init(void)
{
+ /* Compile-time row-count check (rows + sentinel). */
+ BUILD_BUG_ON(ARRAY_SIZE(driver_core_sysfs_entries) !=
+ N_DRIVER_CORE_SYSFS_ENTRIES + 1);
+
devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
if (!devices_kset)
return -ENOMEM;
diff --git a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h
index 94b8aca20bffc..e2ce9e6c12d84 100644
--- a/fs/sysfs/sysfs.h
+++ b/fs/sysfs/sysfs.h
@@ -28,6 +28,7 @@ void sysfs_warn_dup(struct kernfs_node *parent, const char *name);
* file.c
*/
+
/*
* symlink.c
*/
diff --git a/include/linux/device.h b/include/linux/device.h
index e5485bfbc6c84..c0a2ee429280c 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -89,16 +89,10 @@ int subsys_virtual_register(const struct bus_type *subsys,
const struct attribute_group **groups);
/**
- * enum dev_sysfs_action - verb selector for device_sysfs_apply()
- * @DEV_SYSFS_ADD_ONE: realize a single named entry (lazy populate_one
- * miss path); walker stops at the first row whose
- * @applies_to passes and whose create() returns
- * anything other than -ENOENT.
- * @DEV_SYSFS_ADD_ALL: realize every applicable entry (eager device_add
- * or lazy populate_all); best-effort, per-row errors
- * do not abort the walk.
- * @DEV_SYSFS_REMOVE_ALL: tear down every applicable entry in reverse
- * row order (device_del teardown).
+ * enum dev_sysfs_action - operation requested by device_sysfs_apply()
+ * @DEV_SYSFS_ADD_ONE: create a single named entry
+ * @DEV_SYSFS_ADD_ALL: create every applicable entry
+ * @DEV_SYSFS_REMOVE_ALL: tear down every previously-created entry
*/
enum dev_sysfs_action {
DEV_SYSFS_ADD_ONE,
@@ -107,35 +101,15 @@ enum dev_sysfs_action {
};
/**
- * struct device_sysfs_entry - declarative per-device sysfs content row
- * @name: Attribute or symlink name at the top level of the device's
- * sysfs directory, or %NULL for a wildcard row whose
- * create() performs internal name dispatch (typically a
- * group-source row iterating an attribute_group array).
- * @applies_to: Optional predicate gating row eligibility for this
- * device. Must be cheap, non-sleeping, side-effect-free, and
- * monotonic for a given device state. MUST NOT acquire any
- * lock the walker's caller may already hold (notably
- * device_lock()). %NULL means unconditional.
- * @create: Required. Creates the row's sysfs content. For a named
- * row (@name != NULL) the @name argument equals @e->name on
- * ADD_ONE and %NULL on ADD_ALL. For a wildcard row the @name
- * argument is the ADD_ONE target (or %NULL on ADD_ALL) and
- * the row's create() matches internally, returning -ENOENT
- * to signal "not my name" so the walker continues. Must
- * convert -EEXIST from concurrent racers to 0.
- * @remove: Optional reverse-of-realize teardown. Called on
- * REMOVE_ALL in reverse row order. MUST NOT fail the walk.
+ * struct device_sysfs_entry - per-device sysfs entry
+ * @name: entry name; %NULL for a wildcard row
+ * @applies_to: optional predicate gating row eligibility
+ * @create: creates the row's sysfs content
+ * @remove: optional teardown
*
- * Rows are declared in file-static, sentinel-terminated tables whose
- * base pointer is published to the walker via struct kobj_type.entries
- * (device ktype shares a single driver_core table and dispatches to
- * dev->type->entries as well). The row shape mirrors cftype +
- * cgroup_addrm_files (kernel/cgroup/cgroup.c) and the
- * pci_sysfs_entries[] at drivers/pci/pci-sysfs.c (added later in
- * the series); see Documentation/driver-api/sysfs-lazy.rst (also
- * added later in the series) for the full walker contract and
- * error-handling matrix.
+ * Rows are declared in file-static, sentinel-terminated tables and
+ * walked by device_sysfs_apply(). See
+ * Documentation/driver-api/sysfs-lazy.rst for the walker contract.
*/
struct device_sysfs_entry {
const char *name;
--
2.47.3
Amazon Web Services Development Center Germany GmbH
Tamara-Danz-Str. 13
10243 Berlin
Geschaeftsfuehrung: Christof Hellmis, Andreas Stieger
Eingetragen am Amtsgericht Charlottenburg unter HRB 257764 B
Sitz: Berlin
Ust-ID: DE 365 538 597
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 08/14] PCI/sysfs: migrate to device_sysfs_entry, defer physfn symlink
2026-07-02 17:51 ` [RFC PATCH 05/14] driver core: add struct device_sysfs_entry and walker Pavol Sakac
2026-07-02 17:51 ` [RFC PATCH 06/14] driver core: wire device_ktype populate to walker Pavol Sakac
2026-07-02 17:51 ` [RFC PATCH 07/14] driver core: migrate device sysfs to device_sysfs_entry table Pavol Sakac
@ 2026-07-02 17:51 ` Pavol Sakac
2026-07-02 17:51 ` [RFC PATCH 09/14] iommu: lazy-populate iommu_group reserved_regions/type attrs Pavol Sakac
` (3 subsequent siblings)
6 siblings, 0 replies; 15+ messages in thread
From: Pavol Sakac @ 2026-07-02 17:51 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rafael J . Wysocki, Danilo Krummrich,
Tejun Heo
Cc: linux-kernel, driver-core, David Woodhouse, Pasha Tatashin,
Mike Rapoport, Pratyush Yadav, David Matlack, Samiullah Khawaja,
Alexander Graf, linux-mm, kexec, nh-open-source
Migrate PCI's per-device sysfs to the declarative walker, and as
part of that wire the bus/type dispatch chain via two new struct
device_type hooks.
- struct device_type grows .populate / .populate_all hooks
consulted by device_ktype_populate_one /
device_ktype_populate_all when the driver-core walker returns
-ENOENT.
- PCI's bus/type-owned per-device sysfs content (resource%u /
resource%u_wc files plus the physfn back-symlink) becomes
pci_sysfs_entries[] and is dispatched by the same walker.
- Lazy populate skips eager pci_create_sysfs_dev_files() so VFs
materialise their resource files only on first userspace
access.
The physfn symlink moves from per-device add to the wildcard row
on the VF; eager devices still see no change. Idempotent
re-creation is required by the VF lookup-then-readdir VFS order;
sysfs_kn_exists() provides the existence check.
No userspace ABI change.
Cc: Bjorn Helgaas <bhelgaas@google.com>
Cc: linux-pci@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Danilo Krummrich <dakr@kernel.org>
Cc: driver-core@lists.linux.dev
Cc: Rafael J. Wysocki <rafael@kernel.org>
Cc: linux-api@vger.kernel.org
Assisted-by: Claude:claude-opus-4.7
Signed-off-by: Pavol Sakac <sakacpav@amazon.de>
---
drivers/base/core.c | 10 ++-
drivers/pci/iov.c | 5 --
drivers/pci/pci-sysfs.c | 149 ++++++++++++++++++++++++++++++++++++----
drivers/pci/pci.h | 5 ++
drivers/pci/probe.c | 4 +-
include/linux/device.h | 32 ++++++---
6 files changed, 175 insertions(+), 30 deletions(-)
diff --git a/drivers/base/core.c b/drivers/base/core.c
index 15a2dcf922d4b..6c5d1bb66c8f4 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -2811,9 +2811,9 @@ device_sysfs_entries_end(const struct device_sysfs_entry *entries)
return e;
}
-static int device_sysfs_apply(struct device *dev,
- const struct device_sysfs_entry *entries,
- enum dev_sysfs_action action, const char *name)
+int device_sysfs_apply(struct device *dev,
+ const struct device_sysfs_entry *entries,
+ enum dev_sysfs_action action, const char *name)
{
const struct device_sysfs_entry *e;
@@ -2983,6 +2983,8 @@ static int device_ktype_populate_one(struct kobject *kobj, const char *name)
}
ret = device_sysfs_apply(dev, ktype ? ktype->entries : NULL,
DEV_SYSFS_ADD_ONE, name);
+ if (ret == -ENOENT && dev->type && dev->type->populate)
+ ret = dev->type->populate(dev, name);
out:
mutex_unlock(&dev->sysfs_lazy->lock);
return ret;
@@ -3011,6 +3013,8 @@ static void device_ktype_populate_all(struct kobject *kobj)
device_sysfs_apply(dev, ktype ? ktype->entries : NULL,
DEV_SYSFS_ADD_ALL, NULL);
+ if (dev->type && dev->type->populate_all)
+ dev->type->populate_all(dev);
device_sysfs_set_populated(dev);
diff --git a/drivers/pci/iov.c b/drivers/pci/iov.c
index 91ac4e37ecb9c..ec2fca8209593 100644
--- a/drivers/pci/iov.c
+++ b/drivers/pci/iov.c
@@ -212,16 +212,11 @@ int pci_iov_sysfs_link(struct pci_dev *dev,
rc = sysfs_create_link(&dev->dev.kobj, &virtfn->dev.kobj, buf);
if (rc)
goto failed;
- rc = sysfs_create_link(&virtfn->dev.kobj, &dev->dev.kobj, "physfn");
- if (rc)
- goto failed1;
kobject_uevent(&virtfn->dev.kobj, KOBJ_CHANGE);
return 0;
-failed1:
- sysfs_remove_link(&dev->dev.kobj, buf);
failed:
return rc;
}
diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c
index d37860841260c..5a779ebaee326 100644
--- a/drivers/pci/pci-sysfs.c
+++ b/drivers/pci/pci-sysfs.c
@@ -1247,6 +1247,13 @@ static int pci_create_attr(struct pci_dev *pdev, int num, int write_combine)
res_attr->attr.mode = 0600;
res_attr->size = pci_resource_len(pdev, num);
res_attr->private = (void *)(unsigned long)num;
+
+ /* Idempotent: skip if a concurrent populate already created this slot. */
+ if (sysfs_kn_exists(&pdev->dev.kobj, res_attr->attr.name)) {
+ kfree(res_attr);
+ return 0;
+ }
+
retval = sysfs_create_bin_file(&pdev->dev.kobj, res_attr);
if (retval) {
kfree(res_attr);
@@ -1264,8 +1271,6 @@ static int pci_create_attr(struct pci_dev *pdev, int num, int write_combine)
/**
* pci_create_resource_files - create resource files in sysfs for @dev
* @pdev: dev in question
- *
- * Walk the resources in @pdev creating files for each resource available.
*/
static int pci_create_resource_files(struct pci_dev *pdev)
{
@@ -1283,21 +1288,65 @@ static int pci_create_resource_files(struct pci_dev *pdev)
if (!pci_resource_len(pdev, i))
continue;
- retval = pci_create_attr(pdev, i, 0);
+ /* Skip slots already materialised by populate_one(). */
+ if (!pdev->res_attr[i]) {
+ retval = pci_create_attr(pdev, i, 0);
+ if (retval) {
+ pci_remove_resource_files(pdev);
+ return retval;
+ }
+ }
/* for prefetchable resources, create a WC mappable file */
- if (!retval && arch_can_pci_mmap_wc() &&
- pdev->resource[i].flags & IORESOURCE_PREFETCH)
+ if (arch_can_pci_mmap_wc() &&
+ pdev->resource[i].flags & IORESOURCE_PREFETCH &&
+ !pdev->res_attr_wc[i]) {
retval = pci_create_attr(pdev, i, 1);
- if (retval) {
- pci_remove_resource_files(pdev);
- return retval;
+ if (retval) {
+ pci_remove_resource_files(pdev);
+ return retval;
+ }
}
}
return 0;
}
+
+/*
+ * Resolve "resourceN" / "resourceN_wc" and create the single file.
+ * Returns -ENOENT for unrelated names or out-of-range/empty BARs so
+ * the walker continues to the next entry.
+ */
+static int pci_create_resource_file_by_name(struct pci_dev *pdev,
+ const char *name)
+{
+ unsigned int num;
+ int wc = 0;
+ int n = 0;
+
+ if (sscanf(name, "resource%u%n", &num, &n) != 1)
+ return -ENOENT;
+ if (name[n]) {
+ if (strcmp(name + n, "_wc"))
+ return -ENOENT;
+ wc = 1;
+ }
+
+ if (num >= PCI_STD_NUM_BARS || !pci_resource_len(pdev, num))
+ return -ENOENT;
+
+ if (wc && (!arch_can_pci_mmap_wc() ||
+ !(pdev->resource[num].flags & IORESOURCE_PREFETCH)))
+ return -ENOENT;
+
+ return pci_create_attr(pdev, num, wc);
+}
#else /* !(defined(HAVE_PCI_MMAP) || defined(ARCH_GENERIC_PCI_MMAP_RESOURCE)) */
int __weak pci_create_resource_files(struct pci_dev *dev) { return 0; }
void __weak pci_remove_resource_files(struct pci_dev *dev) { return; }
+static int pci_create_resource_file_by_name(struct pci_dev *pdev,
+ const char *name)
+{
+ return -ENOENT;
+}
#endif
/**
@@ -1661,26 +1710,88 @@ static const struct attribute_group pci_dev_resource_resize_group = {
.is_visible = resource_resize_is_visible,
};
+/* Per-device PCI sysfs catalogue. */
+
+/* physfn back-symlink - SR-IOV VF only. */
+
+#ifdef CONFIG_PCI_IOV
+
+static bool pci_applies_to_virtfn(struct device *dev)
+{
+ return to_pci_dev(dev)->is_virtfn;
+}
+
+static int pci_create_physfn_link(struct device *dev, const char *name)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+
+ if (device_is_sysfs_lazy(dev))
+ lockdep_assert_held(&dev->sysfs_lazy->lock);
+
+ if (sysfs_kn_exists(&dev->kobj, "physfn"))
+ return 0;
+
+ return sysfs_create_link(&dev->kobj, &pdev->physfn->dev.kobj, "physfn");
+}
+
+#endif /* CONFIG_PCI_IOV */
+
+/* resource%u[_wc] family - wildcard row, create() dispatches on @name. */
+
+static int pci_create_resource(struct device *dev, const char *name)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+
+ if (device_is_sysfs_lazy(dev))
+ lockdep_assert_held(&dev->sysfs_lazy->lock);
+
+ if (!name)
+ return pci_create_resource_files(pdev);
+
+ return pci_create_resource_file_by_name(pdev, name);
+}
+
+static void pci_remove_resource(struct device *dev)
+{
+ pci_remove_resource_files(to_pci_dev(dev));
+}
+
+static const struct device_sysfs_entry pci_sysfs_entries[] = {
+#ifdef CONFIG_PCI_IOV
+ {
+ .name = "physfn",
+ .applies_to = pci_applies_to_virtfn,
+ .create = pci_create_physfn_link,
+ },
+#endif
+ {
+ /* wildcard: "resource%u" / "resource%u_wc" family */
+ .create = pci_create_resource,
+ .remove = pci_remove_resource,
+ },
+ { }, /* sentinel: .create == NULL */
+};
+
int __must_check pci_create_sysfs_dev_files(struct pci_dev *pdev)
{
if (!sysfs_initialized)
return -EACCES;
- return pci_create_resource_files(pdev);
+ return device_sysfs_apply(&pdev->dev, pci_sysfs_entries,
+ DEV_SYSFS_ADD_ALL, NULL);
}
/**
- * pci_remove_sysfs_dev_files - cleanup PCI specific sysfs files
+ * pci_remove_sysfs_dev_files - cleanup PCI sysfs files for @pdev
* @pdev: device whose entries we should free
- *
- * Cleanup when @pdev is removed from sysfs.
*/
void pci_remove_sysfs_dev_files(struct pci_dev *pdev)
{
if (!sysfs_initialized)
return;
- pci_remove_resource_files(pdev);
+ device_sysfs_apply(&pdev->dev, pci_sysfs_entries,
+ DEV_SYSFS_REMOVE_ALL, NULL);
}
static int __init pci_sysfs_init(void)
@@ -1835,3 +1946,15 @@ const struct attribute_group *pci_dev_attr_groups[] = {
#endif
NULL,
};
+
+int pci_dev_type_populate_one(struct device *dev, const char *name)
+{
+ return device_sysfs_apply(dev, pci_sysfs_entries,
+ DEV_SYSFS_ADD_ONE, name);
+}
+
+void pci_dev_type_populate_all(struct device *dev)
+{
+ device_sysfs_apply(dev, pci_sysfs_entries,
+ DEV_SYSFS_ADD_ALL, NULL);
+}
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 4a14f88e543a2..9c884a158184c 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -395,6 +395,8 @@ static inline int pci_no_d1d2(struct pci_dev *dev)
#ifdef CONFIG_SYSFS
int pci_create_sysfs_dev_files(struct pci_dev *pdev);
void pci_remove_sysfs_dev_files(struct pci_dev *pdev);
+int pci_dev_type_populate_one(struct device *dev, const char *name);
+void pci_dev_type_populate_all(struct device *dev);
extern const struct attribute_group *pci_dev_groups[];
extern const struct attribute_group *pci_dev_attr_groups[];
extern const struct attribute_group *pcibus_groups[];
@@ -403,6 +405,9 @@ extern const struct attribute_group pci_doe_sysfs_group;
#else
static inline int pci_create_sysfs_dev_files(struct pci_dev *pdev) { return 0; }
static inline void pci_remove_sysfs_dev_files(struct pci_dev *pdev) { }
+static inline int pci_dev_type_populate_one(struct device *dev,
+ const char *name) { return -ENOENT; }
+static inline void pci_dev_type_populate_all(struct device *dev) { }
#define pci_dev_groups NULL
#define pci_dev_attr_groups NULL
#define pcibus_groups NULL
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index b63cd0c310bc0..ff7e6016c896f 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -2499,7 +2499,9 @@ static void pci_release_dev(struct device *dev)
}
static const struct device_type pci_dev_type = {
- .groups = pci_dev_attr_groups,
+ .groups = pci_dev_attr_groups,
+ .populate = pci_dev_type_populate_one,
+ .populate_all = pci_dev_type_populate_all,
};
struct pci_dev *pci_alloc_dev(struct pci_bus *bus)
diff --git a/include/linux/device.h b/include/linux/device.h
index c0a2ee429280c..948c3ea8055e5 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -118,14 +118,27 @@ struct device_sysfs_entry {
void (*remove)(struct device *dev);
};
-/*
- * The type of device, "struct device" is embedded in. A class
- * or bus can contain devices of different types
- * like "partitions" and "disks", "mouse" and "event".
- * This identifies the device type and carries type-specific
- * information, equivalent to the kobj_type of a kobject.
- * If "name" is specified, the uevent will contain it in
- * the DEVTYPE variable.
+int device_sysfs_apply(struct device *dev,
+ const struct device_sysfs_entry *entries,
+ enum dev_sysfs_action action, const char *name);
+
+/**
+ * struct device_type - The type of device, embedded in struct device.
+ * @name: if set, appears in the DEVTYPE uevent variable.
+ * @groups: attribute groups always present on devices of this type.
+ * @uevent: type-specific uevent handler (may set DEVTYPE or other).
+ * @devnode: populate /dev node metadata (mode, uid, gid).
+ * @release: release callback run after the last reference drops.
+ * @pm: dev_pm_ops vector for devices of this type.
+ * @populate: lazy ADD_ONE for type-owned synthetic attrs/symlinks.
+ * Called from kernfs populate; device_lock NOT held; may
+ * sleep. Returns -ENOENT for unknown names.
+ * @populate_all: readdir-time counterpart; best-effort, retry-safe.
+ *
+ * A class or bus can contain devices of different types like
+ * "partitions" and "disks", "mouse" and "event". This identifies
+ * the device type and carries type-specific information, equivalent
+ * to the kobj_type of a kobject.
*/
struct device_type {
const char *name;
@@ -136,6 +149,9 @@ struct device_type {
void (*release)(struct device *dev);
const struct dev_pm_ops *pm;
+
+ int (*populate)(struct device *dev, const char *name);
+ void (*populate_all)(struct device *dev);
};
/**
--
2.47.3
Amazon Web Services Development Center Germany GmbH
Tamara-Danz-Str. 13
10243 Berlin
Geschaeftsfuehrung: Christof Hellmis, Andreas Stieger
Eingetragen am Amtsgericht Charlottenburg unter HRB 257764 B
Sitz: Berlin
Ust-ID: DE 365 538 597
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 09/14] iommu: lazy-populate iommu_group reserved_regions/type attrs
2026-07-02 17:51 ` [RFC PATCH 05/14] driver core: add struct device_sysfs_entry and walker Pavol Sakac
` (2 preceding siblings ...)
2026-07-02 17:51 ` [RFC PATCH 08/14] PCI/sysfs: migrate to device_sysfs_entry, defer physfn symlink Pavol Sakac
@ 2026-07-02 17:51 ` Pavol Sakac
2026-07-10 14:19 ` Greg Kroah-Hartman
2026-07-02 17:51 ` [RFC PATCH 10/14] PCI/IOV: opt SR-IOV VFs into sysfs_lazy Pavol Sakac
` (2 subsequent siblings)
6 siblings, 1 reply; 15+ messages in thread
From: Pavol Sakac @ 2026-07-02 17:51 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rafael J . Wysocki, Danilo Krummrich,
Tejun Heo
Cc: linux-kernel, driver-core, David Woodhouse, Pasha Tatashin,
Mike Rapoport, Pratyush Yadav, David Matlack, Samiullah Khawaja,
Alexander Graf, linux-mm, kexec, nh-open-source
iommu_group_alloc() eagerly creates two attribute files -
reserved_regions and type - that are read only on the VFIO /
iommufd ioctl path (VFIO_IOMMU_GET_INFO, iommufd type query).
On systems with thousands of SR-IOV VFs each VF typically gets
its own iommu_group, so deferring these two attrs eliminates two
kernfs nodes per group at boot.
iommu_group_ktype is the first non-device_ktype consumer of the
populate mechanism: wire iommu_group_populate_one /
iommu_group_populate_all and call kernfs_set_lazy() on the
group's kernfs node immediately after kobject_init_and_add().
See the in-diff comment at the kernfs_set_lazy() site for the
ordering rationale (must precede kobject_create_and_add("devices")
to interact correctly with kernfs_inc_rev()).
Both callbacks fast-path on iommu_group_sysfs_populated() so
re-entry after the directory is fully populated is cheap and
cannot fire sysfs_warn_dup().
The name attribute is created on demand by iommu_group_set_name()
on a separate API path and is not handled by these callbacks;
groups without a set name return -ENOENT on lookup of name.
No userspace ABI change. A selftest for this deferral is added
later in the series under tools/testing/selftests/sysfs-lazy/.
Cc: Joerg Roedel <joro@8bytes.org>
Cc: Will Deacon <will@kernel.org>
Cc: Robin Murphy <robin.murphy@arm.com>
Cc: Jason Gunthorpe <jgg@ziepe.ca>
Cc: Kevin Tian <kevin.tian@intel.com>
Cc: Shuah Khan <shuah@kernel.org>
Cc: iommu@lists.linux.dev
Cc: linux-kselftest@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-api@vger.kernel.org
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Danilo Krummrich <dakr@kernel.org>
Cc: driver-core@lists.linux.dev
Assisted-by: Claude:claude-opus-4.7
Signed-off-by: Pavol Sakac <sakacpav@amazon.de>
---
drivers/iommu/iommu.c | 132 +++++++++++++++++++++++++++++++++++++-----
1 file changed, 119 insertions(+), 13 deletions(-)
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index 61c12ba782066..7a8dd43f0a09a 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -21,6 +21,7 @@
#include <linux/iommufd.h>
#include <linux/idr.h>
#include <linux/err.h>
+#include <linux/kernfs.h>
#include <linux/pci.h>
#include <linux/pci-ats.h>
#include <linux/bitops.h>
@@ -71,8 +72,22 @@ struct iommu_group {
struct list_head entry;
unsigned int owner_cnt;
void *owner;
+ /* Embedded lazy-sysfs state (every iommu_group is lazy). */
+ struct sysfs_lazy_state sysfs_lazy;
};
+static inline bool iommu_group_sysfs_populated(const struct iommu_group *group)
+{
+ return READ_ONCE(group->sysfs_lazy.populated);
+}
+
+static inline void
+iommu_group_sysfs_set_populated(struct iommu_group *group)
+{
+ lockdep_assert_held(&group->sysfs_lazy.lock);
+ WRITE_ONCE(group->sysfs_lazy.populated, true);
+}
+
struct group_device {
struct list_head list;
struct device *dev;
@@ -1033,9 +1048,99 @@ static void iommu_group_release(struct kobject *kobj)
kfree(group);
}
+/* Lazy iommu_group attrs (excludes "name", managed separately). */
+static const struct {
+ const char *name;
+ struct iommu_group_attribute *attr;
+} iommu_group_lazy_attrs[] = {
+ { "reserved_regions", &iommu_group_attr_reserved_regions },
+ { "type", &iommu_group_attr_type },
+};
+
+static int iommu_group_populate_one(struct kobject *kobj, const char *name)
+{
+ struct iommu_group *group = to_iommu_group(kobj);
+ size_t i;
+ int ret = -ENOENT;
+ bool name_present = false;
+
+ /* Fast path: directory fully populated; kernfs has authoritative state. */
+ if (iommu_group_sysfs_populated(group))
+ return -ENOENT;
+
+ mutex_lock(&group->sysfs_lazy.lock);
+ /* Re-check under the lock against a concurrent populate_all. */
+ if (iommu_group_sysfs_populated(group)) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ /* Materialise the whole table (2 entries); cheaper than per-name dispatch. */
+ for (i = 0; i < ARRAY_SIZE(iommu_group_lazy_attrs); i++) {
+ struct iommu_group_attribute *attr =
+ iommu_group_lazy_attrs[i].attr;
+ int rc;
+
+ if (sysfs_kn_exists(&group->kobj, attr->attr.name))
+ rc = 0;
+ else
+ rc = iommu_group_create_file(group, attr);
+
+ if (!strcmp(name, iommu_group_lazy_attrs[i].name)) {
+ name_present = true;
+ ret = rc;
+ } else if (rc) {
+ pr_warn("group %d: lazy-create %s failed: %d\n",
+ group->id,
+ iommu_group_lazy_attrs[i].name, rc);
+ }
+ }
+ iommu_group_sysfs_set_populated(group);
+
+out:
+ mutex_unlock(&group->sysfs_lazy.lock);
+ return name_present ? ret : -ENOENT;
+}
+
+static void iommu_group_populate_all(struct kobject *kobj)
+{
+ struct iommu_group *group = to_iommu_group(kobj);
+ size_t i;
+ int ret;
+
+ /* Fast path: directory already fully populated. */
+ if (iommu_group_sysfs_populated(group))
+ return;
+
+ mutex_lock(&group->sysfs_lazy.lock);
+ if (iommu_group_sysfs_populated(group))
+ goto out;
+
+ for (i = 0; i < ARRAY_SIZE(iommu_group_lazy_attrs); i++) {
+ struct iommu_group_attribute *attr =
+ iommu_group_lazy_attrs[i].attr;
+
+ /* Existence check absorbs the populate_one race. */
+ if (sysfs_kn_exists(&group->kobj, attr->attr.name))
+ continue;
+
+ ret = iommu_group_create_file(group, attr);
+ if (ret)
+ pr_warn("group %d: lazy-create %s failed: %d\n",
+ group->id,
+ iommu_group_lazy_attrs[i].name, ret);
+ }
+
+ iommu_group_sysfs_set_populated(group);
+out:
+ mutex_unlock(&group->sysfs_lazy.lock);
+}
+
static const struct kobj_type iommu_group_ktype = {
.sysfs_ops = &iommu_group_sysfs_ops,
.release = iommu_group_release,
+ .populate = iommu_group_populate_one,
+ .populate_all = iommu_group_populate_all,
};
/**
@@ -1060,6 +1165,7 @@ struct iommu_group *iommu_group_alloc(void)
group->kobj.kset = iommu_group_kset;
mutex_init(&group->mutex);
+ mutex_init(&group->sysfs_lazy.lock);
INIT_LIST_HEAD(&group->devices);
INIT_LIST_HEAD(&group->entry);
xa_init(&group->pasid_array);
@@ -1078,6 +1184,19 @@ struct iommu_group *iommu_group_alloc(void)
return ERR_PTR(ret);
}
+ /*
+ * Defer reserved_regions and type - both are read only on
+ * VFIO/iommufd ioctl paths, so let the populate callbacks
+ * materialise them on demand.
+ *
+ * KERNFS_LAZY MUST be set before kobject_create_and_add("devices"),
+ * which calls kernfs_inc_rev() on this kobject's directory and
+ * would otherwise leave a stale negative dentry cached for a
+ * missing reserved_regions/type child. WARN_ON because
+ * iommu_group_alloc() controls all group kobjects directly.
+ */
+ WARN_ON(kernfs_set_lazy(group->kobj.sd));
+
group->devices_kobj = kobject_create_and_add("devices", &group->kobj);
if (!group->devices_kobj) {
kobject_put(&group->kobj); /* triggers .release & free */
@@ -1091,19 +1210,6 @@ struct iommu_group *iommu_group_alloc(void)
*/
kobject_put(&group->kobj);
- ret = iommu_group_create_file(group,
- &iommu_group_attr_reserved_regions);
- if (ret) {
- kobject_put(group->devices_kobj);
- return ERR_PTR(ret);
- }
-
- ret = iommu_group_create_file(group, &iommu_group_attr_type);
- if (ret) {
- kobject_put(group->devices_kobj);
- return ERR_PTR(ret);
- }
-
pr_debug("Allocated group %d\n", group->id);
return group;
--
2.47.3
Amazon Web Services Development Center Germany GmbH
Tamara-Danz-Str. 13
10243 Berlin
Geschaeftsfuehrung: Christof Hellmis, Andreas Stieger
Eingetragen am Amtsgericht Charlottenburg unter HRB 257764 B
Sitz: Berlin
Ust-ID: DE 365 538 597
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 10/14] PCI/IOV: opt SR-IOV VFs into sysfs_lazy
2026-07-02 17:51 ` [RFC PATCH 05/14] driver core: add struct device_sysfs_entry and walker Pavol Sakac
` (3 preceding siblings ...)
2026-07-02 17:51 ` [RFC PATCH 09/14] iommu: lazy-populate iommu_group reserved_regions/type attrs Pavol Sakac
@ 2026-07-02 17:51 ` Pavol Sakac
2026-07-02 17:51 ` [RFC PATCH 11/14] vfio: opt vfio-dev and VFIO group devices " Pavol Sakac
2026-07-02 17:51 ` [RFC PATCH 13/14] Documentation: add lazy sysfs initialisation and device_sysfs_entry docs Pavol Sakac
6 siblings, 0 replies; 15+ messages in thread
From: Pavol Sakac @ 2026-07-02 17:51 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rafael J . Wysocki, Danilo Krummrich,
Tejun Heo
Cc: linux-kernel, driver-core, David Woodhouse, Pasha Tatashin,
Mike Rapoport, Pratyush Yadav, David Matlack, Samiullah Khawaja,
Alexander Graf, linux-mm, kexec, nh-open-source
First behaviour change in the lazy-sysfs series. Call
device_set_sysfs_lazy() on every SR-IOV Virtual Function before
pci_device_add() so the VF kobj directory is marked KERNFS_LAZY and
attribute files materialise on first userspace access.
Place the opt-in in pci_iov_scan_device() (immediately after
pci_setup_device()), which is the single allocation/cleanup site
for a VF struct (Shay Drory, commit 04d50d953ab4 ("PCI: Fix NULL
dereference in SR-IOV VF creation error path")). Any -ENOMEM from
device_set_sysfs_lazy() rolls back through that one site rather
than leaking the VF struct, the physfn ref, and the bus ref.
Add an early-return guard at the top of pci_create_sysfs_dev_files()
so lazy VFs skip eager resource-file creation; the guard covers
both pci_bus_add and the late pci_sysfs_init iterator.
pci_remove_sysfs_dev_files() stays unguarded - its per-entry remove
callbacks are idempotent.
Marking VFs lazy defers the bulk of each VF's sysfs population to
first access. The cost avoided is per VF and scales with the number
of VFs, so on hosts that create a large number of VFs it removes a
correspondingly large amount of up-front kernfs node allocation at
probe. The nodes are deferred, not dropped: reading a VF's
attributes materialises the same tree as before. Quantified results
are deferred to the cover letter.
No userspace ABI change: all files eventually materialize.
Cc: Bjorn Helgaas <bhelgaas@google.com>
Cc: linux-pci@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Danilo Krummrich <dakr@kernel.org>
Cc: driver-core@lists.linux.dev
Cc: linux-api@vger.kernel.org
Assisted-by: Claude:claude-opus-4.7
Signed-off-by: Pavol Sakac <sakacpav@amazon.de>
---
drivers/pci/iov.c | 19 +++++++++++++------
drivers/pci/pci-sysfs.c | 9 +++++++++
2 files changed, 22 insertions(+), 6 deletions(-)
diff --git a/drivers/pci/iov.c b/drivers/pci/iov.c
index ec2fca8209593..5537c3df6f45b 100644
--- a/drivers/pci/iov.c
+++ b/drivers/pci/iov.c
@@ -328,14 +328,21 @@ static struct pci_dev *pci_iov_scan_device(struct pci_dev *dev, int id,
pci_read_vf_config_common(virtfn);
rc = pci_setup_device(virtfn);
- if (rc) {
- pci_dev_put(dev);
- pci_bus_put(virtfn->bus);
- kfree(virtfn);
- return ERR_PTR(rc);
- }
+ if (rc)
+ goto err_free;
+
+ /* Single-site -ENOMEM rollback (kfree + pci_dev_put/pci_bus_put). */
+ rc = device_set_sysfs_lazy(&virtfn->dev);
+ if (rc)
+ goto err_free;
return virtfn;
+
+err_free:
+ pci_dev_put(dev);
+ pci_bus_put(virtfn->bus);
+ kfree(virtfn);
+ return ERR_PTR(rc);
}
int pci_iov_add_virtfn(struct pci_dev *dev, int id)
diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c
index 5a779ebaee326..8bf3837204693 100644
--- a/drivers/pci/pci-sysfs.c
+++ b/drivers/pci/pci-sysfs.c
@@ -1777,6 +1777,9 @@ int __must_check pci_create_sysfs_dev_files(struct pci_dev *pdev)
if (!sysfs_initialized)
return -EACCES;
+ if (device_is_sysfs_lazy(&pdev->dev))
+ return 0; /* lazy: entries materialize on first access */
+
return device_sysfs_apply(&pdev->dev, pci_sysfs_entries,
DEV_SYSFS_ADD_ALL, NULL);
}
@@ -1790,6 +1793,12 @@ void pci_remove_sysfs_dev_files(struct pci_dev *pdev)
if (!sysfs_initialized)
return;
+ /*
+ * No sysfs_lazy early-return here: populate_one/populate_all may
+ * have materialized resource files on first access, so the
+ * per-entry remove() callbacks must run unconditionally. They
+ * are idempotent either way.
+ */
device_sysfs_apply(&pdev->dev, pci_sysfs_entries,
DEV_SYSFS_REMOVE_ALL, NULL);
}
--
2.47.3
Amazon Web Services Development Center Germany GmbH
Tamara-Danz-Str. 13
10243 Berlin
Geschaeftsfuehrung: Christof Hellmis, Andreas Stieger
Eingetragen am Amtsgericht Charlottenburg unter HRB 257764 B
Sitz: Berlin
Ust-ID: DE 365 538 597
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 11/14] vfio: opt vfio-dev and VFIO group devices into sysfs_lazy
2026-07-02 17:51 ` [RFC PATCH 05/14] driver core: add struct device_sysfs_entry and walker Pavol Sakac
` (4 preceding siblings ...)
2026-07-02 17:51 ` [RFC PATCH 10/14] PCI/IOV: opt SR-IOV VFs into sysfs_lazy Pavol Sakac
@ 2026-07-02 17:51 ` Pavol Sakac
2026-07-02 17:51 ` [RFC PATCH 13/14] Documentation: add lazy sysfs initialisation and device_sysfs_entry docs Pavol Sakac
6 siblings, 0 replies; 15+ messages in thread
From: Pavol Sakac @ 2026-07-02 17:51 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rafael J . Wysocki, Danilo Krummrich,
Tejun Heo
Cc: linux-kernel, driver-core, David Woodhouse, Pasha Tatashin,
Mike Rapoport, Pratyush Yadav, David Matlack, Samiullah Khawaja,
Alexander Graf, linux-mm, kexec, nh-open-source
Call device_set_sysfs_lazy() on vfio_device->device (vfio-dev/) and
on the VFIO group's struct device (vfio/<group>/) before device_add().
With opt-in, vfio-dev directories materialise their attribute_groups
(vfio_dev_groups + drvdata->ops->dev_groups) on first access via the
sysfs lookup path instead of eagerly at device_add(). The VFIO
group device's flat attrs follow the same deferral.
Removes the eager creation cost on platforms with thousands of VFs
assigned to VFIO containers. No userspace ABI change.
Cc: Alex Williamson <alex.williamson@redhat.com>
Cc: Jason Gunthorpe <jgg@ziepe.ca>
Cc: kvm@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Danilo Krummrich <dakr@kernel.org>
Cc: driver-core@lists.linux.dev
Cc: linux-api@vger.kernel.org
Assisted-by: Claude:claude-opus-4.7
Signed-off-by: Pavol Sakac <sakacpav@amazon.de>
---
drivers/vfio/group.c | 5 +++++
drivers/vfio/vfio_main.c | 5 +++++
2 files changed, 10 insertions(+)
diff --git a/drivers/vfio/group.c b/drivers/vfio/group.c
index b2299e5bc6df3..aa7e0033b96e8 100644
--- a/drivers/vfio/group.c
+++ b/drivers/vfio/group.c
@@ -533,6 +533,11 @@ static struct vfio_group *vfio_group_alloc(struct iommu_group *iommu_group,
group->dev.devt = MKDEV(MAJOR(vfio.group_devt), minor);
group->dev.class = &vfio_class;
group->dev.release = vfio_group_release;
+ /* Defer per-device attrs to first kernfs access; userspace reads via ioctl. */
+ if (device_set_sysfs_lazy(&group->dev)) {
+ put_device(&group->dev);
+ return ERR_PTR(-ENOMEM);
+ }
cdev_init(&group->cdev, &vfio_group_fops);
group->cdev.owner = THIS_MODULE;
diff --git a/drivers/vfio/vfio_main.c b/drivers/vfio/vfio_main.c
index 6222376ab6ab5..ab90279c44bd0 100644
--- a/drivers/vfio/vfio_main.c
+++ b/drivers/vfio/vfio_main.c
@@ -306,6 +306,11 @@ static int vfio_init_device(struct vfio_device *device, struct device *dev,
goto out_uninit;
}
+ /* Allocated before device_initialize() so out_uninit unwind frees it. */
+ ret = device_set_sysfs_lazy(&device->device);
+ if (ret)
+ goto out_uninit;
+
device_initialize(&device->device);
device->device.release = vfio_device_release;
device->device.class = &vfio_device_class;
--
2.47.3
Amazon Web Services Development Center Germany GmbH
Tamara-Danz-Str. 13
10243 Berlin
Geschaeftsfuehrung: Christof Hellmis, Andreas Stieger
Eingetragen am Amtsgericht Charlottenburg unter HRB 257764 B
Sitz: Berlin
Ust-ID: DE 365 538 597
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 13/14] Documentation: add lazy sysfs initialisation and device_sysfs_entry docs
2026-07-02 17:51 ` [RFC PATCH 05/14] driver core: add struct device_sysfs_entry and walker Pavol Sakac
` (5 preceding siblings ...)
2026-07-02 17:51 ` [RFC PATCH 11/14] vfio: opt vfio-dev and VFIO group devices " Pavol Sakac
@ 2026-07-02 17:51 ` Pavol Sakac
6 siblings, 0 replies; 15+ messages in thread
From: Pavol Sakac @ 2026-07-02 17:51 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rafael J . Wysocki, Danilo Krummrich,
Tejun Heo
Cc: linux-kernel, driver-core, David Woodhouse, Pasha Tatashin,
Mike Rapoport, Pratyush Yadav, David Matlack, Samiullah Khawaja,
Alexander Graf, linux-mm, kexec, nh-open-source
Document the lazy sysfs mechanism and its declarative walker:
- Documentation/driver-api/sysfs-lazy.rst: developer guide
covering overview, opting-in, populate callbacks (per-device
walker and the iommu_group small-table pattern), locking,
teardown, known limitations, and a "See also" section
pointing to the KUnit tests and the kselftest.
- Documentation/ABI/testing/sysfs-lazy: userspace-visible
semantics - lookup vs readdir trigger points, stat() behaviour
on known and unknown names, inotify / fanotify interaction,
udev cold-plug compatibility, and the list of currently
opted-in device classes (SR-IOV VFs, VFIO, iommu_groups).
- Documentation/driver-api/index.rst: add sysfs-lazy to the
toctree.
- MAINTAINERS: add F: entries for the new docs, the KUnit test
drivers/base/test/device_sysfs_apply_test.c, and the
tools/testing/selftests/sysfs-lazy/ directory (added in the
next commit) under DRIVER CORE, KOBJECTS, DEBUGFS AND SYSFS.
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Rafael J. Wysocki <rafael@kernel.org>
Cc: Jonathan Corbet <corbet@lwn.net>
Cc: linux-doc@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-api@vger.kernel.org
Cc: Danilo Krummrich <dakr@kernel.org>
Cc: driver-core@lists.linux.dev
Cc: Shuah Khan <shuah@kernel.org>
Cc: linux-kselftest@vger.kernel.org
Assisted-by: Claude:claude-opus-4.7
Signed-off-by: Pavol Sakac <sakacpav@amazon.de>
---
Documentation/ABI/testing/sysfs-lazy | 77 +++++++++++++
Documentation/driver-api/index.rst | 1 +
Documentation/driver-api/sysfs-lazy.rst | 146 ++++++++++++++++++++++++
MAINTAINERS | 4 +
4 files changed, 228 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-lazy
create mode 100644 Documentation/driver-api/sysfs-lazy.rst
diff --git a/Documentation/ABI/testing/sysfs-lazy b/Documentation/ABI/testing/sysfs-lazy
new file mode 100644
index 0000000000000..3a1b2a0e97e1c
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-lazy
@@ -0,0 +1,77 @@
+What: /sys/devices/.../
+ /sys/kernel/iommu_groups/<N>/
+Date: May 2026
+KernelVersion: 7.1
+Contact: Pavol Sakac <sakacpav@amazon.de>
+Description:
+ This entry documents the *realisation timing* contract
+ for sysfs entries under devices that have opted into
+ lazy population. All attribute names, contents, modes,
+ and ownership are identical to the eager-device tree;
+ only the moment of creation differs. The opt-in set is
+ enumerated in "Currently opted-in device classes" below.
+
+ When a device driver sets the sysfs_lazy flag on a struct
+ device before calling device_add(), the kernel defers
+ creation of attribute files, attribute groups, and
+ class/bus/driver symlinks under that device's sysfs
+ directory.
+
+ These entries are materialised on demand:
+
+ - A lookup (open, stat, readlink) of a specific filename
+ triggers creation of that single file or symlink via
+ the ktype->populate callback, which drives
+ device_sysfs_apply() with DEV_SYSFS_ADD_ONE.
+ - A directory listing (readdir / getdents) triggers
+ creation of all deferred entries at once via the
+ ktype->populate_all callback, which drives
+ device_sysfs_apply() with DEV_SYSFS_ADD_ALL.
+
+ A stat() call on a not-yet-realised filename:
+
+ - If the name corresponds to a known row (including one
+ owned by dev->type->entries), the attribute or
+ symlink is created and stat() succeeds with the same
+ metadata an eager device would show.
+ - If the name is unknown, stat() returns -ENOENT and
+ kernfs caches a negative dentry. Transient errors
+ (-ENOMEM, -EBUSY) propagate unchanged and are NOT
+ negative-cached; the next lookup retries.
+ access(F_OK) behaves identically to stat().
+
+ readdir() returns the full set of names the device
+ would expose if eager - applies_to gating is evaluated
+ during populate_all(), so the visible set exactly
+ matches the eager-device tree.
+
+ Once a row's entry is realised, it persists for the
+ lifetime of the device. A second open()/stat() is a
+ plain kernfs lookup and involves no walker invocation.
+
+ What does NOT change relative to an eager device:
+
+ - The set of files and their names (applies_to is the
+ single gate, identical between eager and lazy paths).
+ - File contents, permissions, ownership, and SELinux
+ labels.
+ - The device directory itself (always eager) and its
+ parent chain.
+
+ Interaction with inotify / fanotify:
+ Watchers see IN_CREATE events at first-access time,
+ not at device_add() time.
+
+ Interaction with udev / systemd-udevd:
+ udev cold-plug works without modification. udev
+ reads the device directory on KOBJ_ADD, which
+ triggers readdir -> full realisation.
+
+ Currently opted-in device classes:
+
+ - PCI SR-IOV Virtual Functions (drivers/pci/iov.c)
+ - VFIO group and vfio-dev children (drivers/vfio/)
+ - IOMMU groups (/sys/kernel/iommu_groups/<N>/,
+ drivers/iommu/iommu.c)
+
+Users: udev, systemd-udevd, libvirt, DPDK, SPDK
diff --git a/Documentation/driver-api/index.rst b/Documentation/driver-api/index.rst
index eaf7161ff9578..b1807ca6cd1ce 100644
--- a/Documentation/driver-api/index.rst
+++ b/Documentation/driver-api/index.rst
@@ -23,6 +23,7 @@ of interest to most developers working on device drivers.
driver-model/index
device_link
infrastructure
+ sysfs-lazy
ioctl
pm/index
diff --git a/Documentation/driver-api/sysfs-lazy.rst b/Documentation/driver-api/sysfs-lazy.rst
new file mode 100644
index 0000000000000..5fbd7732afe0d
--- /dev/null
+++ b/Documentation/driver-api/sysfs-lazy.rst
@@ -0,0 +1,146 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+=========================
+Lazy sysfs initialisation
+=========================
+
+Overview
+========
+
+``device_add()`` normally creates every sysfs attribute, attribute
+group, and class/bus/driver symlink under a new device's directory
+synchronously. For populations created in bulk - hundreds of PCI
+SR-IOV VFs, fleets of vfio-dev children - that work dominates
+``device_add()`` latency yet the resulting sysfs nodes are rarely
+read.
+
+Lazy sysfs defers attribute creation until the directory is first
+opened by userspace. The device directory itself, its parent chain,
+and uevent broadcast remain eager; only the *content* of the
+directory is deferred.
+
+Opting in
+=========
+
+A subsystem opts a device in by:
+
+1. Calling ``device_set_sysfs_lazy(dev)`` before ``device_add()``.
+2. Making the kernfs directory created by ``device_add()`` a lazy
+ directory via ``kernfs_set_lazy()``.
+
+``kernfs_set_lazy()`` flips ``KERNFS_LAZY`` on a directory under
+``kernfs_rwsem`` and returns ``-EINVAL`` for namespaced nodes (a
+non-NULL namespace tag or the ``KERNFS_NS`` flag) and non-directory
+nodes: lazy dispatch does not carry an active namespace tag into the
+populate callback.
+
+The flag is set once and never cleared.
+
+Populate callbacks
+==================
+
+A lazy directory's first ``readdir(3)`` or ``open(2)`` of a
+not-yet-created child invokes one of two callbacks defined on
+``struct kobj_type``::
+
+ int (*populate)(struct kobject *kobj, const char *name);
+ void (*populate_all)(struct kobject *kobj);
+
+``populate(name)`` runs on a named lookup miss and creates the single
+matching attribute. ``populate_all()`` runs on the first
+``readdir(3)`` and creates every attribute the device exposes.
+
+For a ``struct device``, the driver core wires both callbacks to
+``device_sysfs_apply()``, which walks an immutable
+``device_sysfs_entry`` table. Each row declares an
+``applies_to(dev)`` predicate, a ``create(dev, name)`` action, and a
+``remove(dev)`` action. The walker dispatches the rows whose
+predicate passes for ``@dev``; the rows themselves call
+``sysfs_create_file()``, ``sysfs_create_link()``, or
+``sysfs_create_group()`` directly.
+
+The same pattern is used by ``iommu_group``: a lazy ``iommu_group``
+kobject's ``populate``/``populate_all`` walks a small fixed table of
+group attributes (``reserved_regions``, ``type``).
+
+Locking
+=======
+
+Each lazy device holds ``dev->sysfs_lazy->lock`` (a mutex). All
+three populate paths take it::
+
+ populate(name) -> mutex_lock(lock)
+ walk(ADD_ONE, name)
+ mutex_unlock
+ populate_all() -> if (test_bit(POPULATED)) return;
+ mutex_lock(lock)
+ if (test_bit(POPULATED)) goto out;
+ if (dev->p->dead) goto out;
+ walk(ADD_ALL, NULL)
+ set_bit(POPULATED)
+ mutex_unlock
+ REMOVE_ALL teardown -> mutex_lock(lock)
+ walk(REMOVE_ALL, NULL)
+ mutex_unlock
+
+The ``dev->sysfs_lazy->populated`` bool is the
+``populate_all()`` once-latch. The unlocked read fast path is a
+performance optimisation; the field is only authoritative under
+``lock``. A stale false on the fast path harmlessly retakes
+the mutex and re-checks.
+
+Lock ordering: ``lock`` nests inside any caller-held VFS or
+kernfs lock that brought us into ``populate``/``populate_all``. No
+sysfs / kernfs operation that requires ``kernfs_rwsem`` write may be
+issued while holding ``lock``.
+
+The ``iommu_group`` path uses the same pattern:
+``group->sysfs_lazy.lock`` and
+``group->sysfs_lazy.populated``.
+
+Teardown
+========
+
+``device_del()`` sets ``dev->p->dead`` *before* the
+``REMOVE_ALL`` walker call and takes ``lock`` around it.
+Any populate callback racing with ``device_del()``:
+
+1. Either takes the lock first and runs against a not-yet-dead
+ device (its created files are torn down a moment later by
+ ``REMOVE_ALL``, which is idempotent against the row's
+ ``remove()``); or
+2. Sees ``dev->p->dead == true`` after acquiring the lock and bails
+ without creating anything.
+
+Either way, the device exits with a quiesced sysfs subtree and no
+populate callback sees a half-deleted device.
+
+``iommu_group_release()`` follows the same dead-flag + lock-then-walk
+contract.
+
+Known limitations
+=================
+
+* Lazy sysfs is only available for devices whose populate path is
+ driven through ``device_sysfs_entry`` rows. Subsystems with bespoke
+ ``device_add()``-time sysfs creation must migrate before opting in.
+* Namespaced kobjects cannot be marked lazy: ``kernfs_set_lazy()``
+ rejects them because the populate callback does not carry the
+ active namespace tag.
+* The directory's eager skeleton (the directory inode itself and the
+ ``uevent`` file emitted by ``device_add()``) is not deferred.
+* Per-attribute creation cost is unchanged. Lazy sysfs amortises
+ the cost across first-access events instead of paying it during
+ ``device_add()``.
+
+See also
+========
+
+* ``include/linux/kernfs.h`` - ``kernfs_set_lazy()``, ``KERNFS_LAZY``.
+* ``drivers/base/core.c`` - ``device_sysfs_apply()``,
+ ``driver_core_sysfs_entries[]``.
+* ``drivers/iommu/iommu.c`` - ``iommu_group_lazy_attrs[]``.
+* ``drivers/base/test/device_sysfs_apply_test.c`` - KUnit walker
+ contract tests.
+* ``tools/testing/selftests/sysfs-lazy/`` - userspace VFS-level
+ parity tests.
diff --git a/MAINTAINERS b/MAINTAINERS
index 5d8a887c868ed..1424e641d535f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7800,9 +7800,12 @@ M: Danilo Krummrich <dakr@kernel.org>
L: driver-core@lists.linux.dev
S: Supported
T: git git://git.kernel.org/pub/scm/linux/kernel/git/driver-core/driver-core.git
+F: Documentation/ABI/testing/sysfs-lazy
F: Documentation/core-api/kobject.rst
F: Documentation/driver-api/driver-model/
+F: Documentation/driver-api/sysfs-lazy.rst
F: drivers/base/
+F: drivers/base/test/device_sysfs_apply_test.c
F: fs/debugfs/
F: fs/sysfs/
F: include/linux/device/
@@ -7830,6 +7833,7 @@ F: samples/rust/rust_debugfs_scoped.rs
F: samples/rust/rust_driver_platform.rs
F: samples/rust/rust_driver_faux.rs
F: samples/rust/rust_soc.rs
+F: tools/testing/selftests/sysfs-lazy/
DRIVERS FOR OMAP ADAPTIVE VOLTAGE SCALING (AVS)
M: Nishanth Menon <nm@ti.com>
--
2.47.3
Amazon Web Services Development Center Germany GmbH
Tamara-Danz-Str. 13
10243 Berlin
Geschaeftsfuehrung: Christof Hellmis, Andreas Stieger
Eingetragen am Amtsgericht Charlottenburg unter HRB 257764 B
Sitz: Berlin
Ust-ID: DE 365 538 597
^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [RFC PATCH 00/14] driver core: defer per-VF sysfs creation for fast SR-IOV bring-up
2026-07-02 17:40 [RFC PATCH 00/14] driver core: defer per-VF sysfs creation for fast SR-IOV bring-up Pavol Sakac
` (4 preceding siblings ...)
2026-07-02 17:51 ` [RFC PATCH 05/14] driver core: add struct device_sysfs_entry and walker Pavol Sakac
@ 2026-07-10 14:16 ` Greg Kroah-Hartman
5 siblings, 0 replies; 15+ messages in thread
From: Greg Kroah-Hartman @ 2026-07-10 14:16 UTC (permalink / raw)
To: Pavol Sakac
Cc: Rafael J . Wysocki, Danilo Krummrich, Tejun Heo, linux-kernel,
driver-core, David Woodhouse, Pasha Tatashin, Mike Rapoport,
Pratyush Yadav, David Matlack, Samiullah Khawaja, Alexander Graf,
linux-mm, kexec, nh-open-source
On Thu, Jul 02, 2026 at 07:40:19PM +0200, Pavol Sakac wrote:
> Virtualization hardware keeps increasing in CPU-core and VF density.
> Kernel is trending towards preserving VFs using LUO across a kexec
> which will put SR-IOV in live-update hotpath. VFs must be re-added to
> the sysfs tree, along with its supporting devices (e.g. vfio,
> iommu_groups). On production hardware a single VF can consume ~80 kernfs
> nodes. With thousands of VFs on modern hardware, hundreds of thousands
> of kernfs nodes need to be created, which can add 100ms+ to the boot
> time and to guest downtime.
Hundreds of thousands? You have a hundred sysfs files per device?
> Proposed here is a PoC of deferred materialization of sysfs files until
> first access to avoid cost of kernfs node creation in hot path. In the
> reproducer below using a synthetic VF to isolate the sysfs overhead,
> the per-device sysfs-creation time is reduced by ~74%. It exploits the
> expected access pattern of hypervisors, where only a subset of device
> files need to be accessed to attach a VF to a VM on the hot path.
Ick, that access pattern could change with other users, this feels very
fragile.
> To allow lazy init scheme, a minimal set of files and directories is
> built per device: a device directory resolves directly to the device's
> kobject and is used to materialize attributes at access time. The
> access is trapped in kernfs at the dir-read or path-walk stage and
> depending on the trigger, either the full directory or a single file is
> materialized.
>
> The changes are two logically distinct parts:
>
> 1. Refactor of hardcoded device_add / iommu_group sysfs attribute
> creation to a table-driven form to allow walkability of all
> attributes and single point of definition for attributes
> regardless of lazy opt-in to avoid duplicate definitions and
> associated maintainability issues with it.
This is probably a good idea anyway. Want to just send this as a series
to start with, so we can take that?
> 2. Lazy-init infra to front-run access to device files at lookup/
> readdir time to trigger materialization. Opt-in per device: PCI VFs
> and VFIO devices opt in via device_set_sysfs_lazy(); iommu_group
> opts in at the kobject level.
This is going to get complex. Where exactly is the bottleneck? I'm
guessing that systems with that many devices also have many hundreds of
CPUs, right? Are we hitting a single lock somewhere? There have been
changes in kernfs in the past to split up the locks to be more
fine-grained, perhaps that needs to continue to solve this?
> As a PoC, the code does not yet follow this logical structure in the
> commit sequence and likely does not yet cover all corner cases.
>
> Synthetic reproducer measurements
> ---------------------------------
> devices per-dev time total time kernfs nodes (base -> lazy)
> ------- ------------ -------------- ----------------------------
> 500 31.8->8.3 us 15.9-> 4.1 ms 36,417 -> 3,922 (72.8->7.8/dev)
> 1000 33.2->8.7 us 33.2-> 8.7 ms 73,427 -> 8,432 (73.4->8.4/dev)
> 2500 32.8->9.1 us 82.0->22.7 ms 184,421 -> 21,925 (73.8->8.8/dev)
> 5000 33.0->8.7 us 165.1->43.5 ms 369,401 -> 44,406 (73.9->8.9/dev)
>
> The reproducer is synthetic: an in-kernel module registers N unbound
> platform devices (60 attributes / 4 groups, ~74 kernfs nodes each; no
> config space, BARs, or probe) in one timed burst that models SR-IOV
> enablement. It runs as a two-kernel A/B from one vanilla 7.1-rc2 tree
> (eager baseline vs patched, plus a patched/opt-in-OFF arm), 20 runs per
> point. Measures only sysfs-creation slice.
I sure hope sr-iov devices are NOT platform devices. If so, please go
fix that up first :)
> Deferral removes ~74% of the sysfs-creation time and ~88% of the kernfs
> nodes. At 5000 devices that is 121.6 ms removed (165.1 -> 43.5 ms) and
> ~325,000 fewer kernfs nodes (369,401 -> 44,406). The table-driven rework
> adds a +7..+11% eager-path time overhead to non-opted devices.
You're just deferring this work to happen later, so when is that
"later"? And what about multi-threaded probing of the bus, doesn't that
speed stuff up too?
> Available as a docker image that orchestrates builds in qemu guest and
> prints results as a table (x86_64 Linux host with /dev/kvm assumed):
>
> docker run --device /dev/kvm ghcr.io/pavsa/linux-lazy-sysfs-vf-bench:7.1-rc2
>
> Assumptions / Trade-offs
> ------------------------
> The saving depends on the access pattern of hypervisors, and on no
> component walking the whole device tree on the hot path to avoid full
> materialization. Both are expected to hold on optimized hypervisors.
Hah, as if. Many userspace tools, once a device is notified, walk the
whole tree of it to read the attributes. You are going to be fighting
that problem constantly...
> An actual production hypervisor was observed in local testing to
> materialize an additional 5-10% of total nodes on average on the hot
> path.
>
> Overhead: +7..+11% eager-path time overhead applies to every non-opted
> device going through device_add() as all struct devices traverse the
> walker and can't be gated under a config option.
> The lazy opt-in infra can be gated under config option to have zero
> impact if disabled.
Again, where exactly is the time being spent? What lock is "hot"?
> Final words
> -----------
> This is an RFC to first get feedback on the decisions taken at driver
> core + kernfs level before engaging maintainers of other subsystems
> if we want to pursue this further or pivot to something else.
>
> The code is at a proof-of-concept quality written with AI assistance
> with some known limitations with main target to gather potential
> savings and initiate conversation.
>
> Feedback appreciated for:
> - table-driven attribute refactor targeting common device add path /
> suggestions for more efficient/less invasive approach
What's wrong with the normal way attributes are defined and allocated?
They should all be tables today, and the driver core creating them when
needed, no individual driver should ever be doing that on its own.
thanks,
greg k-h
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [RFC PATCH 09/14] iommu: lazy-populate iommu_group reserved_regions/type attrs
2026-07-02 17:51 ` [RFC PATCH 09/14] iommu: lazy-populate iommu_group reserved_regions/type attrs Pavol Sakac
@ 2026-07-10 14:19 ` Greg Kroah-Hartman
0 siblings, 0 replies; 15+ messages in thread
From: Greg Kroah-Hartman @ 2026-07-10 14:19 UTC (permalink / raw)
To: Pavol Sakac
Cc: Rafael J . Wysocki, Danilo Krummrich, Tejun Heo, linux-kernel,
driver-core, David Woodhouse, Pasha Tatashin, Mike Rapoport,
Pratyush Yadav, David Matlack, Samiullah Khawaja, Alexander Graf,
linux-mm, kexec, nh-open-source
On Thu, Jul 02, 2026 at 07:51:09PM +0200, Pavol Sakac wrote:
> + /*
> + * Defer reserved_regions and type - both are read only on
> + * VFIO/iommufd ioctl paths, so let the populate callbacks
> + * materialise them on demand.
> + *
> + * KERNFS_LAZY MUST be set before kobject_create_and_add("devices"),
> + * which calls kernfs_inc_rev() on this kobject's directory and
> + * would otherwise leave a stale negative dentry cached for a
> + * missing reserved_regions/type child. WARN_ON because
> + * iommu_group_alloc() controls all group kobjects directly.
> + */
> + WARN_ON(kernfs_set_lazy(group->kobj.sd));
You just crashed the kernel, that's not nice :(
> group->devices_kobj = kobject_create_and_add("devices", &group->kobj);
> if (!group->devices_kobj) {
> kobject_put(&group->kobj); /* triggers .release & free */
> @@ -1091,19 +1210,6 @@ struct iommu_group *iommu_group_alloc(void)
> */
> kobject_put(&group->kobj);
>
> - ret = iommu_group_create_file(group,
> - &iommu_group_attr_reserved_regions);
> - if (ret) {
> - kobject_put(group->devices_kobj);
> - return ERR_PTR(ret);
> - }
> -
> - ret = iommu_group_create_file(group, &iommu_group_attr_type);
> - if (ret) {
> - kobject_put(group->devices_kobj);
> - return ERR_PTR(ret);
> - }
Why isn't the driver core creating these properly on its own today?
These calls should not have to be done. Also, what's up with the "raw"
kobject? That's not ok, this is circumventing the driver core in a way
that is not going to work out at all (as you are finding here...)
thanks,
greg k-h
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-07-10 14:19 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-02 17:40 [RFC PATCH 00/14] driver core: defer per-VF sysfs creation for fast SR-IOV bring-up Pavol Sakac
2026-07-02 17:40 ` [RFC PATCH 01/14] kernfs: add populate callbacks and KERNFS_LAZY flag for lazy dir population Pavol Sakac
2026-07-02 17:40 ` [RFC PATCH 02/14] sysfs: add existence-check helpers for lazy populate races Pavol Sakac
2026-07-02 17:40 ` [RFC PATCH 03/14] sysfs: introduce sysfs_kf_syscall_ops dispatching to kobj_type Pavol Sakac
2026-07-02 17:40 ` [RFC PATCH 04/14] driver core: add struct sysfs_lazy_state and device_set_sysfs_lazy() Pavol Sakac
2026-07-02 17:51 ` [RFC PATCH 05/14] driver core: add struct device_sysfs_entry and walker Pavol Sakac
2026-07-02 17:51 ` [RFC PATCH 06/14] driver core: wire device_ktype populate to walker Pavol Sakac
2026-07-02 17:51 ` [RFC PATCH 07/14] driver core: migrate device sysfs to device_sysfs_entry table Pavol Sakac
2026-07-02 17:51 ` [RFC PATCH 08/14] PCI/sysfs: migrate to device_sysfs_entry, defer physfn symlink Pavol Sakac
2026-07-02 17:51 ` [RFC PATCH 09/14] iommu: lazy-populate iommu_group reserved_regions/type attrs Pavol Sakac
2026-07-10 14:19 ` Greg Kroah-Hartman
2026-07-02 17:51 ` [RFC PATCH 10/14] PCI/IOV: opt SR-IOV VFs into sysfs_lazy Pavol Sakac
2026-07-02 17:51 ` [RFC PATCH 11/14] vfio: opt vfio-dev and VFIO group devices " Pavol Sakac
2026-07-02 17:51 ` [RFC PATCH 13/14] Documentation: add lazy sysfs initialisation and device_sysfs_entry docs Pavol Sakac
2026-07-10 14:16 ` [RFC PATCH 00/14] driver core: defer per-VF sysfs creation for fast SR-IOV bring-up Greg Kroah-Hartman
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox