mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] driver core: hand off fwnode ownership when shared fwnode owner is rejected
@ 2026-09-28 11:49 Peng Fan (OSS)
  2026-09-28 12:09 ` Sudeep Holla
  0 siblings, 1 reply; 5+ messages in thread
From: Peng Fan (OSS) @ 2026-09-28 11:49 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
	Saravana Kannan, Sudeep Holla, Hans de Goede
  Cc: driver-core, linux-kernel, imx, Peng Fan, stable

From: Peng Fan <peng.fan@nxp.com>

When multiple devices share the same fwnode (e.g. the SCMI bus creates
both "pinctrl" and "pinctrl-imx" devices for SCMI_PROTOCOL_PINCTRL), only
the first device registered becomes the fwnode owner (fwnode->dev, set in
device_add()). If that owner never binds -- for example its driver returns
-ENODEV because it is blocklisted on this SoC, or because its driver is
not compiled in at all -- then driver_bound() is never called for it, so
fwnode_links_purge_suppliers() and fw_devlink_pickup_dangling_consumers()
are never run for the fwnode. The child fwnode supplier links (pin group
nodes such as lpi2c3grp, uart5grp, ...) stay unsatisfied and every
consumer of those child nodes defers probe forever.

On i.MX95 this manifests as a complete boot failure: the generic "pinctrl"
SCMI device claims fwnode ownership but its driver returns -ENODEV, while
the vendor "pinctrl-imx" device binds successfully. Because
dev->fwnode->dev still points at the rejected "pinctrl" device,
driver_bound() of "pinctrl-imx" skips the supplier purge and dangling
consumer pickup, so all I2C buses, SPI, UART, MMC, USB and PCIe
controllers wait forever for their pinctrl suppliers.

Fix this in two places:

1. In really_probe() failure path: when the driver definitively rejects
   a device (-ENODEV / -ENXIO), fw_devlink_release_shared_fwnode() is
   called. If the rejected device is the fwnode owner, it either
   transfers ownership to an already-bound sibling (and runs the
   purge/pickup on its behalf) or clears ownership so the next sibling
   to bind can re-acquire it.

2. In device_links_driver_bound(): re-acquire the fwnode when it is
   unowned (!fwnode->dev) or when the current owner has no driver at
   all (!fwnode->dev->driver, meaning the driver was never compiled in
   or loaded as a module). This covers the case where probe rejection
   never happens because no driver ever matches.

fwnode->dev is not serialized by a lock; instead every writer only ever
touches a fwnode->dev it already owns (== dev, as device_del() does when
it clears ownership) or one that is currently unowned (== NULL, as
device_add() does when it claims ownership). This patch follows the same
discipline: fw_devlink_release_shared_fwnode() only writes fwnode->dev
when this device is the current owner; device_links_driver_bound() only
claims fwnode->dev when it is NULL or when the current owner has no
driver (and therefore cannot be in the process of binding).

Fixes: f9aa460672c9 ("driver core: Refactor fw_devlink feature")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
This issue is triggered by 
aac4e67d6eb9 ("firmware: arm_scmi: Always create devices for standard protocols")
in linux-next next-20260925.

But I think this is a fix to
f9aa460672c9 ("driver core: Refactor fw_devlink feature")

Detailed information could be found in patch commit log.
---
 drivers/base/base.h |  1 +
 drivers/base/core.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/base/dd.c   |  9 ++++++++
 3 files changed, 73 insertions(+)

diff --git a/drivers/base/base.h b/drivers/base/base.h
index a5b7abc10ff0..36d34aa328c5 100644
--- a/drivers/base/base.h
+++ b/drivers/base/base.h
@@ -292,6 +292,7 @@ void device_links_unbind_consumers(struct device *dev);
 bool device_link_flag_is_sync_state_only(u32 flags);
 void fw_devlink_drivers_done(void);
 void fw_devlink_probing_done(void);
+void fw_devlink_release_shared_fwnode(struct device *dev);
 
 #define dev_for_each_link_to_supplier(__link, __dev)	\
 	list_for_each_entry_srcu(__link, &(__dev)->links.suppliers, c_node, \
diff --git a/drivers/base/core.c b/drivers/base/core.c
index 5daa88fa724a..e76153d5f124 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -1376,6 +1376,20 @@ void device_links_driver_bound(struct device *dev)
 	struct device_link *link, *ln;
 	LIST_HEAD(sync_list);
 
+	/*
+	 * Multiple devices may share the same fwnode (e.g. the SCMI bus
+	 * creates several devices per protocol node).  The first one
+	 * registered owns the fwnode; if that owner's driver is rejected
+	 * it relinquishes ownership (fwnode->dev == NULL, see
+	 * fw_devlink_release_shared_fwnode()).  If the owning device has
+	 * no driver at all (driver never compiled / loaded as module), it
+	 * will never bind, so it is safe to take over.  Re-acquire the
+	 * fwnode here so the purge/pickup still happens.
+	 */
+	if (dev->fwnode && (!dev->fwnode->dev ||
+	    (dev->fwnode->dev != dev && !dev->fwnode->dev->driver)))
+		dev->fwnode->dev = dev;
+
 	/*
 	 * If a device binds successfully, it's expected to have created all
 	 * the device links it needs to or make new device links as it needs
@@ -1390,6 +1404,7 @@ void device_links_driver_bound(struct device *dev)
 	 * consumers to defer probe indefinitely waiting for a device for the
 	 * child firmware node.
 	 */
+
 	if (dev->fwnode && dev->fwnode->dev == dev) {
 		fwnode_links_purge_suppliers(dev->fwnode);
 		fw_devlink_pickup_dangling_consumers(dev);
@@ -1474,6 +1489,54 @@ void device_links_driver_bound(struct device *dev)
 	device_links_flush_sync_list(&sync_list, dev);
 }
 
+static int fwnode_shared_bound_match(struct device *dev, const void *data)
+{
+	const struct device *rejected = data;
+
+	return dev != rejected && dev->fwnode == rejected->fwnode &&
+	       device_is_bound(dev);
+}
+
+/**
+ * fw_devlink_release_shared_fwnode - Hand off a shared fwnode on probe reject.
+ * @dev: Device whose driver just definitively rejected it (-ENODEV/-ENXIO).
+ *
+ * Several devices can share one fwnode (e.g. the SCMI bus creates both a
+ * "pinctrl" and a "pinctrl-imx" device for one protocol node). Only the first
+ * one registered owns the fwnode (fwnode->dev). If that owner's driver rejects
+ * the device, driver_bound() never runs for it, so the dangling consumers of
+ * the fwnode's child nodes are never picked up and defer probe forever.
+ *
+ * When the rejected device is the fwnode owner, relinquish ownership so those
+ * consumers can be resolved by a sibling that does bind:
+ *
+ *  - If a sibling has already bound, transfer ownership to it and run the
+ *    purge/pickup the owner could not do.
+ *  - Otherwise clear ownership so the next sibling to bind re-acquires it in
+ *    device_links_driver_bound() and runs the purge/pickup there.
+ *
+ * fwnode->dev is not lock-serialized; this only ever writes it when @dev is
+ * the current owner (fwnode->dev == dev), the same ownership-gated rule
+ * device_del() uses when it clears fwnode->dev on the owner.
+ */
+void fw_devlink_release_shared_fwnode(struct device *dev)
+{
+	struct device *sibling;
+
+	if (!dev->fwnode || dev->fwnode->dev != dev)
+		return;
+
+	sibling = bus_find_device(dev->bus, NULL, dev, fwnode_shared_bound_match);
+	if (sibling) {
+		dev->fwnode->dev = sibling;
+		fwnode_links_purge_suppliers(dev->fwnode);
+		fw_devlink_pickup_dangling_consumers(sibling);
+		put_device(sibling);
+	} else {
+		dev->fwnode->dev = NULL;
+	}
+}
+
 /**
  * __device_links_no_driver - Update links of a device without a driver.
  * @dev: Device without a drvier.
diff --git a/drivers/base/dd.c b/drivers/base/dd.c
index f6525a7ee8c5..e19cfc91ebe1 100644
--- a/drivers/base/dd.c
+++ b/drivers/base/dd.c
@@ -762,6 +762,15 @@ static int really_probe(struct device *dev, const struct device_driver *drv)
 dev_groups_failed:
 	device_remove(dev);
 probe_failed:
+	/*
+	 * -ENODEV/-ENXIO mean the driver definitively rejected this device
+	 * (errors are returned as positive values at this label). If it owns a
+	 * fwnode shared with sibling devices (e.g. SCMI creates several devices
+	 * per protocol node), hand the fwnode off so its dangling child
+	 * consumers can be resolved by a sibling instead of deferring forever.
+	 */
+	if (ret == ENODEV || ret == ENXIO)
+		fw_devlink_release_shared_fwnode(dev);
 	driver_sysfs_remove(dev);
 sysfs_failed:
 	bus_notify(dev, BUS_NOTIFY_DRIVER_NOT_BOUND);

---
base-commit: f5f84daefcd92d7a630066635ecea1433ed5eac7
change-id: 20260928-driver-core-f1d9e991edb2

Best regards,
--  
Peng Fan <peng.fan@nxp.com>


^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-09-29 15:56 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-28 11:49 [PATCH] driver core: hand off fwnode ownership when shared fwnode owner is rejected Peng Fan (OSS)
2026-09-28 12:09 ` Sudeep Holla
2026-09-29  1:47   ` Peng Fan
2026-09-29  8:35     ` Sudeep Holla
2026-09-29 15:56       ` Sudeep Holla

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®