| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/18 07:43 | flow | patch-triage |
1mArgs:null Results: {
"EnableConfigs": [
"DM_KUNIT_TEST",
"OF_MDIO"
],
"FocusSymbols": [
"fw_devlink_copy_suppliers",
"device_links_driver_bound",
"device_links_driver_cleanup",
"device_links_unbind_consumers",
"of_mdiobus_phy_device_register",
"__of_mdiobus_register"
],
"KMSANReasoning": "The patch series modifies the device link and fwnode link subsystem to support driverless class devices in the managed-link state machine. The changes involve internal kernel state machine logic, locking, and list manipulations. There are no new structures sent to user space, no complex data structures allocated and left uninitialized, and no interactions with user space buffers (e.g., via copy_to_user, netlink, ioctl). Any potential bugs introduced by these changes, such as use-after-free, deadlocks, or out-of-bounds accesses, would be effectively caught by standard KASAN and LOCKDEP. There is no risk of uninitialized memory usage or info-leaks that would necessitate a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false,
"Reasoning": "The patch series modifies the device link API to support driverless class devices, allowing them to participate in the managed-link state machine as supplier and consumer endpoints. It modifies core driver model functions like `device_add`, `device_del`, `device_links_driver_bound`, and `device_links_driver_cleanup`. It also adds a new exported function `fw_devlink_copy_suppliers` and updates the OF MDIO subsystem to link PHY packages. Since this modifies reachable core kernel logic and driver subsystems, it has functional impact and is worth fuzzing.",
"WorthFuzzing": true
} |
| 1/1 | 2026/08/18 07:43 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 3e5bcfae81622146a78f94161df2c8f0e249bd1a\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Tue Aug 18 07:43:55 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/Documentation/driver-api/device_link.rst b/Documentation/driver-api/device_link.rst\nindex ee913ae163711..c248ec9fa9f87 100644\n--- a/Documentation/driver-api/device_link.rst\n+++ b/Documentation/driver-api/device_link.rst\n@@ -253,6 +253,18 @@ child one.\n State machine\n =============\n \n+Driverless class devices participate in the managed-link state machine as\n+supplier and consumer endpoints, including as consumers of sync-state-only\n+proxy links. Their registration is treated like a successful driver probe:\n+links are completed after :c:func:`device_add()` has finished running class\n+interfaces. During removal, driver-backed consumers are unbound before the\n+class device is deleted and its links return to the no-driver state.\n+\n+Class devices are not probed, so an ordinary supplier link cannot defer their\n+registration in the way that it defers a driver probe. Frameworks must not use\n+such a link as the only mechanism guaranteeing that a class consumer's\n+supplier is functional.\n+\n .. kernel-doc:: include/linux/device.h\n :functions: device_link_state\n \n@@ -271,10 +283,13 @@ State machine\n and consumer. If the link is created before any devices are probed, it\n is set to ``DL_STATE_DORMANT``.\n \n-* When a supplier device is bound to a driver, links to its consumers\n- progress to ``DL_STATE_AVAILABLE``.\n- (Call to :c:func:`device_links_driver_bound()` from\n- :c:func:`driver_bound()`.)\n+* When a supplier device is bound to a driver, links to its consumers progress\n+ to ``DL_STATE_AVAILABLE``. Registering a driverless class supplier performs\n+ the equivalent transition, or activates a late link whose consumer is\n+ already functional.\n+ The driver-backed path calls :c:func:`device_links_driver_bound()` from\n+ :c:func:`driver_bound()`, while the class-device completion path performs\n+ the equivalent transition from :c:func:`device_add()`.\n \n * Before a consumer device is probed, presence of supplier drivers is\n verified by checking the consumer device is not in the wait_for_suppliers\n@@ -286,17 +301,28 @@ State machine\n (Call to :c:func:`wait_for_device_probe()` from\n :c:func:`device_links_unbind_consumers()`.)\n \n+ Registering a driverless class consumer similarly moves existing available\n+ links to ``DL_STATE_CONSUMER_PROBE`` before publishing the device, and its\n+ registration participates in :c:func:`wait_for_device_probe()`. Unlike a\n+ driver probe, class registration is not deferred for unavailable suppliers.\n+\n * If the probe fails, links to suppliers revert back to ``DL_STATE_AVAILABLE``.\n (Call to :c:func:`device_links_no_driver()` from :c:func:`really_probe()`.)\n \n * If the probe succeeds, links to suppliers progress to ``DL_STATE_ACTIVE``.\n- (Call to :c:func:`device_links_driver_bound()` from :c:func:`driver_bound()`.)\n+ A driverless class consumer activates usable incoming links when its\n+ registration completes, drops sync-state-only proxy links and preserves\n+ links whose suppliers are dormant or unbinding. The driver-backed path\n+ calls :c:func:`device_links_driver_bound()` from :c:func:`driver_bound()`,\n+ while :c:func:`device_add()` performs the equivalent class-device\n+ transition.\n \n * When the consumer's driver is later on removed, links to suppliers revert\n back to ``DL_STATE_AVAILABLE``.\n (Call to :c:func:`__device_links_no_driver()` from\n :c:func:`device_links_driver_cleanup()`, which in turn is called from\n- :c:func:`__device_release_driver()`.)\n+ :c:func:`__device_release_driver()`.) Removing a driverless class consumer\n+ performs the equivalent cleanup from :c:func:`device_del()`.\n \n * Before a supplier's driver is removed, links to consumers that are not\n bound to a driver are updated to ``DL_STATE_SUPPLIER_UNBIND``.\n@@ -305,14 +331,17 @@ State machine\n This prevents the consumers from binding.\n (Call to :c:func:`device_links_check_suppliers()` from\n :c:func:`really_probe()`.)\n- Consumers that are bound are freed from their driver; consumers that are\n- probing are waited for until they are done.\n+ Driver-backed consumers that are bound are freed from their driver;\n+ driverless class consumers remain registered and their links move directly\n+ to ``DL_STATE_SUPPLIER_UNBIND``. Consumers that are probing or being\n+ registered as class devices are waited for until they are done.\n (Call to :c:func:`device_links_unbind_consumers()` from\n :c:func:`__device_release_driver()`.)\n Once all links to consumers are in ``DL_STATE_SUPPLIER_UNBIND`` state,\n the supplier driver is released and the links revert to ``DL_STATE_DORMANT``.\n (Call to :c:func:`device_links_driver_cleanup()` from\n- :c:func:`__device_release_driver()`.)\n+ :c:func:`__device_release_driver()`.) Removing a driverless class supplier\n+ follows the same sequence from :c:func:`device_del()`.\n \n API\n ===\ndiff --git a/drivers/base/base.h b/drivers/base/base.h\nindex a5b7abc10ff02..8bdf0a7094015 100644\n--- a/drivers/base/base.h\n+++ b/drivers/base/base.h\n@@ -166,6 +166,8 @@ void device_release_driver_internal(struct device *dev, const struct device_driv\n void driver_detach(const struct device_driver *drv);\n void driver_deferred_probe_del(struct device *dev);\n void device_set_deferred_probe_reason(const struct device *dev, struct va_format *vaf);\n+void device_probe_begin(void);\n+void device_probe_end(void);\n static inline int driver_match_device(const struct device_driver *drv,\n \t\t\t\t struct device *dev)\n {\ndiff --git a/drivers/base/core.c b/drivers/base/core.c\nindex 4d026682944f2..0c26ba38e6cfc 100644\n--- a/drivers/base/core.c\n+++ b/drivers/base/core.c\n@@ -213,10 +213,10 @@ static void __fwnode_links_move_consumers(struct fwnode_handle *from,\n * @fwnode: fwnode from which to pick up dangling consumers\n * @new_sup: fwnode of new supplier\n *\n- * If the @fwnode has a corresponding struct device and the device supports\n- * probing (that is, added to a bus), then we want to let fw_devlink create\n- * MANAGED device links to this device, so leave @fwnode and its descendant's\n- * fwnode links alone.\n+ * If the @fwnode has a corresponding struct device whose device-link state is\n+ * managed by the driver core (that is, a bus or class device), then we want to\n+ * let fw_devlink create MANAGED device links to this device, so leave @fwnode\n+ * and its descendants' fwnode links alone.\n *\n * Otherwise, move its consumers to the new supplier @new_sup.\n */\n@@ -225,7 +225,7 @@ static void __fw_devlink_pickup_dangling_consumers(struct fwnode_handle *fwnode,\n {\n \tstruct fwnode_handle *child;\n \n-\tif (fwnode-\u003edev \u0026\u0026 fwnode-\u003edev-\u003ebus)\n+\tif (fwnode-\u003edev \u0026\u0026 (fwnode-\u003edev-\u003ebus || fwnode-\u003edev-\u003eclass))\n \t\treturn;\n \n \tfwnode_set_flag(fwnode, FWNODE_FLAG_NOT_DEVICE);\n@@ -259,7 +259,7 @@ void fw_devlink_refresh_fwnode(struct fwnode_handle *fwnode)\n \n \t/*\n \t * Find the closest ancestor fwnode that has been converted to a device\n-\t * that can bind to a driver (bus device).\n+\t * whose managed-link lifecycle is handled by the driver core.\n \t */\n \tfwnode_handle_get(fwnode);\n \tdo {\n@@ -270,15 +270,15 @@ void fw_devlink_refresh_fwnode(struct fwnode_handle *fwnode)\n \t\tif (!dev)\n \t\t\tcontinue;\n \n-\t\tif (dev-\u003ebus)\n+\t\tif (dev-\u003ebus || dev-\u003eclass)\n \t\t\tbreak;\n \n \t\tput_device(dev);\n \t} while ((fwnode = fwnode_get_next_parent(fwnode)));\n \n \t/*\n-\t * If none of the ancestor fwnodes have (yet) been converted to a device\n-\t * that can bind to a driver, there's nothing to fix up.\n+\t * If none of the ancestor fwnodes have (yet) been converted to such a\n+\t * device, there's nothing to fix up.\n \t */\n \tif (!fwnode)\n \t\treturn;\n@@ -287,10 +287,9 @@ void fw_devlink_refresh_fwnode(struct fwnode_handle *fwnode)\n \t \"Don't multithread overlaying and probing the same device!\\n\");\n \n \t/*\n-\t * If the device has already bound to a driver, then we need to redo\n-\t * some of the work that was done after the device was bound to a\n-\t * driver. If the device hasn't bound to a driver, running things too\n-\t * soon would incorrectly pick up consumers that it shouldn't.\n+\t * If the device's managed links are available, redo some of the work\n+\t * performed when it reached that state. Running this too soon would\n+\t * incorrectly pick up consumers that it shouldn't.\n \t */\n \tif (dev-\u003elinks.status == DL_DEV_DRIVER_BOUND) {\n \t\tfw_devlink_pickup_dangling_consumers(dev);\n@@ -1088,6 +1087,12 @@ static bool dev_is_best_effort(struct device *dev)\n \t\t(dev-\u003efwnode \u0026\u0026 fwnode_test_flag(dev-\u003efwnode, FWNODE_FLAG_BEST_EFFORT));\n }\n \n+static bool dev_is_unmatchable(const struct device *dev)\n+{\n+\treturn READ_ONCE(dev-\u003elinks.status) == DL_DEV_NO_DRIVER \u0026\u0026\n+\t !dev_can_match(dev);\n+}\n+\n static struct fwnode_handle *fwnode_links_check_suppliers(\n \t\t\t\t\t\tstruct fwnode_handle *fwnode)\n {\n@@ -1152,7 +1157,7 @@ int device_links_check_suppliers(struct device *dev)\n \n \t\t\tif (dev_is_best_effort(dev) \u0026\u0026\n \t\t\t device_link_test(link, DL_FLAG_INFERRED) \u0026\u0026\n-\t\t\t !dev_can_match(link-\u003esupplier)) {\n+\t\t\t dev_is_unmatchable(link-\u003esupplier)) {\n \t\t\t\tret = -EAGAIN;\n \t\t\t\tcontinue;\n \t\t\t}\n@@ -1256,6 +1261,7 @@ void device_links_supplier_sync_state_pause(void)\n \tdefer_sync_state_count++;\n \tdevice_links_write_unlock();\n }\n+EXPORT_SYMBOL_GPL(device_links_supplier_sync_state_pause);\n \n void device_links_supplier_sync_state_resume(void)\n {\n@@ -1284,6 +1290,7 @@ void device_links_supplier_sync_state_resume(void)\n \n \tdevice_links_flush_sync_list(\u0026sync_list, NULL);\n }\n+EXPORT_SYMBOL_GPL(device_links_supplier_sync_state_resume);\n \n static int sync_state_resume_initcall(void)\n {\n@@ -1305,6 +1312,33 @@ static void device_link_drop_managed(struct device_link *link)\n \tkref_put(\u0026link-\u003ekref, __device_link_del);\n }\n \n+static bool device_links_track_class(const struct device *dev)\n+{\n+\t/* Device-link objects are class devices registered with the lock held. */\n+\treturn dev-\u003eclass \u0026\u0026 !dev-\u003ebus \u0026\u0026 dev-\u003eclass != \u0026devlink_class;\n+}\n+\n+static void device_links_class_start(struct device *dev)\n+{\n+\tstruct device_link *link;\n+\n+\tdevice_probe_begin();\n+\tdevice_links_write_lock();\n+\tWARN_ON(dev-\u003elinks.status != DL_DEV_NO_DRIVER);\n+\tlist_for_each_entry(link, \u0026dev-\u003elinks.suppliers, c_node) {\n+\t\tif (!device_link_test(link, DL_FLAG_MANAGED))\n+\t\t\tcontinue;\n+\n+\t\t/* Do not revive a link whose supplier is already unbinding. */\n+\t\tif (link-\u003estatus == DL_STATE_AVAILABLE ||\n+\t\t (device_link_test(link, DL_FLAG_SYNC_STATE_ONLY) \u0026\u0026\n+\t\t link-\u003estatus != DL_STATE_SUPPLIER_UNBIND))\n+\t\t\tWRITE_ONCE(link-\u003estatus, DL_STATE_CONSUMER_PROBE);\n+\t}\n+\tdev-\u003elinks.status = DL_DEV_PROBING;\n+\tdevice_links_write_unlock();\n+}\n+\n static ssize_t waiting_for_supplier_show(struct device *dev,\n \t\t\t\t\t const struct device_attribute *attr,\n \t\t\t\t\t char *buf)\n@@ -1354,43 +1388,11 @@ void device_links_force_bind(struct device *dev)\n \tdevice_links_write_unlock();\n }\n \n-/**\n- * device_links_driver_bound - Update device links after probing its driver.\n- * @dev: Device to update the links for.\n- *\n- * The probe has been successful, so update links from this device to any\n- * consumers by changing their status to \"available\".\n- *\n- * Also change the status of @dev's links to suppliers to \"active\".\n- *\n- * Links without the DL_FLAG_MANAGED flag set are ignored.\n- */\n-void device_links_driver_bound(struct device *dev)\n+static void __device_links_supplier_bound(struct device *dev,\n+\t\t\t\t\t struct list_head *sync_list)\n {\n-\tstruct device_link *link, *ln;\n-\tLIST_HEAD(sync_list);\n-\n-\t/*\n-\t * If a device binds successfully, it's expected to have created all\n-\t * the device links it needs to or make new device links as it needs\n-\t * them. So, fw_devlink no longer needs to create device links to any\n-\t * of the device's suppliers.\n-\t *\n-\t * Also, if a child firmware node of this bound device is not added as a\n-\t * device by now, assume it is never going to be added. Make this bound\n-\t * device the fallback supplier to the dangling consumers of the child\n-\t * firmware node because this bound device is probably implementing the\n-\t * child firmware node functionality and we don't want the dangling\n-\t * consumers to defer probe indefinitely waiting for a device for the\n-\t * child firmware node.\n-\t */\n-\tif (dev-\u003efwnode \u0026\u0026 dev-\u003efwnode-\u003edev == dev) {\n-\t\tfwnode_links_purge_suppliers(dev-\u003efwnode);\n-\t\tfw_devlink_pickup_dangling_consumers(dev);\n-\t}\n-\tdevice_remove_file(dev, \u0026dev_attr_waiting_for_supplier);\n-\n-\tdevice_links_write_lock();\n+\tstruct device_link *link;\n+\tbool class_device = device_links_track_class(dev);\n \n \tlist_for_each_entry(link, \u0026dev-\u003elinks.consumers, s_node) {\n \t\tif (!device_link_test(link, DL_FLAG_MANAGED))\n@@ -1407,16 +1409,35 @@ void device_links_driver_bound(struct device *dev)\n \t\t\tcontinue;\n \n \t\tWARN_ON(link-\u003estatus != DL_STATE_DORMANT);\n-\t\tWRITE_ONCE(link-\u003estatus, DL_STATE_AVAILABLE);\n+\t\t/*\n+\t\t * A class interface can add a link from an already-bound consumer\n+\t\t * while the class supplier is being registered. Likewise, a class\n+\t\t * consumer has no driver probe to retry when a late supplier binds.\n+\t\t * Those consumers are already functional, so activate their links.\n+\t\t */\n+\t\tif ((class_device ||\n+\t\t device_links_track_class(link-\u003econsumer)) \u0026\u0026\n+\t\t link-\u003econsumer-\u003elinks.status == DL_DEV_DRIVER_BOUND) {\n+\t\t\tWRITE_ONCE(link-\u003estatus, DL_STATE_ACTIVE);\n+\t\t} else {\n+\t\t\tWRITE_ONCE(link-\u003estatus, DL_STATE_AVAILABLE);\n \n-\t\tif (device_link_test(link, DL_FLAG_AUTOPROBE_CONSUMER))\n-\t\t\tdriver_deferred_probe_add(link-\u003econsumer);\n+\t\t\tif (device_link_test(link, DL_FLAG_AUTOPROBE_CONSUMER))\n+\t\t\t\tdriver_deferred_probe_add(link-\u003econsumer);\n+\t\t}\n \t}\n \n \tif (defer_sync_state_count)\n \t\t__device_links_supplier_defer_sync(dev);\n \telse\n-\t\t__device_links_queue_sync_state(dev, \u0026sync_list);\n+\t\t__device_links_queue_sync_state(dev, sync_list);\n+}\n+\n+static void __device_links_consumer_bound(struct device *dev,\n+\t\t\t\t\t struct list_head *sync_list)\n+{\n+\tstruct device_link *link, *ln;\n+\tbool class_device = device_links_track_class(dev);\n \n \tlist_for_each_entry_safe(link, ln, \u0026dev-\u003elinks.suppliers, c_node) {\n \t\tstruct device *supplier;\n@@ -1429,13 +1450,13 @@ void device_links_driver_bound(struct device *dev)\n \t\t\t/*\n \t\t\t * When DL_FLAG_SYNC_STATE_ONLY is set, it means no\n \t\t\t * other DL_MANAGED_LINK_FLAGS have been set. So, it's\n-\t\t\t * save to drop the managed link completely.\n+\t\t\t * safe to drop the managed link completely.\n \t\t\t */\n \t\t\tdevice_link_drop_managed(link);\n-\t\t} else if (dev_is_best_effort(dev) \u0026\u0026\n+\t\t} else if (!class_device \u0026\u0026 dev_is_best_effort(dev) \u0026\u0026\n \t\t\t device_link_test(link, DL_FLAG_INFERRED) \u0026\u0026\n \t\t\t link-\u003estatus != DL_STATE_CONSUMER_PROBE \u0026\u0026\n-\t\t\t !dev_can_match(link-\u003esupplier)) {\n+\t\t\t dev_is_unmatchable(link-\u003esupplier)) {\n \t\t\t/*\n \t\t\t * When dev_is_best_effort() is true, we ignore device\n \t\t\t * links to suppliers that don't have a driver. If the\n@@ -1444,9 +1465,17 @@ void device_links_driver_bound(struct device *dev)\n \t\t\t * (consumer probed before supplier). So delete it.\n \t\t\t */\n \t\t\tdevice_link_drop_managed(link);\n-\t\t} else {\n+\t\t} else if (!class_device) {\n \t\t\tWARN_ON(link-\u003estatus != DL_STATE_CONSUMER_PROBE);\n \t\t\tWRITE_ONCE(link-\u003estatus, DL_STATE_ACTIVE);\n+\t\t} else if (link-\u003estatus == DL_STATE_CONSUMER_PROBE ||\n+\t\t\t link-\u003estatus == DL_STATE_AVAILABLE) {\n+\t\t\tWRITE_ONCE(link-\u003estatus, DL_STATE_ACTIVE);\n+\t\t} else {\n+\t\t\tWARN_ON(link-\u003estatus != DL_STATE_ACTIVE \u0026\u0026\n+\t\t\t\tlink-\u003estatus != DL_STATE_DORMANT \u0026\u0026\n+\t\t\t\tlink-\u003estatus != DL_STATE_SUPPLIER_UNBIND);\n+\t\t\tcontinue;\n \t\t}\n \n \t\t/*\n@@ -1458,10 +1487,67 @@ void device_links_driver_bound(struct device *dev)\n \t\tif (defer_sync_state_count)\n \t\t\t__device_links_supplier_defer_sync(supplier);\n \t\telse\n-\t\t\t__device_links_queue_sync_state(supplier, \u0026sync_list);\n+\t\t\t__device_links_queue_sync_state(supplier, sync_list);\n \t}\n \n \tdev-\u003elinks.status = DL_DEV_DRIVER_BOUND;\n+}\n+\n+static void device_links_class_bound(struct device *dev)\n+{\n+\tLIST_HEAD(sync_list);\n+\n+\tdevice_remove_file(dev, \u0026dev_attr_waiting_for_supplier);\n+\n+\tdevice_links_write_lock();\n+\t__device_links_supplier_bound(dev, \u0026sync_list);\n+\t__device_links_consumer_bound(dev, \u0026sync_list);\n+\tdevice_links_write_unlock();\n+\n+\tdevice_links_flush_sync_list(\u0026sync_list, NULL);\n+\tdriver_deferred_probe_trigger();\n+\tdevice_probe_end();\n+}\n+\n+/**\n+ * device_links_driver_bound - Update device links after probing its driver.\n+ * @dev: Device to update the links for.\n+ *\n+ * The probe has been successful, so update links from this device to any\n+ * consumers by changing their status to \"available\".\n+ *\n+ * Also change the status of @dev's links to suppliers to \"active\".\n+ *\n+ * Links without the DL_FLAG_MANAGED flag set are ignored.\n+ */\n+void device_links_driver_bound(struct device *dev)\n+{\n+\tLIST_HEAD(sync_list);\n+\n+\t/*\n+\t * If a device binds successfully, it's expected to have created all\n+\t * the device links it needs to or make new device links as it needs\n+\t * them. So, fw_devlink no longer needs to create device links to any\n+\t * of the device's suppliers.\n+\t *\n+\t * Also, if a child firmware node of this bound device is not added as a\n+\t * device by now, assume it is never going to be added. Make this bound\n+\t * device the fallback supplier to the dangling consumers of the child\n+\t * firmware node because this bound device is probably implementing the\n+\t * child firmware node functionality and we don't want the dangling\n+\t * consumers to defer probe indefinitely waiting for a device for the\n+\t * child firmware node.\n+\t */\n+\tif (dev-\u003efwnode \u0026\u0026 dev-\u003efwnode-\u003edev == dev) {\n+\t\tfwnode_links_purge_suppliers(dev-\u003efwnode);\n+\t\tfw_devlink_pickup_dangling_consumers(dev);\n+\t}\n+\n+\tdevice_remove_file(dev, \u0026dev_attr_waiting_for_supplier);\n+\n+\tdevice_links_write_lock();\n+\t__device_links_supplier_bound(dev, \u0026sync_list);\n+\t__device_links_consumer_bound(dev, \u0026sync_list);\n \n \tdevice_links_write_unlock();\n \n@@ -1546,17 +1632,7 @@ void device_links_no_driver(struct device *dev)\n \tdevice_links_write_unlock();\n }\n \n-/**\n- * device_links_driver_cleanup - Update links after driver removal.\n- * @dev: Device whose driver has just gone away.\n- *\n- * Update links to consumers for @dev by changing their status to \"dormant\" and\n- * invoke %__device_links_no_driver() to update links to suppliers for it as\n- * appropriate.\n- *\n- * Links without the DL_FLAG_MANAGED flag set are ignored.\n- */\n-void device_links_driver_cleanup(struct device *dev)\n+static void device_links_cleanup(struct device *dev)\n {\n \tstruct device_link *link, *ln;\n \n@@ -1566,7 +1642,8 @@ void device_links_driver_cleanup(struct device *dev)\n \t\tif (!device_link_test(link, DL_FLAG_MANAGED))\n \t\t\tcontinue;\n \n-\t\tWARN_ON(device_link_test(link, DL_FLAG_AUTOREMOVE_CONSUMER));\n+\t\tWARN_ON(device_link_test(link, DL_FLAG_AUTOREMOVE_CONSUMER) \u0026\u0026\n+\t\t\t!device_links_track_class(link-\u003econsumer));\n \t\tWARN_ON(link-\u003estatus != DL_STATE_SUPPLIER_UNBIND);\n \n \t\t/*\n@@ -1587,6 +1664,21 @@ void device_links_driver_cleanup(struct device *dev)\n \tdevice_links_write_unlock();\n }\n \n+/**\n+ * device_links_driver_cleanup - Update links after driver removal.\n+ * @dev: Device whose driver has just gone away.\n+ *\n+ * Update links to consumers for @dev by changing their status to \"dormant\" and\n+ * invoke %__device_links_no_driver() to update links to suppliers for it as\n+ * appropriate.\n+ *\n+ * Links without the DL_FLAG_MANAGED flag set are ignored.\n+ */\n+void device_links_driver_cleanup(struct device *dev)\n+{\n+\tdevice_links_cleanup(dev);\n+}\n+\n /**\n * device_links_busy - Check if there are any busy links to consumers.\n * @dev: Device to check.\n@@ -1597,7 +1689,9 @@ void device_links_driver_cleanup(struct device *dev)\n * state to \"supplier unbind\" to prevent the consumer from being probed\n * successfully going forward.\n *\n- * Return 'false' if there are no probing or active consumers.\n+ * Sync-state-only links are moved to \"supplier unbind\" without making the\n+ * supplier busy, since they do not require the consumer to be unbound.\n+ * Return 'false' if there are no other probing or active consumers.\n *\n * Links without the DL_FLAG_MANAGED flag set are ignored.\n */\n@@ -1611,6 +1705,11 @@ bool device_links_busy(struct device *dev)\n \tlist_for_each_entry(link, \u0026dev-\u003elinks.consumers, s_node) {\n \t\tif (!device_link_test(link, DL_FLAG_MANAGED))\n \t\t\tcontinue;\n+\t\t/* Sync-state-only links do not require consumer unbinding. */\n+\t\tif (device_link_test(link, DL_FLAG_SYNC_STATE_ONLY)) {\n+\t\t\tWRITE_ONCE(link-\u003estatus, DL_STATE_SUPPLIER_UNBIND);\n+\t\t\tcontinue;\n+\t\t}\n \n \t\tif (link-\u003estatus == DL_STATE_CONSUMER_PROBE\n \t\t || link-\u003estatus == DL_STATE_ACTIVE) {\n@@ -1666,6 +1765,10 @@ void device_links_unbind_consumers(struct device *dev)\n \t\tif (status == DL_STATE_ACTIVE) {\n \t\t\tstruct device *consumer = link-\u003econsumer;\n \n+\t\t\t/* Class consumers have no driver to release. */\n+\t\t\tif (device_links_track_class(consumer))\n+\t\t\t\tcontinue;\n+\n \t\t\tget_device(consumer);\n \n \t\t\tdevice_links_write_unlock();\n@@ -1824,7 +1927,7 @@ static int fw_devlink_no_driver(struct device *dev, void *data)\n {\n \tstruct device_link *link = to_devlink(dev);\n \n-\tif (!dev_can_match(link-\u003esupplier))\n+\tif (dev_is_unmatchable(link-\u003esupplier))\n \t\tfw_devlink_relax_link(link);\n \n \treturn 0;\n@@ -2394,6 +2497,66 @@ static void fw_devlink_link_device(struct device *dev)\n \t__fw_devlink_link_to_suppliers(dev, fwnode);\n }\n \n+/**\n+ * fw_devlink_copy_suppliers - Copy supplier links between firmware nodes\n+ * @to: Firmware node which consumes the copied suppliers\n+ * @from: Firmware node whose supplier links should be copied\n+ *\n+ * Frameworks can use this when a container firmware node describes resources\n+ * shared by devices created for its children. The source links remain in\n+ * place, while links are added for the real consumer firmware node. Call this\n+ * before device_add() associates @to with a registered device, so the normal\n+ * fw_devlink path can convert the copied dependencies into device links.\n+ *\n+ * Ignored source links are skipped. Cycle flags are topology-specific and are\n+ * recalculated for the new consumer when its links are converted.\n+ *\n+ * Return: 0 on success, -EINVAL for invalid firmware nodes, -EBUSY if @to is\n+ * already associated with a device, or -ENOMEM if a link could not be\n+ * allocated.\n+ */\n+int fw_devlink_copy_suppliers(struct fwnode_handle *to,\n+\t\t\t struct fwnode_handle *from)\n+{\n+\tstruct list_head *first;\n+\tstruct fwnode_link *link;\n+\tint ret;\n+\n+\tif (!to || !from)\n+\t\treturn -EINVAL;\n+\tif (!fw_devlink_flags || to == from)\n+\t\treturn 0;\n+\tfw_devlink_parse_fwnode(from);\n+\n+\tguard(mutex)(\u0026fwnode_link_lock);\n+\tif (READ_ONCE(to-\u003edev))\n+\t\treturn -EBUSY;\n+\n+\tfirst = to-\u003esuppliers.next;\n+\tlist_for_each_entry(link, \u0026from-\u003esuppliers, c_hook) {\n+\t\tu8 flags = link-\u003eflags \u0026 ~FWLINK_FLAG_CYCLE;\n+\n+\t\tif (flags \u0026 FWLINK_FLAG_IGNORE)\n+\t\t\tcontinue;\n+\n+\t\tret = __fwnode_link_add(to, link-\u003esupplier, flags);\n+\t\tif (ret)\n+\t\t\tgoto rollback;\n+\t}\n+\n+\treturn 0;\n+\n+rollback:\n+\twhile (to-\u003esuppliers.next != first) {\n+\t\tlink = list_first_entry(\u0026to-\u003esuppliers, struct fwnode_link,\n+\t\t\t\t\tc_hook);\n+\t\t__fwnode_link_del(link);\n+\t}\n+\n+\treturn ret;\n+}\n+EXPORT_SYMBOL_GPL(fw_devlink_copy_suppliers);\n+\n /* Device links support end. */\n \n static struct kobject *dev_kobj;\n@@ -3642,6 +3805,7 @@ int device_add(struct device *dev)\n \tstruct device *parent;\n \tstruct kobject *kobj;\n \tstruct class_interface *class_intf;\n+\tbool track_class_links = false;\n \tint error = -EINVAL;\n \tstruct kobject *glue_dir = NULL;\n \n@@ -3675,6 +3839,10 @@ int device_add(struct device *dev)\n \tif (error)\n \t\tgoto name_error;\n \n+\ttrack_class_links = device_links_track_class(dev);\n+\tif (track_class_links)\n+\t\tdevice_links_class_start(dev);\n+\n \tpr_debug(\"device: '%s': %s\\n\", dev_name(dev), __func__);\n \n \tparent = get_device(dev-\u003eparent);\n@@ -3776,7 +3944,7 @@ int device_add(struct device *dev)\n \t * match with any driver, don't block its consumers from probing in\n \t * case the consumer device is able to operate without this supplier.\n \t */\n-\tif (dev-\u003efwnode \u0026\u0026 fw_devlink_drv_reg_done \u0026\u0026 !dev_can_match(dev))\n+\tif (dev-\u003efwnode \u0026\u0026 fw_devlink_drv_reg_done \u0026\u0026 dev_is_unmatchable(dev))\n \t\tfw_devlink_unblock_consumers(dev);\n \n \tif (parent)\n@@ -3796,6 +3964,8 @@ int device_add(struct device *dev)\n \t\tmutex_unlock(\u0026sp-\u003emutex);\n \t\tsubsys_put(sp);\n \t}\n+\tif (track_class_links)\n+\t\tdevice_links_class_bound(dev);\n done:\n \tput_device(dev);\n \treturn error;\n@@ -3824,6 +3994,10 @@ int device_add(struct device *dev)\n parent_error:\n \tput_device(parent);\n name_error:\n+\tif (track_class_links) {\n+\t\tdevice_links_no_driver(dev);\n+\t\tdevice_probe_end();\n+\t}\n \tkfree(dev-\u003ep);\n \tdev-\u003ep = NULL;\n \tgoto done;\n@@ -3918,9 +4092,17 @@ void device_del(struct device *dev)\n \tstruct device *parent = dev-\u003eparent;\n \tstruct kobject *glue_dir = NULL;\n \tstruct class_interface *class_intf;\n+\tbool track_class_links = device_links_track_class(dev);\n \tunsigned int noio_flag;\n \n \tdevice_lock(dev);\n+\tif (track_class_links) {\n+\t\twhile (device_links_busy(dev)) {\n+\t\t\tdevice_unlock(dev);\n+\t\t\tdevice_links_unbind_consumers(dev);\n+\t\t\tdevice_lock(dev);\n+\t\t}\n+\t}\n \tkill_device(dev);\n \tdevice_unlock(dev);\n \n@@ -3962,6 +4144,8 @@ void device_del(struct device *dev)\n \tdevice_pm_remove(dev);\n \tdriver_deferred_probe_del(dev);\n \tdevice_platform_notify_remove(dev);\n+\tif (track_class_links)\n+\t\tdevice_links_cleanup(dev);\n \tdevice_links_purge(dev);\n \n \t/*\ndiff --git a/drivers/base/dd.c b/drivers/base/dd.c\nindex 60c005223844d..61288fcceb5b9 100644\n--- a/drivers/base/dd.c\n+++ b/drivers/base/dd.c\n@@ -555,9 +555,21 @@ int device_bind_driver(struct device *dev)\n }\n EXPORT_SYMBOL_GPL(device_bind_driver);\n \n+/* Driver probes and driverless class-device registrations in progress. */\n static atomic_t probe_count = ATOMIC_INIT(0);\n static DECLARE_WAIT_QUEUE_HEAD(probe_waitqueue);\n \n+void device_probe_begin(void)\n+{\n+\tatomic_inc(\u0026probe_count);\n+}\n+\n+void device_probe_end(void)\n+{\n+\tatomic_dec(\u0026probe_count);\n+\twake_up_all(\u0026probe_waitqueue);\n+}\n+\n static ssize_t state_synced_store(struct device *dev,\n \t\t\t\t struct device_attribute *attr,\n \t\t\t\t const char *buf, size_t count)\n@@ -797,7 +809,7 @@ static int really_probe_debug(struct device *dev, const struct device_driver *dr\n \n /**\n * driver_probe_done\n- * Determine if the probe sequence is finished or not.\n+ * Determine if the probe and class-device registration sequence is finished.\n *\n * Should somehow figure out how to use a semaphore, not an atomic variable...\n */\n@@ -811,7 +823,7 @@ bool __init driver_probe_done(void)\n \n /**\n * wait_for_device_probe\n- * Wait for device probing to be completed.\n+ * Wait for device probing and driverless class registration to be completed.\n */\n void wait_for_device_probe(void)\n {\n@@ -894,7 +906,7 @@ static int driver_probe_device(const struct device_driver *drv, struct device *d\n \tint trigger_count = atomic_read(\u0026deferred_trigger_count);\n \tint ret;\n \n-\tatomic_inc(\u0026probe_count);\n+\tdevice_probe_begin();\n \tret = __driver_probe_device(drv, dev);\n \tif (ret == -EPROBE_DEFER || ret == EPROBE_DEFER) {\n \t\tdriver_deferred_probe_add(dev);\n@@ -906,8 +918,7 @@ static int driver_probe_device(const struct device_driver *drv, struct device *d\n \t\t !defer_all_probes)\n \t\t\tdriver_deferred_probe_trigger();\n \t}\n-\tatomic_dec(\u0026probe_count);\n-\twake_up_all(\u0026probe_waitqueue);\n+\tdevice_probe_end();\n \treturn ret;\n }\n \ndiff --git a/drivers/base/test/Makefile b/drivers/base/test/Makefile\nindex e321dfc7e9226..c33c615eaabf0 100644\n--- a/drivers/base/test/Makefile\n+++ b/drivers/base/test/Makefile\n@@ -3,6 +3,8 @@ obj-$(CONFIG_TEST_ASYNC_DRIVER_PROBE)\t+= test_async_driver_probe.o\n \n obj-$(CONFIG_DM_KUNIT_TEST)\t+= root-device-test.o\n obj-$(CONFIG_DM_KUNIT_TEST)\t+= platform-device-test.o\n+obj-$(CONFIG_DM_KUNIT_TEST)\t+= device-link-class-test.o\n+obj-$(CONFIG_DM_KUNIT_TEST)\t+= fwnode-link-test.o\n \n obj-$(CONFIG_DRIVER_PE_KUNIT_TEST) += property-entry-test.o\n CFLAGS_property-entry-test.o += $(DISABLE_STRUCTLEAK_PLUGIN)\ndiff --git a/drivers/base/test/device-link-class-test.c b/drivers/base/test/device-link-class-test.c\nnew file mode 100644\nindex 0000000000000..7d52efb55aa5f\n--- /dev/null\n+++ b/drivers/base/test/device-link-class-test.c\n@@ -0,0 +1,479 @@\n+// SPDX-License-Identifier: GPL-2.0\n+\n+#include \u003ckunit/platform_device.h\u003e\n+#include \u003ckunit/resource.h\u003e\n+#include \u003ckunit/test.h\u003e\n+\n+#include \u003clinux/completion.h\u003e\n+#include \u003clinux/device.h\u003e\n+#include \u003clinux/device/driver.h\u003e\n+#include \u003clinux/fwnode.h\u003e\n+#include \u003clinux/jiffies.h\u003e\n+#include \u003clinux/kernfs.h\u003e\n+#include \u003clinux/kthread.h\u003e\n+#include \u003clinux/platform_device.h\u003e\n+#include \u003clinux/sched/task.h\u003e\n+#include \u003clinux/slab.h\u003e\n+#include \u003clinux/sysfs.h\u003e\n+\n+#define TEST_CLASS_NAME\t\t\"device-link-test\"\n+#define TEST_DRIVER_NAME\t\"device-link-test-consumer\"\n+\n+enum test_class_action {\n+\tTEST_CLASS_NONE,\n+\tTEST_CLASS_SUPPLIER,\n+\tTEST_CLASS_SYNC_CONSUMER,\n+\tTEST_CLASS_OBSERVE,\n+\tTEST_CLASS_WAIT,\n+\tTEST_CLASS_UNBIND_RACE,\n+};\n+\n+struct device_link_class_context {\n+\tenum test_class_action action;\n+\tstruct device *endpoint;\n+\tstruct device_link *link;\n+\tstruct completion waiter_started;\n+\tstruct completion waiter_done;\n+\tstruct task_struct *waiter;\n+\tstruct completion unbind_started;\n+\tstruct completion unbind_done;\n+\tstruct task_struct *unbinder;\n+\tbool waiter_completed_during_add;\n+\tbool unbind_completed_during_add;\n+\tbool waiting_for_supplier_seen;\n+};\n+\n+struct test_class_device {\n+\tstruct device dev;\n+\tstruct fwnode_handle fwnode;\n+\tstruct fwnode_handle missing_supplier;\n+\tbool added;\n+};\n+\n+static struct class *test_class;\n+static struct device_link_class_context *test_context;\n+static atomic_t remove_count;\n+static atomic_t sync_count;\n+\n+static int test_driver_probe(struct platform_device *pdev)\n+{\n+\treturn 0;\n+}\n+\n+static void test_driver_remove(struct platform_device *pdev)\n+{\n+\tatomic_inc(\u0026remove_count);\n+}\n+\n+static void test_driver_sync_state(struct device *dev)\n+{\n+\tatomic_inc(\u0026sync_count);\n+}\n+\n+static struct platform_driver test_driver = {\n+\t.probe = test_driver_probe,\n+\t.remove = test_driver_remove,\n+\t.driver = {\n+\t\t.name = TEST_DRIVER_NAME,\n+\t\t.sync_state = test_driver_sync_state,\n+\t},\n+};\n+\n+static bool test_has_waiting_for_supplier(struct device *dev)\n+{\n+\tstruct kernfs_node *kn;\n+\n+\tkn = sysfs_get_dirent(dev-\u003ekobj.sd, \"waiting_for_supplier\");\n+\tif (kn)\n+\t\tkernfs_put(kn);\n+\n+\treturn !!kn;\n+}\n+\n+static int test_wait_for_probe(void *data)\n+{\n+\tstruct device_link_class_context *context = data;\n+\n+\tcomplete(\u0026context-\u003ewaiter_started);\n+\twait_for_device_probe();\n+\tcomplete(\u0026context-\u003ewaiter_done);\n+\n+\treturn 0;\n+}\n+\n+static int test_unbind_supplier(void *data)\n+{\n+\tstruct device_link_class_context *context = data;\n+\n+\tcomplete(\u0026context-\u003eunbind_started);\n+\tdevice_release_driver(context-\u003eendpoint);\n+\tcomplete(\u0026context-\u003eunbind_done);\n+\n+\treturn 0;\n+}\n+\n+static int test_class_add(struct device *dev)\n+{\n+\tstruct device_link_class_context *context = test_context;\n+\n+\tif (!context)\n+\t\treturn 0;\n+\n+\tswitch (context-\u003eaction) {\n+\tcase TEST_CLASS_SUPPLIER:\n+\t\tcontext-\u003elink = device_link_add(context-\u003eendpoint, dev,\n+\t\t\t\t\t\tDL_FLAG_INFERRED |\n+\t\t\t\t\t\tDL_FLAG_AUTOPROBE_CONSUMER);\n+\t\treturn context-\u003elink ? 0 : -ENOMEM;\n+\tcase TEST_CLASS_SYNC_CONSUMER:\n+\t\tcontext-\u003elink = device_link_add(dev, context-\u003eendpoint,\n+\t\t\t\t\t\tDL_FLAG_INFERRED |\n+\t\t\t\t\t\tDL_FLAG_SYNC_STATE_ONLY);\n+\t\treturn context-\u003elink ? 0 : -ENOMEM;\n+\tcase TEST_CLASS_OBSERVE:\n+\t\tcontext-\u003ewaiting_for_supplier_seen =\n+\t\t\ttest_has_waiting_for_supplier(dev);\n+\t\treturn 0;\n+\tcase TEST_CLASS_WAIT:\n+\t\tcontext-\u003ewaiter = kthread_create(test_wait_for_probe, context,\n+\t\t\t\t\t\t \"device-link-wait\");\n+\t\tif (IS_ERR(context-\u003ewaiter))\n+\t\t\treturn PTR_ERR(context-\u003ewaiter);\n+\t\tget_task_struct(context-\u003ewaiter);\n+\t\twake_up_process(context-\u003ewaiter);\n+\t\twait_for_completion(\u0026context-\u003ewaiter_started);\n+\t\tcontext-\u003ewaiter_completed_during_add =\n+\t\t\twait_for_completion_timeout(\u0026context-\u003ewaiter_done,\n+\t\t\t\t\t\t msecs_to_jiffies(50));\n+\t\treturn 0;\n+\tcase TEST_CLASS_UNBIND_RACE:\n+\t\tcontext-\u003elink = device_link_add(dev, context-\u003eendpoint, 0);\n+\t\tif (!context-\u003elink)\n+\t\t\treturn -ENOMEM;\n+\t\tcontext-\u003eunbinder = kthread_create(test_unbind_supplier, context,\n+\t\t\t\t\t\t \"device-link-unbind\");\n+\t\tif (IS_ERR(context-\u003eunbinder))\n+\t\t\treturn PTR_ERR(context-\u003eunbinder);\n+\t\tget_task_struct(context-\u003eunbinder);\n+\t\twake_up_process(context-\u003eunbinder);\n+\t\twait_for_completion(\u0026context-\u003eunbind_started);\n+\t\tcontext-\u003eunbind_completed_during_add =\n+\t\t\twait_for_completion_timeout(\u0026context-\u003eunbind_done,\n+\t\t\t\t\t\t msecs_to_jiffies(50));\n+\t\treturn 0;\n+\tcase TEST_CLASS_NONE:\n+\t\treturn 0;\n+\t}\n+\n+\treturn -EINVAL;\n+}\n+\n+static struct class_interface test_class_interface = {\n+\t.add_dev = test_class_add,\n+};\n+\n+static void test_unregister_device(void *data)\n+{\n+\tdevice_unregister(data);\n+}\n+\n+static void test_resume_sync_state(void *data)\n+{\n+\tdevice_links_supplier_sync_state_resume();\n+}\n+\n+static struct platform_device *\n+test_register_platform_device(struct kunit *test)\n+{\n+\tstruct platform_device_info info = {\n+\t\t.name = TEST_DRIVER_NAME,\n+\t\t.id = PLATFORM_DEVID_AUTO,\n+\t};\n+\tstruct platform_device *pdev;\n+\n+\tpdev = kunit_platform_device_register_full(test, \u0026info);\n+\tKUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev);\n+\tKUNIT_ASSERT_EQ(test, pdev-\u003edev.links.status, DL_DEV_DRIVER_BOUND);\n+\n+\treturn pdev;\n+}\n+\n+static void test_class_device_release(struct device *dev)\n+{\n+\tstruct test_class_device *tdev =\n+\t\tcontainer_of(dev, struct test_class_device, dev);\n+\n+\tfwnode_links_purge(\u0026tdev-\u003efwnode);\n+\tfwnode_links_purge(\u0026tdev-\u003emissing_supplier);\n+\tkfree(tdev);\n+}\n+\n+static void test_class_device_cleanup(void *data)\n+{\n+\tstruct test_class_device *tdev = data;\n+\n+\tif (tdev-\u003eadded)\n+\t\tdevice_unregister(\u0026tdev-\u003edev);\n+\telse\n+\t\tput_device(\u0026tdev-\u003edev);\n+}\n+\n+static struct test_class_device *\n+test_class_device_alloc(struct kunit *test, const char *name)\n+{\n+\tstruct test_class_device *tdev;\n+\tint ret;\n+\n+\ttdev = kzalloc_obj(*tdev);\n+\tKUNIT_ASSERT_NOT_NULL(test, tdev);\n+\n+\tdevice_initialize(\u0026tdev-\u003edev);\n+\tfwnode_init(\u0026tdev-\u003efwnode, NULL);\n+\tfwnode_init(\u0026tdev-\u003emissing_supplier, NULL);\n+\ttdev-\u003edev.class = test_class;\n+\ttdev-\u003edev.release = test_class_device_release;\n+\tdevice_set_node(\u0026tdev-\u003edev, \u0026tdev-\u003efwnode);\n+\tret = dev_set_name(\u0026tdev-\u003edev, \"%s\", name);\n+\tif (ret) {\n+\t\tput_device(\u0026tdev-\u003edev);\n+\t\tKUNIT_FAIL(test, \"failed to name class device: %d\", ret);\n+\t\treturn NULL;\n+\t}\n+\n+\tret = kunit_add_action_or_reset(test, test_class_device_cleanup, tdev);\n+\tKUNIT_ASSERT_EQ(test, ret, 0);\n+\n+\treturn tdev;\n+}\n+\n+static void device_link_class_supplier_test(struct kunit *test)\n+{\n+\tstruct device_link_class_context *context = test-\u003epriv;\n+\tstruct platform_device *consumer;\n+\tstruct device *supplier;\n+\tint ret;\n+\n+\tconsumer = test_register_platform_device(test);\n+\tcontext-\u003eaction = TEST_CLASS_SUPPLIER;\n+\tcontext-\u003eendpoint = \u0026consumer-\u003edev;\n+\n+\tsupplier = device_create(test_class, NULL, 0, NULL,\n+\t\t\t\t TEST_CLASS_NAME \"-supplier\");\n+\tKUNIT_ASSERT_NOT_ERR_OR_NULL(test, supplier);\n+\tret = kunit_add_action_or_reset(test, test_unregister_device, supplier);\n+\tKUNIT_ASSERT_EQ(test, ret, 0);\n+\n+\tKUNIT_ASSERT_NOT_NULL(test, context-\u003elink);\n+\tKUNIT_EXPECT_EQ(test, supplier-\u003elinks.status, DL_DEV_DRIVER_BOUND);\n+\tKUNIT_EXPECT_EQ(test, READ_ONCE(context-\u003elink-\u003estatus), DL_STATE_ACTIVE);\n+\tKUNIT_EXPECT_TRUE(test, device_link_test(context-\u003elink,\n+\t\t\t\t\t\t DL_FLAG_AUTOPROBE_CONSUMER));\n+\tKUNIT_EXPECT_FALSE(test, device_link_test(context-\u003elink,\n+\t\t\t\t\t\t DL_FLAG_SYNC_STATE_ONLY));\n+\n+\tkunit_release_action(test, test_unregister_device, supplier);\n+\tdevice_link_wait_removal();\n+\n+\tKUNIT_EXPECT_EQ(test, atomic_read(\u0026remove_count), 1);\n+\tKUNIT_EXPECT_EQ(test, consumer-\u003edev.links.status, DL_DEV_NO_DRIVER);\n+}\n+\n+static void device_link_class_consumer_sync_test(struct kunit *test)\n+{\n+\tstruct device_link_class_context *context = test-\u003epriv;\n+\tstruct platform_device *supplier;\n+\tstruct device *consumer;\n+\tint ret;\n+\n+\tdevice_links_supplier_sync_state_pause();\n+\tret = kunit_add_action_or_reset(test, test_resume_sync_state, NULL);\n+\tKUNIT_ASSERT_EQ(test, ret, 0);\n+\n+\tsupplier = test_register_platform_device(test);\n+\tcontext-\u003eaction = TEST_CLASS_SYNC_CONSUMER;\n+\tcontext-\u003eendpoint = \u0026supplier-\u003edev;\n+\n+\tconsumer = device_create(test_class, NULL, 0, NULL,\n+\t\t\t\t TEST_CLASS_NAME \"-consumer\");\n+\tKUNIT_ASSERT_NOT_ERR_OR_NULL(test, consumer);\n+\tret = kunit_add_action_or_reset(test, test_unregister_device, consumer);\n+\tKUNIT_ASSERT_EQ(test, ret, 0);\n+\n+\tKUNIT_ASSERT_NOT_NULL(test, context-\u003elink);\n+\tKUNIT_EXPECT_EQ(test, consumer-\u003elinks.status, DL_DEV_DRIVER_BOUND);\n+\tKUNIT_EXPECT_TRUE(test, list_empty(\u0026consumer-\u003elinks.suppliers));\n+\tkunit_release_action(test, test_resume_sync_state, NULL);\n+\tKUNIT_EXPECT_EQ(test, atomic_read(\u0026sync_count), 1);\n+}\n+\n+static void device_link_class_existing_consumer_link_test(struct kunit *test)\n+{\n+\tstruct device_link_class_context *context = test-\u003epriv;\n+\tstruct test_class_device *consumer;\n+\tstruct platform_device *supplier;\n+\tstruct device_link *link;\n+\tint ret;\n+\n+\tsupplier = test_register_platform_device(test);\n+\tconsumer = test_class_device_alloc(test,\n+\t\t\t\t\t TEST_CLASS_NAME \"-existing-link\");\n+\tKUNIT_ASSERT_NOT_NULL(test, consumer);\n+\n+\tlink = device_link_add(\u0026consumer-\u003edev, \u0026supplier-\u003edev,\n+\t\t\t DL_FLAG_AUTOREMOVE_CONSUMER);\n+\tKUNIT_ASSERT_NOT_NULL(test, link);\n+\tKUNIT_ASSERT_EQ(test, READ_ONCE(link-\u003estatus), DL_STATE_AVAILABLE);\n+\tret = fwnode_link_add(\u0026consumer-\u003efwnode,\n+\t\t\t \u0026consumer-\u003emissing_supplier, 0);\n+\tKUNIT_ASSERT_EQ(test, ret, 0);\n+\n+\tcontext-\u003eaction = TEST_CLASS_OBSERVE;\n+\tret = device_add(\u0026consumer-\u003edev);\n+\tKUNIT_ASSERT_EQ(test, ret, 0);\n+\tconsumer-\u003eadded = true;\n+\n+\tKUNIT_EXPECT_EQ(test, consumer-\u003edev.links.status, DL_DEV_DRIVER_BOUND);\n+\tKUNIT_EXPECT_EQ(test, READ_ONCE(link-\u003estatus), DL_STATE_ACTIVE);\n+\tKUNIT_EXPECT_TRUE(test, context-\u003ewaiting_for_supplier_seen);\n+\tKUNIT_EXPECT_FALSE(test, test_has_waiting_for_supplier(\u0026consumer-\u003edev));\n+}\n+\n+static void device_link_class_registration_wait_test(struct kunit *test)\n+{\n+\tstruct device_link_class_context *context = test-\u003epriv;\n+\tstruct device *dev;\n+\tunsigned long timeout;\n+\tint ret;\n+\n+\twait_for_device_probe();\n+\tcontext-\u003eaction = TEST_CLASS_WAIT;\n+\tdev = device_create(test_class, NULL, 0, NULL,\n+\t\t\t TEST_CLASS_NAME \"-wait\");\n+\tKUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);\n+\tret = kunit_add_action_or_reset(test, test_unregister_device, dev);\n+\tKUNIT_ASSERT_EQ(test, ret, 0);\n+\n+\tKUNIT_EXPECT_FALSE(test, context-\u003ewaiter_completed_during_add);\n+\ttimeout = wait_for_completion_timeout(\u0026context-\u003ewaiter_done,\n+\t\t\t\t\t msecs_to_jiffies(1000));\n+\tKUNIT_EXPECT_GT(test, timeout, 0UL);\n+}\n+\n+static void device_link_class_supplier_unbind_race_test(struct kunit *test)\n+{\n+\tstruct device_link_class_context *context = test-\u003epriv;\n+\tstruct platform_device *supplier;\n+\tstruct device *consumer;\n+\tunsigned long timeout;\n+\tint ret;\n+\n+\tsupplier = test_register_platform_device(test);\n+\tcontext-\u003eaction = TEST_CLASS_UNBIND_RACE;\n+\tcontext-\u003eendpoint = \u0026supplier-\u003edev;\n+\tconsumer = device_create(test_class, NULL, 0, NULL,\n+\t\t\t\t TEST_CLASS_NAME \"-unbind-race\");\n+\tKUNIT_ASSERT_NOT_ERR_OR_NULL(test, consumer);\n+\tret = kunit_add_action_or_reset(test, test_unregister_device, consumer);\n+\tKUNIT_ASSERT_EQ(test, ret, 0);\n+\n+\tKUNIT_ASSERT_NOT_NULL(test, context-\u003elink);\n+\tKUNIT_EXPECT_FALSE(test, context-\u003eunbind_completed_during_add);\n+\ttimeout = wait_for_completion_timeout(\u0026context-\u003eunbind_done,\n+\t\t\t\t\t msecs_to_jiffies(1000));\n+\tKUNIT_EXPECT_GT(test, timeout, 0UL);\n+\tKUNIT_EXPECT_EQ(test, atomic_read(\u0026remove_count), 1);\n+\tKUNIT_EXPECT_EQ(test, supplier-\u003edev.links.status, DL_DEV_NO_DRIVER);\n+}\n+\n+static int device_link_class_test_init(struct kunit *test)\n+{\n+\tstruct device_link_class_context *context;\n+\n+\tcontext = kunit_kzalloc(test, sizeof(*context), GFP_KERNEL);\n+\tif (!context)\n+\t\treturn -ENOMEM;\n+\n+\tinit_completion(\u0026context-\u003ewaiter_started);\n+\tinit_completion(\u0026context-\u003ewaiter_done);\n+\tinit_completion(\u0026context-\u003eunbind_started);\n+\tinit_completion(\u0026context-\u003eunbind_done);\n+\ttest-\u003epriv = context;\n+\ttest_context = context;\n+\tatomic_set(\u0026remove_count, 0);\n+\tatomic_set(\u0026sync_count, 0);\n+\n+\treturn 0;\n+}\n+\n+static void device_link_class_test_exit(struct kunit *test)\n+{\n+\tstruct device_link_class_context *context = test-\u003epriv;\n+\n+\tif (context-\u003ewaiter \u0026\u0026 !IS_ERR(context-\u003ewaiter)) {\n+\t\tkthread_stop(context-\u003ewaiter);\n+\t\tput_task_struct(context-\u003ewaiter);\n+\t}\n+\tif (context-\u003eunbinder \u0026\u0026 !IS_ERR(context-\u003eunbinder)) {\n+\t\tkthread_stop(context-\u003eunbinder);\n+\t\tput_task_struct(context-\u003eunbinder);\n+\t}\n+\ttest_context = NULL;\n+}\n+\n+static int device_link_class_suite_init(struct kunit_suite *suite)\n+{\n+\tint ret;\n+\n+\ttest_class = class_create(TEST_CLASS_NAME);\n+\tif (IS_ERR(test_class))\n+\t\treturn PTR_ERR(test_class);\n+\n+\tret = platform_driver_register(\u0026test_driver);\n+\tif (ret)\n+\t\tgoto destroy_class;\n+\n+\ttest_class_interface.class = test_class;\n+\tret = class_interface_register(\u0026test_class_interface);\n+\tif (ret)\n+\t\tgoto unregister_driver;\n+\n+\treturn 0;\n+\n+unregister_driver:\n+\tplatform_driver_unregister(\u0026test_driver);\n+destroy_class:\n+\tclass_destroy(test_class);\n+\n+\treturn ret;\n+}\n+\n+static void device_link_class_suite_exit(struct kunit_suite *suite)\n+{\n+\tclass_interface_unregister(\u0026test_class_interface);\n+\tplatform_driver_unregister(\u0026test_driver);\n+\tclass_destroy(test_class);\n+\tdevice_link_wait_removal();\n+}\n+\n+static struct kunit_case device_link_class_test_cases[] = {\n+\tKUNIT_CASE(device_link_class_supplier_test),\n+\tKUNIT_CASE(device_link_class_consumer_sync_test),\n+\tKUNIT_CASE(device_link_class_existing_consumer_link_test),\n+\tKUNIT_CASE(device_link_class_registration_wait_test),\n+\tKUNIT_CASE(device_link_class_supplier_unbind_race_test),\n+\t{}\n+};\n+\n+static struct kunit_suite device_link_class_test_suite = {\n+\t.name = \"device-link-class\",\n+\t.suite_init = device_link_class_suite_init,\n+\t.suite_exit = device_link_class_suite_exit,\n+\t.init = device_link_class_test_init,\n+\t.exit = device_link_class_test_exit,\n+\t.test_cases = device_link_class_test_cases,\n+};\n+\n+kunit_test_suite(device_link_class_test_suite);\n+\n+MODULE_DESCRIPTION(\"KUnit tests for class device links\");\n+MODULE_LICENSE(\"GPL\");\ndiff --git a/drivers/base/test/fwnode-link-test.c b/drivers/base/test/fwnode-link-test.c\nnew file mode 100644\nindex 0000000000000..fee18613d2302\n--- /dev/null\n+++ b/drivers/base/test/fwnode-link-test.c\n@@ -0,0 +1,207 @@\n+// SPDX-License-Identifier: GPL-2.0\n+\n+#include \u003ckunit/platform_device.h\u003e\n+#include \u003ckunit/test.h\u003e\n+\n+#include \u003clinux/device.h\u003e\n+#include \u003clinux/fwnode.h\u003e\n+#include \u003clinux/platform_device.h\u003e\n+\n+#define FWNODE_LINK_TEST_DRIVER_NAME\t\"fwnode-link-test\"\n+\n+struct fwnode_link_test_context {\n+\tstruct fwnode_handle consumer;\n+\tstruct fwnode_handle container;\n+\tstruct fwnode_handle supplier_a;\n+\tstruct fwnode_handle supplier_b;\n+};\n+\n+static int fwnode_link_test_probe(struct platform_device *pdev)\n+{\n+\treturn 0;\n+}\n+\n+static struct platform_driver fwnode_link_test_driver = {\n+\t.probe = fwnode_link_test_probe,\n+\t.driver = {\n+\t\t.name = FWNODE_LINK_TEST_DRIVER_NAME,\n+\t},\n+};\n+\n+static void fwnode_link_test_cleanup(void *data)\n+{\n+\tstruct fwnode_link_test_context *context = data;\n+\n+\tfwnode_links_purge(\u0026context-\u003econsumer);\n+\tfwnode_links_purge(\u0026context-\u003econtainer);\n+\tfwnode_links_purge(\u0026context-\u003esupplier_a);\n+\tfwnode_links_purge(\u0026context-\u003esupplier_b);\n+}\n+\n+static struct fwnode_link_test_context *\n+fwnode_link_test_init(struct kunit *test)\n+{\n+\tstruct fwnode_link_test_context *context;\n+\tint ret;\n+\n+\tcontext = kunit_kzalloc(test, sizeof(*context), GFP_KERNEL);\n+\tKUNIT_ASSERT_NOT_NULL(test, context);\n+\n+\tfwnode_init(\u0026context-\u003econsumer, NULL);\n+\tfwnode_init(\u0026context-\u003econtainer, NULL);\n+\tfwnode_init(\u0026context-\u003esupplier_a, NULL);\n+\tfwnode_init(\u0026context-\u003esupplier_b, NULL);\n+\tret = kunit_add_action_or_reset(test, fwnode_link_test_cleanup,\n+\t\t\t\t\tcontext);\n+\tKUNIT_ASSERT_EQ(test, ret, 0);\n+\n+\treturn context;\n+}\n+\n+static struct platform_device *\n+fwnode_link_test_register_pdev(struct kunit *test,\n+\t\t\t struct fwnode_handle *fwnode)\n+{\n+\tstruct platform_device *pdev;\n+\tint ret;\n+\n+\tpdev = kunit_platform_device_alloc(test, FWNODE_LINK_TEST_DRIVER_NAME,\n+\t\t\t\t\t PLATFORM_DEVID_AUTO);\n+\tKUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev);\n+\n+\tdevice_set_node(\u0026pdev-\u003edev, fwnode);\n+\tret = kunit_platform_device_add(test, pdev);\n+\tif (ret) {\n+\t\tKUNIT_FAIL(test, \"failed to register platform device: %d\", ret);\n+\t\treturn NULL;\n+\t}\n+\n+\treturn pdev;\n+}\n+\n+static unsigned int fwnode_supplier_count(struct fwnode_handle *fwnode)\n+{\n+\tstruct fwnode_link *link;\n+\tunsigned int count = 0;\n+\n+\tlist_for_each_entry(link, \u0026fwnode-\u003esuppliers, c_hook)\n+\t\tcount++;\n+\n+\treturn count;\n+}\n+\n+static struct fwnode_link *\n+fwnode_find_supplier(struct fwnode_handle *consumer,\n+\t\t struct fwnode_handle *supplier)\n+{\n+\tstruct fwnode_link *link;\n+\n+\tlist_for_each_entry(link, \u0026consumer-\u003esuppliers, c_hook) {\n+\t\tif (link-\u003esupplier == supplier)\n+\t\t\treturn link;\n+\t}\n+\n+\treturn NULL;\n+}\n+\n+static void fwnode_link_copy_suppliers_test(struct kunit *test)\n+{\n+\tstruct fwnode_link_test_context *context;\n+\tstruct fwnode_link *link;\n+\tint ret;\n+\n+\tcontext = fwnode_link_test_init(test);\n+\tKUNIT_ASSERT_NOT_NULL(test, context);\n+\n+\tret = fwnode_link_add(\u0026context-\u003econtainer, \u0026context-\u003esupplier_a,\n+\t\t\t FWLINK_FLAG_CYCLE);\n+\tKUNIT_ASSERT_EQ(test, ret, 0);\n+\tret = fwnode_link_add(\u0026context-\u003econtainer, \u0026context-\u003esupplier_b,\n+\t\t\t FWLINK_FLAG_IGNORE);\n+\tKUNIT_ASSERT_EQ(test, ret, 0);\n+\n+\tret = fw_devlink_copy_suppliers(\u0026context-\u003econsumer,\n+\t\t\t\t\t\u0026context-\u003econtainer);\n+\tKUNIT_ASSERT_EQ(test, ret, 0);\n+\tKUNIT_EXPECT_EQ(test, fwnode_supplier_count(\u0026context-\u003econtainer), 2U);\n+\tKUNIT_EXPECT_EQ(test, fwnode_supplier_count(\u0026context-\u003econsumer), 1U);\n+\n+\tlink = fwnode_find_supplier(\u0026context-\u003econsumer, \u0026context-\u003esupplier_a);\n+\tKUNIT_ASSERT_NOT_NULL(test, link);\n+\tKUNIT_EXPECT_EQ(test, link-\u003eflags, (u8)0);\n+\tlink = fwnode_find_supplier(\u0026context-\u003econsumer, \u0026context-\u003esupplier_b);\n+\tKUNIT_EXPECT_NULL(test, link);\n+\n+\tret = fw_devlink_copy_suppliers(\u0026context-\u003econsumer,\n+\t\t\t\t\t\u0026context-\u003econtainer);\n+\tKUNIT_ASSERT_EQ(test, ret, 0);\n+\tKUNIT_EXPECT_EQ(test, fwnode_supplier_count(\u0026context-\u003econsumer), 1U);\n+}\n+\n+static void fwnode_link_copy_before_device_add_test(struct kunit *test)\n+{\n+\tstruct fwnode_link_test_context *context;\n+\tstruct platform_device *consumer;\n+\tstruct platform_device *supplier;\n+\tstruct device_link *link;\n+\tint ret;\n+\n+\tcontext = fwnode_link_test_init(test);\n+\tKUNIT_ASSERT_NOT_NULL(test, context);\n+\n+\tret = fwnode_link_add(\u0026context-\u003econtainer, \u0026context-\u003esupplier_a, 0);\n+\tKUNIT_ASSERT_EQ(test, ret, 0);\n+\tsupplier = fwnode_link_test_register_pdev(test, \u0026context-\u003esupplier_a);\n+\tKUNIT_ASSERT_NOT_NULL(test, supplier);\n+\n+\tret = fw_devlink_copy_suppliers(\u0026context-\u003econsumer,\n+\t\t\t\t\t\u0026context-\u003econtainer);\n+\tKUNIT_ASSERT_EQ(test, ret, 0);\n+\tconsumer = fwnode_link_test_register_pdev(test, \u0026context-\u003econsumer);\n+\tKUNIT_ASSERT_NOT_NULL(test, consumer);\n+\n+\tKUNIT_EXPECT_EQ(test, fwnode_supplier_count(\u0026context-\u003econtainer), 1U);\n+\tKUNIT_EXPECT_EQ(test, fwnode_supplier_count(\u0026context-\u003econsumer), 0U);\n+\n+\tif (list_empty(\u0026consumer-\u003edev.links.suppliers)) {\n+\t\tKUNIT_FAIL(test, \"consumer device link was not created\");\n+\t\treturn;\n+\t}\n+\tlink = list_first_entry(\u0026consumer-\u003edev.links.suppliers,\n+\t\t\t\tstruct device_link, c_node);\n+\tKUNIT_EXPECT_PTR_EQ(test, link-\u003esupplier, \u0026supplier-\u003edev);\n+\tKUNIT_EXPECT_EQ(test, READ_ONCE(link-\u003estatus), DL_STATE_ACTIVE);\n+\n+\tret = fw_devlink_copy_suppliers(\u0026context-\u003econsumer,\n+\t\t\t\t\t\u0026context-\u003econtainer);\n+\tKUNIT_EXPECT_EQ(test, ret, -EBUSY);\n+}\n+\n+static struct kunit_case fwnode_link_test_cases[] = {\n+\tKUNIT_CASE(fwnode_link_copy_suppliers_test),\n+\tKUNIT_CASE(fwnode_link_copy_before_device_add_test),\n+\t{}\n+};\n+\n+static int fwnode_link_test_suite_init(struct kunit_suite *suite)\n+{\n+\treturn platform_driver_register(\u0026fwnode_link_test_driver);\n+}\n+\n+static void fwnode_link_test_suite_exit(struct kunit_suite *suite)\n+{\n+\tplatform_driver_unregister(\u0026fwnode_link_test_driver);\n+\tdevice_link_wait_removal();\n+}\n+\n+static struct kunit_suite fwnode_link_test_suite = {\n+\t.name = \"fwnode-link\",\n+\t.suite_init = fwnode_link_test_suite_init,\n+\t.suite_exit = fwnode_link_test_suite_exit,\n+\t.test_cases = fwnode_link_test_cases,\n+};\n+\n+kunit_test_suite(fwnode_link_test_suite);\n+\n+MODULE_DESCRIPTION(\"KUnit tests for firmware-node links\");\n+MODULE_LICENSE(\"GPL\");\ndiff --git a/drivers/net/mdio/of_mdio.c b/drivers/net/mdio/of_mdio.c\nindex b8d298c04d3ff..9b73fd8eed416 100644\n--- a/drivers/net/mdio/of_mdio.c\n+++ b/drivers/net/mdio/of_mdio.c\n@@ -10,6 +10,7 @@\n \n #include \u003clinux/device.h\u003e\n #include \u003clinux/err.h\u003e\n+#include \u003clinux/fwnode.h\u003e\n #include \u003clinux/fwnode_mdio.h\u003e\n #include \u003clinux/kernel.h\u003e\n #include \u003clinux/module.h\u003e\n@@ -34,9 +35,26 @@ static int of_get_phy_id(struct device_node *device, u32 *phy_id)\n \treturn fwnode_get_phy_id(of_fwnode_handle(device), phy_id);\n }\n \n+static int of_mdiobus_link_phy_package(struct device_node *child)\n+{\n+\tstruct device_node *package __free(device_node) = of_get_parent(child);\n+\n+\tif (!package || !of_node_name_eq(package, \"ethernet-phy-package\"))\n+\t\treturn 0;\n+\n+\treturn fw_devlink_copy_suppliers(of_fwnode_handle(child),\n+\t\t\t\t\t of_fwnode_handle(package));\n+}\n+\n int of_mdiobus_phy_device_register(struct mii_bus *mdio, struct phy_device *phy,\n \t\t\t\t struct device_node *child, u32 addr)\n {\n+\tint ret;\n+\n+\tret = of_mdiobus_link_phy_package(child);\n+\tif (ret)\n+\t\treturn ret;\n+\n \treturn fwnode_mdiobus_phy_device_register(mdio, phy,\n \t\t\t\t\t\t of_fwnode_handle(child),\n \t\t\t\t\t\t addr);\n@@ -46,6 +64,12 @@ EXPORT_SYMBOL(of_mdiobus_phy_device_register);\n static int of_mdiobus_register_phy(struct mii_bus *mdio,\n \t\t\t\t struct device_node *child, u32 addr)\n {\n+\tint ret;\n+\n+\tret = of_mdiobus_link_phy_package(child);\n+\tif (ret)\n+\t\treturn ret;\n+\n \treturn fwnode_mdiobus_register_phy(mdio, of_fwnode_handle(child), addr);\n }\n \n@@ -219,9 +243,10 @@ int __of_mdiobus_register(struct mii_bus *mdio, struct device_node *np,\n \tof_property_read_u32(np, \"reset-post-delay-us\", \u0026mdio-\u003ereset_post_delay_us);\n \n \t/* Register the MDIO bus */\n+\tdevice_links_supplier_sync_state_pause();\n \trc = __mdiobus_register(mdio, owner);\n \tif (rc)\n-\t\treturn rc;\n+\t\tgoto resume;\n \n \t/* Loop over the child nodes and register a phy_device for each phy */\n \trc = __of_mdiobus_parse_phys(mdio, np, \u0026scanphys);\n@@ -229,7 +254,7 @@ int __of_mdiobus_register(struct mii_bus *mdio, struct device_node *np,\n \t\tgoto unregister;\n \n \tif (!scanphys)\n-\t\treturn 0;\n+\t\tgoto resume;\n \n \t/* auto scan for PHYs with empty reg property */\n \tfor_each_available_child_of_node(np, child) {\n@@ -261,12 +286,15 @@ int __of_mdiobus_register(struct mii_bus *mdio, struct device_node *np,\n \t\t}\n \t}\n \n-\treturn 0;\n+\trc = 0;\n+\tgoto resume;\n \n put_unregister:\n \tof_node_put(child);\n unregister:\n \tmdiobus_unregister(mdio);\n+resume:\n+\tdevice_links_supplier_sync_state_resume();\n \treturn rc;\n }\n EXPORT_SYMBOL(__of_mdiobus_register);\ndiff --git a/include/linux/device.h b/include/linux/device.h\nindex aee79fd6b32b4..3783744df86e3 100644\n--- a/include/linux/device.h\n+++ b/include/linux/device.h\n@@ -446,11 +446,16 @@ enum device_link_state {\n #define DL_FLAG_CYCLE\t\t\tBIT(9)\n \n /**\n- * enum dl_dev_state - Device driver presence tracking information.\n+ * enum dl_dev_state - Device availability tracking information.\n * @DL_DEV_NO_DRIVER: There is no driver attached to the device.\n * @DL_DEV_PROBING: A driver is probing.\n * @DL_DEV_DRIVER_BOUND: The driver has been bound to the device.\n * @DL_DEV_UNBINDING: The driver is unbinding from the device.\n+ *\n+ * Class devices which are not on a bus use the same states for their\n+ * registration lifecycle. In that case, PROBING and DRIVER_BOUND mean that\n+ * device_add() is in progress or has completed, respectively, and UNBINDING\n+ * means that device_del() is in progress.\n */\n enum dl_dev_state {\n \tDL_DEV_NO_DRIVER = 0,\n@@ -480,7 +485,7 @@ enum device_removable {\n * @suppliers: List of links to supplier devices.\n * @consumers: List of links to consumer devices.\n * @defer_sync: Hook to global list of devices that have deferred sync_state.\n- * @status: Driver status information.\n+ * @status: Driver or class-device availability status.\n */\n struct dev_links_info {\n \tstruct list_head suppliers;\ndiff --git a/include/linux/fwnode.h b/include/linux/fwnode.h\nindex 4e86e6990d284..ad4250fc7afb6 100644\n--- a/include/linux/fwnode.h\n+++ b/include/linux/fwnode.h\n@@ -254,6 +254,8 @@ int fwnode_link_add(struct fwnode_handle *con, struct fwnode_handle *sup,\n void fwnode_links_purge(struct fwnode_handle *fwnode);\n void fw_devlink_purge_absent_suppliers(struct fwnode_handle *fwnode);\n void fw_devlink_refresh_fwnode(struct fwnode_handle *fwnode);\n+int fw_devlink_copy_suppliers(struct fwnode_handle *to,\n+\t\t\t struct fwnode_handle *from);\n bool fw_devlink_is_strict(void);\n \n #endif\n"
} |
| 2/1 | 2026/08/18 07:43 | agent | patch-evaluator |
1mModel:best-expensive Args: null Results: {
"EnableConfigs": [
"DM_KUNIT_TEST",
"OF_MDIO"
],
"FocusSymbols": [
"fw_devlink_copy_suppliers",
"device_links_driver_bound",
"device_links_driver_cleanup",
"device_links_unbind_consumers",
"of_mdiobus_phy_device_register",
"__of_mdiobus_register"
],
"Reasoning": "The patch series modifies the device link API to support driverless class devices, allowing them to participate in the managed-link state machine as supplier and consumer endpoints. It modifies core driver model functions like `device_add`, `device_del`, `device_links_driver_bound`, and `device_links_driver_cleanup`. It also adds a new exported function `fw_devlink_copy_suppliers` and updates the OF MDIO subsystem to link PHY packages. Since this modifies reachable core kernel logic and driver subsystems, it has functional impact and is worth fuzzing.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit 3e5bcfae81622146a78f94161df2c8f0e249bd1a
Author: syz-cluster <triage@syzkaller.com>
Date: Tue Aug 18 07:43:55 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/driver-api/device_link.rst b/Documentation/driver-api/device_link.rst
index ee913ae163711..c248ec9fa9f87 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 a5b7abc10ff02..8bdf0a7094015 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 4d026682944f2..0c26ba38e6cfc 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;
}
@@ -1256,6 +1261,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 +1290,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)
{
@@ -1305,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)
@@ -1354,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))
@@ -1407,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;
@@ -1429,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
@@ -1444,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;
}
/*
@@ -1458,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();
@@ -1546,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;
@@ -1566,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);
/*
@@ -1587,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.
@@ -1597,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.
*/
@@ -1611,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) {
@@ -1666,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();
@@ -1824,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;
@@ -2394,6 +2497,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;
@@ -3642,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;
@@ -3675,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);
@@ -3776,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)
@@ -3796,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;
@@ -3824,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;
@@ -3918,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);
@@ -3962,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 60c005223844d..61288fcceb5b9 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 e321dfc7e9226..c33c615eaabf0 100644
--- a/drivers/base/test/Makefile
+++ b/drivers/base/test/Makefile
@@ -3,6 +3,8 @@ 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
CFLAGS_property-entry-test.o += $(DISABLE_STRUCTLEAK_PLUGIN)
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 0000000000000..7d52efb55aa5f
--- /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/drivers/base/test/fwnode-link-test.c b/drivers/base/test/fwnode-link-test.c
new file mode 100644
index 0000000000000..fee18613d2302
--- /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/drivers/net/mdio/of_mdio.c b/drivers/net/mdio/of_mdio.c
index b8d298c04d3ff..9b73fd8eed416 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);
}
@@ -219,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);
@@ -229,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) {
@@ -261,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);
diff --git a/include/linux/device.h b/include/linux/device.h
index aee79fd6b32b4..3783744df86e3 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;
diff --git a/include/linux/fwnode.h b/include/linux/fwnode.h
index 4e86e6990d284..ad4250fc7afb6 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
|
| 3/2 | 2026/08/18 07:43 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 20999 output: 22 thoughts: 451 Thoughts: **My Initial Assessment of this Patch** Alright, let's break down this patch. My initial thought is that we have a pretty significant piece of work here. This patch touches the device link API, specifically in the context of driverless class devices, and includes KUnit tests. The scope is pretty broad, judging by the number of files modified. We are talking about changes in documentation, core driver base code, testing infrastructure, and even a network driver (MDIO). Specifically, I see modifications to crucial functions like `device_add`, `device_del`, `device_links_driver_bound`, and `device_links_driver_cleanup`. It also looks like the patch impacts the MDIO bus registration functions (`of_mdiobus_register_phy`, `of_mdiobus_phy_device_register`, `__of_mdiobus_register`). This definitely screams "functional changes." Before I dive too deep into the code itself, I'm going to pay close attention to the KUnit tests and, of course, the specific core functions the patch alters. My initial focus will be fuzzing these entrypoints: * `device_links_driver_bound` * `device_links_driver_cleanup` * `device_links_unbind_consumers` * `device_add` * `device_del` * `fw_devlink_copy_suppliers` * `of_mdiobus_phy_device_register` * `__of_mdiobus_register` Let's start with `fw_devlink_copy_suppliers`. |
| 4/2 | 2026/08/18 07:44 | tool | grepper |
0mArgs:{
"Expression": "fw_devlink_copy_suppliers"
}Results: {
"Output": "drivers/base/core.c=2485=static void fw_devlink_link_device(struct device *dev)\n--\ndrivers/base/core.c-2500-/**\ndrivers/base/core.c:2501: * fw_devlink_copy_suppliers - Copy supplier links between firmware nodes\ndrivers/base/core.c-2502- * @to: Firmware node which consumes the copied suppliers\n--\ndrivers/base/core.c-2517- */\ndrivers/base/core.c:2518:int fw_devlink_copy_suppliers(struct fwnode_handle *to,\ndrivers/base/core.c-2519-\t\t\t struct fwnode_handle *from)\n--\ndrivers/base/core.c-2557-}\ndrivers/base/core.c:2558:EXPORT_SYMBOL_GPL(fw_devlink_copy_suppliers);\ndrivers/base/core.c-2559-\n--\ndrivers/base/test/fwnode-link-test.c=107=static void fwnode_link_copy_suppliers_test(struct kunit *test)\n--\ndrivers/base/test/fwnode-link-test.c-122-\ndrivers/base/test/fwnode-link-test.c:123:\tret = fw_devlink_copy_suppliers(\u0026context-\u003econsumer,\ndrivers/base/test/fwnode-link-test.c-124-\t\t\t\t\t\u0026context-\u003econtainer);\n--\ndrivers/base/test/fwnode-link-test.c-134-\ndrivers/base/test/fwnode-link-test.c:135:\tret = fw_devlink_copy_suppliers(\u0026context-\u003econsumer,\ndrivers/base/test/fwnode-link-test.c-136-\t\t\t\t\t\u0026context-\u003econtainer);\n--\ndrivers/base/test/fwnode-link-test.c=141=static void fwnode_link_copy_before_device_add_test(struct kunit *test)\n--\ndrivers/base/test/fwnode-link-test.c-156-\ndrivers/base/test/fwnode-link-test.c:157:\tret = fw_devlink_copy_suppliers(\u0026context-\u003econsumer,\ndrivers/base/test/fwnode-link-test.c-158-\t\t\t\t\t\u0026context-\u003econtainer);\n--\ndrivers/base/test/fwnode-link-test.c-174-\ndrivers/base/test/fwnode-link-test.c:175:\tret = fw_devlink_copy_suppliers(\u0026context-\u003econsumer,\ndrivers/base/test/fwnode-link-test.c-176-\t\t\t\t\t\u0026context-\u003econtainer);\n--\ndrivers/net/mdio/of_mdio.c=38=static int of_mdiobus_link_phy_package(struct device_node *child)\n--\ndrivers/net/mdio/of_mdio.c-44-\ndrivers/net/mdio/of_mdio.c:45:\treturn fw_devlink_copy_suppliers(of_fwnode_handle(child),\ndrivers/net/mdio/of_mdio.c-46-\t\t\t\t\t of_fwnode_handle(package));\n--\ninclude/linux/fwnode.h=256=void fw_devlink_refresh_fwnode(struct fwnode_handle *fwnode);\ninclude/linux/fwnode.h:257:int fw_devlink_copy_suppliers(struct fwnode_handle *to,\ninclude/linux/fwnode.h-258-\t\t\t struct fwnode_handle *from);\n"
} |
| 5/2 | 2026/08/18 07:44 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 22738 output: 20 thoughts: 271 Thoughts: **My Initial Assessment of the Patch** Okay, so this patch is introducing `fw_devlink_copy_suppliers` and tweaking the core device link management – specifically `device_add`, `device_del`, `device_links_driver_bound`, and `device_links_driver_cleanup`. Hmm, I can see what they're trying to do here. However, `device_add` and `device_del` are *ridiculously* hot paths; we really want to avoid any significant performance impact there. I'd rule those out immediately. `device_links_driver_bound` and `device_links_driver_cleanup` are also accessed frequently, though probably not quite as intensely as the `add` and `del` operations. Still, we should carefully consider the overhead. `fw_devlink_copy_suppliers`, on the other hand, seems like a good target for instrumentation. It's more specific and likely less frequently called in comparison. And let's not forget about `of_mdiobus_phy_device_register` and `__of_mdiobus_register`; they could also be candidates depending on how they're used. Now, I need to check for any conditional compilation or specific configurations that this patch relies on. I need to make sure I don't inadvertently enable or disable this feature for systems where it shouldn't be active. Looking at the KUnit tests in `drivers/base/test/Makefile`, I see: ``` obj-$(CONFIG_DM_KUNIT_TEST) += device-link-class-test.o obj-$(CONFIG_DM_KUNIT_TEST) += fwnode-link-test.o ``` Ah, right, so `DM_KUNIT_TEST` seems like the config to enable. That's good to know. I should check `drivers/net/mdio/of_mdio.c` as well to make sure there are no other configurations I need to be aware of. |
| 6/2 | 2026/08/18 07:44 | tool | grepper |
0mArgs:{
"Expression": "CONFIG_OF_MDIO"
}Results: {
"Output": "drivers/net/ethernet/cavium/thunder/thunder_bgx.c=1459=static int bgx_init_acpi_phy(struct bgx *bgx)\n--\ndrivers/net/ethernet/cavium/thunder/thunder_bgx.c-1465-\ndrivers/net/ethernet/cavium/thunder/thunder_bgx.c:1466:#if IS_ENABLED(CONFIG_OF_MDIO)\ndrivers/net/ethernet/cavium/thunder/thunder_bgx.c-1467-\n--\ndrivers/net/ethernet/cavium/thunder/thunder_bgx.c=1533=static int bgx_init_of_phy(struct bgx *bgx)\n--\ndrivers/net/ethernet/cavium/thunder/thunder_bgx.c-1537-\ndrivers/net/ethernet/cavium/thunder/thunder_bgx.c:1538:#endif /* CONFIG_OF_MDIO */\ndrivers/net/ethernet/cavium/thunder/thunder_bgx.c-1539-\n--\ndrivers/net/mdio/Makefile=5=obj-$(CONFIG_FWNODE_MDIO)\t+= fwnode_mdio.o\ndrivers/net/mdio/Makefile:6:obj-$(CONFIG_OF_MDIO)\t\t+= of_mdio.o\ndrivers/net/mdio/Makefile-7-\n--\ndrivers/net/phy/bcm87xx.c-19-\ndrivers/net/phy/bcm87xx.c:20:#if IS_ENABLED(CONFIG_OF_MDIO)\ndrivers/net/phy/bcm87xx.c-21-/* Set and/or override some configuration registers based on the\n--\ndrivers/net/phy/bcm87xx.c=77=static int bcm87xx_of_reg_init(struct phy_device *phydev)\n--\ndrivers/net/phy/bcm87xx.c-80-}\ndrivers/net/phy/bcm87xx.c:81:#endif /* CONFIG_OF_MDIO */\ndrivers/net/phy/bcm87xx.c-82-\n--\ndrivers/net/phy/dp83822.c=732=static int dp83822_phy_reset(struct phy_device *phydev)\n--\ndrivers/net/phy/dp83822.c-742-\ndrivers/net/phy/dp83822.c:743:#if IS_ENABLED(CONFIG_OF_MDIO)\ndrivers/net/phy/dp83822.c-744-static const u32 tx_amplitude_100base_tx_gain[] = {\n--\ndrivers/net/phy/dp83822.c=912=static void dp83826_of_init(struct phy_device *phydev)\n--\ndrivers/net/phy/dp83822.c-914-}\ndrivers/net/phy/dp83822.c:915:#endif /* CONFIG_OF_MDIO */\ndrivers/net/phy/dp83822.c-916-\n--\ndrivers/net/phy/dp83822.c=944=static int dp83822_attach_mdi_port(struct phy_device *phydev,\n--\ndrivers/net/phy/dp83822.c-957-\ndrivers/net/phy/dp83822.c:958:#if IS_ENABLED(CONFIG_OF_MDIO)\ndrivers/net/phy/dp83822.c-959-\t\tif (dp83822-\u003efx_enabled \u0026\u0026 dp83822-\u003efx_sd_enable)\n--\ndrivers/net/phy/dp83822.c-971-\t\t\t\t\t\t\t\"ti,fiber-mode\");\ndrivers/net/phy/dp83822.c:972:#endif /* CONFIG_OF_MDIO */\ndrivers/net/phy/dp83822.c-973-\n--\ndrivers/net/phy/dp83867.c=514=static int dp83867_config_port_mirroring(struct phy_device *phydev)\n--\ndrivers/net/phy/dp83867.c-526-\ndrivers/net/phy/dp83867.c:527:#if IS_ENABLED(CONFIG_OF_MDIO)\ndrivers/net/phy/dp83867.c-528-static int dp83867_of_init_io_impedance(struct phy_device *phydev)\n--\ndrivers/net/phy/dp83867.c=666=static int dp83867_of_init(struct phy_device *phydev)\n--\ndrivers/net/phy/dp83867.c-695-}\ndrivers/net/phy/dp83867.c:696:#endif /* CONFIG_OF_MDIO */\ndrivers/net/phy/dp83867.c-697-\n--\ndrivers/net/phy/dp83869.c=523=static int dp83869_set_strapped_mode(struct phy_device *phydev)\n--\ndrivers/net/phy/dp83869.c-536-\ndrivers/net/phy/dp83869.c:537:#if IS_ENABLED(CONFIG_OF_MDIO)\ndrivers/net/phy/dp83869.c-538-static const int dp83869_internal_delay[] = {250, 500, 750, 1000, 1250, 1500,\n--\ndrivers/net/phy/dp83869.c=615=static int dp83869_of_init(struct phy_device *phydev)\n--\ndrivers/net/phy/dp83869.c-618-}\ndrivers/net/phy/dp83869.c:619:#endif /* CONFIG_OF_MDIO */\ndrivers/net/phy/dp83869.c-620-\n--\ndrivers/net/phy/marvell-88q2xxx.c=748=static int mv88q2xxx_hwmon_probe(struct phy_device *phydev)\n--\ndrivers/net/phy/marvell-88q2xxx.c-753-\ndrivers/net/phy/marvell-88q2xxx.c:754:#if IS_ENABLED(CONFIG_OF_MDIO)\ndrivers/net/phy/marvell-88q2xxx.c-755-static int mv88q2xxx_leds_probe(struct phy_device *phydev)\n--\ndrivers/net/phy/marvell.c=483=static int m88e1101_config_aneg(struct phy_device *phydev)\n--\ndrivers/net/phy/marvell.c-517-\ndrivers/net/phy/marvell.c:518:#if IS_ENABLED(CONFIG_OF_MDIO)\ndrivers/net/phy/marvell.c-519-/* Set and/or override some configuration registers based on the\n--\ndrivers/net/phy/marvell.c=584=static int marvell_of_reg_init(struct phy_device *phydev)\n--\ndrivers/net/phy/marvell.c-587-}\ndrivers/net/phy/marvell.c:588:#endif /* CONFIG_OF_MDIO */\ndrivers/net/phy/marvell.c-589-\n--\ndrivers/net/phy/mdio_bus_provider.c=344=EXPORT_SYMBOL(mdiobus_alloc_size);\ndrivers/net/phy/mdio_bus_provider.c-345-\ndrivers/net/phy/mdio_bus_provider.c:346:#if IS_ENABLED(CONFIG_OF_MDIO)\ndrivers/net/phy/mdio_bus_provider.c-347-/* Walk the list of subnodes of a mdio bus and look for a node that\n--\ndrivers/net/phy/mdio_bus_provider.c=408=static struct phy_device *mdiobus_scan(struct mii_bus *bus, int addr, bool c45)\n--\ndrivers/net/phy/mdio_bus_provider.c-418-\ndrivers/net/phy/mdio_bus_provider.c:419:#if IS_ENABLED(CONFIG_OF_MDIO)\ndrivers/net/phy/mdio_bus_provider.c-420-\t/* For DT, see if the auto-probed phy has a corresponding child\n--\ndrivers/net/phy/mdio_bus_provider.c=734=EXPORT_SYMBOL(mdio_find_bus);\ndrivers/net/phy/mdio_bus_provider.c-735-\ndrivers/net/phy/mdio_bus_provider.c:736:#if IS_ENABLED(CONFIG_OF_MDIO)\ndrivers/net/phy/mdio_bus_provider.c-737-/**\n--\ndrivers/net/phy/mdio_devres.c=97=EXPORT_SYMBOL(__devm_mdiobus_register);\ndrivers/net/phy/mdio_devres.c-98-\ndrivers/net/phy/mdio_devres.c:99:#if IS_ENABLED(CONFIG_OF_MDIO)\ndrivers/net/phy/mdio_devres.c-100-/**\n--\ndrivers/net/phy/mdio_devres.c=131=EXPORT_SYMBOL(__devm_of_mdiobus_register);\ndrivers/net/phy/mdio_devres.c:132:#endif /* CONFIG_OF_MDIO */\ndrivers/net/phy/mdio_devres.c-133-\n--\ndrivers/net/phy/mscc/mscc.h=443=struct vsc85xx_shared_private {\n--\ndrivers/net/phy/mscc/mscc.h-446-\ndrivers/net/phy/mscc/mscc.h:447:#if IS_ENABLED(CONFIG_OF_MDIO)\ndrivers/net/phy/mscc/mscc.h-448-struct vsc8531_edge_rate_table {\n--\ndrivers/net/phy/mscc/mscc.h-451-};\ndrivers/net/phy/mscc/mscc.h:452:#endif /* CONFIG_OF_MDIO */\ndrivers/net/phy/mscc/mscc.h-453-\n--\ndrivers/net/phy/mscc/mscc_main.c=72=static const struct vsc85xx_hw_stat vsc8584_hw_stats[] = {\n--\ndrivers/net/phy/mscc/mscc_main.c-120-\ndrivers/net/phy/mscc/mscc_main.c:121:#if IS_ENABLED(CONFIG_OF_MDIO)\ndrivers/net/phy/mscc/mscc_main.c-122-static const struct vsc8531_edge_rate_table edge_table[] = {\n--\ndrivers/net/phy/mscc/mscc_main.c=370=static void vsc85xx_wol_get(struct phy_device *phydev,\n--\ndrivers/net/phy/mscc/mscc_main.c-400-\ndrivers/net/phy/mscc/mscc_main.c:401:#if IS_ENABLED(CONFIG_OF_MDIO)\ndrivers/net/phy/mscc/mscc_main.c-402-static int vsc85xx_edge_rate_magic_get(struct phy_device *phydev)\n--\ndrivers/net/phy/mscc/mscc_main.c=457=static int vsc85xx_dt_led_mode_get(struct phy_device *phydev,\n--\ndrivers/net/phy/mscc/mscc_main.c-462-}\ndrivers/net/phy/mscc/mscc_main.c:463:#endif /* CONFIG_OF_MDIO */\ndrivers/net/phy/mscc/mscc_main.c-464-\n--\ndrivers/net/phy/nxp-c45-tja11xx.c=1716=static int nxp_c45_parse_dt(struct phy_device *phydev)\n--\ndrivers/net/phy/nxp-c45-tja11xx.c-1720-\ndrivers/net/phy/nxp-c45-tja11xx.c:1721:\tif (!IS_ENABLED(CONFIG_OF_MDIO))\ndrivers/net/phy/nxp-c45-tja11xx.c-1722-\t\treturn 0;\n--\ndrivers/net/phy/nxp-tja11xx.c=517=static int tja11xx_parse_dt(struct phy_device *phydev)\n--\ndrivers/net/phy/nxp-tja11xx.c-521-\ndrivers/net/phy/nxp-tja11xx.c:522:\tif (!IS_ENABLED(CONFIG_OF_MDIO))\ndrivers/net/phy/nxp-tja11xx.c-523-\t\treturn 0;\n--\ndrivers/net/phy/phy-core.c=240=void of_set_phy_supported(struct phy_device *phydev)\n--\ndrivers/net/phy/phy-core.c-244-\ndrivers/net/phy/phy-core.c:245:\tif (!IS_ENABLED(CONFIG_OF_MDIO))\ndrivers/net/phy/phy-core.c-246-\t\treturn;\n--\ndrivers/net/phy/phy-core.c=255=void of_set_phy_eee_broken(struct phy_device *phydev)\n--\ndrivers/net/phy/phy-core.c-259-\ndrivers/net/phy/phy-core.c:260:\tif (!IS_ENABLED(CONFIG_OF_MDIO) || !node)\ndrivers/net/phy/phy-core.c-261-\t\treturn;\n--\ndrivers/net/phy/phy-core.c=286=void of_set_phy_timing_role(struct phy_device *phydev)\n--\ndrivers/net/phy/phy-core.c-290-\ndrivers/net/phy/phy-core.c:291:\tif (!IS_ENABLED(CONFIG_OF_MDIO))\ndrivers/net/phy/phy-core.c-292-\t\treturn;\n--\ndrivers/net/phy/phy_device.c=3099=EXPORT_SYMBOL(phy_get_pause);\ndrivers/net/phy/phy_device.c-3100-\ndrivers/net/phy/phy_device.c:3101:#if IS_ENABLED(CONFIG_OF_MDIO)\ndrivers/net/phy/phy_device.c-3102-static int phy_get_u32_property(struct device *dev, const char *name, u32 *val)\n--\ndrivers/net/phy/phy_device.c=3403=static int of_phy_leds(struct phy_device *phydev)\n--\ndrivers/net/phy/phy_device.c-3408-\ndrivers/net/phy/phy_device.c:3409:\tif (!IS_ENABLED(CONFIG_OF_MDIO))\ndrivers/net/phy/phy_device.c-3410-\t\treturn 0;\n--\ndrivers/net/phy/phy_device.c=3488=static int of_phy_ports(struct phy_device *phydev)\n--\ndrivers/net/phy/phy_device.c-3494-\ndrivers/net/phy/phy_device.c:3495:\tif (!IS_ENABLED(CONFIG_OF_MDIO))\ndrivers/net/phy/phy_device.c-3496-\t\treturn 0;\n--\ndrivers/net/phy/qcom/at803x.c=284=static int at803x_parse_dt(struct phy_device *phydev)\n--\ndrivers/net/phy/qcom/at803x.c-291-\ndrivers/net/phy/qcom/at803x.c:292:\tif (!IS_ENABLED(CONFIG_OF_MDIO))\ndrivers/net/phy/qcom/at803x.c-293-\t\treturn 0;\n--\ninclude/linux/of_mdio.h-14-\ninclude/linux/of_mdio.h:15:#if IS_ENABLED(CONFIG_OF_MDIO)\ninclude/linux/of_mdio.h-16-bool of_mdiobus_child_is_phy(struct device_node *child);\n--\ninclude/linux/of_mdio.h=53=static inline int of_mdio_parse_addr(struct device *dev,\n--\ninclude/linux/of_mdio.h-74-\ninclude/linux/of_mdio.h:75:#else /* CONFIG_OF_MDIO */\ninclude/linux/of_mdio.h-76-static inline bool of_mdiobus_child_is_phy(struct device_node *child)\n"
} |
| 7/2 | 2026/08/18 07:44 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 27805 output: 19 thoughts: 55 Thoughts: **Examining the MDIO Configuration** Okay, so I'm looking at `OF_MDIO`, which I believe is the key configuration option here. Hang on, though. I recall that `CONFIG_OF_MDIO` is actually a tristate variable, and it's conditional. It's dependent on `OF`, meaning Device Tree support is enabled, and also on `PHYLIB`, which implies the physical layer library is available. To get a clearer understanding of this dependency and its implications, I should really consult `drivers/net/mdio/Kconfig`. That's where I can find the precise definitions and relationships for these options within the MDIO subsystem. |
| 8/2 | 2026/08/18 07:44 | tool | grepper |
0mArgs:{
"Expression": "config OF_MDIO"
}Results: {
"Output": "drivers/net/mdio/Kconfig=9=config FWNODE_MDIO\n--\ndrivers/net/mdio/Kconfig-13-\ndrivers/net/mdio/Kconfig:14:config OF_MDIO\ndrivers/net/mdio/Kconfig-15-\tdef_tristate OF\n"
} |
| 9/2 | 2026/08/18 07:44 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 28108 output: 21 thoughts: 22 Thoughts: Okay, here's my interpretation of those thoughts, summarized from a first-person expert perspective: **Debugging Logic Flow** Alright, the initial indicator points towards `OF_MDIO`. I know what that entails – the Open Firmware MDIO bus, likely implying issues related to device tree interaction or communication with PHYs connected via MDIO. Now, given that clue, my next logical step is to immediately investigate `DM_KUNIT_TEST`. This is the direct next logical step as I know the debug tools inside the kernel unit tests are the correct tools for the job, as KUnit is how I can best diagnose the MDIO driver. A deeper dive into those unit tests and associated debug output related to the MDIO driver should provide the necessary diagnostic data and pinpoint the root cause of the problem, allowing me to start my work as a good, strong, Linux kernel engineer. |
| 10/2 | 2026/08/18 07:44 | tool | grepper |
0mArgs:{
"Expression": "config DM_KUNIT_TEST"
}Results: {
"Output": "drivers/base/test/Kconfig=2=config TEST_ASYNC_DRIVER_PROBE\n--\ndrivers/base/test/Kconfig-11-\ndrivers/base/test/Kconfig:12:config DM_KUNIT_TEST\ndrivers/base/test/Kconfig-13-\ttristate \"KUnit Tests for the device model\" if !KUNIT_ALL_TESTS\n"
} |
| 11/2 | 2026/08/18 07:44 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 28437 output: 22 thoughts: 146 Thoughts: **Analyzing Kernel Driver Binding and Cleanup** Okay, so I'm looking at this `DM_KUNIT_TEST` configuration, seems like a decent starting point. The task at hand is to consider how driver binding and cleanup are handled, specifically focusing on the preference for non-static, non-inlined API entrypoints over internal static helper functions. I need to keep in mind, and that the static helper functions are inlined by the compiler and don't have distinct symbol addresses. My initial thought is to check `device_links_driver_bound` and `device_links_driver_cleanup` within `drivers/base/core.c`. Let me just double check the prompt again to make sure I'm focused on the right details. Ah yes, the prompt emphasizes the non-static, non-inlined approach. Now, wait a moment, is `device_links_driver_bound` actually exported? No, it's not. But the function is non-static, so the compiler will not inline it. Alright, back to examining the code for `device_links_driver_bound` in `drivers/base/core.c`. |
| 12/2 | 2026/08/18 07:44 | tool | grepper |
0mArgs:{
"Expression": "void device_links_driver_bound"
}Results: {
"Output": "drivers/base/base.h=288=void device_links_force_bind(struct device *dev);\ndrivers/base/base.h:289:void device_links_driver_bound(struct device *dev);\ndrivers/base/base.h-290-void device_links_driver_cleanup(struct device *dev);\n--\ndrivers/base/core.c=1496=static void device_links_class_bound(struct device *dev)\n--\ndrivers/base/core.c-1522- */\ndrivers/base/core.c:1523:void device_links_driver_bound(struct device *dev)\ndrivers/base/core.c-1524-{\n"
} |
| 13/2 | 2026/08/18 07:44 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 29033 output: 22 thoughts: 22 Thoughts: Okay, here's my interpretation of those thoughts, summarized from a professional standpoint: **Considering the Dynamic Nature** Right, so this is definitely not a static situation. The key is in the change itself. But the question is: what's going to happen to `device_links_driver_cleanup` in this particular scenario? We need to consider that specific function or method and how it's going to be implemented, changed, or removed in the new dynamic process. It's a critical piece that could be significantly impacted by the non-static nature of this setup. |
| 14/2 | 2026/08/18 07:44 | tool | grepper |
0mArgs:{
"Expression": "void device_links_driver_cleanup"
}Results: {
"Output": "drivers/base/base.h=289=void device_links_driver_bound(struct device *dev);\ndrivers/base/base.h:290:void device_links_driver_cleanup(struct device *dev);\ndrivers/base/base.h-291-void device_links_no_driver(struct device *dev);\n--\ndrivers/base/core.c=1635=static void device_links_cleanup(struct device *dev)\n--\ndrivers/base/core.c-1676- */\ndrivers/base/core.c:1677:void device_links_driver_cleanup(struct device *dev)\ndrivers/base/core.c-1678-{\n"
} |
| 15/2 | 2026/08/18 07:44 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 29379 output: 22 thoughts: 19 Thoughts: Okay, here's my interpretation of those thoughts, presented as a personal thought process, as requested: **Considering Device Link Unbinding** Alright, the core concept is definitely non-static – that's clear. Now, I need to zoom in on the specific implications. My mind keeps circling around this function or variable: `device_links_unbind_consumers`. What's the context? What exactly are we unbinding, and from whom? The consumers here... Are we talking about a driver, a process, a specific resource? The mechanics of this are critical. I need to understand the relationship between devices and consumers and how unbinding plays into it. Is this for resource management, error handling, hot-plugging, or something else entirely? A deeper dive is definitely required. |
| 16/2 | 2026/08/18 07:44 | tool | grepper |
0mArgs:{
"Expression": "void device_links_unbind_consumers"
}Results: {
"Output": "drivers/base/base.h=292=bool device_links_busy(struct device *dev);\ndrivers/base/base.h:293:void device_links_unbind_consumers(struct device *dev);\ndrivers/base/base.h-294-bool device_link_flag_is_sync_state_only(u32 flags);\n--\ndrivers/base/core.c=1698=bool device_links_busy(struct device *dev)\n--\ndrivers/base/core.c-1742- */\ndrivers/base/core.c:1743:void device_links_unbind_consumers(struct device *dev)\ndrivers/base/core.c-1744-{\n"
} |
| 17/2 | 2026/08/18 07:44 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 29768 output: 23 thoughts: 20 Thoughts: Okay, here's my interpretation of those thoughts, tailored for an expert audience, presented in the first person: **Non-Static Considerations & Supplier Copying** Alright, that's a key observation – the need for a non-static context here is definitely on point. Now, digging deeper, my mind immediately jumps to `fw_devlink_copy_suppliers`. Is that the right function to use in this scenario? I have a feeling there could be something more optimal available, depending on the exact requirements. We need to assess the implications. My gut says there could be a cleaner or more efficient way to handle this dependency transfer. I'll probably need to consider factors like memory management, potential race conditions, and any subtleties related to firmware loading or device initialization that could influence this decision. A thorough review of the associated code and documentation is required to ensure this is truly the best approach. |
| 18/2 | 2026/08/18 07:44 | tool | grepper |
0mArgs:{
"Expression": "int fw_devlink_copy_suppliers"
}Results: {
"Output": "drivers/base/core.c=2485=static void fw_devlink_link_device(struct device *dev)\n--\ndrivers/base/core.c-2517- */\ndrivers/base/core.c:2518:int fw_devlink_copy_suppliers(struct fwnode_handle *to,\ndrivers/base/core.c-2519-\t\t\t struct fwnode_handle *from)\n--\ninclude/linux/fwnode.h=256=void fw_devlink_refresh_fwnode(struct fwnode_handle *fwnode);\ninclude/linux/fwnode.h:257:int fw_devlink_copy_suppliers(struct fwnode_handle *to,\ninclude/linux/fwnode.h-258-\t\t\t struct fwnode_handle *from);\n"
} |
| 19/2 | 2026/08/18 07:44 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 30213 output: 26 thoughts: 21 Thoughts: Okay, here's my interpretation of those thoughts, crafted with the understanding that I'm speaking to a fellow expert: **Concerning Exported Symbols and PHY Device Registration** Alright, that's done. Exported. The next question that immediately pops into my head, and it's a critical one, is: did we get `of_mdiobus_phy_device_register` right? That's the function that's going to be essential for bringing up the PHY devices in an environment where we're relying on Device Tree to handle the hardware setup. It’s the linchpin, and I need to make sure I haven't missed anything or introduced any subtle flaws in the implementation. If there are issues here, we could have serious upstreaming issues. |
| 20/2 | 2026/08/18 07:45 | tool | grepper |
0mArgs:{
"Expression": "int of_mdiobus_phy_device_register"
}Results: {
"Output": "drivers/net/mdio/of_mdio.c=38=static int of_mdiobus_link_phy_package(struct device_node *child)\n--\ndrivers/net/mdio/of_mdio.c-48-\ndrivers/net/mdio/of_mdio.c:49:int of_mdiobus_phy_device_register(struct mii_bus *mdio, struct phy_device *phy,\ndrivers/net/mdio/of_mdio.c-50-\t\t\t\t struct device_node *child, u32 addr)\n--\ninclude/linux/of_mdio.h=49=bool of_phy_is_fixed_link(struct device_node *np);\ninclude/linux/of_mdio.h:50:int of_mdiobus_phy_device_register(struct mii_bus *mdio, struct phy_device *phy,\ninclude/linux/of_mdio.h-51-\t\t\t\t struct device_node *child, u32 addr);\n--\ninclude/linux/of_mdio.h=140=static inline bool of_phy_is_fixed_link(struct device_node *np)\n--\ninclude/linux/of_mdio.h-144-\ninclude/linux/of_mdio.h:145:static inline int of_mdiobus_phy_device_register(struct mii_bus *mdio,\ninclude/linux/of_mdio.h-146-\t\t\t\t\t struct phy_device *phy,\n"
} |
| 21/2 | 2026/08/18 07:45 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 30803 output: 23 thoughts: 18 Thoughts: Okay, here's my summary, presented as a stream of thought: **Checking the Exported Symbols** Right, so I've confirmed it's exported. Now, a critical point comes to mind... what about the function `__of_mdiobus_register`? I need to quickly recall its role and if it relates to what I was just examining. Understanding its significance is key to this entire context... I need to consider it within the scope of this work. |
| 22/2 | 2026/08/18 07:45 | tool | grepper |
0mArgs:{
"Expression": "int __of_mdiobus_register"
}Results: {
"Output": "drivers/net/mdio/of_mdio.c=163=static int __of_mdiobus_parse_phys(struct mii_bus *mdio, struct device_node *np,\n--\ndrivers/net/mdio/of_mdio.c-218- */\ndrivers/net/mdio/of_mdio.c:219:int __of_mdiobus_register(struct mii_bus *mdio, struct device_node *np,\ndrivers/net/mdio/of_mdio.c-220-\t\t\t struct module *owner)\n--\ninclude/linux/of_mdio.h=16=bool of_mdiobus_child_is_phy(struct device_node *child);\ninclude/linux/of_mdio.h:17:int __of_mdiobus_register(struct mii_bus *mdio, struct device_node *np,\ninclude/linux/of_mdio.h-18-\t\t\t struct module *owner);\n"
} |
| 23/2 | 2026/08/18 07:45 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 31198 output: 229 thoughts: 314 Thoughts: **My Assessment of the Device Link API Patch Series** Okay, so I've just processed that information. Yes, I've confirmed the changes are exported and here's what I'm seeing as the key functional areas: * `fw_devlink_copy_suppliers` * `device_links_driver_bound` * `device_links_driver_cleanup` * `device_links_unbind_consumers` * `of_mdiobus_phy_device_register` * `__of_mdiobus_register` And the relevant configurations are these: * `DM_KUNIT_TEST` * `OF_MDIO` Now, let me just quickly check dependencies because that's always good practice. `DM_KUNIT_TEST` needs `KUNIT`. And `OF_MDIO` relies on both `OF` and `PHYLIB`. Okay, just to be precise, I'll list the configs without the `CONFIG_` prefix. Alright, let's step back and summarize my reasoning here. This patch series is tweaking the device link API, so it can support driverless class devices, which in turn means these devices can behave as either suppliers or consumers within the managed-link state machine. This impacts core driver model functions such as `device_add`, `device_del`, `device_links_driver_bound` and `device_links_driver_cleanup`. Plus, there's a new exported function, `fw_devlink_copy_suppliers`. And on top of it, the OF MDIO subsystem is being modified to link PHY packages. Given these modifications, I can see this has functional impact, as it's touching core kernel logic and various driver subsystems. Given this, it warrants fuzzing. I'm confident in my assessment, so I will now call `set-results` with the findings. |
| 24/2 | 2026/08/18 07:45 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"DM_KUNIT_TEST",
"OF_MDIO"
],
"FocusSymbols": [
"fw_devlink_copy_suppliers",
"device_links_driver_bound",
"device_links_driver_cleanup",
"device_links_unbind_consumers",
"of_mdiobus_phy_device_register",
"__of_mdiobus_register"
],
"Reasoning": "The patch series modifies the device link API to support driverless class devices, allowing them to participate in the managed-link state machine as supplier and consumer endpoints. It modifies core driver model functions like `device_add`, `device_del`, `device_links_driver_bound`, and `device_links_driver_cleanup`. It also adds a new exported function `fw_devlink_copy_suppliers` and updates the OF MDIO subsystem to link PHY packages. Since this modifies reachable core kernel logic and driver subsystems, it has functional impact and is worth fuzzing.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"DM_KUNIT_TEST",
"OF_MDIO"
],
"FocusSymbols": [
"fw_devlink_copy_suppliers",
"device_links_driver_bound",
"device_links_driver_cleanup",
"device_links_unbind_consumers",
"of_mdiobus_phy_device_register",
"__of_mdiobus_register"
],
"Reasoning": "The patch series modifies the device link API to support driverless class devices, allowing them to participate in the managed-link state machine as supplier and consumer endpoints. It modifies core driver model functions like `device_add`, `device_del`, `device_links_driver_bound`, and `device_links_driver_cleanup`. It also adds a new exported function `fw_devlink_copy_suppliers` and updates the OF MDIO subsystem to link PHY packages. Since this modifies reachable core kernel logic and driver subsystems, it has functional impact and is worth fuzzing.",
"WorthFuzzing": true
} |
| 25/1 | 2026/08/18 07:45 | action | If |
0mArgs:{
"WorthFuzzing": true
}Results: null |
| 26/2 | 2026/08/18 07:45 | agent | kmsan-evaluator |
0mModel:best-expensive Args: null Results: {
"KMSANReasoning": "The patch series modifies the device link and fwnode link subsystem to support driverless class devices in the managed-link state machine. The changes involve internal kernel state machine logic, locking, and list manipulations. There are no new structures sent to user space, no complex data structures allocated and left uninitialized, and no interactions with user space buffers (e.g., via copy_to_user, netlink, ioctl). Any potential bugs introduced by these changes, such as use-after-free, deadlocks, or out-of-bounds accesses, would be effectively caught by standard KASAN and LOCKDEP. There is no risk of uninitialized memory usage or info-leaks that would necessitate a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false
}Instruction: You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.
CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
a comprehensive suite of debugging tools and sanitizers, including KASAN
(out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
(locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
or page allocations) and kernel-to-user memory info-leaks.
Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.
Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.
Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
attributes, ioctl output arguments, socket options, or BPF buffers) where fields
or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
uninitialized bytes of existing buffers.
Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
dereferences, locking deadlocks, or use-after-free bugs (these are already caught
by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.
Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: For your convenience, here is the diff of the changes:
commit 3e5bcfae81622146a78f94161df2c8f0e249bd1a
Author: syz-cluster <triage@syzkaller.com>
Date: Tue Aug 18 07:43:55 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/driver-api/device_link.rst b/Documentation/driver-api/device_link.rst
index ee913ae163711..c248ec9fa9f87 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 a5b7abc10ff02..8bdf0a7094015 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 4d026682944f2..0c26ba38e6cfc 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;
}
@@ -1256,6 +1261,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 +1290,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)
{
@@ -1305,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)
@@ -1354,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))
@@ -1407,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;
@@ -1429,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
@@ -1444,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;
}
/*
@@ -1458,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();
@@ -1546,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;
@@ -1566,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);
/*
@@ -1587,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.
@@ -1597,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.
*/
@@ -1611,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) {
@@ -1666,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();
@@ -1824,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;
@@ -2394,6 +2497,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;
@@ -3642,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;
@@ -3675,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);
@@ -3776,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)
@@ -3796,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;
@@ -3824,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;
@@ -3918,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);
@@ -3962,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 60c005223844d..61288fcceb5b9 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 e321dfc7e9226..c33c615eaabf0 100644
--- a/drivers/base/test/Makefile
+++ b/drivers/base/test/Makefile
@@ -3,6 +3,8 @@ 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
CFLAGS_property-entry-test.o += $(DISABLE_STRUCTLEAK_PLUGIN)
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 0000000000000..7d52efb55aa5f
--- /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/drivers/base/test/fwnode-link-test.c b/drivers/base/test/fwnode-link-test.c
new file mode 100644
index 0000000000000..fee18613d2302
--- /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/drivers/net/mdio/of_mdio.c b/drivers/net/mdio/of_mdio.c
index b8d298c04d3ff..9b73fd8eed416 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);
}
@@ -219,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);
@@ -229,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) {
@@ -261,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);
diff --git a/include/linux/device.h b/include/linux/device.h
index aee79fd6b32b4..3783744df86e3 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;
diff --git a/include/linux/fwnode.h b/include/linux/fwnode.h
index 4e86e6990d284..ad4250fc7afb6 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
|
| 27/3 | 2026/08/18 07:45 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 20996 output: 178 thoughts: 966 Thoughts: **KMSAN Assessment: Driverless Class Device Link Support** Alright, let's break this down. My initial assessment is that this patch series is focused on enhancing the Linux kernel's device link and fwnode link subsystems. Specifically, it's adding support for driverless class devices to act as both suppliers and consumers within the managed-link state machine. This touches `drivers/base/core.c`, `drivers/base/dd.c`, `drivers/base/base.h`, `include/linux/device.h`, `include/linux/fwnode.h`, `drivers/net/mdio/of_mdio.c`, and includes some KUnit tests. My immediate concern, as always, is uninitialized memory. Let's make sure we're not introducing any vulnerabilities here. Looking at the changes, the core modifications seem to revolve around state machine logic, the mechanics of device and fwnode links, and MDIO bus registration. Crucially, there's no evidence of new structures being exposed to user space. There are no complex data structures being allocated and left uninitialized, and everything appears to be internal kernel logic involved with device links and driver probing. I'm zeroing in on specific functions now to double-check. For example, `fw_devlink_copy_suppliers` in `drivers/base/core.c` iterates through suppliers and adds links, rolling back on failure. I don't see any uninitialized memory risks there. The function seems to be sound, with proper initialization and cleanup, especially with the rollback mechanism. I've also examined `device_links_class_start`. This function handles device state transitions and link traversal. It uses `list_for_each_entry` to iterate through supplier links, updating their status based on the managed link flags. I see no issues here either, it's operating on existing structures in a well-defined state. In conclusion, this patch series is purely focused on internal kernel workings; it has no external interfaces, which means there are no new interactions with user space buffers, no new ioctls, netlink attributes, or BPF maps. The changes are all contained within the device link and fwnode link subsystem. Therefore, given the nature of these changes, the lack of user-space exposure, and the internal focus on state machine transitions, locking, and list manipulations, I'm confident there is no risk of uninitialized memory being exposed or used in a way that necessitates KMSAN. KASAN and LOCKDEP should be sufficient to catch any bugs (like use-after-free, deadlocks, and out-of-bounds accesses) introduced by these changes. I'll mark this as NeedsKMSAN = false. |
| 28/3 | 2026/08/18 07:45 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch series modifies the device link and fwnode link subsystem to support driverless class devices in the managed-link state machine. The changes involve internal kernel state machine logic, locking, and list manipulations. There are no new structures sent to user space, no complex data structures allocated and left uninitialized, and no interactions with user space buffers (e.g., via copy_to_user, netlink, ioctl). Any potential bugs introduced by these changes, such as use-after-free, deadlocks, or out-of-bounds accesses, would be effectively caught by standard KASAN and LOCKDEP. There is no risk of uninitialized memory usage or info-leaks that would necessitate a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch series modifies the device link and fwnode link subsystem to support driverless class devices in the managed-link state machine. The changes involve internal kernel state machine logic, locking, and list manipulations. There are no new structures sent to user space, no complex data structures allocated and left uninitialized, and no interactions with user space buffers (e.g., via copy_to_user, netlink, ioctl). Any potential bugs introduced by these changes, such as use-after-free, deadlocks, or out-of-bounds accesses, would be effectively caught by standard KASAN and LOCKDEP. There is no risk of uninitialized memory usage or info-leaks that would necessitate a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|