[RFC PATCH 13/14] Documentation: add lazy sysfs initialisation and device_sysfs_entry docs

Pavol Sakac <[email protected]>
Newsgroups org.infradead.lists.kexec,dev.linux.lists.driver-core,org.kernel.vger.linux-kernel,org.kvack.linux-mm
Message-ID <[email protected]>
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 <[email protected]>
Cc: Rafael J. Wysocki <[email protected]>
Cc: Jonathan Corbet <[email protected]>
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: Danilo Krummrich <[email protected]>
Cc: [email protected]
Cc: Shuah Khan <[email protected]>
Cc: [email protected]
Assisted-by: Claude:claude-opus-4.7
Signed-off-by: Pavol Sakac <[email protected]>
---
 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 <[email protected]>
+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 <[email protected]>
 L:	[email protected]
 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 <[email protected]>
-- 
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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.