* [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* 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
* [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