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


Reply via email to