[PATCH 1/4] software node: support automatic secondary fwnode assignment

From: Bartosz Golaszewski

Date: Thu Mar 19 2026 - 12:21:15 EST


Provide a structure and a set of functions allowing to set up automatic
secondary firmware node assignment for platform devices. It uses
a behind-the-scenes bus notifier for a group of named software nodes. It
will wait for bus events and when a device is added, it will check its
name against the software node's name and - on match - assign the
software node as the secondary firmware node of the device's *real*
firmware node.

Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@xxxxxxxxxxxxxxxx>
---
drivers/base/swnode.c | 105 +++++++++++++++++++++++++++++++++++++++++++++++
include/linux/property.h | 18 ++++++++
2 files changed, 123 insertions(+)

diff --git a/drivers/base/swnode.c b/drivers/base/swnode.c
index 51320837f3a9f1bf4f65aa161d9b941affc74936..97e3354cdafd94e175d29acb697a0dc61186a9c8 100644
--- a/drivers/base/swnode.c
+++ b/drivers/base/swnode.c
@@ -6,6 +6,7 @@
* Author: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>
*/

+#include <linux/cleanup.h>
#include <linux/container_of.h>
#include <linux/device.h>
#include <linux/err.h>
@@ -1088,6 +1089,110 @@ int device_create_managed_software_node(struct device *dev,
}
EXPORT_SYMBOL_GPL(device_create_managed_software_node);

+static struct software_node_auto_secondary *to_auto_sec(struct notifier_block *nb)
+{
+ return container_of(nb, struct software_node_auto_secondary, nb);
+}
+
+static void swnode_set_secondary_fwnode(struct device *dev,
+ const struct software_node *swnode)
+{
+ struct fwnode_handle *fwnode;
+
+ fwnode = software_node_fwnode(swnode);
+ if (WARN(!fwnode, "Software node %s should have been registered before", swnode->name))
+ return;
+
+ set_secondary_fwnode(dev, fwnode);
+}
+
+static int swnode_auto_secondary_notifier(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct software_node_auto_secondary *auto_sec = to_auto_sec(nb);
+ const struct software_node * const *swnode;
+ struct device *dev = data;
+
+ switch (action) {
+ case BUS_NOTIFY_ADD_DEVICE:
+ for (swnode = auto_sec->node_group; *swnode; swnode++) {
+ if (strcmp(dev_name(dev), (*swnode)->name))
+ continue;
+
+ swnode_set_secondary_fwnode(dev, *swnode);
+ return NOTIFY_OK;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
+
+/**
+ * software_node_register_auto_secondary() - set up automatic assignment of
+ * secondary firmware nodes
+ * @auto_sec: Context data to use.
+ *
+ * NOTE: All software nodes passed in @auto_sec must be named.
+ *
+ * Returns:
+ * 0 on success, negative error number on failure.
+ *
+ * This registers the software node group passed in @auto_sec and sets up
+ * automatic assignment of them as secondary firmware nodes of real nodes
+ * attached to appropriate devices on the bus specified in @auto_sec. The
+ * software nodes must be named and their names must be the same as the
+ * devices they should reference. The assignment happens when the device is
+ * first added to the bus.
+ */
+int software_node_register_auto_secondary(struct software_node_auto_secondary *auto_sec)
+{
+ const struct software_node * const *swnode;
+ int ret;
+
+ if (!auto_sec->node_group || !auto_sec->bus)
+ return -EINVAL;
+
+ for (swnode = auto_sec->node_group; *swnode; swnode++) {
+ if (!(*swnode)->name)
+ return -EINVAL;
+ }
+
+ ret = software_node_register_node_group(auto_sec->node_group);
+ if (ret)
+ return ret;
+
+ auto_sec->nb.notifier_call = swnode_auto_secondary_notifier;
+ ret = bus_register_notifier(auto_sec->bus, &auto_sec->nb);
+ if (ret)
+ software_node_unregister_node_group(auto_sec->node_group);
+
+ /* Device may have been already added. */
+ for (swnode = auto_sec->node_group; *swnode; swnode++) {
+ struct device *dev __free(put_device) =
+ bus_find_device_by_name(auto_sec->bus, NULL, (*swnode)->name);
+ if (dev)
+ swnode_set_secondary_fwnode(dev, *swnode);
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(software_node_register_auto_secondary);
+
+/**
+ * software_node_unregister_auto_secondary() - unregister automatic assignment
+ * of secondary firmware nodes
+ * @auto_sec: Address of the context structure used at registration
+ */
+void software_node_unregister_auto_secondary(struct software_node_auto_secondary *auto_sec)
+{
+ bus_unregister_notifier(auto_sec->bus, &auto_sec->nb);
+ software_node_unregister_node_group(auto_sec->node_group);
+}
+EXPORT_SYMBOL_GPL(software_node_unregister_auto_secondary);
+
void software_node_notify(struct device *dev)
{
struct swnode *swnode;
diff --git a/include/linux/property.h b/include/linux/property.h
index e30ef23a9af3396455d5bb19bb6d41089d81d77f..2a7af60cbb8ecc2ba83819ce92562db42705b82a 100644
--- a/include/linux/property.h
+++ b/include/linux/property.h
@@ -15,10 +15,12 @@
#include <linux/bits.h>
#include <linux/cleanup.h>
#include <linux/fwnode.h>
+#include <linux/notifier.h>
#include <linux/stddef.h>
#include <linux/types.h>
#include <linux/util_macros.h>

+struct bus_type;
struct device;

enum dev_prop_type {
@@ -610,4 +612,20 @@ int device_create_managed_software_node(struct device *dev,
const struct property_entry *properties,
const struct software_node *parent);

+/**
+ * struct software_node_auto_secondary - context data for automatic secondary
+ * fwnode assignment
+ * @nb: Private bus notifier data - don't use
+ * @node_group: NULL-terminated array of software node addresses
+ * @bus: Bus on which to wait for devices
+ */
+struct software_node_auto_secondary {
+ struct notifier_block nb;
+ const struct software_node * const *node_group;
+ const struct bus_type *bus;
+};
+
+int software_node_register_auto_secondary(struct software_node_auto_secondary *auto_sec);
+void software_node_unregister_auto_secondary(struct software_node_auto_secondary *auto_sec);
+
#endif /* _LINUX_PROPERTY_H_ */

--
2.47.3