Devicetree
 help / color / mirror / Atom feed
* [PATCH net-next v3 0/4] driver core, net: handle fw_devlink for class devices and PHY packages
@ 2026-08-18  6:58 James Hilliard
  2026-08-18  6:58 ` [PATCH net-next v3 1/4] driver core: add fw_devlink supplier-copy helper James Hilliard
                   ` (4 more replies)
  0 siblings, 5 replies; 9+ messages in thread
From: James Hilliard @ 2026-08-18  6:58 UTC (permalink / raw)
  To: Rob Herring, Saravana Kannan, Greg Kroah-Hartman,
	Rafael J. Wysocki, Danilo Krummrich, Jonathan Corbet, Shuah Khan,
	Andy Shevchenko, Daniel Scally, Heikki Krogerus, Sakari Ailus,
	Len Brown, Andrew Lunn, Heiner Kallweit, Russell King,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Christian Marangi
  Cc: devicetree, linux-doc, linux-kernel, driver-core, linux-acpi,
	netdev, James Hilliard

Managed device links normally advance through probe and removal alongside
a device driver. Driverless class devices are added directly, so their
links do not currently receive equivalent transitions. A class supplier
can leave its consumers dormant, while sync-state-only links from a class
consumer can indefinitely prevent suppliers from receiving sync_state().
Removal also does not unbind consumers of a class supplier.

Ethernet PHY packages expose both sides of this problem. A package node
describes resources shared by member PHYs, but is a container rather than
a device. fw_devlink preserves that topology and temporarily represents
those dependencies through the closest device, which for OF MDIO is the
driverless MDIO bus class device. Its proxy links previously remained
after the real PHY had probed and kept the package suppliers from reaching
sync_state().

Handle the generic class-device lifecycle in the driver core. Put
driverless class devices into the probing state while device_add()
publishes them, complete their incoming and outgoing managed links after
class interfaces run, and apply the corresponding unbind and cleanup
transitions during device_del(). This supports class devices as either
suppliers or consumers and keeps descendant firmware links available for
devices created later. Keep inferred links to probing and registered class
suppliers out of no-driver fallback paths, and recognize class-backed
nodes when refreshing dependencies after firmware-tree overlays.

Keep generic firmware parsing faithful to the described topology. Add a
helper through which a subsystem can copy a container node's direct
supplier links onto the firmware node for a real child device, then use it
from PHY registration before device_add(). The ordinary fw_devlink path
consequently creates each managed PHY link at the normal
device-registration stage.

Finally, defer supplier sync-state callbacks across OF MDIO bus
registration and child population. This closes the interval between
completion of the bus class device and installation of the member PHY
links without adding an MDIO-specific device-link state transition.

This was found while validating an X-Powers AC300 package whose input
clock is supplied by PWM. Before the fix, the PWM driver reported that
sync_state() was pending due to stmmac-0 after the PHY had probed.

The complete series was tested on an Allwinner H616 board with an X-Powers
AC300 PHY package. All five package-derived supplier links targeted the
member PHY and no proxy link remained on the driverless MDIO bus. The PWM
supplier reached state_synced while continuing to generate the PHY clock.
These properties remained true after unbinding and rebinding the MAC, and
the PHY renegotiated a 100 Mbps full-duplex link. Both before and after
that lifecycle test, 8 MiB transfers in each direction compared correctly
and 100 consecutive pings completed without loss.

Validation also included:

  - a full arm64 defconfig Image and modules build with W=1;
  - KUnit tests for class supplier/consumer transitions, inferred-link
    preservation, and fwnode supplier copying and device-link conversion;
    and
  - strict per-patch checkpatch and whitespace checks.

Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
Changes in v3:
- Replace the framework-specific proxy cleanup with generic managed-link
  lifecycle handling for driverless class suppliers and consumers, as
  suggested by Saravana Kannan
- Handle registered class consumers during supplier teardown without a
  no-op driver release or driver-only cleanup warning
- Derive class handling inside the shared bound helpers and clarify the
  lifecycle documentation
- Keep inferred links to probing and registered class suppliers out of
  no-driver fallback paths and include class nodes in overlay refresh
- Keep firmware parsing topology-only and let the PHY layer project package
  suppliers onto member PHYs through a new fwnode-link copy helper, as
  suggested by Saravana Kannan
- Defer supplier sync across MDIO bus and OF child population instead of
  deleting proxy links from the MDIO framework
- Add driver-core KUnit coverage and device-link lifecycle documentation
- Drop the unrelated malformed-graph guard because no concrete DT or
  runtime reproducer was found
- Rebase on current net-next/main
- Link to v2: https://patch.msgid.link/20260816-submit-phy-package-fwdevlink-v1-v2-0-23e55dd59fad@gmail.com

Changes in v2:
- Add a preparatory guard for malformed graph consumers
  (reported by Sashiko)
- Rebase on net-next/main
- Link to v1: https://patch.msgid.link/20260814-submit-phy-package-fwdevlink-v1-v1-0-2319844f057a@gmail.com

---
James Hilliard (4):
      driver core: add fw_devlink supplier-copy helper
      net: mdio: link PHY package suppliers to member PHYs
      net: mdio: defer supplier sync during OF population
      driver core: handle managed links for class devices

 Documentation/driver-api/device_link.rst   |  47 ++-
 drivers/base/base.h                        |   2 +
 drivers/base/core.c                        | 332 +++++++++++++++-----
 drivers/base/dd.c                          |  21 +-
 drivers/base/test/Makefile                 |   2 +
 drivers/base/test/device-link-class-test.c | 479 +++++++++++++++++++++++++++++
 drivers/base/test/fwnode-link-test.c       | 207 +++++++++++++
 drivers/net/mdio/of_mdio.c                 |  34 +-
 include/linux/device.h                     |   9 +-
 include/linux/fwnode.h                     |   2 +
 10 files changed, 1042 insertions(+), 93 deletions(-)
---
base-commit: 47226bbdef69fc0de815b8f5b81b39673f50bbee
change-id: 20260814-submit-phy-package-fwdevlink-v1-76a8f9efe539

Best regards,
--  
James Hilliard <james.hilliard1@gmail.com>


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

* [PATCH net-next v3 1/4] driver core: add fw_devlink supplier-copy helper
  2026-08-18  6:58 [PATCH net-next v3 0/4] driver core, net: handle fw_devlink for class devices and PHY packages James Hilliard
@ 2026-08-18  6:58 ` James Hilliard
  2026-08-18  7:30   ` Andy Shevchenko
  2026-08-18  6:58 ` [PATCH net-next v3 2/4] net: mdio: link PHY package suppliers to member PHYs James Hilliard
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 9+ messages in thread
From: James Hilliard @ 2026-08-18  6:58 UTC (permalink / raw)
  To: Rob Herring, Saravana Kannan, Greg Kroah-Hartman,
	Rafael J. Wysocki, Danilo Krummrich, Jonathan Corbet, Shuah Khan,
	Andy Shevchenko, Daniel Scally, Heikki Krogerus, Sakari Ailus,
	Len Brown, Andrew Lunn, Heiner Kallweit, Russell King,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Christian Marangi
  Cc: devicetree, linux-doc, linux-kernel, driver-core, linux-acpi,
	netdev, James Hilliard

Some firmware nodes describe resources shared by devices instantiated for
their children, but the container node itself is never converted to a
struct device. The firmware parser should retain the topology as
described, while the framework which creates the children can identify
the actual consumers.

Add fw_devlink_copy_suppliers() so such a framework can copy the direct
supplier links from a container to a real consumer firmware node before
the consumer is registered. The normal device_add() path then converts
the copied dependencies into device links at the correct point in device
registration.

Leave the source links in place for other children, suppress duplicate
links and roll back newly allocated links if a copy fails. Skip ignored
links and clear cycle flags on newly copied links, since cycle
classification must be recomputed for the new consumer topology. Reject
calls after the target firmware node has been associated with a device.
Check that association while holding the fwnode-link lock so a
concurrent device_add() either observes the copied links or makes the
helper reject the request.

Add KUnit coverage for filtering, cycle-flag handling, idempotency, the
pre-registration contract and conversion into an active device link.

Signed-off-by: James Hilliard <james.hilliard1@gmail.com>

---
Changes v2 -> v3:
  - Keep generic firmware parsing faithful to the described topology and
    add a helper for frameworks to project container suppliers onto real
    child consumers, as suggested by Saravana Kannan
  - Require copying before device_add() and let the normal fw_devlink path
    create device links after consumer sysfs and PM initialization
  - Serialize the target-association check with fwnode link copying
  - Skip ignored links, recalculate cycle state for copied links and add
    KUnit coverage
---
 drivers/base/core.c                  |  60 ++++++++++
 drivers/base/test/Makefile           |   1 +
 drivers/base/test/fwnode-link-test.c | 207 +++++++++++++++++++++++++++++++++++
 include/linux/fwnode.h               |   2 +
 4 files changed, 270 insertions(+)

diff --git a/drivers/base/core.c b/drivers/base/core.c
index 4d026682944f..cf3f4133391d 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -2394,6 +2394,66 @@ static void fw_devlink_link_device(struct device *dev)
 	__fw_devlink_link_to_suppliers(dev, fwnode);
 }
 
+/**
+ * fw_devlink_copy_suppliers - Copy supplier links between firmware nodes
+ * @to: Firmware node which consumes the copied suppliers
+ * @from: Firmware node whose supplier links should be copied
+ *
+ * Frameworks can use this when a container firmware node describes resources
+ * shared by devices created for its children. The source links remain in
+ * place, while links are added for the real consumer firmware node. Call this
+ * before device_add() associates @to with a registered device, so the normal
+ * fw_devlink path can convert the copied dependencies into device links.
+ *
+ * Ignored source links are skipped. Cycle flags are topology-specific and are
+ * recalculated for the new consumer when its links are converted.
+ *
+ * Return: 0 on success, -EINVAL for invalid firmware nodes, -EBUSY if @to is
+ * already associated with a device, or -ENOMEM if a link could not be
+ * allocated.
+ */
+int fw_devlink_copy_suppliers(struct fwnode_handle *to,
+			      struct fwnode_handle *from)
+{
+	struct list_head *first;
+	struct fwnode_link *link;
+	int ret;
+
+	if (!to || !from)
+		return -EINVAL;
+	if (!fw_devlink_flags || to == from)
+		return 0;
+	fw_devlink_parse_fwnode(from);
+
+	guard(mutex)(&fwnode_link_lock);
+	if (READ_ONCE(to->dev))
+		return -EBUSY;
+
+	first = to->suppliers.next;
+	list_for_each_entry(link, &from->suppliers, c_hook) {
+		u8 flags = link->flags & ~FWLINK_FLAG_CYCLE;
+
+		if (flags & FWLINK_FLAG_IGNORE)
+			continue;
+
+		ret = __fwnode_link_add(to, link->supplier, flags);
+		if (ret)
+			goto rollback;
+	}
+
+	return 0;
+
+rollback:
+	while (to->suppliers.next != first) {
+		link = list_first_entry(&to->suppliers, struct fwnode_link,
+					c_hook);
+		__fwnode_link_del(link);
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(fw_devlink_copy_suppliers);
+
 /* Device links support end. */
 
 static struct kobject *dev_kobj;
diff --git a/drivers/base/test/Makefile b/drivers/base/test/Makefile
index e321dfc7e922..d871a71dbbaa 100644
--- a/drivers/base/test/Makefile
+++ b/drivers/base/test/Makefile
@@ -3,6 +3,7 @@ obj-$(CONFIG_TEST_ASYNC_DRIVER_PROBE)	+= test_async_driver_probe.o
 
 obj-$(CONFIG_DM_KUNIT_TEST)	+= root-device-test.o
 obj-$(CONFIG_DM_KUNIT_TEST)	+= platform-device-test.o
+obj-$(CONFIG_DM_KUNIT_TEST)	+= fwnode-link-test.o
 
 obj-$(CONFIG_DRIVER_PE_KUNIT_TEST) += property-entry-test.o
 CFLAGS_property-entry-test.o += $(DISABLE_STRUCTLEAK_PLUGIN)
diff --git a/drivers/base/test/fwnode-link-test.c b/drivers/base/test/fwnode-link-test.c
new file mode 100644
index 000000000000..fee18613d230
--- /dev/null
+++ b/drivers/base/test/fwnode-link-test.c
@@ -0,0 +1,207 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <kunit/platform_device.h>
+#include <kunit/test.h>
+
+#include <linux/device.h>
+#include <linux/fwnode.h>
+#include <linux/platform_device.h>
+
+#define FWNODE_LINK_TEST_DRIVER_NAME	"fwnode-link-test"
+
+struct fwnode_link_test_context {
+	struct fwnode_handle consumer;
+	struct fwnode_handle container;
+	struct fwnode_handle supplier_a;
+	struct fwnode_handle supplier_b;
+};
+
+static int fwnode_link_test_probe(struct platform_device *pdev)
+{
+	return 0;
+}
+
+static struct platform_driver fwnode_link_test_driver = {
+	.probe = fwnode_link_test_probe,
+	.driver = {
+		.name = FWNODE_LINK_TEST_DRIVER_NAME,
+	},
+};
+
+static void fwnode_link_test_cleanup(void *data)
+{
+	struct fwnode_link_test_context *context = data;
+
+	fwnode_links_purge(&context->consumer);
+	fwnode_links_purge(&context->container);
+	fwnode_links_purge(&context->supplier_a);
+	fwnode_links_purge(&context->supplier_b);
+}
+
+static struct fwnode_link_test_context *
+fwnode_link_test_init(struct kunit *test)
+{
+	struct fwnode_link_test_context *context;
+	int ret;
+
+	context = kunit_kzalloc(test, sizeof(*context), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, context);
+
+	fwnode_init(&context->consumer, NULL);
+	fwnode_init(&context->container, NULL);
+	fwnode_init(&context->supplier_a, NULL);
+	fwnode_init(&context->supplier_b, NULL);
+	ret = kunit_add_action_or_reset(test, fwnode_link_test_cleanup,
+					context);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	return context;
+}
+
+static struct platform_device *
+fwnode_link_test_register_pdev(struct kunit *test,
+			       struct fwnode_handle *fwnode)
+{
+	struct platform_device *pdev;
+	int ret;
+
+	pdev = kunit_platform_device_alloc(test, FWNODE_LINK_TEST_DRIVER_NAME,
+					   PLATFORM_DEVID_AUTO);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev);
+
+	device_set_node(&pdev->dev, fwnode);
+	ret = kunit_platform_device_add(test, pdev);
+	if (ret) {
+		KUNIT_FAIL(test, "failed to register platform device: %d", ret);
+		return NULL;
+	}
+
+	return pdev;
+}
+
+static unsigned int fwnode_supplier_count(struct fwnode_handle *fwnode)
+{
+	struct fwnode_link *link;
+	unsigned int count = 0;
+
+	list_for_each_entry(link, &fwnode->suppliers, c_hook)
+		count++;
+
+	return count;
+}
+
+static struct fwnode_link *
+fwnode_find_supplier(struct fwnode_handle *consumer,
+		     struct fwnode_handle *supplier)
+{
+	struct fwnode_link *link;
+
+	list_for_each_entry(link, &consumer->suppliers, c_hook) {
+		if (link->supplier == supplier)
+			return link;
+	}
+
+	return NULL;
+}
+
+static void fwnode_link_copy_suppliers_test(struct kunit *test)
+{
+	struct fwnode_link_test_context *context;
+	struct fwnode_link *link;
+	int ret;
+
+	context = fwnode_link_test_init(test);
+	KUNIT_ASSERT_NOT_NULL(test, context);
+
+	ret = fwnode_link_add(&context->container, &context->supplier_a,
+			      FWLINK_FLAG_CYCLE);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	ret = fwnode_link_add(&context->container, &context->supplier_b,
+			      FWLINK_FLAG_IGNORE);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	ret = fw_devlink_copy_suppliers(&context->consumer,
+					&context->container);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test, fwnode_supplier_count(&context->container), 2U);
+	KUNIT_EXPECT_EQ(test, fwnode_supplier_count(&context->consumer), 1U);
+
+	link = fwnode_find_supplier(&context->consumer, &context->supplier_a);
+	KUNIT_ASSERT_NOT_NULL(test, link);
+	KUNIT_EXPECT_EQ(test, link->flags, (u8)0);
+	link = fwnode_find_supplier(&context->consumer, &context->supplier_b);
+	KUNIT_EXPECT_NULL(test, link);
+
+	ret = fw_devlink_copy_suppliers(&context->consumer,
+					&context->container);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test, fwnode_supplier_count(&context->consumer), 1U);
+}
+
+static void fwnode_link_copy_before_device_add_test(struct kunit *test)
+{
+	struct fwnode_link_test_context *context;
+	struct platform_device *consumer;
+	struct platform_device *supplier;
+	struct device_link *link;
+	int ret;
+
+	context = fwnode_link_test_init(test);
+	KUNIT_ASSERT_NOT_NULL(test, context);
+
+	ret = fwnode_link_add(&context->container, &context->supplier_a, 0);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	supplier = fwnode_link_test_register_pdev(test, &context->supplier_a);
+	KUNIT_ASSERT_NOT_NULL(test, supplier);
+
+	ret = fw_devlink_copy_suppliers(&context->consumer,
+					&context->container);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	consumer = fwnode_link_test_register_pdev(test, &context->consumer);
+	KUNIT_ASSERT_NOT_NULL(test, consumer);
+
+	KUNIT_EXPECT_EQ(test, fwnode_supplier_count(&context->container), 1U);
+	KUNIT_EXPECT_EQ(test, fwnode_supplier_count(&context->consumer), 0U);
+
+	if (list_empty(&consumer->dev.links.suppliers)) {
+		KUNIT_FAIL(test, "consumer device link was not created");
+		return;
+	}
+	link = list_first_entry(&consumer->dev.links.suppliers,
+				struct device_link, c_node);
+	KUNIT_EXPECT_PTR_EQ(test, link->supplier, &supplier->dev);
+	KUNIT_EXPECT_EQ(test, READ_ONCE(link->status), DL_STATE_ACTIVE);
+
+	ret = fw_devlink_copy_suppliers(&context->consumer,
+					&context->container);
+	KUNIT_EXPECT_EQ(test, ret, -EBUSY);
+}
+
+static struct kunit_case fwnode_link_test_cases[] = {
+	KUNIT_CASE(fwnode_link_copy_suppliers_test),
+	KUNIT_CASE(fwnode_link_copy_before_device_add_test),
+	{}
+};
+
+static int fwnode_link_test_suite_init(struct kunit_suite *suite)
+{
+	return platform_driver_register(&fwnode_link_test_driver);
+}
+
+static void fwnode_link_test_suite_exit(struct kunit_suite *suite)
+{
+	platform_driver_unregister(&fwnode_link_test_driver);
+	device_link_wait_removal();
+}
+
+static struct kunit_suite fwnode_link_test_suite = {
+	.name = "fwnode-link",
+	.suite_init = fwnode_link_test_suite_init,
+	.suite_exit = fwnode_link_test_suite_exit,
+	.test_cases = fwnode_link_test_cases,
+};
+
+kunit_test_suite(fwnode_link_test_suite);
+
+MODULE_DESCRIPTION("KUnit tests for firmware-node links");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/fwnode.h b/include/linux/fwnode.h
index 4e86e6990d28..ad4250fc7afb 100644
--- a/include/linux/fwnode.h
+++ b/include/linux/fwnode.h
@@ -254,6 +254,8 @@ int fwnode_link_add(struct fwnode_handle *con, struct fwnode_handle *sup,
 void fwnode_links_purge(struct fwnode_handle *fwnode);
 void fw_devlink_purge_absent_suppliers(struct fwnode_handle *fwnode);
 void fw_devlink_refresh_fwnode(struct fwnode_handle *fwnode);
+int fw_devlink_copy_suppliers(struct fwnode_handle *to,
+			      struct fwnode_handle *from);
 bool fw_devlink_is_strict(void);
 
 #endif

-- 
2.53.0


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

* [PATCH net-next v3 2/4] net: mdio: link PHY package suppliers to member PHYs
  2026-08-18  6:58 [PATCH net-next v3 0/4] driver core, net: handle fw_devlink for class devices and PHY packages James Hilliard
  2026-08-18  6:58 ` [PATCH net-next v3 1/4] driver core: add fw_devlink supplier-copy helper James Hilliard
@ 2026-08-18  6:58 ` James Hilliard
  2026-08-18  7:39   ` Andy Shevchenko
  2026-08-18  6:58 ` [PATCH net-next v3 3/4] net: mdio: defer supplier sync during OF population James Hilliard
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 9+ messages in thread
From: James Hilliard @ 2026-08-18  6:58 UTC (permalink / raw)
  To: Rob Herring, Saravana Kannan, Greg Kroah-Hartman,
	Rafael J. Wysocki, Danilo Krummrich, Jonathan Corbet, Shuah Khan,
	Andy Shevchenko, Daniel Scally, Heikki Krogerus, Sakari Ailus,
	Len Brown, Andrew Lunn, Heiner Kallweit, Russell King,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Christian Marangi
  Cc: devicetree, linux-doc, linux-kernel, driver-core, linux-acpi,
	netdev, James Hilliard

Ethernet PHY package nodes describe resources shared by their member
PHYs, but a package node is not instantiated as a device. Its
firmware-node supplier links therefore cannot become normal device links
for the PHY members without help from the MDIO layer.

When registering an OF-backed PHY whose parent is an
ethernet-phy-package node, copy the package supplier links onto the
member's firmware node before the PHY device is added. The ordinary
fw_devlink path then creates managed links after the PHY device has been
initialized for link registration and before its driver can probe.

Apply this to both ordinary OF discovery and the exported registration
path for a pre-created PHY device. Leave the package links intact so
every member, including one registered later, can acquire the same
dependencies. This preserves probe, unbind and runtime-PM ordering
without adding PHY-package policy to generic firmware parsing.

Fixes: 385ef48f4686 ("net: phy: add support for scanning PHY in PHY packages nodes")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>

---
Changes v2 -> v3:
  - Move PHY-package handling out of generic OF property parsing and into
    OF MDIO registration, as suggested by Saravana Kannan
  - Copy package supplier links before device_add() so the normal
    fw_devlink path creates real member links at the correct stage
  - Cover both normal OF discovery and pre-created PHY registration
---
 drivers/net/mdio/of_mdio.c | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/drivers/net/mdio/of_mdio.c b/drivers/net/mdio/of_mdio.c
index b8d298c04d3f..ff540049b847 100644
--- a/drivers/net/mdio/of_mdio.c
+++ b/drivers/net/mdio/of_mdio.c
@@ -10,6 +10,7 @@
 
 #include <linux/device.h>
 #include <linux/err.h>
+#include <linux/fwnode.h>
 #include <linux/fwnode_mdio.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
@@ -34,9 +35,26 @@ static int of_get_phy_id(struct device_node *device, u32 *phy_id)
 	return fwnode_get_phy_id(of_fwnode_handle(device), phy_id);
 }
 
+static int of_mdiobus_link_phy_package(struct device_node *child)
+{
+	struct device_node *package __free(device_node) = of_get_parent(child);
+
+	if (!package || !of_node_name_eq(package, "ethernet-phy-package"))
+		return 0;
+
+	return fw_devlink_copy_suppliers(of_fwnode_handle(child),
+					 of_fwnode_handle(package));
+}
+
 int of_mdiobus_phy_device_register(struct mii_bus *mdio, struct phy_device *phy,
 				   struct device_node *child, u32 addr)
 {
+	int ret;
+
+	ret = of_mdiobus_link_phy_package(child);
+	if (ret)
+		return ret;
+
 	return fwnode_mdiobus_phy_device_register(mdio, phy,
 						  of_fwnode_handle(child),
 						  addr);
@@ -46,6 +64,12 @@ EXPORT_SYMBOL(of_mdiobus_phy_device_register);
 static int of_mdiobus_register_phy(struct mii_bus *mdio,
 				    struct device_node *child, u32 addr)
 {
+	int ret;
+
+	ret = of_mdiobus_link_phy_package(child);
+	if (ret)
+		return ret;
+
 	return fwnode_mdiobus_register_phy(mdio, of_fwnode_handle(child), addr);
 }
 

-- 
2.53.0


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

* [PATCH net-next v3 3/4] net: mdio: defer supplier sync during OF population
  2026-08-18  6:58 [PATCH net-next v3 0/4] driver core, net: handle fw_devlink for class devices and PHY packages James Hilliard
  2026-08-18  6:58 ` [PATCH net-next v3 1/4] driver core: add fw_devlink supplier-copy helper James Hilliard
  2026-08-18  6:58 ` [PATCH net-next v3 2/4] net: mdio: link PHY package suppliers to member PHYs James Hilliard
@ 2026-08-18  6:58 ` James Hilliard
  2026-08-18  6:59 ` [PATCH net-next v3 4/4] driver core: handle managed links for class devices James Hilliard
  2026-08-18  7:15 ` [PATCH net-next v3 0/4] driver core, net: handle fw_devlink for class devices and PHY packages Andy Shevchenko
  4 siblings, 0 replies; 9+ messages in thread
From: James Hilliard @ 2026-08-18  6:58 UTC (permalink / raw)
  To: Rob Herring, Saravana Kannan, Greg Kroah-Hartman,
	Rafael J. Wysocki, Danilo Krummrich, Jonathan Corbet, Shuah Khan,
	Andy Shevchenko, Daniel Scally, Heikki Krogerus, Sakari Ailus,
	Len Brown, Andrew Lunn, Heiner Kallweit, Russell King,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Christian Marangi
  Cc: devicetree, linux-doc, linux-kernel, driver-core, linux-acpi,
	netdev, James Hilliard

The MDIO bus class device is fully added before the OF helper creates its
PHY children. Once driverless class devices complete their managed links,
the bus proxy links are retired at the end of __mdiobus_register(), before
PHY package member links have been installed.

Pause supplier sync-state callbacks across both MDIO bus registration and
OF child population. Each member PHY acquires its real package supplier
links before the matching resume, so suppliers cannot observe the interval
between proxy retirement and member registration.

Use the existing nestable device-link deferral mechanism, export it for
modular OF MDIO code and resume it on every success and error path.

Fixes: 385ef48f4686 ("net: phy: add support for scanning PHY in PHY packages nodes")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>

---
Changes v2 -> v3:
  - Replace MDIO-specific proxy-link deletion with scoped sync-state
    deferral around bus and child population
  - Put the deferral in place before enabling generic class-device proxy
    retirement so every intermediate patch remains safe
---
 drivers/base/core.c        |  2 ++
 drivers/net/mdio/of_mdio.c | 10 +++++++---
 2 files changed, 9 insertions(+), 3 deletions(-)

diff --git a/drivers/base/core.c b/drivers/base/core.c
index cf3f4133391d..d2f9ff65138c 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -1256,6 +1256,7 @@ void device_links_supplier_sync_state_pause(void)
 	defer_sync_state_count++;
 	device_links_write_unlock();
 }
+EXPORT_SYMBOL_GPL(device_links_supplier_sync_state_pause);
 
 void device_links_supplier_sync_state_resume(void)
 {
@@ -1284,6 +1285,7 @@ void device_links_supplier_sync_state_resume(void)
 
 	device_links_flush_sync_list(&sync_list, NULL);
 }
+EXPORT_SYMBOL_GPL(device_links_supplier_sync_state_resume);
 
 static int sync_state_resume_initcall(void)
 {
diff --git a/drivers/net/mdio/of_mdio.c b/drivers/net/mdio/of_mdio.c
index ff540049b847..9b73fd8eed41 100644
--- a/drivers/net/mdio/of_mdio.c
+++ b/drivers/net/mdio/of_mdio.c
@@ -243,9 +243,10 @@ int __of_mdiobus_register(struct mii_bus *mdio, struct device_node *np,
 	of_property_read_u32(np, "reset-post-delay-us", &mdio->reset_post_delay_us);
 
 	/* Register the MDIO bus */
+	device_links_supplier_sync_state_pause();
 	rc = __mdiobus_register(mdio, owner);
 	if (rc)
-		return rc;
+		goto resume;
 
 	/* Loop over the child nodes and register a phy_device for each phy */
 	rc = __of_mdiobus_parse_phys(mdio, np, &scanphys);
@@ -253,7 +254,7 @@ int __of_mdiobus_register(struct mii_bus *mdio, struct device_node *np,
 		goto unregister;
 
 	if (!scanphys)
-		return 0;
+		goto resume;
 
 	/* auto scan for PHYs with empty reg property */
 	for_each_available_child_of_node(np, child) {
@@ -285,12 +286,15 @@ int __of_mdiobus_register(struct mii_bus *mdio, struct device_node *np,
 		}
 	}
 
-	return 0;
+	rc = 0;
+	goto resume;
 
 put_unregister:
 	of_node_put(child);
 unregister:
 	mdiobus_unregister(mdio);
+resume:
+	device_links_supplier_sync_state_resume();
 	return rc;
 }
 EXPORT_SYMBOL(__of_mdiobus_register);

-- 
2.53.0


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

* [PATCH net-next v3 4/4] driver core: handle managed links for class devices
  2026-08-18  6:58 [PATCH net-next v3 0/4] driver core, net: handle fw_devlink for class devices and PHY packages James Hilliard
                   ` (2 preceding siblings ...)
  2026-08-18  6:58 ` [PATCH net-next v3 3/4] net: mdio: defer supplier sync during OF population James Hilliard
@ 2026-08-18  6:59 ` James Hilliard
  2026-08-18  7:48   ` Andy Shevchenko
  2026-08-18  7:15 ` [PATCH net-next v3 0/4] driver core, net: handle fw_devlink for class devices and PHY packages Andy Shevchenko
  4 siblings, 1 reply; 9+ messages in thread
From: James Hilliard @ 2026-08-18  6:59 UTC (permalink / raw)
  To: Rob Herring, Saravana Kannan, Greg Kroah-Hartman,
	Rafael J. Wysocki, Danilo Krummrich, Jonathan Corbet, Shuah Khan,
	Andy Shevchenko, Daniel Scally, Heikki Krogerus, Sakari Ailus,
	Len Brown, Andrew Lunn, Heiner Kallweit, Russell King,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Christian Marangi
  Cc: devicetree, linux-doc, linux-kernel, driver-core, linux-acpi,
	netdev, James Hilliard

fw_devlink deliberately lets the nearest device pick up dependencies below
class-backed firmware nodes because driverless class devices do not
currently progress through managed device-link states. This can leave
consumers of class suppliers dormant, while sync-state-only links from
class consumers never reach the successful-probe cleanup.

Track class-only devices through equivalent registration and removal
transitions. Mark them probing before device_add() publishes them, complete
their incoming and outgoing links after class interfaces run, and enter the
unbind and no-driver states during device_del(). Preserve firmware links
below class-backed nodes so later-created descendants can acquire their own
links.

Include class registration in wait_for_device_probe() so supplier unbind
cannot race a class consumer that is still being published. Transition
managed links which existed before device_add(), remove stale
waiting_for_supplier attributes and do not revive a link whose supplier has
already started unbinding.

During supplier teardown, wait for class registration to finish, but move
an already registered class consumer directly to the unbinding link state
because it has no driver to release. Keep consumer-autoremove links valid
until class consumer removal without triggering driver-only cleanup
warnings.

Factor the supplier-bound, consumer-bound and cleanup transitions shared
with driver-backed devices. Activate late links involving an already
functional class endpoint instead of trying to reprobe it, and let
sync-state-only links retire without forcing consumer unbind. Ordinary
supplier links cannot defer class registration, so they must not be the
only mechanism on which a class consumer relies for functionality.

Class devices do not match drivers, so distinguish a genuinely unavailable
supplier from a probing or registered class endpoint before relaxing
inferred links. Preserve normal probe and runtime-PM ordering for those
links. Also recognize class-backed nodes when refreshing dependencies after
firmware-tree overlays.

Exclude the internal device-link class because device-link objects are
registered while the device-links lock is held. Document the class-device
state transitions and add KUnit coverage for supplier and consumer links,
pre-existing links, inferred-link preservation, registration waiters and a
concurrent supplier unbind.

Signed-off-by: James Hilliard <james.hilliard1@gmail.com>

---
Changes v2 -> v3:
  - Replace the framework-specific proxy cleanup API with generic
    class-device managed-link lifecycle handling, as suggested by
    Saravana Kannan
  - Preserve inferred links to probing and registered class suppliers in
    no-driver fallback paths and include class nodes in overlay refresh
  - Cover pre-existing and inferred links, wait_for_device_probe()
    integration, waiting_for_supplier cleanup and supplier-unbind races
  - Document the limits of ordinary supplier links for driverless class
    consumers
  - Handle registered class consumers during supplier teardown without a
    no-op driver release or driver-only cleanup warning
  - Derive class handling inside the shared bound helpers and clarify the
    lifecycle documentation
---
 Documentation/driver-api/device_link.rst   |  47 ++-
 drivers/base/base.h                        |   2 +
 drivers/base/core.c                        | 270 +++++++++++-----
 drivers/base/dd.c                          |  21 +-
 drivers/base/test/Makefile                 |   1 +
 drivers/base/test/device-link-class-test.c | 479 +++++++++++++++++++++++++++++
 include/linux/device.h                     |   9 +-
 7 files changed, 739 insertions(+), 90 deletions(-)

diff --git a/Documentation/driver-api/device_link.rst b/Documentation/driver-api/device_link.rst
index ee913ae16371..c248ec9fa9f8 100644
--- a/Documentation/driver-api/device_link.rst
+++ b/Documentation/driver-api/device_link.rst
@@ -253,6 +253,18 @@ child one.
 State machine
 =============
 
+Driverless class devices participate in the managed-link state machine as
+supplier and consumer endpoints, including as consumers of sync-state-only
+proxy links.  Their registration is treated like a successful driver probe:
+links are completed after :c:func:`device_add()` has finished running class
+interfaces.  During removal, driver-backed consumers are unbound before the
+class device is deleted and its links return to the no-driver state.
+
+Class devices are not probed, so an ordinary supplier link cannot defer their
+registration in the way that it defers a driver probe.  Frameworks must not use
+such a link as the only mechanism guaranteeing that a class consumer's
+supplier is functional.
+
 .. kernel-doc:: include/linux/device.h
    :functions: device_link_state
 
@@ -271,10 +283,13 @@ State machine
   and consumer.  If the link is created before any devices are probed, it
   is set to ``DL_STATE_DORMANT``.
 
-* When a supplier device is bound to a driver, links to its consumers
-  progress to ``DL_STATE_AVAILABLE``.
-  (Call to :c:func:`device_links_driver_bound()` from
-  :c:func:`driver_bound()`.)
+* When a supplier device is bound to a driver, links to its consumers progress
+  to ``DL_STATE_AVAILABLE``.  Registering a driverless class supplier performs
+  the equivalent transition, or activates a late link whose consumer is
+  already functional.
+  The driver-backed path calls :c:func:`device_links_driver_bound()` from
+  :c:func:`driver_bound()`, while the class-device completion path performs
+  the equivalent transition from :c:func:`device_add()`.
 
 * Before a consumer device is probed, presence of supplier drivers is
   verified by checking the consumer device is not in the wait_for_suppliers
@@ -286,17 +301,28 @@ State machine
   (Call to :c:func:`wait_for_device_probe()` from
   :c:func:`device_links_unbind_consumers()`.)
 
+  Registering a driverless class consumer similarly moves existing available
+  links to ``DL_STATE_CONSUMER_PROBE`` before publishing the device, and its
+  registration participates in :c:func:`wait_for_device_probe()`.  Unlike a
+  driver probe, class registration is not deferred for unavailable suppliers.
+
 * If the probe fails, links to suppliers revert back to ``DL_STATE_AVAILABLE``.
   (Call to :c:func:`device_links_no_driver()` from :c:func:`really_probe()`.)
 
 * If the probe succeeds, links to suppliers progress to ``DL_STATE_ACTIVE``.
-  (Call to :c:func:`device_links_driver_bound()` from :c:func:`driver_bound()`.)
+  A driverless class consumer activates usable incoming links when its
+  registration completes, drops sync-state-only proxy links and preserves
+  links whose suppliers are dormant or unbinding.  The driver-backed path
+  calls :c:func:`device_links_driver_bound()` from :c:func:`driver_bound()`,
+  while :c:func:`device_add()` performs the equivalent class-device
+  transition.
 
 * When the consumer's driver is later on removed, links to suppliers revert
   back to ``DL_STATE_AVAILABLE``.
   (Call to :c:func:`__device_links_no_driver()` from
   :c:func:`device_links_driver_cleanup()`, which in turn is called from
-  :c:func:`__device_release_driver()`.)
+  :c:func:`__device_release_driver()`.)  Removing a driverless class consumer
+  performs the equivalent cleanup from :c:func:`device_del()`.
 
 * Before a supplier's driver is removed, links to consumers that are not
   bound to a driver are updated to ``DL_STATE_SUPPLIER_UNBIND``.
@@ -305,14 +331,17 @@ State machine
   This prevents the consumers from binding.
   (Call to :c:func:`device_links_check_suppliers()` from
   :c:func:`really_probe()`.)
-  Consumers that are bound are freed from their driver; consumers that are
-  probing are waited for until they are done.
+  Driver-backed consumers that are bound are freed from their driver;
+  driverless class consumers remain registered and their links move directly
+  to ``DL_STATE_SUPPLIER_UNBIND``.  Consumers that are probing or being
+  registered as class devices are waited for until they are done.
   (Call to :c:func:`device_links_unbind_consumers()` from
   :c:func:`__device_release_driver()`.)
   Once all links to consumers are in ``DL_STATE_SUPPLIER_UNBIND`` state,
   the supplier driver is released and the links revert to ``DL_STATE_DORMANT``.
   (Call to :c:func:`device_links_driver_cleanup()` from
-  :c:func:`__device_release_driver()`.)
+  :c:func:`__device_release_driver()`.)  Removing a driverless class supplier
+  follows the same sequence from :c:func:`device_del()`.
 
 API
 ===
diff --git a/drivers/base/base.h b/drivers/base/base.h
index a5b7abc10ff0..8bdf0a709401 100644
--- a/drivers/base/base.h
+++ b/drivers/base/base.h
@@ -166,6 +166,8 @@ void device_release_driver_internal(struct device *dev, const struct device_driv
 void driver_detach(const struct device_driver *drv);
 void driver_deferred_probe_del(struct device *dev);
 void device_set_deferred_probe_reason(const struct device *dev, struct va_format *vaf);
+void device_probe_begin(void);
+void device_probe_end(void);
 static inline int driver_match_device(const struct device_driver *drv,
 				      struct device *dev)
 {
diff --git a/drivers/base/core.c b/drivers/base/core.c
index d2f9ff65138c..0c26ba38e6cf 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -213,10 +213,10 @@ static void __fwnode_links_move_consumers(struct fwnode_handle *from,
  * @fwnode: fwnode from which to pick up dangling consumers
  * @new_sup: fwnode of new supplier
  *
- * If the @fwnode has a corresponding struct device and the device supports
- * probing (that is, added to a bus), then we want to let fw_devlink create
- * MANAGED device links to this device, so leave @fwnode and its descendant's
- * fwnode links alone.
+ * If the @fwnode has a corresponding struct device whose device-link state is
+ * managed by the driver core (that is, a bus or class device), then we want to
+ * let fw_devlink create MANAGED device links to this device, so leave @fwnode
+ * and its descendants' fwnode links alone.
  *
  * Otherwise, move its consumers to the new supplier @new_sup.
  */
@@ -225,7 +225,7 @@ static void __fw_devlink_pickup_dangling_consumers(struct fwnode_handle *fwnode,
 {
 	struct fwnode_handle *child;
 
-	if (fwnode->dev && fwnode->dev->bus)
+	if (fwnode->dev && (fwnode->dev->bus || fwnode->dev->class))
 		return;
 
 	fwnode_set_flag(fwnode, FWNODE_FLAG_NOT_DEVICE);
@@ -259,7 +259,7 @@ void fw_devlink_refresh_fwnode(struct fwnode_handle *fwnode)
 
 	/*
 	 * Find the closest ancestor fwnode that has been converted to a device
-	 * that can bind to a driver (bus device).
+	 * whose managed-link lifecycle is handled by the driver core.
 	 */
 	fwnode_handle_get(fwnode);
 	do {
@@ -270,15 +270,15 @@ void fw_devlink_refresh_fwnode(struct fwnode_handle *fwnode)
 		if (!dev)
 			continue;
 
-		if (dev->bus)
+		if (dev->bus || dev->class)
 			break;
 
 		put_device(dev);
 	} while ((fwnode = fwnode_get_next_parent(fwnode)));
 
 	/*
-	 * If none of the ancestor fwnodes have (yet) been converted to a device
-	 * that can bind to a driver, there's nothing to fix up.
+	 * If none of the ancestor fwnodes have (yet) been converted to such a
+	 * device, there's nothing to fix up.
 	 */
 	if (!fwnode)
 		return;
@@ -287,10 +287,9 @@ void fw_devlink_refresh_fwnode(struct fwnode_handle *fwnode)
 	     "Don't multithread overlaying and probing the same device!\n");
 
 	/*
-	 * If the device has already bound to a driver, then we need to redo
-	 * some of the work that was done after the device was bound to a
-	 * driver. If the device hasn't bound to a driver, running things too
-	 * soon would incorrectly pick up consumers that it shouldn't.
+	 * If the device's managed links are available, redo some of the work
+	 * performed when it reached that state. Running this too soon would
+	 * incorrectly pick up consumers that it shouldn't.
 	 */
 	if (dev->links.status == DL_DEV_DRIVER_BOUND) {
 		fw_devlink_pickup_dangling_consumers(dev);
@@ -1088,6 +1087,12 @@ static bool dev_is_best_effort(struct device *dev)
 		(dev->fwnode && fwnode_test_flag(dev->fwnode, FWNODE_FLAG_BEST_EFFORT));
 }
 
+static bool dev_is_unmatchable(const struct device *dev)
+{
+	return READ_ONCE(dev->links.status) == DL_DEV_NO_DRIVER &&
+	       !dev_can_match(dev);
+}
+
 static struct fwnode_handle *fwnode_links_check_suppliers(
 						struct fwnode_handle *fwnode)
 {
@@ -1152,7 +1157,7 @@ int device_links_check_suppliers(struct device *dev)
 
 			if (dev_is_best_effort(dev) &&
 			    device_link_test(link, DL_FLAG_INFERRED) &&
-			    !dev_can_match(link->supplier)) {
+			    dev_is_unmatchable(link->supplier)) {
 				ret = -EAGAIN;
 				continue;
 			}
@@ -1307,6 +1312,33 @@ static void device_link_drop_managed(struct device_link *link)
 	kref_put(&link->kref, __device_link_del);
 }
 
+static bool device_links_track_class(const struct device *dev)
+{
+	/* Device-link objects are class devices registered with the lock held. */
+	return dev->class && !dev->bus && dev->class != &devlink_class;
+}
+
+static void device_links_class_start(struct device *dev)
+{
+	struct device_link *link;
+
+	device_probe_begin();
+	device_links_write_lock();
+	WARN_ON(dev->links.status != DL_DEV_NO_DRIVER);
+	list_for_each_entry(link, &dev->links.suppliers, c_node) {
+		if (!device_link_test(link, DL_FLAG_MANAGED))
+			continue;
+
+		/* Do not revive a link whose supplier is already unbinding. */
+		if (link->status == DL_STATE_AVAILABLE ||
+		    (device_link_test(link, DL_FLAG_SYNC_STATE_ONLY) &&
+		     link->status != DL_STATE_SUPPLIER_UNBIND))
+			WRITE_ONCE(link->status, DL_STATE_CONSUMER_PROBE);
+	}
+	dev->links.status = DL_DEV_PROBING;
+	device_links_write_unlock();
+}
+
 static ssize_t waiting_for_supplier_show(struct device *dev,
 					 const struct device_attribute *attr,
 					 char *buf)
@@ -1356,43 +1388,11 @@ void device_links_force_bind(struct device *dev)
 	device_links_write_unlock();
 }
 
-/**
- * device_links_driver_bound - Update device links after probing its driver.
- * @dev: Device to update the links for.
- *
- * The probe has been successful, so update links from this device to any
- * consumers by changing their status to "available".
- *
- * Also change the status of @dev's links to suppliers to "active".
- *
- * Links without the DL_FLAG_MANAGED flag set are ignored.
- */
-void device_links_driver_bound(struct device *dev)
+static void __device_links_supplier_bound(struct device *dev,
+					  struct list_head *sync_list)
 {
-	struct device_link *link, *ln;
-	LIST_HEAD(sync_list);
-
-	/*
-	 * 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
-	 * them. So, fw_devlink no longer needs to create device links to any
-	 * of the device's suppliers.
-	 *
-	 * Also, if a child firmware node of this bound device is not added as a
-	 * device by now, assume it is never going to be added. Make this bound
-	 * device the fallback supplier to the dangling consumers of the child
-	 * firmware node because this bound device is probably implementing the
-	 * child firmware node functionality and we don't want the dangling
-	 * 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);
-	}
-	device_remove_file(dev, &dev_attr_waiting_for_supplier);
-
-	device_links_write_lock();
+	struct device_link *link;
+	bool class_device = device_links_track_class(dev);
 
 	list_for_each_entry(link, &dev->links.consumers, s_node) {
 		if (!device_link_test(link, DL_FLAG_MANAGED))
@@ -1409,16 +1409,35 @@ void device_links_driver_bound(struct device *dev)
 			continue;
 
 		WARN_ON(link->status != DL_STATE_DORMANT);
-		WRITE_ONCE(link->status, DL_STATE_AVAILABLE);
+		/*
+		 * A class interface can add a link from an already-bound consumer
+		 * while the class supplier is being registered.  Likewise, a class
+		 * consumer has no driver probe to retry when a late supplier binds.
+		 * Those consumers are already functional, so activate their links.
+		 */
+		if ((class_device ||
+		     device_links_track_class(link->consumer)) &&
+		    link->consumer->links.status == DL_DEV_DRIVER_BOUND) {
+			WRITE_ONCE(link->status, DL_STATE_ACTIVE);
+		} else {
+			WRITE_ONCE(link->status, DL_STATE_AVAILABLE);
 
-		if (device_link_test(link, DL_FLAG_AUTOPROBE_CONSUMER))
-			driver_deferred_probe_add(link->consumer);
+			if (device_link_test(link, DL_FLAG_AUTOPROBE_CONSUMER))
+				driver_deferred_probe_add(link->consumer);
+		}
 	}
 
 	if (defer_sync_state_count)
 		__device_links_supplier_defer_sync(dev);
 	else
-		__device_links_queue_sync_state(dev, &sync_list);
+		__device_links_queue_sync_state(dev, sync_list);
+}
+
+static void __device_links_consumer_bound(struct device *dev,
+					  struct list_head *sync_list)
+{
+	struct device_link *link, *ln;
+	bool class_device = device_links_track_class(dev);
 
 	list_for_each_entry_safe(link, ln, &dev->links.suppliers, c_node) {
 		struct device *supplier;
@@ -1431,13 +1450,13 @@ void device_links_driver_bound(struct device *dev)
 			/*
 			 * When DL_FLAG_SYNC_STATE_ONLY is set, it means no
 			 * other DL_MANAGED_LINK_FLAGS have been set. So, it's
-			 * save to drop the managed link completely.
+			 * safe to drop the managed link completely.
 			 */
 			device_link_drop_managed(link);
-		} else if (dev_is_best_effort(dev) &&
+		} else if (!class_device && dev_is_best_effort(dev) &&
 			   device_link_test(link, DL_FLAG_INFERRED) &&
 			   link->status != DL_STATE_CONSUMER_PROBE &&
-			   !dev_can_match(link->supplier)) {
+			   dev_is_unmatchable(link->supplier)) {
 			/*
 			 * When dev_is_best_effort() is true, we ignore device
 			 * links to suppliers that don't have a driver.  If the
@@ -1446,9 +1465,17 @@ void device_links_driver_bound(struct device *dev)
 			 * (consumer probed before supplier). So delete it.
 			 */
 			device_link_drop_managed(link);
-		} else {
+		} else if (!class_device) {
 			WARN_ON(link->status != DL_STATE_CONSUMER_PROBE);
 			WRITE_ONCE(link->status, DL_STATE_ACTIVE);
+		} else if (link->status == DL_STATE_CONSUMER_PROBE ||
+			   link->status == DL_STATE_AVAILABLE) {
+			WRITE_ONCE(link->status, DL_STATE_ACTIVE);
+		} else {
+			WARN_ON(link->status != DL_STATE_ACTIVE &&
+				link->status != DL_STATE_DORMANT &&
+				link->status != DL_STATE_SUPPLIER_UNBIND);
+			continue;
 		}
 
 		/*
@@ -1460,10 +1487,67 @@ void device_links_driver_bound(struct device *dev)
 		if (defer_sync_state_count)
 			__device_links_supplier_defer_sync(supplier);
 		else
-			__device_links_queue_sync_state(supplier, &sync_list);
+			__device_links_queue_sync_state(supplier, sync_list);
 	}
 
 	dev->links.status = DL_DEV_DRIVER_BOUND;
+}
+
+static void device_links_class_bound(struct device *dev)
+{
+	LIST_HEAD(sync_list);
+
+	device_remove_file(dev, &dev_attr_waiting_for_supplier);
+
+	device_links_write_lock();
+	__device_links_supplier_bound(dev, &sync_list);
+	__device_links_consumer_bound(dev, &sync_list);
+	device_links_write_unlock();
+
+	device_links_flush_sync_list(&sync_list, NULL);
+	driver_deferred_probe_trigger();
+	device_probe_end();
+}
+
+/**
+ * device_links_driver_bound - Update device links after probing its driver.
+ * @dev: Device to update the links for.
+ *
+ * The probe has been successful, so update links from this device to any
+ * consumers by changing their status to "available".
+ *
+ * Also change the status of @dev's links to suppliers to "active".
+ *
+ * Links without the DL_FLAG_MANAGED flag set are ignored.
+ */
+void device_links_driver_bound(struct device *dev)
+{
+	LIST_HEAD(sync_list);
+
+	/*
+	 * 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
+	 * them. So, fw_devlink no longer needs to create device links to any
+	 * of the device's suppliers.
+	 *
+	 * Also, if a child firmware node of this bound device is not added as a
+	 * device by now, assume it is never going to be added. Make this bound
+	 * device the fallback supplier to the dangling consumers of the child
+	 * firmware node because this bound device is probably implementing the
+	 * child firmware node functionality and we don't want the dangling
+	 * 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);
+	}
+
+	device_remove_file(dev, &dev_attr_waiting_for_supplier);
+
+	device_links_write_lock();
+	__device_links_supplier_bound(dev, &sync_list);
+	__device_links_consumer_bound(dev, &sync_list);
 
 	device_links_write_unlock();
 
@@ -1548,17 +1632,7 @@ void device_links_no_driver(struct device *dev)
 	device_links_write_unlock();
 }
 
-/**
- * device_links_driver_cleanup - Update links after driver removal.
- * @dev: Device whose driver has just gone away.
- *
- * Update links to consumers for @dev by changing their status to "dormant" and
- * invoke %__device_links_no_driver() to update links to suppliers for it as
- * appropriate.
- *
- * Links without the DL_FLAG_MANAGED flag set are ignored.
- */
-void device_links_driver_cleanup(struct device *dev)
+static void device_links_cleanup(struct device *dev)
 {
 	struct device_link *link, *ln;
 
@@ -1568,7 +1642,8 @@ void device_links_driver_cleanup(struct device *dev)
 		if (!device_link_test(link, DL_FLAG_MANAGED))
 			continue;
 
-		WARN_ON(device_link_test(link, DL_FLAG_AUTOREMOVE_CONSUMER));
+		WARN_ON(device_link_test(link, DL_FLAG_AUTOREMOVE_CONSUMER) &&
+			!device_links_track_class(link->consumer));
 		WARN_ON(link->status != DL_STATE_SUPPLIER_UNBIND);
 
 		/*
@@ -1589,6 +1664,21 @@ void device_links_driver_cleanup(struct device *dev)
 	device_links_write_unlock();
 }
 
+/**
+ * device_links_driver_cleanup - Update links after driver removal.
+ * @dev: Device whose driver has just gone away.
+ *
+ * Update links to consumers for @dev by changing their status to "dormant" and
+ * invoke %__device_links_no_driver() to update links to suppliers for it as
+ * appropriate.
+ *
+ * Links without the DL_FLAG_MANAGED flag set are ignored.
+ */
+void device_links_driver_cleanup(struct device *dev)
+{
+	device_links_cleanup(dev);
+}
+
 /**
  * device_links_busy - Check if there are any busy links to consumers.
  * @dev: Device to check.
@@ -1599,7 +1689,9 @@ void device_links_driver_cleanup(struct device *dev)
  * state to "supplier unbind" to prevent the consumer from being probed
  * successfully going forward.
  *
- * Return 'false' if there are no probing or active consumers.
+ * Sync-state-only links are moved to "supplier unbind" without making the
+ * supplier busy, since they do not require the consumer to be unbound.
+ * Return 'false' if there are no other probing or active consumers.
  *
  * Links without the DL_FLAG_MANAGED flag set are ignored.
  */
@@ -1613,6 +1705,11 @@ bool device_links_busy(struct device *dev)
 	list_for_each_entry(link, &dev->links.consumers, s_node) {
 		if (!device_link_test(link, DL_FLAG_MANAGED))
 			continue;
+		/* Sync-state-only links do not require consumer unbinding. */
+		if (device_link_test(link, DL_FLAG_SYNC_STATE_ONLY)) {
+			WRITE_ONCE(link->status, DL_STATE_SUPPLIER_UNBIND);
+			continue;
+		}
 
 		if (link->status == DL_STATE_CONSUMER_PROBE
 		    || link->status == DL_STATE_ACTIVE) {
@@ -1668,6 +1765,10 @@ void device_links_unbind_consumers(struct device *dev)
 		if (status == DL_STATE_ACTIVE) {
 			struct device *consumer = link->consumer;
 
+			/* Class consumers have no driver to release. */
+			if (device_links_track_class(consumer))
+				continue;
+
 			get_device(consumer);
 
 			device_links_write_unlock();
@@ -1826,7 +1927,7 @@ static int fw_devlink_no_driver(struct device *dev, void *data)
 {
 	struct device_link *link = to_devlink(dev);
 
-	if (!dev_can_match(link->supplier))
+	if (dev_is_unmatchable(link->supplier))
 		fw_devlink_relax_link(link);
 
 	return 0;
@@ -3704,6 +3805,7 @@ int device_add(struct device *dev)
 	struct device *parent;
 	struct kobject *kobj;
 	struct class_interface *class_intf;
+	bool track_class_links = false;
 	int error = -EINVAL;
 	struct kobject *glue_dir = NULL;
 
@@ -3737,6 +3839,10 @@ int device_add(struct device *dev)
 	if (error)
 		goto name_error;
 
+	track_class_links = device_links_track_class(dev);
+	if (track_class_links)
+		device_links_class_start(dev);
+
 	pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
 
 	parent = get_device(dev->parent);
@@ -3838,7 +3944,7 @@ int device_add(struct device *dev)
 	 * match with any driver, don't block its consumers from probing in
 	 * case the consumer device is able to operate without this supplier.
 	 */
-	if (dev->fwnode && fw_devlink_drv_reg_done && !dev_can_match(dev))
+	if (dev->fwnode && fw_devlink_drv_reg_done && dev_is_unmatchable(dev))
 		fw_devlink_unblock_consumers(dev);
 
 	if (parent)
@@ -3858,6 +3964,8 @@ int device_add(struct device *dev)
 		mutex_unlock(&sp->mutex);
 		subsys_put(sp);
 	}
+	if (track_class_links)
+		device_links_class_bound(dev);
 done:
 	put_device(dev);
 	return error;
@@ -3886,6 +3994,10 @@ int device_add(struct device *dev)
 parent_error:
 	put_device(parent);
 name_error:
+	if (track_class_links) {
+		device_links_no_driver(dev);
+		device_probe_end();
+	}
 	kfree(dev->p);
 	dev->p = NULL;
 	goto done;
@@ -3980,9 +4092,17 @@ void device_del(struct device *dev)
 	struct device *parent = dev->parent;
 	struct kobject *glue_dir = NULL;
 	struct class_interface *class_intf;
+	bool track_class_links = device_links_track_class(dev);
 	unsigned int noio_flag;
 
 	device_lock(dev);
+	if (track_class_links) {
+		while (device_links_busy(dev)) {
+			device_unlock(dev);
+			device_links_unbind_consumers(dev);
+			device_lock(dev);
+		}
+	}
 	kill_device(dev);
 	device_unlock(dev);
 
@@ -4024,6 +4144,8 @@ void device_del(struct device *dev)
 	device_pm_remove(dev);
 	driver_deferred_probe_del(dev);
 	device_platform_notify_remove(dev);
+	if (track_class_links)
+		device_links_cleanup(dev);
 	device_links_purge(dev);
 
 	/*
diff --git a/drivers/base/dd.c b/drivers/base/dd.c
index 60c005223844..61288fcceb5b 100644
--- a/drivers/base/dd.c
+++ b/drivers/base/dd.c
@@ -555,9 +555,21 @@ int device_bind_driver(struct device *dev)
 }
 EXPORT_SYMBOL_GPL(device_bind_driver);
 
+/* Driver probes and driverless class-device registrations in progress. */
 static atomic_t probe_count = ATOMIC_INIT(0);
 static DECLARE_WAIT_QUEUE_HEAD(probe_waitqueue);
 
+void device_probe_begin(void)
+{
+	atomic_inc(&probe_count);
+}
+
+void device_probe_end(void)
+{
+	atomic_dec(&probe_count);
+	wake_up_all(&probe_waitqueue);
+}
+
 static ssize_t state_synced_store(struct device *dev,
 				  struct device_attribute *attr,
 				  const char *buf, size_t count)
@@ -797,7 +809,7 @@ static int really_probe_debug(struct device *dev, const struct device_driver *dr
 
 /**
  * driver_probe_done
- * Determine if the probe sequence is finished or not.
+ * Determine if the probe and class-device registration sequence is finished.
  *
  * Should somehow figure out how to use a semaphore, not an atomic variable...
  */
@@ -811,7 +823,7 @@ bool __init driver_probe_done(void)
 
 /**
  * wait_for_device_probe
- * Wait for device probing to be completed.
+ * Wait for device probing and driverless class registration to be completed.
  */
 void wait_for_device_probe(void)
 {
@@ -894,7 +906,7 @@ static int driver_probe_device(const struct device_driver *drv, struct device *d
 	int trigger_count = atomic_read(&deferred_trigger_count);
 	int ret;
 
-	atomic_inc(&probe_count);
+	device_probe_begin();
 	ret = __driver_probe_device(drv, dev);
 	if (ret == -EPROBE_DEFER || ret == EPROBE_DEFER) {
 		driver_deferred_probe_add(dev);
@@ -906,8 +918,7 @@ static int driver_probe_device(const struct device_driver *drv, struct device *d
 		    !defer_all_probes)
 			driver_deferred_probe_trigger();
 	}
-	atomic_dec(&probe_count);
-	wake_up_all(&probe_waitqueue);
+	device_probe_end();
 	return ret;
 }
 
diff --git a/drivers/base/test/Makefile b/drivers/base/test/Makefile
index d871a71dbbaa..c33c615eaabf 100644
--- a/drivers/base/test/Makefile
+++ b/drivers/base/test/Makefile
@@ -3,6 +3,7 @@ obj-$(CONFIG_TEST_ASYNC_DRIVER_PROBE)	+= test_async_driver_probe.o
 
 obj-$(CONFIG_DM_KUNIT_TEST)	+= root-device-test.o
 obj-$(CONFIG_DM_KUNIT_TEST)	+= platform-device-test.o
+obj-$(CONFIG_DM_KUNIT_TEST)	+= device-link-class-test.o
 obj-$(CONFIG_DM_KUNIT_TEST)	+= fwnode-link-test.o
 
 obj-$(CONFIG_DRIVER_PE_KUNIT_TEST) += property-entry-test.o
diff --git a/drivers/base/test/device-link-class-test.c b/drivers/base/test/device-link-class-test.c
new file mode 100644
index 000000000000..7d52efb55aa5
--- /dev/null
+++ b/drivers/base/test/device-link-class-test.c
@@ -0,0 +1,479 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <kunit/platform_device.h>
+#include <kunit/resource.h>
+#include <kunit/test.h>
+
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/device/driver.h>
+#include <linux/fwnode.h>
+#include <linux/jiffies.h>
+#include <linux/kernfs.h>
+#include <linux/kthread.h>
+#include <linux/platform_device.h>
+#include <linux/sched/task.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+
+#define TEST_CLASS_NAME		"device-link-test"
+#define TEST_DRIVER_NAME	"device-link-test-consumer"
+
+enum test_class_action {
+	TEST_CLASS_NONE,
+	TEST_CLASS_SUPPLIER,
+	TEST_CLASS_SYNC_CONSUMER,
+	TEST_CLASS_OBSERVE,
+	TEST_CLASS_WAIT,
+	TEST_CLASS_UNBIND_RACE,
+};
+
+struct device_link_class_context {
+	enum test_class_action action;
+	struct device *endpoint;
+	struct device_link *link;
+	struct completion waiter_started;
+	struct completion waiter_done;
+	struct task_struct *waiter;
+	struct completion unbind_started;
+	struct completion unbind_done;
+	struct task_struct *unbinder;
+	bool waiter_completed_during_add;
+	bool unbind_completed_during_add;
+	bool waiting_for_supplier_seen;
+};
+
+struct test_class_device {
+	struct device dev;
+	struct fwnode_handle fwnode;
+	struct fwnode_handle missing_supplier;
+	bool added;
+};
+
+static struct class *test_class;
+static struct device_link_class_context *test_context;
+static atomic_t remove_count;
+static atomic_t sync_count;
+
+static int test_driver_probe(struct platform_device *pdev)
+{
+	return 0;
+}
+
+static void test_driver_remove(struct platform_device *pdev)
+{
+	atomic_inc(&remove_count);
+}
+
+static void test_driver_sync_state(struct device *dev)
+{
+	atomic_inc(&sync_count);
+}
+
+static struct platform_driver test_driver = {
+	.probe = test_driver_probe,
+	.remove = test_driver_remove,
+	.driver = {
+		.name = TEST_DRIVER_NAME,
+		.sync_state = test_driver_sync_state,
+	},
+};
+
+static bool test_has_waiting_for_supplier(struct device *dev)
+{
+	struct kernfs_node *kn;
+
+	kn = sysfs_get_dirent(dev->kobj.sd, "waiting_for_supplier");
+	if (kn)
+		kernfs_put(kn);
+
+	return !!kn;
+}
+
+static int test_wait_for_probe(void *data)
+{
+	struct device_link_class_context *context = data;
+
+	complete(&context->waiter_started);
+	wait_for_device_probe();
+	complete(&context->waiter_done);
+
+	return 0;
+}
+
+static int test_unbind_supplier(void *data)
+{
+	struct device_link_class_context *context = data;
+
+	complete(&context->unbind_started);
+	device_release_driver(context->endpoint);
+	complete(&context->unbind_done);
+
+	return 0;
+}
+
+static int test_class_add(struct device *dev)
+{
+	struct device_link_class_context *context = test_context;
+
+	if (!context)
+		return 0;
+
+	switch (context->action) {
+	case TEST_CLASS_SUPPLIER:
+		context->link = device_link_add(context->endpoint, dev,
+						DL_FLAG_INFERRED |
+						DL_FLAG_AUTOPROBE_CONSUMER);
+		return context->link ? 0 : -ENOMEM;
+	case TEST_CLASS_SYNC_CONSUMER:
+		context->link = device_link_add(dev, context->endpoint,
+						DL_FLAG_INFERRED |
+						DL_FLAG_SYNC_STATE_ONLY);
+		return context->link ? 0 : -ENOMEM;
+	case TEST_CLASS_OBSERVE:
+		context->waiting_for_supplier_seen =
+			test_has_waiting_for_supplier(dev);
+		return 0;
+	case TEST_CLASS_WAIT:
+		context->waiter = kthread_create(test_wait_for_probe, context,
+						 "device-link-wait");
+		if (IS_ERR(context->waiter))
+			return PTR_ERR(context->waiter);
+		get_task_struct(context->waiter);
+		wake_up_process(context->waiter);
+		wait_for_completion(&context->waiter_started);
+		context->waiter_completed_during_add =
+			wait_for_completion_timeout(&context->waiter_done,
+						    msecs_to_jiffies(50));
+		return 0;
+	case TEST_CLASS_UNBIND_RACE:
+		context->link = device_link_add(dev, context->endpoint, 0);
+		if (!context->link)
+			return -ENOMEM;
+		context->unbinder = kthread_create(test_unbind_supplier, context,
+						   "device-link-unbind");
+		if (IS_ERR(context->unbinder))
+			return PTR_ERR(context->unbinder);
+		get_task_struct(context->unbinder);
+		wake_up_process(context->unbinder);
+		wait_for_completion(&context->unbind_started);
+		context->unbind_completed_during_add =
+			wait_for_completion_timeout(&context->unbind_done,
+						    msecs_to_jiffies(50));
+		return 0;
+	case TEST_CLASS_NONE:
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static struct class_interface test_class_interface = {
+	.add_dev = test_class_add,
+};
+
+static void test_unregister_device(void *data)
+{
+	device_unregister(data);
+}
+
+static void test_resume_sync_state(void *data)
+{
+	device_links_supplier_sync_state_resume();
+}
+
+static struct platform_device *
+test_register_platform_device(struct kunit *test)
+{
+	struct platform_device_info info = {
+		.name = TEST_DRIVER_NAME,
+		.id = PLATFORM_DEVID_AUTO,
+	};
+	struct platform_device *pdev;
+
+	pdev = kunit_platform_device_register_full(test, &info);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev);
+	KUNIT_ASSERT_EQ(test, pdev->dev.links.status, DL_DEV_DRIVER_BOUND);
+
+	return pdev;
+}
+
+static void test_class_device_release(struct device *dev)
+{
+	struct test_class_device *tdev =
+		container_of(dev, struct test_class_device, dev);
+
+	fwnode_links_purge(&tdev->fwnode);
+	fwnode_links_purge(&tdev->missing_supplier);
+	kfree(tdev);
+}
+
+static void test_class_device_cleanup(void *data)
+{
+	struct test_class_device *tdev = data;
+
+	if (tdev->added)
+		device_unregister(&tdev->dev);
+	else
+		put_device(&tdev->dev);
+}
+
+static struct test_class_device *
+test_class_device_alloc(struct kunit *test, const char *name)
+{
+	struct test_class_device *tdev;
+	int ret;
+
+	tdev = kzalloc_obj(*tdev);
+	KUNIT_ASSERT_NOT_NULL(test, tdev);
+
+	device_initialize(&tdev->dev);
+	fwnode_init(&tdev->fwnode, NULL);
+	fwnode_init(&tdev->missing_supplier, NULL);
+	tdev->dev.class = test_class;
+	tdev->dev.release = test_class_device_release;
+	device_set_node(&tdev->dev, &tdev->fwnode);
+	ret = dev_set_name(&tdev->dev, "%s", name);
+	if (ret) {
+		put_device(&tdev->dev);
+		KUNIT_FAIL(test, "failed to name class device: %d", ret);
+		return NULL;
+	}
+
+	ret = kunit_add_action_or_reset(test, test_class_device_cleanup, tdev);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	return tdev;
+}
+
+static void device_link_class_supplier_test(struct kunit *test)
+{
+	struct device_link_class_context *context = test->priv;
+	struct platform_device *consumer;
+	struct device *supplier;
+	int ret;
+
+	consumer = test_register_platform_device(test);
+	context->action = TEST_CLASS_SUPPLIER;
+	context->endpoint = &consumer->dev;
+
+	supplier = device_create(test_class, NULL, 0, NULL,
+				 TEST_CLASS_NAME "-supplier");
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, supplier);
+	ret = kunit_add_action_or_reset(test, test_unregister_device, supplier);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	KUNIT_ASSERT_NOT_NULL(test, context->link);
+	KUNIT_EXPECT_EQ(test, supplier->links.status, DL_DEV_DRIVER_BOUND);
+	KUNIT_EXPECT_EQ(test, READ_ONCE(context->link->status), DL_STATE_ACTIVE);
+	KUNIT_EXPECT_TRUE(test, device_link_test(context->link,
+						 DL_FLAG_AUTOPROBE_CONSUMER));
+	KUNIT_EXPECT_FALSE(test, device_link_test(context->link,
+						  DL_FLAG_SYNC_STATE_ONLY));
+
+	kunit_release_action(test, test_unregister_device, supplier);
+	device_link_wait_removal();
+
+	KUNIT_EXPECT_EQ(test, atomic_read(&remove_count), 1);
+	KUNIT_EXPECT_EQ(test, consumer->dev.links.status, DL_DEV_NO_DRIVER);
+}
+
+static void device_link_class_consumer_sync_test(struct kunit *test)
+{
+	struct device_link_class_context *context = test->priv;
+	struct platform_device *supplier;
+	struct device *consumer;
+	int ret;
+
+	device_links_supplier_sync_state_pause();
+	ret = kunit_add_action_or_reset(test, test_resume_sync_state, NULL);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	supplier = test_register_platform_device(test);
+	context->action = TEST_CLASS_SYNC_CONSUMER;
+	context->endpoint = &supplier->dev;
+
+	consumer = device_create(test_class, NULL, 0, NULL,
+				 TEST_CLASS_NAME "-consumer");
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, consumer);
+	ret = kunit_add_action_or_reset(test, test_unregister_device, consumer);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	KUNIT_ASSERT_NOT_NULL(test, context->link);
+	KUNIT_EXPECT_EQ(test, consumer->links.status, DL_DEV_DRIVER_BOUND);
+	KUNIT_EXPECT_TRUE(test, list_empty(&consumer->links.suppliers));
+	kunit_release_action(test, test_resume_sync_state, NULL);
+	KUNIT_EXPECT_EQ(test, atomic_read(&sync_count), 1);
+}
+
+static void device_link_class_existing_consumer_link_test(struct kunit *test)
+{
+	struct device_link_class_context *context = test->priv;
+	struct test_class_device *consumer;
+	struct platform_device *supplier;
+	struct device_link *link;
+	int ret;
+
+	supplier = test_register_platform_device(test);
+	consumer = test_class_device_alloc(test,
+					   TEST_CLASS_NAME "-existing-link");
+	KUNIT_ASSERT_NOT_NULL(test, consumer);
+
+	link = device_link_add(&consumer->dev, &supplier->dev,
+			       DL_FLAG_AUTOREMOVE_CONSUMER);
+	KUNIT_ASSERT_NOT_NULL(test, link);
+	KUNIT_ASSERT_EQ(test, READ_ONCE(link->status), DL_STATE_AVAILABLE);
+	ret = fwnode_link_add(&consumer->fwnode,
+			      &consumer->missing_supplier, 0);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	context->action = TEST_CLASS_OBSERVE;
+	ret = device_add(&consumer->dev);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	consumer->added = true;
+
+	KUNIT_EXPECT_EQ(test, consumer->dev.links.status, DL_DEV_DRIVER_BOUND);
+	KUNIT_EXPECT_EQ(test, READ_ONCE(link->status), DL_STATE_ACTIVE);
+	KUNIT_EXPECT_TRUE(test, context->waiting_for_supplier_seen);
+	KUNIT_EXPECT_FALSE(test, test_has_waiting_for_supplier(&consumer->dev));
+}
+
+static void device_link_class_registration_wait_test(struct kunit *test)
+{
+	struct device_link_class_context *context = test->priv;
+	struct device *dev;
+	unsigned long timeout;
+	int ret;
+
+	wait_for_device_probe();
+	context->action = TEST_CLASS_WAIT;
+	dev = device_create(test_class, NULL, 0, NULL,
+			    TEST_CLASS_NAME "-wait");
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+	ret = kunit_add_action_or_reset(test, test_unregister_device, dev);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	KUNIT_EXPECT_FALSE(test, context->waiter_completed_during_add);
+	timeout = wait_for_completion_timeout(&context->waiter_done,
+					      msecs_to_jiffies(1000));
+	KUNIT_EXPECT_GT(test, timeout, 0UL);
+}
+
+static void device_link_class_supplier_unbind_race_test(struct kunit *test)
+{
+	struct device_link_class_context *context = test->priv;
+	struct platform_device *supplier;
+	struct device *consumer;
+	unsigned long timeout;
+	int ret;
+
+	supplier = test_register_platform_device(test);
+	context->action = TEST_CLASS_UNBIND_RACE;
+	context->endpoint = &supplier->dev;
+	consumer = device_create(test_class, NULL, 0, NULL,
+				 TEST_CLASS_NAME "-unbind-race");
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, consumer);
+	ret = kunit_add_action_or_reset(test, test_unregister_device, consumer);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	KUNIT_ASSERT_NOT_NULL(test, context->link);
+	KUNIT_EXPECT_FALSE(test, context->unbind_completed_during_add);
+	timeout = wait_for_completion_timeout(&context->unbind_done,
+					      msecs_to_jiffies(1000));
+	KUNIT_EXPECT_GT(test, timeout, 0UL);
+	KUNIT_EXPECT_EQ(test, atomic_read(&remove_count), 1);
+	KUNIT_EXPECT_EQ(test, supplier->dev.links.status, DL_DEV_NO_DRIVER);
+}
+
+static int device_link_class_test_init(struct kunit *test)
+{
+	struct device_link_class_context *context;
+
+	context = kunit_kzalloc(test, sizeof(*context), GFP_KERNEL);
+	if (!context)
+		return -ENOMEM;
+
+	init_completion(&context->waiter_started);
+	init_completion(&context->waiter_done);
+	init_completion(&context->unbind_started);
+	init_completion(&context->unbind_done);
+	test->priv = context;
+	test_context = context;
+	atomic_set(&remove_count, 0);
+	atomic_set(&sync_count, 0);
+
+	return 0;
+}
+
+static void device_link_class_test_exit(struct kunit *test)
+{
+	struct device_link_class_context *context = test->priv;
+
+	if (context->waiter && !IS_ERR(context->waiter)) {
+		kthread_stop(context->waiter);
+		put_task_struct(context->waiter);
+	}
+	if (context->unbinder && !IS_ERR(context->unbinder)) {
+		kthread_stop(context->unbinder);
+		put_task_struct(context->unbinder);
+	}
+	test_context = NULL;
+}
+
+static int device_link_class_suite_init(struct kunit_suite *suite)
+{
+	int ret;
+
+	test_class = class_create(TEST_CLASS_NAME);
+	if (IS_ERR(test_class))
+		return PTR_ERR(test_class);
+
+	ret = platform_driver_register(&test_driver);
+	if (ret)
+		goto destroy_class;
+
+	test_class_interface.class = test_class;
+	ret = class_interface_register(&test_class_interface);
+	if (ret)
+		goto unregister_driver;
+
+	return 0;
+
+unregister_driver:
+	platform_driver_unregister(&test_driver);
+destroy_class:
+	class_destroy(test_class);
+
+	return ret;
+}
+
+static void device_link_class_suite_exit(struct kunit_suite *suite)
+{
+	class_interface_unregister(&test_class_interface);
+	platform_driver_unregister(&test_driver);
+	class_destroy(test_class);
+	device_link_wait_removal();
+}
+
+static struct kunit_case device_link_class_test_cases[] = {
+	KUNIT_CASE(device_link_class_supplier_test),
+	KUNIT_CASE(device_link_class_consumer_sync_test),
+	KUNIT_CASE(device_link_class_existing_consumer_link_test),
+	KUNIT_CASE(device_link_class_registration_wait_test),
+	KUNIT_CASE(device_link_class_supplier_unbind_race_test),
+	{}
+};
+
+static struct kunit_suite device_link_class_test_suite = {
+	.name = "device-link-class",
+	.suite_init = device_link_class_suite_init,
+	.suite_exit = device_link_class_suite_exit,
+	.init = device_link_class_test_init,
+	.exit = device_link_class_test_exit,
+	.test_cases = device_link_class_test_cases,
+};
+
+kunit_test_suite(device_link_class_test_suite);
+
+MODULE_DESCRIPTION("KUnit tests for class device links");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/device.h b/include/linux/device.h
index aee79fd6b32b..3783744df86e 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -446,11 +446,16 @@ enum device_link_state {
 #define DL_FLAG_CYCLE			BIT(9)
 
 /**
- * enum dl_dev_state - Device driver presence tracking information.
+ * enum dl_dev_state - Device availability tracking information.
  * @DL_DEV_NO_DRIVER: There is no driver attached to the device.
  * @DL_DEV_PROBING: A driver is probing.
  * @DL_DEV_DRIVER_BOUND: The driver has been bound to the device.
  * @DL_DEV_UNBINDING: The driver is unbinding from the device.
+ *
+ * Class devices which are not on a bus use the same states for their
+ * registration lifecycle.  In that case, PROBING and DRIVER_BOUND mean that
+ * device_add() is in progress or has completed, respectively, and UNBINDING
+ * means that device_del() is in progress.
  */
 enum dl_dev_state {
 	DL_DEV_NO_DRIVER = 0,
@@ -480,7 +485,7 @@ enum device_removable {
  * @suppliers: List of links to supplier devices.
  * @consumers: List of links to consumer devices.
  * @defer_sync: Hook to global list of devices that have deferred sync_state.
- * @status: Driver status information.
+ * @status: Driver or class-device availability status.
  */
 struct dev_links_info {
 	struct list_head suppliers;

-- 
2.53.0


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

* Re: [PATCH net-next v3 0/4] driver core, net: handle fw_devlink for class devices and PHY packages
  2026-08-18  6:58 [PATCH net-next v3 0/4] driver core, net: handle fw_devlink for class devices and PHY packages James Hilliard
                   ` (3 preceding siblings ...)
  2026-08-18  6:59 ` [PATCH net-next v3 4/4] driver core: handle managed links for class devices James Hilliard
@ 2026-08-18  7:15 ` Andy Shevchenko
  4 siblings, 0 replies; 9+ messages in thread
From: Andy Shevchenko @ 2026-08-18  7:15 UTC (permalink / raw)
  To: James Hilliard
  Cc: Rob Herring, Saravana Kannan, Greg Kroah-Hartman,
	Rafael J. Wysocki, Danilo Krummrich, Jonathan Corbet, Shuah Khan,
	Daniel Scally, Heikki Krogerus, Sakari Ailus, Len Brown,
	Andrew Lunn, Heiner Kallweit, Russell King, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Christian Marangi,
	devicetree, linux-doc, linux-kernel, driver-core, linux-acpi,
	netdev

On Tue, Aug 18, 2026 at 12:58:56AM -0600, James Hilliard wrote:
> Managed device links normally advance through probe and removal alongside
> a device driver. Driverless class devices are added directly, so their
> links do not currently receive equivalent transitions. A class supplier
> can leave its consumers dormant, while sync-state-only links from a class
> consumer can indefinitely prevent suppliers from receiving sync_state().
> Removal also does not unbind consumers of a class supplier.
> 
> Ethernet PHY packages expose both sides of this problem. A package node
> describes resources shared by member PHYs, but is a container rather than
> a device. fw_devlink preserves that topology and temporarily represents
> those dependencies through the closest device, which for OF MDIO is the
> driverless MDIO bus class device. Its proxy links previously remained
> after the real PHY had probed and kept the package suppliers from reaching
> sync_state().
> 
> Handle the generic class-device lifecycle in the driver core. Put
> driverless class devices into the probing state while device_add()
> publishes them, complete their incoming and outgoing managed links after
> class interfaces run, and apply the corresponding unbind and cleanup
> transitions during device_del(). This supports class devices as either
> suppliers or consumers and keeps descendant firmware links available for
> devices created later. Keep inferred links to probing and registered class
> suppliers out of no-driver fallback paths, and recognize class-backed
> nodes when refreshing dependencies after firmware-tree overlays.
> 
> Keep generic firmware parsing faithful to the described topology. Add a
> helper through which a subsystem can copy a container node's direct
> supplier links onto the firmware node for a real child device, then use it
> from PHY registration before device_add(). The ordinary fw_devlink path
> consequently creates each managed PHY link at the normal
> device-registration stage.
> 
> Finally, defer supplier sync-state callbacks across OF MDIO bus
> registration and child population. This closes the interval between
> completion of the bus class device and installation of the member PHY
> links without adding an MDIO-specific device-link state transition.
> 
> This was found while validating an X-Powers AC300 package whose input
> clock is supplied by PWM. Before the fix, the PWM driver reported that
> sync_state() was pending due to stmmac-0 after the PHY had probed.
> 
> The complete series was tested on an Allwinner H616 board with an X-Powers
> AC300 PHY package. All five package-derived supplier links targeted the
> member PHY and no proxy link remained on the driverless MDIO bus. The PWM
> supplier reached state_synced while continuing to generate the PHY clock.
> These properties remained true after unbinding and rebinding the MAC, and
> the PHY renegotiated a 100 Mbps full-duplex link. Both before and after
> that lifecycle test, 8 MiB transfers in each direction compared correctly
> and 100 consecutive pings completed without loss.
> 
> Validation also included:
> 
>   - a full arm64 defconfig Image and modules build with W=1;
>   - KUnit tests for class supplier/consumer transitions, inferred-link
>     preservation, and fwnode supplier copying and device-link conversion;
>     and
>   - strict per-patch checkpatch and whitespace checks.
> 
> Assisted-by: Codex:gpt-5.6-sol

Seems the commit messages are also copy'n'pasted from AI. Please, make sure
that it's clear, straight to the point and not a saga with unneeded details
and obvious explanations.

-- 
With Best Regards,
Andy Shevchenko



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

* Re: [PATCH net-next v3 1/4] driver core: add fw_devlink supplier-copy helper
  2026-08-18  6:58 ` [PATCH net-next v3 1/4] driver core: add fw_devlink supplier-copy helper James Hilliard
@ 2026-08-18  7:30   ` Andy Shevchenko
  0 siblings, 0 replies; 9+ messages in thread
From: Andy Shevchenko @ 2026-08-18  7:30 UTC (permalink / raw)
  To: James Hilliard
  Cc: Rob Herring, Saravana Kannan, Greg Kroah-Hartman,
	Rafael J. Wysocki, Danilo Krummrich, Jonathan Corbet, Shuah Khan,
	Daniel Scally, Heikki Krogerus, Sakari Ailus, Len Brown,
	Andrew Lunn, Heiner Kallweit, Russell King, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Christian Marangi,
	devicetree, linux-doc, linux-kernel, driver-core, linux-acpi,
	netdev

On Tue, Aug 18, 2026 at 12:58:57AM -0600, James Hilliard wrote:
> Some firmware nodes describe resources shared by devices instantiated for
> their children, but the container node itself is never converted to a
> struct device. The firmware parser should retain the topology as
> described, while the framework which creates the children can identify
> the actual consumers.
> 
> Add fw_devlink_copy_suppliers() so such a framework can copy the direct
> supplier links from a container to a real consumer firmware node before
> the consumer is registered. The normal device_add() path then converts
> the copied dependencies into device links at the correct point in device
> registration.
> 
> Leave the source links in place for other children, suppress duplicate
> links and roll back newly allocated links if a copy fails. Skip ignored
> links and clear cycle flags on newly copied links, since cycle
> classification must be recomputed for the new consumer topology. Reject
> calls after the target firmware node has been associated with a device.
> Check that association while holding the fwnode-link lock so a
> concurrent device_add() either observes the copied links or makes the
> helper reject the request.

What is this paragraph about? The workflow?

> Add KUnit coverage for filtering, cycle-flag handling, idempotency, the
> pre-registration contract and conversion into an active device link.

...

> +int fw_devlink_copy_suppliers(struct fwnode_handle *to,
> +			      struct fwnode_handle *from)
> +{
> +	struct list_head *first;
> +	struct fwnode_link *link;
> +	int ret;
> +
> +	if (!to || !from)
> +		return -EINVAL;
> +	if (!fw_devlink_flags || to == from)
> +		return 0;

I think if to == NULL and from == NULL, it's fine to return 0.

	if (to == from)
		return 0;
	if (!to || !from)
		return -EINVAL;
	if (!fw_devlink_flags)
		return 0;

> +	fw_devlink_parse_fwnode(from);
> +
> +	guard(mutex)(&fwnode_link_lock);

+ blank line.

> +	if (READ_ONCE(to->dev))
> +		return -EBUSY;
> +
> +	first = to->suppliers.next;

No, we have list.h and APIs for a reason.

> +	list_for_each_entry(link, &from->suppliers, c_hook) {
> +		u8 flags = link->flags & ~FWLINK_FLAG_CYCLE;
> +
> +		if (flags & FWLINK_FLAG_IGNORE)
> +			continue;
> +
> +		ret = __fwnode_link_add(to, link->supplier, flags);
> +		if (ret)
> +			goto rollback;
> +	}
> +
> +	return 0;
> +
> +rollback:
> +	while (to->suppliers.next != first) {

Same here. I think the above and this needs to be thought through as this looks
like an AI shortcut without thinking of the existing APIs and possible different
(better) implementation.

> +		link = list_first_entry(&to->suppliers, struct fwnode_link,
> +					c_hook);
> +		__fwnode_link_del(link);
> +	}
> +
> +	return ret;
> +}

...

> +++ b/drivers/base/test/fwnode-link-test.c

> +#include <kunit/platform_device.h>
> +#include <kunit/test.h>
> +
> +#include <linux/device.h>
> +#include <linux/fwnode.h>

+ list.h

> +#include <linux/platform_device.h>
> +
> +#define FWNODE_LINK_TEST_DRIVER_NAME	"fwnode-link-test"
> +
> +struct fwnode_link_test_context {
> +	struct fwnode_handle consumer;
> +	struct fwnode_handle container;
> +	struct fwnode_handle supplier_a;
> +	struct fwnode_handle supplier_b;
> +};
> +
> +static int fwnode_link_test_probe(struct platform_device *pdev)
> +{
> +	return 0;
> +}
> +
> +static struct platform_driver fwnode_link_test_driver = {
> +	.probe = fwnode_link_test_probe,
> +	.driver = {
> +		.name = FWNODE_LINK_TEST_DRIVER_NAME,
> +	},
> +};
> +
> +static void fwnode_link_test_cleanup(void *data)
> +{
> +	struct fwnode_link_test_context *context = data;
> +
> +	fwnode_links_purge(&context->consumer);
> +	fwnode_links_purge(&context->container);
> +	fwnode_links_purge(&context->supplier_a);
> +	fwnode_links_purge(&context->supplier_b);
> +}
> +
> +static struct fwnode_link_test_context *
> +fwnode_link_test_init(struct kunit *test)
> +{
> +	struct fwnode_link_test_context *context;
> +	int ret;
> +
> +	context = kunit_kzalloc(test, sizeof(*context), GFP_KERNEL);
> +	KUNIT_ASSERT_NOT_NULL(test, context);
> +
> +	fwnode_init(&context->consumer, NULL);
> +	fwnode_init(&context->container, NULL);
> +	fwnode_init(&context->supplier_a, NULL);
> +	fwnode_init(&context->supplier_b, NULL);
> +	ret = kunit_add_action_or_reset(test, fwnode_link_test_cleanup,
> +					context);
> +	KUNIT_ASSERT_EQ(test, ret, 0);
> +
> +	return context;
> +}
> +
> +static struct platform_device *
> +fwnode_link_test_register_pdev(struct kunit *test,
> +			       struct fwnode_handle *fwnode)
> +{
> +	struct platform_device *pdev;
> +	int ret;
> +
> +	pdev = kunit_platform_device_alloc(test, FWNODE_LINK_TEST_DRIVER_NAME,
> +					   PLATFORM_DEVID_AUTO);
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev);
> +
> +	device_set_node(&pdev->dev, fwnode);
> +	ret = kunit_platform_device_add(test, pdev);
> +	if (ret) {
> +		KUNIT_FAIL(test, "failed to register platform device: %d", ret);
> +		return NULL;
> +	}
> +
> +	return pdev;
> +}
> +
> +static unsigned int fwnode_supplier_count(struct fwnode_handle *fwnode)
> +{
> +	struct fwnode_link *link;
> +	unsigned int count = 0;
> +
> +	list_for_each_entry(link, &fwnode->suppliers, c_hook)
> +		count++;

We have an existing API for this. I recommend to stop using AI for a moment
and just read the existing code thoroughly (list.h) and see what we have
in the kernel.

> +	return count;
> +}

...

> --- a/include/linux/fwnode.h
> +++ b/include/linux/fwnode.h

> +int fw_devlink_copy_suppliers(struct fwnode_handle *to,
> +			      struct fwnode_handle *from);
>  bool fw_devlink_is_strict(void);

It might be better to split a test into a separate patch. But I don't care
about this much.

-- 
With Best Regards,
Andy Shevchenko



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

* Re: [PATCH net-next v3 2/4] net: mdio: link PHY package suppliers to member PHYs
  2026-08-18  6:58 ` [PATCH net-next v3 2/4] net: mdio: link PHY package suppliers to member PHYs James Hilliard
@ 2026-08-18  7:39   ` Andy Shevchenko
  0 siblings, 0 replies; 9+ messages in thread
From: Andy Shevchenko @ 2026-08-18  7:39 UTC (permalink / raw)
  To: James Hilliard
  Cc: Rob Herring, Saravana Kannan, Greg Kroah-Hartman,
	Rafael J. Wysocki, Danilo Krummrich, Jonathan Corbet, Shuah Khan,
	Daniel Scally, Heikki Krogerus, Sakari Ailus, Len Brown,
	Andrew Lunn, Heiner Kallweit, Russell King, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Christian Marangi,
	devicetree, linux-doc, linux-kernel, driver-core, linux-acpi,
	netdev

On Tue, Aug 18, 2026 at 12:58:58AM -0600, James Hilliard wrote:
> Ethernet PHY package nodes describe resources shared by their member
> PHYs, but a package node is not instantiated as a device. Its
> firmware-node supplier links therefore cannot become normal device links
> for the PHY members without help from the MDIO layer.
> 
> When registering an OF-backed PHY whose parent is an
> ethernet-phy-package node, copy the package supplier links onto the
> member's firmware node before the PHY device is added. The ordinary
> fw_devlink path then creates managed links after the PHY device has been
> initialized for link registration and before its driver can probe.
> 
> Apply this to both ordinary OF discovery and the exported registration
> path for a pre-created PHY device. Leave the package links intact so
> every member, including one registered later, can acquire the same
> dependencies. This preserves probe, unbind and runtime-PM ordering
> without adding PHY-package policy to generic firmware parsing.

...

>  #include <linux/device.h>
>  #include <linux/err.h>

> +#include <linux/fwnode.h>

How is this being used?

>  #include <linux/fwnode_mdio.h>
>  #include <linux/kernel.h>
>  #include <linux/module.h>

...

> +static int of_mdiobus_link_phy_package(struct device_node *child)
> +{
> +	struct device_node *package __free(device_node) = of_get_parent(child);
> +
> +	if (!package || !of_node_name_eq(package, "ethernet-phy-package"))
> +		return 0;
> +
> +	return fw_devlink_copy_suppliers(of_fwnode_handle(child),
> +					 of_fwnode_handle(package));

It could be moved to fwnode from the start. Don't we have the respective parts
already?

> +}

-- 
With Best Regards,
Andy Shevchenko



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

* Re: [PATCH net-next v3 4/4] driver core: handle managed links for class devices
  2026-08-18  6:59 ` [PATCH net-next v3 4/4] driver core: handle managed links for class devices James Hilliard
@ 2026-08-18  7:48   ` Andy Shevchenko
  0 siblings, 0 replies; 9+ messages in thread
From: Andy Shevchenko @ 2026-08-18  7:48 UTC (permalink / raw)
  To: James Hilliard
  Cc: Rob Herring, Saravana Kannan, Greg Kroah-Hartman,
	Rafael J. Wysocki, Danilo Krummrich, Jonathan Corbet, Shuah Khan,
	Daniel Scally, Heikki Krogerus, Sakari Ailus, Len Brown,
	Andrew Lunn, Heiner Kallweit, Russell King, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Christian Marangi,
	devicetree, linux-doc, linux-kernel, driver-core, linux-acpi,
	netdev

On Tue, Aug 18, 2026 at 12:59:00AM -0600, James Hilliard wrote:
> fw_devlink deliberately lets the nearest device pick up dependencies below
> class-backed firmware nodes because driverless class devices do not
> currently progress through managed device-link states. This can leave
> consumers of class suppliers dormant, while sync-state-only links from
> class consumers never reach the successful-probe cleanup.
> 
> Track class-only devices through equivalent registration and removal
> transitions. Mark them probing before device_add() publishes them, complete
> their incoming and outgoing links after class interfaces run, and enter the
> unbind and no-driver states during device_del(). Preserve firmware links
> below class-backed nodes so later-created descendants can acquire their own
> links.
> 
> Include class registration in wait_for_device_probe() so supplier unbind
> cannot race a class consumer that is still being published. Transition
> managed links which existed before device_add(), remove stale
> waiting_for_supplier attributes and do not revive a link whose supplier has
> already started unbinding.
> 
> During supplier teardown, wait for class registration to finish, but move
> an already registered class consumer directly to the unbinding link state
> because it has no driver to release. Keep consumer-autoremove links valid
> until class consumer removal without triggering driver-only cleanup
> warnings.
> 
> Factor the supplier-bound, consumer-bound and cleanup transitions shared
> with driver-backed devices. Activate late links involving an already
> functional class endpoint instead of trying to reprobe it, and let
> sync-state-only links retire without forcing consumer unbind. Ordinary
> supplier links cannot defer class registration, so they must not be the
> only mechanism on which a class consumer relies for functionality.
> 
> Class devices do not match drivers, so distinguish a genuinely unavailable
> supplier from a probing or registered class endpoint before relaxing
> inferred links. Preserve normal probe and runtime-PM ordering for those
> links. Also recognize class-backed nodes when refreshing dependencies after
> firmware-tree overlays.
> 
> Exclude the internal device-link class because device-link objects are
> registered while the device-links lock is held. Document the class-device
> state transitions and add KUnit coverage for supplier and consumer links,
> pre-existing links, inferred-link preservation, registration waiters and a
> concurrent supplier unbind.

This is enormous change, can it be split to several logically isolated patches?

-- 
With Best Regards,
Andy Shevchenko



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

end of thread, other threads:[~2026-08-18  7:48 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-18  6:58 [PATCH net-next v3 0/4] driver core, net: handle fw_devlink for class devices and PHY packages James Hilliard
2026-08-18  6:58 ` [PATCH net-next v3 1/4] driver core: add fw_devlink supplier-copy helper James Hilliard
2026-08-18  7:30   ` Andy Shevchenko
2026-08-18  6:58 ` [PATCH net-next v3 2/4] net: mdio: link PHY package suppliers to member PHYs James Hilliard
2026-08-18  7:39   ` Andy Shevchenko
2026-08-18  6:58 ` [PATCH net-next v3 3/4] net: mdio: defer supplier sync during OF population James Hilliard
2026-08-18  6:59 ` [PATCH net-next v3 4/4] driver core: handle managed links for class devices James Hilliard
2026-08-18  7:48   ` Andy Shevchenko
2026-08-18  7:15 ` [PATCH net-next v3 0/4] driver core, net: handle fw_devlink for class devices and PHY packages Andy Shevchenko

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