[PATCH v2 4/8] swnode: ignore primary nodes in device_remove_software_node()

From: Johan Hovold

Date: Tue Nov 10 2026 - 03:55:33 EST


device_remove_software_node() should only be used to remove secondary
firmware nodes added by device_add_software_node() but will currently
drop a reference also if a device has a primary software node.

Bail out early if device_remove_software_node() is called for a device
whose primary firmware node is a software node and fix up the two
callers that currently work around this themselves.

Signed-off-by: Johan Hovold <johan@xxxxxxxxxx>
---
drivers/base/core.c | 3 ++-
drivers/base/platform.c | 9 ++++-----
drivers/base/swnode.c | 3 +++
drivers/i2c/i2c-core-base.c | 9 +++------
include/linux/device.h | 1 +
5 files changed, 13 insertions(+), 12 deletions(-)

diff --git a/drivers/base/core.c b/drivers/base/core.c
index bf97b06337a1..460c7ad1eaeb 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -5212,10 +5212,11 @@ int dev_warn_probe(const struct device *dev, int err, const char *fmt, ...)
}
EXPORT_SYMBOL_GPL(dev_warn_probe);

-static inline bool fwnode_is_primary(struct fwnode_handle *fwnode)
+bool fwnode_is_primary(struct fwnode_handle *fwnode)
{
return fwnode && !IS_ERR(fwnode->secondary);
}
+EXPORT_SYMBOL_GPL(fwnode_is_primary);

/**
* set_primary_fwnode - Change the primary firmware node of a given device.
diff --git a/drivers/base/platform.c b/drivers/base/platform.c
index be5d2f315fdb..e0b38dbd5acf 100644
--- a/drivers/base/platform.c
+++ b/drivers/base/platform.c
@@ -598,15 +598,14 @@ static void platform_device_release(struct device *dev)
{
struct platform_object *pa = container_of(dev, struct platform_object,
pdev.dev);
- bool fwnode_is_software_node = is_software_node(dev_fwnode(dev));

device_remove_software_node(dev);
/*
- * If the firmware node is a software node, its reference count
- * was decremented by device_remove_software_node().
+ * Must be done after device_remove_software_node() which may have
+ * freed a secondary firmware node and cleared the fwnode pointer.
*/
- if (!fwnode_is_software_node)
- fwnode_handle_put(pa->pdev.dev.fwnode);
+ fwnode_handle_put(dev->fwnode);
+
kfree(pa->pdev.dev.platform_data);
kfree(pa->pdev.mfd_cell);
kfree(pa->pdev.resource);
diff --git a/drivers/base/swnode.c b/drivers/base/swnode.c
index 5dc2029f9a7b..a90c20cb84e6 100644
--- a/drivers/base/swnode.c
+++ b/drivers/base/swnode.c
@@ -1108,6 +1108,9 @@ void device_remove_software_node(struct device *dev)
if (!swnode)
return;

+ if (fwnode_is_primary(&swnode->fwnode))
+ return;
+
if (device_is_registered(dev))
software_node_notify_remove(dev);

diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c
index c55ca14eaad9..af3ae2a83626 100644
--- a/drivers/i2c/i2c-core-base.c
+++ b/drivers/i2c/i2c-core-base.c
@@ -1056,7 +1056,6 @@ EXPORT_SYMBOL_GPL(i2c_new_client_device);
void i2c_unregister_device(struct i2c_client *client)
{
struct fwnode_handle *fwnode;
- bool fwnode_is_software_node;

if (IS_ERR_OR_NULL(client))
return;
@@ -1067,14 +1066,12 @@ void i2c_unregister_device(struct i2c_client *client)
else if (is_acpi_device_node(fwnode))
acpi_device_clear_enumerated(to_acpi_device_node(fwnode));

- fwnode_is_software_node = is_software_node(fwnode);
device_remove_software_node(&client->dev);
/*
- * If the firmware node is a software node, its reference count
- * was decremented by device_remove_software_node().
+ * Must be done after device_remove_software_node() which may have
+ * freed a secondary firmware node and cleared the fwnode pointer.
*/
- if (!fwnode_is_software_node)
- fwnode_handle_put(fwnode);
+ fwnode_handle_put(client->dev.fwnode);
device_unregister(&client->dev);
}
EXPORT_SYMBOL_GPL(i2c_unregister_device);
diff --git a/include/linux/device.h b/include/linux/device.h
index 6b082c638d45..229a15625766 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -1280,6 +1280,7 @@ int lock_device_hotplug_sysfs(void);
int device_offline(struct device *dev);
int device_online(struct device *dev);

+bool fwnode_is_primary(struct fwnode_handle *fwnode);
void set_primary_fwnode(struct device *dev, struct fwnode_handle *fwnode);
void set_secondary_fwnode(struct device *dev, struct fwnode_handle *fwnode);
void device_set_node(struct device *dev, struct fwnode_handle *fwnode);
--
2.56.0