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 <[email protected]>
Cc: Rafael J. Wysocki <[email protected]>
Cc: Tejun Heo <[email protected]>
Cc: Danilo Krummrich <[email protected]>
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Assisted-by: Claude:claude-opus-4.7
Signed-off-by: Pavol Sakac <[email protected]>
---
 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


Reply via email to