* [PATCH net-next v3 1/4] driver core: add fw_devlink supplier-copy helper
2026-08-18 6:58 [PATCH net-next v3 0/4] driver core, net: handle fw_devlink for class devices and PHY packages James Hilliard
@ 2026-08-18 6:58 ` James Hilliard
2026-08-18 7:30 ` Andy Shevchenko
2026-08-18 6:58 ` [PATCH net-next v3 2/4] net: mdio: link PHY package suppliers to member PHYs James Hilliard
` (3 subsequent siblings)
4 siblings, 1 reply; 9+ messages in thread
From: James Hilliard @ 2026-08-18 6:58 UTC (permalink / raw)
To: Rob Herring, Saravana Kannan, Greg Kroah-Hartman,
Rafael J. Wysocki, Danilo Krummrich, Jonathan Corbet, Shuah Khan,
Andy Shevchenko, Daniel Scally, Heikki Krogerus, Sakari Ailus,
Len Brown, Andrew Lunn, Heiner Kallweit, Russell King,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Christian Marangi
Cc: devicetree, linux-doc, linux-kernel, driver-core, linux-acpi,
netdev, James Hilliard
Some firmware nodes describe resources shared by devices instantiated for
their children, but the container node itself is never converted to a
struct device. The firmware parser should retain the topology as
described, while the framework which creates the children can identify
the actual consumers.
Add fw_devlink_copy_suppliers() so such a framework can copy the direct
supplier links from a container to a real consumer firmware node before
the consumer is registered. The normal device_add() path then converts
the copied dependencies into device links at the correct point in device
registration.
Leave the source links in place for other children, suppress duplicate
links and roll back newly allocated links if a copy fails. Skip ignored
links and clear cycle flags on newly copied links, since cycle
classification must be recomputed for the new consumer topology. Reject
calls after the target firmware node has been associated with a device.
Check that association while holding the fwnode-link lock so a
concurrent device_add() either observes the copied links or makes the
helper reject the request.
Add KUnit coverage for filtering, cycle-flag handling, idempotency, the
pre-registration contract and conversion into an active device link.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
Changes v2 -> v3:
- Keep generic firmware parsing faithful to the described topology and
add a helper for frameworks to project container suppliers onto real
child consumers, as suggested by Saravana Kannan
- Require copying before device_add() and let the normal fw_devlink path
create device links after consumer sysfs and PM initialization
- Serialize the target-association check with fwnode link copying
- Skip ignored links, recalculate cycle state for copied links and add
KUnit coverage
---
drivers/base/core.c | 60 ++++++++++
drivers/base/test/Makefile | 1 +
drivers/base/test/fwnode-link-test.c | 207 +++++++++++++++++++++++++++++++++++
include/linux/fwnode.h | 2 +
4 files changed, 270 insertions(+)
diff --git a/drivers/base/core.c b/drivers/base/core.c
index 4d026682944f..cf3f4133391d 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -2394,6 +2394,66 @@ static void fw_devlink_link_device(struct device *dev)
__fw_devlink_link_to_suppliers(dev, fwnode);
}
+/**
+ * fw_devlink_copy_suppliers - Copy supplier links between firmware nodes
+ * @to: Firmware node which consumes the copied suppliers
+ * @from: Firmware node whose supplier links should be copied
+ *
+ * Frameworks can use this when a container firmware node describes resources
+ * shared by devices created for its children. The source links remain in
+ * place, while links are added for the real consumer firmware node. Call this
+ * before device_add() associates @to with a registered device, so the normal
+ * fw_devlink path can convert the copied dependencies into device links.
+ *
+ * Ignored source links are skipped. Cycle flags are topology-specific and are
+ * recalculated for the new consumer when its links are converted.
+ *
+ * Return: 0 on success, -EINVAL for invalid firmware nodes, -EBUSY if @to is
+ * already associated with a device, or -ENOMEM if a link could not be
+ * allocated.
+ */
+int fw_devlink_copy_suppliers(struct fwnode_handle *to,
+ struct fwnode_handle *from)
+{
+ struct list_head *first;
+ struct fwnode_link *link;
+ int ret;
+
+ if (!to || !from)
+ return -EINVAL;
+ if (!fw_devlink_flags || to == from)
+ return 0;
+ fw_devlink_parse_fwnode(from);
+
+ guard(mutex)(&fwnode_link_lock);
+ if (READ_ONCE(to->dev))
+ return -EBUSY;
+
+ first = to->suppliers.next;
+ list_for_each_entry(link, &from->suppliers, c_hook) {
+ u8 flags = link->flags & ~FWLINK_FLAG_CYCLE;
+
+ if (flags & FWLINK_FLAG_IGNORE)
+ continue;
+
+ ret = __fwnode_link_add(to, link->supplier, flags);
+ if (ret)
+ goto rollback;
+ }
+
+ return 0;
+
+rollback:
+ while (to->suppliers.next != first) {
+ link = list_first_entry(&to->suppliers, struct fwnode_link,
+ c_hook);
+ __fwnode_link_del(link);
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(fw_devlink_copy_suppliers);
+
/* Device links support end. */
static struct kobject *dev_kobj;
diff --git a/drivers/base/test/Makefile b/drivers/base/test/Makefile
index e321dfc7e922..d871a71dbbaa 100644
--- a/drivers/base/test/Makefile
+++ b/drivers/base/test/Makefile
@@ -3,6 +3,7 @@ obj-$(CONFIG_TEST_ASYNC_DRIVER_PROBE) += test_async_driver_probe.o
obj-$(CONFIG_DM_KUNIT_TEST) += root-device-test.o
obj-$(CONFIG_DM_KUNIT_TEST) += platform-device-test.o
+obj-$(CONFIG_DM_KUNIT_TEST) += fwnode-link-test.o
obj-$(CONFIG_DRIVER_PE_KUNIT_TEST) += property-entry-test.o
CFLAGS_property-entry-test.o += $(DISABLE_STRUCTLEAK_PLUGIN)
diff --git a/drivers/base/test/fwnode-link-test.c b/drivers/base/test/fwnode-link-test.c
new file mode 100644
index 000000000000..fee18613d230
--- /dev/null
+++ b/drivers/base/test/fwnode-link-test.c
@@ -0,0 +1,207 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <kunit/platform_device.h>
+#include <kunit/test.h>
+
+#include <linux/device.h>
+#include <linux/fwnode.h>
+#include <linux/platform_device.h>
+
+#define FWNODE_LINK_TEST_DRIVER_NAME "fwnode-link-test"
+
+struct fwnode_link_test_context {
+ struct fwnode_handle consumer;
+ struct fwnode_handle container;
+ struct fwnode_handle supplier_a;
+ struct fwnode_handle supplier_b;
+};
+
+static int fwnode_link_test_probe(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static struct platform_driver fwnode_link_test_driver = {
+ .probe = fwnode_link_test_probe,
+ .driver = {
+ .name = FWNODE_LINK_TEST_DRIVER_NAME,
+ },
+};
+
+static void fwnode_link_test_cleanup(void *data)
+{
+ struct fwnode_link_test_context *context = data;
+
+ fwnode_links_purge(&context->consumer);
+ fwnode_links_purge(&context->container);
+ fwnode_links_purge(&context->supplier_a);
+ fwnode_links_purge(&context->supplier_b);
+}
+
+static struct fwnode_link_test_context *
+fwnode_link_test_init(struct kunit *test)
+{
+ struct fwnode_link_test_context *context;
+ int ret;
+
+ context = kunit_kzalloc(test, sizeof(*context), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, context);
+
+ fwnode_init(&context->consumer, NULL);
+ fwnode_init(&context->container, NULL);
+ fwnode_init(&context->supplier_a, NULL);
+ fwnode_init(&context->supplier_b, NULL);
+ ret = kunit_add_action_or_reset(test, fwnode_link_test_cleanup,
+ context);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ return context;
+}
+
+static struct platform_device *
+fwnode_link_test_register_pdev(struct kunit *test,
+ struct fwnode_handle *fwnode)
+{
+ struct platform_device *pdev;
+ int ret;
+
+ pdev = kunit_platform_device_alloc(test, FWNODE_LINK_TEST_DRIVER_NAME,
+ PLATFORM_DEVID_AUTO);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev);
+
+ device_set_node(&pdev->dev, fwnode);
+ ret = kunit_platform_device_add(test, pdev);
+ if (ret) {
+ KUNIT_FAIL(test, "failed to register platform device: %d", ret);
+ return NULL;
+ }
+
+ return pdev;
+}
+
+static unsigned int fwnode_supplier_count(struct fwnode_handle *fwnode)
+{
+ struct fwnode_link *link;
+ unsigned int count = 0;
+
+ list_for_each_entry(link, &fwnode->suppliers, c_hook)
+ count++;
+
+ return count;
+}
+
+static struct fwnode_link *
+fwnode_find_supplier(struct fwnode_handle *consumer,
+ struct fwnode_handle *supplier)
+{
+ struct fwnode_link *link;
+
+ list_for_each_entry(link, &consumer->suppliers, c_hook) {
+ if (link->supplier == supplier)
+ return link;
+ }
+
+ return NULL;
+}
+
+static void fwnode_link_copy_suppliers_test(struct kunit *test)
+{
+ struct fwnode_link_test_context *context;
+ struct fwnode_link *link;
+ int ret;
+
+ context = fwnode_link_test_init(test);
+ KUNIT_ASSERT_NOT_NULL(test, context);
+
+ ret = fwnode_link_add(&context->container, &context->supplier_a,
+ FWLINK_FLAG_CYCLE);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ ret = fwnode_link_add(&context->container, &context->supplier_b,
+ FWLINK_FLAG_IGNORE);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ ret = fw_devlink_copy_suppliers(&context->consumer,
+ &context->container);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, fwnode_supplier_count(&context->container), 2U);
+ KUNIT_EXPECT_EQ(test, fwnode_supplier_count(&context->consumer), 1U);
+
+ link = fwnode_find_supplier(&context->consumer, &context->supplier_a);
+ KUNIT_ASSERT_NOT_NULL(test, link);
+ KUNIT_EXPECT_EQ(test, link->flags, (u8)0);
+ link = fwnode_find_supplier(&context->consumer, &context->supplier_b);
+ KUNIT_EXPECT_NULL(test, link);
+
+ ret = fw_devlink_copy_suppliers(&context->consumer,
+ &context->container);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, fwnode_supplier_count(&context->consumer), 1U);
+}
+
+static void fwnode_link_copy_before_device_add_test(struct kunit *test)
+{
+ struct fwnode_link_test_context *context;
+ struct platform_device *consumer;
+ struct platform_device *supplier;
+ struct device_link *link;
+ int ret;
+
+ context = fwnode_link_test_init(test);
+ KUNIT_ASSERT_NOT_NULL(test, context);
+
+ ret = fwnode_link_add(&context->container, &context->supplier_a, 0);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ supplier = fwnode_link_test_register_pdev(test, &context->supplier_a);
+ KUNIT_ASSERT_NOT_NULL(test, supplier);
+
+ ret = fw_devlink_copy_suppliers(&context->consumer,
+ &context->container);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ consumer = fwnode_link_test_register_pdev(test, &context->consumer);
+ KUNIT_ASSERT_NOT_NULL(test, consumer);
+
+ KUNIT_EXPECT_EQ(test, fwnode_supplier_count(&context->container), 1U);
+ KUNIT_EXPECT_EQ(test, fwnode_supplier_count(&context->consumer), 0U);
+
+ if (list_empty(&consumer->dev.links.suppliers)) {
+ KUNIT_FAIL(test, "consumer device link was not created");
+ return;
+ }
+ link = list_first_entry(&consumer->dev.links.suppliers,
+ struct device_link, c_node);
+ KUNIT_EXPECT_PTR_EQ(test, link->supplier, &supplier->dev);
+ KUNIT_EXPECT_EQ(test, READ_ONCE(link->status), DL_STATE_ACTIVE);
+
+ ret = fw_devlink_copy_suppliers(&context->consumer,
+ &context->container);
+ KUNIT_EXPECT_EQ(test, ret, -EBUSY);
+}
+
+static struct kunit_case fwnode_link_test_cases[] = {
+ KUNIT_CASE(fwnode_link_copy_suppliers_test),
+ KUNIT_CASE(fwnode_link_copy_before_device_add_test),
+ {}
+};
+
+static int fwnode_link_test_suite_init(struct kunit_suite *suite)
+{
+ return platform_driver_register(&fwnode_link_test_driver);
+}
+
+static void fwnode_link_test_suite_exit(struct kunit_suite *suite)
+{
+ platform_driver_unregister(&fwnode_link_test_driver);
+ device_link_wait_removal();
+}
+
+static struct kunit_suite fwnode_link_test_suite = {
+ .name = "fwnode-link",
+ .suite_init = fwnode_link_test_suite_init,
+ .suite_exit = fwnode_link_test_suite_exit,
+ .test_cases = fwnode_link_test_cases,
+};
+
+kunit_test_suite(fwnode_link_test_suite);
+
+MODULE_DESCRIPTION("KUnit tests for firmware-node links");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/fwnode.h b/include/linux/fwnode.h
index 4e86e6990d28..ad4250fc7afb 100644
--- a/include/linux/fwnode.h
+++ b/include/linux/fwnode.h
@@ -254,6 +254,8 @@ int fwnode_link_add(struct fwnode_handle *con, struct fwnode_handle *sup,
void fwnode_links_purge(struct fwnode_handle *fwnode);
void fw_devlink_purge_absent_suppliers(struct fwnode_handle *fwnode);
void fw_devlink_refresh_fwnode(struct fwnode_handle *fwnode);
+int fw_devlink_copy_suppliers(struct fwnode_handle *to,
+ struct fwnode_handle *from);
bool fw_devlink_is_strict(void);
#endif
--
2.53.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH net-next v3 4/4] driver core: handle managed links for class devices
2026-08-18 6:58 [PATCH net-next v3 0/4] driver core, net: handle fw_devlink for class devices and PHY packages James Hilliard
` (2 preceding siblings ...)
2026-08-18 6:58 ` [PATCH net-next v3 3/4] net: mdio: defer supplier sync during OF population James Hilliard
@ 2026-08-18 6:59 ` James Hilliard
2026-08-18 7:48 ` Andy Shevchenko
2026-08-18 7:15 ` [PATCH net-next v3 0/4] driver core, net: handle fw_devlink for class devices and PHY packages Andy Shevchenko
4 siblings, 1 reply; 9+ messages in thread
From: James Hilliard @ 2026-08-18 6:59 UTC (permalink / raw)
To: Rob Herring, Saravana Kannan, Greg Kroah-Hartman,
Rafael J. Wysocki, Danilo Krummrich, Jonathan Corbet, Shuah Khan,
Andy Shevchenko, Daniel Scally, Heikki Krogerus, Sakari Ailus,
Len Brown, Andrew Lunn, Heiner Kallweit, Russell King,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Christian Marangi
Cc: devicetree, linux-doc, linux-kernel, driver-core, linux-acpi,
netdev, James Hilliard
fw_devlink deliberately lets the nearest device pick up dependencies below
class-backed firmware nodes because driverless class devices do not
currently progress through managed device-link states. This can leave
consumers of class suppliers dormant, while sync-state-only links from
class consumers never reach the successful-probe cleanup.
Track class-only devices through equivalent registration and removal
transitions. Mark them probing before device_add() publishes them, complete
their incoming and outgoing links after class interfaces run, and enter the
unbind and no-driver states during device_del(). Preserve firmware links
below class-backed nodes so later-created descendants can acquire their own
links.
Include class registration in wait_for_device_probe() so supplier unbind
cannot race a class consumer that is still being published. Transition
managed links which existed before device_add(), remove stale
waiting_for_supplier attributes and do not revive a link whose supplier has
already started unbinding.
During supplier teardown, wait for class registration to finish, but move
an already registered class consumer directly to the unbinding link state
because it has no driver to release. Keep consumer-autoremove links valid
until class consumer removal without triggering driver-only cleanup
warnings.
Factor the supplier-bound, consumer-bound and cleanup transitions shared
with driver-backed devices. Activate late links involving an already
functional class endpoint instead of trying to reprobe it, and let
sync-state-only links retire without forcing consumer unbind. Ordinary
supplier links cannot defer class registration, so they must not be the
only mechanism on which a class consumer relies for functionality.
Class devices do not match drivers, so distinguish a genuinely unavailable
supplier from a probing or registered class endpoint before relaxing
inferred links. Preserve normal probe and runtime-PM ordering for those
links. Also recognize class-backed nodes when refreshing dependencies after
firmware-tree overlays.
Exclude the internal device-link class because device-link objects are
registered while the device-links lock is held. Document the class-device
state transitions and add KUnit coverage for supplier and consumer links,
pre-existing links, inferred-link preservation, registration waiters and a
concurrent supplier unbind.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
Changes v2 -> v3:
- Replace the framework-specific proxy cleanup API with generic
class-device managed-link lifecycle handling, as suggested by
Saravana Kannan
- Preserve inferred links to probing and registered class suppliers in
no-driver fallback paths and include class nodes in overlay refresh
- Cover pre-existing and inferred links, wait_for_device_probe()
integration, waiting_for_supplier cleanup and supplier-unbind races
- Document the limits of ordinary supplier links for driverless class
consumers
- Handle registered class consumers during supplier teardown without a
no-op driver release or driver-only cleanup warning
- Derive class handling inside the shared bound helpers and clarify the
lifecycle documentation
---
Documentation/driver-api/device_link.rst | 47 ++-
drivers/base/base.h | 2 +
drivers/base/core.c | 270 +++++++++++-----
drivers/base/dd.c | 21 +-
drivers/base/test/Makefile | 1 +
drivers/base/test/device-link-class-test.c | 479 +++++++++++++++++++++++++++++
include/linux/device.h | 9 +-
7 files changed, 739 insertions(+), 90 deletions(-)
diff --git a/Documentation/driver-api/device_link.rst b/Documentation/driver-api/device_link.rst
index ee913ae16371..c248ec9fa9f8 100644
--- a/Documentation/driver-api/device_link.rst
+++ b/Documentation/driver-api/device_link.rst
@@ -253,6 +253,18 @@ child one.
State machine
=============
+Driverless class devices participate in the managed-link state machine as
+supplier and consumer endpoints, including as consumers of sync-state-only
+proxy links. Their registration is treated like a successful driver probe:
+links are completed after :c:func:`device_add()` has finished running class
+interfaces. During removal, driver-backed consumers are unbound before the
+class device is deleted and its links return to the no-driver state.
+
+Class devices are not probed, so an ordinary supplier link cannot defer their
+registration in the way that it defers a driver probe. Frameworks must not use
+such a link as the only mechanism guaranteeing that a class consumer's
+supplier is functional.
+
.. kernel-doc:: include/linux/device.h
:functions: device_link_state
@@ -271,10 +283,13 @@ State machine
and consumer. If the link is created before any devices are probed, it
is set to ``DL_STATE_DORMANT``.
-* When a supplier device is bound to a driver, links to its consumers
- progress to ``DL_STATE_AVAILABLE``.
- (Call to :c:func:`device_links_driver_bound()` from
- :c:func:`driver_bound()`.)
+* When a supplier device is bound to a driver, links to its consumers progress
+ to ``DL_STATE_AVAILABLE``. Registering a driverless class supplier performs
+ the equivalent transition, or activates a late link whose consumer is
+ already functional.
+ The driver-backed path calls :c:func:`device_links_driver_bound()` from
+ :c:func:`driver_bound()`, while the class-device completion path performs
+ the equivalent transition from :c:func:`device_add()`.
* Before a consumer device is probed, presence of supplier drivers is
verified by checking the consumer device is not in the wait_for_suppliers
@@ -286,17 +301,28 @@ State machine
(Call to :c:func:`wait_for_device_probe()` from
:c:func:`device_links_unbind_consumers()`.)
+ Registering a driverless class consumer similarly moves existing available
+ links to ``DL_STATE_CONSUMER_PROBE`` before publishing the device, and its
+ registration participates in :c:func:`wait_for_device_probe()`. Unlike a
+ driver probe, class registration is not deferred for unavailable suppliers.
+
* If the probe fails, links to suppliers revert back to ``DL_STATE_AVAILABLE``.
(Call to :c:func:`device_links_no_driver()` from :c:func:`really_probe()`.)
* If the probe succeeds, links to suppliers progress to ``DL_STATE_ACTIVE``.
- (Call to :c:func:`device_links_driver_bound()` from :c:func:`driver_bound()`.)
+ A driverless class consumer activates usable incoming links when its
+ registration completes, drops sync-state-only proxy links and preserves
+ links whose suppliers are dormant or unbinding. The driver-backed path
+ calls :c:func:`device_links_driver_bound()` from :c:func:`driver_bound()`,
+ while :c:func:`device_add()` performs the equivalent class-device
+ transition.
* When the consumer's driver is later on removed, links to suppliers revert
back to ``DL_STATE_AVAILABLE``.
(Call to :c:func:`__device_links_no_driver()` from
:c:func:`device_links_driver_cleanup()`, which in turn is called from
- :c:func:`__device_release_driver()`.)
+ :c:func:`__device_release_driver()`.) Removing a driverless class consumer
+ performs the equivalent cleanup from :c:func:`device_del()`.
* Before a supplier's driver is removed, links to consumers that are not
bound to a driver are updated to ``DL_STATE_SUPPLIER_UNBIND``.
@@ -305,14 +331,17 @@ State machine
This prevents the consumers from binding.
(Call to :c:func:`device_links_check_suppliers()` from
:c:func:`really_probe()`.)
- Consumers that are bound are freed from their driver; consumers that are
- probing are waited for until they are done.
+ Driver-backed consumers that are bound are freed from their driver;
+ driverless class consumers remain registered and their links move directly
+ to ``DL_STATE_SUPPLIER_UNBIND``. Consumers that are probing or being
+ registered as class devices are waited for until they are done.
(Call to :c:func:`device_links_unbind_consumers()` from
:c:func:`__device_release_driver()`.)
Once all links to consumers are in ``DL_STATE_SUPPLIER_UNBIND`` state,
the supplier driver is released and the links revert to ``DL_STATE_DORMANT``.
(Call to :c:func:`device_links_driver_cleanup()` from
- :c:func:`__device_release_driver()`.)
+ :c:func:`__device_release_driver()`.) Removing a driverless class supplier
+ follows the same sequence from :c:func:`device_del()`.
API
===
diff --git a/drivers/base/base.h b/drivers/base/base.h
index a5b7abc10ff0..8bdf0a709401 100644
--- a/drivers/base/base.h
+++ b/drivers/base/base.h
@@ -166,6 +166,8 @@ void device_release_driver_internal(struct device *dev, const struct device_driv
void driver_detach(const struct device_driver *drv);
void driver_deferred_probe_del(struct device *dev);
void device_set_deferred_probe_reason(const struct device *dev, struct va_format *vaf);
+void device_probe_begin(void);
+void device_probe_end(void);
static inline int driver_match_device(const struct device_driver *drv,
struct device *dev)
{
diff --git a/drivers/base/core.c b/drivers/base/core.c
index d2f9ff65138c..0c26ba38e6cf 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -213,10 +213,10 @@ static void __fwnode_links_move_consumers(struct fwnode_handle *from,
* @fwnode: fwnode from which to pick up dangling consumers
* @new_sup: fwnode of new supplier
*
- * If the @fwnode has a corresponding struct device and the device supports
- * probing (that is, added to a bus), then we want to let fw_devlink create
- * MANAGED device links to this device, so leave @fwnode and its descendant's
- * fwnode links alone.
+ * If the @fwnode has a corresponding struct device whose device-link state is
+ * managed by the driver core (that is, a bus or class device), then we want to
+ * let fw_devlink create MANAGED device links to this device, so leave @fwnode
+ * and its descendants' fwnode links alone.
*
* Otherwise, move its consumers to the new supplier @new_sup.
*/
@@ -225,7 +225,7 @@ static void __fw_devlink_pickup_dangling_consumers(struct fwnode_handle *fwnode,
{
struct fwnode_handle *child;
- if (fwnode->dev && fwnode->dev->bus)
+ if (fwnode->dev && (fwnode->dev->bus || fwnode->dev->class))
return;
fwnode_set_flag(fwnode, FWNODE_FLAG_NOT_DEVICE);
@@ -259,7 +259,7 @@ void fw_devlink_refresh_fwnode(struct fwnode_handle *fwnode)
/*
* Find the closest ancestor fwnode that has been converted to a device
- * that can bind to a driver (bus device).
+ * whose managed-link lifecycle is handled by the driver core.
*/
fwnode_handle_get(fwnode);
do {
@@ -270,15 +270,15 @@ void fw_devlink_refresh_fwnode(struct fwnode_handle *fwnode)
if (!dev)
continue;
- if (dev->bus)
+ if (dev->bus || dev->class)
break;
put_device(dev);
} while ((fwnode = fwnode_get_next_parent(fwnode)));
/*
- * If none of the ancestor fwnodes have (yet) been converted to a device
- * that can bind to a driver, there's nothing to fix up.
+ * If none of the ancestor fwnodes have (yet) been converted to such a
+ * device, there's nothing to fix up.
*/
if (!fwnode)
return;
@@ -287,10 +287,9 @@ void fw_devlink_refresh_fwnode(struct fwnode_handle *fwnode)
"Don't multithread overlaying and probing the same device!\n");
/*
- * If the device has already bound to a driver, then we need to redo
- * some of the work that was done after the device was bound to a
- * driver. If the device hasn't bound to a driver, running things too
- * soon would incorrectly pick up consumers that it shouldn't.
+ * If the device's managed links are available, redo some of the work
+ * performed when it reached that state. Running this too soon would
+ * incorrectly pick up consumers that it shouldn't.
*/
if (dev->links.status == DL_DEV_DRIVER_BOUND) {
fw_devlink_pickup_dangling_consumers(dev);
@@ -1088,6 +1087,12 @@ static bool dev_is_best_effort(struct device *dev)
(dev->fwnode && fwnode_test_flag(dev->fwnode, FWNODE_FLAG_BEST_EFFORT));
}
+static bool dev_is_unmatchable(const struct device *dev)
+{
+ return READ_ONCE(dev->links.status) == DL_DEV_NO_DRIVER &&
+ !dev_can_match(dev);
+}
+
static struct fwnode_handle *fwnode_links_check_suppliers(
struct fwnode_handle *fwnode)
{
@@ -1152,7 +1157,7 @@ int device_links_check_suppliers(struct device *dev)
if (dev_is_best_effort(dev) &&
device_link_test(link, DL_FLAG_INFERRED) &&
- !dev_can_match(link->supplier)) {
+ dev_is_unmatchable(link->supplier)) {
ret = -EAGAIN;
continue;
}
@@ -1307,6 +1312,33 @@ static void device_link_drop_managed(struct device_link *link)
kref_put(&link->kref, __device_link_del);
}
+static bool device_links_track_class(const struct device *dev)
+{
+ /* Device-link objects are class devices registered with the lock held. */
+ return dev->class && !dev->bus && dev->class != &devlink_class;
+}
+
+static void device_links_class_start(struct device *dev)
+{
+ struct device_link *link;
+
+ device_probe_begin();
+ device_links_write_lock();
+ WARN_ON(dev->links.status != DL_DEV_NO_DRIVER);
+ list_for_each_entry(link, &dev->links.suppliers, c_node) {
+ if (!device_link_test(link, DL_FLAG_MANAGED))
+ continue;
+
+ /* Do not revive a link whose supplier is already unbinding. */
+ if (link->status == DL_STATE_AVAILABLE ||
+ (device_link_test(link, DL_FLAG_SYNC_STATE_ONLY) &&
+ link->status != DL_STATE_SUPPLIER_UNBIND))
+ WRITE_ONCE(link->status, DL_STATE_CONSUMER_PROBE);
+ }
+ dev->links.status = DL_DEV_PROBING;
+ device_links_write_unlock();
+}
+
static ssize_t waiting_for_supplier_show(struct device *dev,
const struct device_attribute *attr,
char *buf)
@@ -1356,43 +1388,11 @@ void device_links_force_bind(struct device *dev)
device_links_write_unlock();
}
-/**
- * device_links_driver_bound - Update device links after probing its driver.
- * @dev: Device to update the links for.
- *
- * The probe has been successful, so update links from this device to any
- * consumers by changing their status to "available".
- *
- * Also change the status of @dev's links to suppliers to "active".
- *
- * Links without the DL_FLAG_MANAGED flag set are ignored.
- */
-void device_links_driver_bound(struct device *dev)
+static void __device_links_supplier_bound(struct device *dev,
+ struct list_head *sync_list)
{
- struct device_link *link, *ln;
- LIST_HEAD(sync_list);
-
- /*
- * If a device binds successfully, it's expected to have created all
- * the device links it needs to or make new device links as it needs
- * them. So, fw_devlink no longer needs to create device links to any
- * of the device's suppliers.
- *
- * Also, if a child firmware node of this bound device is not added as a
- * device by now, assume it is never going to be added. Make this bound
- * device the fallback supplier to the dangling consumers of the child
- * firmware node because this bound device is probably implementing the
- * child firmware node functionality and we don't want the dangling
- * consumers to defer probe indefinitely waiting for a device for the
- * child firmware node.
- */
- if (dev->fwnode && dev->fwnode->dev == dev) {
- fwnode_links_purge_suppliers(dev->fwnode);
- fw_devlink_pickup_dangling_consumers(dev);
- }
- device_remove_file(dev, &dev_attr_waiting_for_supplier);
-
- device_links_write_lock();
+ struct device_link *link;
+ bool class_device = device_links_track_class(dev);
list_for_each_entry(link, &dev->links.consumers, s_node) {
if (!device_link_test(link, DL_FLAG_MANAGED))
@@ -1409,16 +1409,35 @@ void device_links_driver_bound(struct device *dev)
continue;
WARN_ON(link->status != DL_STATE_DORMANT);
- WRITE_ONCE(link->status, DL_STATE_AVAILABLE);
+ /*
+ * A class interface can add a link from an already-bound consumer
+ * while the class supplier is being registered. Likewise, a class
+ * consumer has no driver probe to retry when a late supplier binds.
+ * Those consumers are already functional, so activate their links.
+ */
+ if ((class_device ||
+ device_links_track_class(link->consumer)) &&
+ link->consumer->links.status == DL_DEV_DRIVER_BOUND) {
+ WRITE_ONCE(link->status, DL_STATE_ACTIVE);
+ } else {
+ WRITE_ONCE(link->status, DL_STATE_AVAILABLE);
- if (device_link_test(link, DL_FLAG_AUTOPROBE_CONSUMER))
- driver_deferred_probe_add(link->consumer);
+ if (device_link_test(link, DL_FLAG_AUTOPROBE_CONSUMER))
+ driver_deferred_probe_add(link->consumer);
+ }
}
if (defer_sync_state_count)
__device_links_supplier_defer_sync(dev);
else
- __device_links_queue_sync_state(dev, &sync_list);
+ __device_links_queue_sync_state(dev, sync_list);
+}
+
+static void __device_links_consumer_bound(struct device *dev,
+ struct list_head *sync_list)
+{
+ struct device_link *link, *ln;
+ bool class_device = device_links_track_class(dev);
list_for_each_entry_safe(link, ln, &dev->links.suppliers, c_node) {
struct device *supplier;
@@ -1431,13 +1450,13 @@ void device_links_driver_bound(struct device *dev)
/*
* When DL_FLAG_SYNC_STATE_ONLY is set, it means no
* other DL_MANAGED_LINK_FLAGS have been set. So, it's
- * save to drop the managed link completely.
+ * safe to drop the managed link completely.
*/
device_link_drop_managed(link);
- } else if (dev_is_best_effort(dev) &&
+ } else if (!class_device && dev_is_best_effort(dev) &&
device_link_test(link, DL_FLAG_INFERRED) &&
link->status != DL_STATE_CONSUMER_PROBE &&
- !dev_can_match(link->supplier)) {
+ dev_is_unmatchable(link->supplier)) {
/*
* When dev_is_best_effort() is true, we ignore device
* links to suppliers that don't have a driver. If the
@@ -1446,9 +1465,17 @@ void device_links_driver_bound(struct device *dev)
* (consumer probed before supplier). So delete it.
*/
device_link_drop_managed(link);
- } else {
+ } else if (!class_device) {
WARN_ON(link->status != DL_STATE_CONSUMER_PROBE);
WRITE_ONCE(link->status, DL_STATE_ACTIVE);
+ } else if (link->status == DL_STATE_CONSUMER_PROBE ||
+ link->status == DL_STATE_AVAILABLE) {
+ WRITE_ONCE(link->status, DL_STATE_ACTIVE);
+ } else {
+ WARN_ON(link->status != DL_STATE_ACTIVE &&
+ link->status != DL_STATE_DORMANT &&
+ link->status != DL_STATE_SUPPLIER_UNBIND);
+ continue;
}
/*
@@ -1460,10 +1487,67 @@ void device_links_driver_bound(struct device *dev)
if (defer_sync_state_count)
__device_links_supplier_defer_sync(supplier);
else
- __device_links_queue_sync_state(supplier, &sync_list);
+ __device_links_queue_sync_state(supplier, sync_list);
}
dev->links.status = DL_DEV_DRIVER_BOUND;
+}
+
+static void device_links_class_bound(struct device *dev)
+{
+ LIST_HEAD(sync_list);
+
+ device_remove_file(dev, &dev_attr_waiting_for_supplier);
+
+ device_links_write_lock();
+ __device_links_supplier_bound(dev, &sync_list);
+ __device_links_consumer_bound(dev, &sync_list);
+ device_links_write_unlock();
+
+ device_links_flush_sync_list(&sync_list, NULL);
+ driver_deferred_probe_trigger();
+ device_probe_end();
+}
+
+/**
+ * device_links_driver_bound - Update device links after probing its driver.
+ * @dev: Device to update the links for.
+ *
+ * The probe has been successful, so update links from this device to any
+ * consumers by changing their status to "available".
+ *
+ * Also change the status of @dev's links to suppliers to "active".
+ *
+ * Links without the DL_FLAG_MANAGED flag set are ignored.
+ */
+void device_links_driver_bound(struct device *dev)
+{
+ LIST_HEAD(sync_list);
+
+ /*
+ * If a device binds successfully, it's expected to have created all
+ * the device links it needs to or make new device links as it needs
+ * them. So, fw_devlink no longer needs to create device links to any
+ * of the device's suppliers.
+ *
+ * Also, if a child firmware node of this bound device is not added as a
+ * device by now, assume it is never going to be added. Make this bound
+ * device the fallback supplier to the dangling consumers of the child
+ * firmware node because this bound device is probably implementing the
+ * child firmware node functionality and we don't want the dangling
+ * consumers to defer probe indefinitely waiting for a device for the
+ * child firmware node.
+ */
+ if (dev->fwnode && dev->fwnode->dev == dev) {
+ fwnode_links_purge_suppliers(dev->fwnode);
+ fw_devlink_pickup_dangling_consumers(dev);
+ }
+
+ device_remove_file(dev, &dev_attr_waiting_for_supplier);
+
+ device_links_write_lock();
+ __device_links_supplier_bound(dev, &sync_list);
+ __device_links_consumer_bound(dev, &sync_list);
device_links_write_unlock();
@@ -1548,17 +1632,7 @@ void device_links_no_driver(struct device *dev)
device_links_write_unlock();
}
-/**
- * device_links_driver_cleanup - Update links after driver removal.
- * @dev: Device whose driver has just gone away.
- *
- * Update links to consumers for @dev by changing their status to "dormant" and
- * invoke %__device_links_no_driver() to update links to suppliers for it as
- * appropriate.
- *
- * Links without the DL_FLAG_MANAGED flag set are ignored.
- */
-void device_links_driver_cleanup(struct device *dev)
+static void device_links_cleanup(struct device *dev)
{
struct device_link *link, *ln;
@@ -1568,7 +1642,8 @@ void device_links_driver_cleanup(struct device *dev)
if (!device_link_test(link, DL_FLAG_MANAGED))
continue;
- WARN_ON(device_link_test(link, DL_FLAG_AUTOREMOVE_CONSUMER));
+ WARN_ON(device_link_test(link, DL_FLAG_AUTOREMOVE_CONSUMER) &&
+ !device_links_track_class(link->consumer));
WARN_ON(link->status != DL_STATE_SUPPLIER_UNBIND);
/*
@@ -1589,6 +1664,21 @@ void device_links_driver_cleanup(struct device *dev)
device_links_write_unlock();
}
+/**
+ * device_links_driver_cleanup - Update links after driver removal.
+ * @dev: Device whose driver has just gone away.
+ *
+ * Update links to consumers for @dev by changing their status to "dormant" and
+ * invoke %__device_links_no_driver() to update links to suppliers for it as
+ * appropriate.
+ *
+ * Links without the DL_FLAG_MANAGED flag set are ignored.
+ */
+void device_links_driver_cleanup(struct device *dev)
+{
+ device_links_cleanup(dev);
+}
+
/**
* device_links_busy - Check if there are any busy links to consumers.
* @dev: Device to check.
@@ -1599,7 +1689,9 @@ void device_links_driver_cleanup(struct device *dev)
* state to "supplier unbind" to prevent the consumer from being probed
* successfully going forward.
*
- * Return 'false' if there are no probing or active consumers.
+ * Sync-state-only links are moved to "supplier unbind" without making the
+ * supplier busy, since they do not require the consumer to be unbound.
+ * Return 'false' if there are no other probing or active consumers.
*
* Links without the DL_FLAG_MANAGED flag set are ignored.
*/
@@ -1613,6 +1705,11 @@ bool device_links_busy(struct device *dev)
list_for_each_entry(link, &dev->links.consumers, s_node) {
if (!device_link_test(link, DL_FLAG_MANAGED))
continue;
+ /* Sync-state-only links do not require consumer unbinding. */
+ if (device_link_test(link, DL_FLAG_SYNC_STATE_ONLY)) {
+ WRITE_ONCE(link->status, DL_STATE_SUPPLIER_UNBIND);
+ continue;
+ }
if (link->status == DL_STATE_CONSUMER_PROBE
|| link->status == DL_STATE_ACTIVE) {
@@ -1668,6 +1765,10 @@ void device_links_unbind_consumers(struct device *dev)
if (status == DL_STATE_ACTIVE) {
struct device *consumer = link->consumer;
+ /* Class consumers have no driver to release. */
+ if (device_links_track_class(consumer))
+ continue;
+
get_device(consumer);
device_links_write_unlock();
@@ -1826,7 +1927,7 @@ static int fw_devlink_no_driver(struct device *dev, void *data)
{
struct device_link *link = to_devlink(dev);
- if (!dev_can_match(link->supplier))
+ if (dev_is_unmatchable(link->supplier))
fw_devlink_relax_link(link);
return 0;
@@ -3704,6 +3805,7 @@ int device_add(struct device *dev)
struct device *parent;
struct kobject *kobj;
struct class_interface *class_intf;
+ bool track_class_links = false;
int error = -EINVAL;
struct kobject *glue_dir = NULL;
@@ -3737,6 +3839,10 @@ int device_add(struct device *dev)
if (error)
goto name_error;
+ track_class_links = device_links_track_class(dev);
+ if (track_class_links)
+ device_links_class_start(dev);
+
pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
parent = get_device(dev->parent);
@@ -3838,7 +3944,7 @@ int device_add(struct device *dev)
* match with any driver, don't block its consumers from probing in
* case the consumer device is able to operate without this supplier.
*/
- if (dev->fwnode && fw_devlink_drv_reg_done && !dev_can_match(dev))
+ if (dev->fwnode && fw_devlink_drv_reg_done && dev_is_unmatchable(dev))
fw_devlink_unblock_consumers(dev);
if (parent)
@@ -3858,6 +3964,8 @@ int device_add(struct device *dev)
mutex_unlock(&sp->mutex);
subsys_put(sp);
}
+ if (track_class_links)
+ device_links_class_bound(dev);
done:
put_device(dev);
return error;
@@ -3886,6 +3994,10 @@ int device_add(struct device *dev)
parent_error:
put_device(parent);
name_error:
+ if (track_class_links) {
+ device_links_no_driver(dev);
+ device_probe_end();
+ }
kfree(dev->p);
dev->p = NULL;
goto done;
@@ -3980,9 +4092,17 @@ void device_del(struct device *dev)
struct device *parent = dev->parent;
struct kobject *glue_dir = NULL;
struct class_interface *class_intf;
+ bool track_class_links = device_links_track_class(dev);
unsigned int noio_flag;
device_lock(dev);
+ if (track_class_links) {
+ while (device_links_busy(dev)) {
+ device_unlock(dev);
+ device_links_unbind_consumers(dev);
+ device_lock(dev);
+ }
+ }
kill_device(dev);
device_unlock(dev);
@@ -4024,6 +4144,8 @@ void device_del(struct device *dev)
device_pm_remove(dev);
driver_deferred_probe_del(dev);
device_platform_notify_remove(dev);
+ if (track_class_links)
+ device_links_cleanup(dev);
device_links_purge(dev);
/*
diff --git a/drivers/base/dd.c b/drivers/base/dd.c
index 60c005223844..61288fcceb5b 100644
--- a/drivers/base/dd.c
+++ b/drivers/base/dd.c
@@ -555,9 +555,21 @@ int device_bind_driver(struct device *dev)
}
EXPORT_SYMBOL_GPL(device_bind_driver);
+/* Driver probes and driverless class-device registrations in progress. */
static atomic_t probe_count = ATOMIC_INIT(0);
static DECLARE_WAIT_QUEUE_HEAD(probe_waitqueue);
+void device_probe_begin(void)
+{
+ atomic_inc(&probe_count);
+}
+
+void device_probe_end(void)
+{
+ atomic_dec(&probe_count);
+ wake_up_all(&probe_waitqueue);
+}
+
static ssize_t state_synced_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
@@ -797,7 +809,7 @@ static int really_probe_debug(struct device *dev, const struct device_driver *dr
/**
* driver_probe_done
- * Determine if the probe sequence is finished or not.
+ * Determine if the probe and class-device registration sequence is finished.
*
* Should somehow figure out how to use a semaphore, not an atomic variable...
*/
@@ -811,7 +823,7 @@ bool __init driver_probe_done(void)
/**
* wait_for_device_probe
- * Wait for device probing to be completed.
+ * Wait for device probing and driverless class registration to be completed.
*/
void wait_for_device_probe(void)
{
@@ -894,7 +906,7 @@ static int driver_probe_device(const struct device_driver *drv, struct device *d
int trigger_count = atomic_read(&deferred_trigger_count);
int ret;
- atomic_inc(&probe_count);
+ device_probe_begin();
ret = __driver_probe_device(drv, dev);
if (ret == -EPROBE_DEFER || ret == EPROBE_DEFER) {
driver_deferred_probe_add(dev);
@@ -906,8 +918,7 @@ static int driver_probe_device(const struct device_driver *drv, struct device *d
!defer_all_probes)
driver_deferred_probe_trigger();
}
- atomic_dec(&probe_count);
- wake_up_all(&probe_waitqueue);
+ device_probe_end();
return ret;
}
diff --git a/drivers/base/test/Makefile b/drivers/base/test/Makefile
index d871a71dbbaa..c33c615eaabf 100644
--- a/drivers/base/test/Makefile
+++ b/drivers/base/test/Makefile
@@ -3,6 +3,7 @@ obj-$(CONFIG_TEST_ASYNC_DRIVER_PROBE) += test_async_driver_probe.o
obj-$(CONFIG_DM_KUNIT_TEST) += root-device-test.o
obj-$(CONFIG_DM_KUNIT_TEST) += platform-device-test.o
+obj-$(CONFIG_DM_KUNIT_TEST) += device-link-class-test.o
obj-$(CONFIG_DM_KUNIT_TEST) += fwnode-link-test.o
obj-$(CONFIG_DRIVER_PE_KUNIT_TEST) += property-entry-test.o
diff --git a/drivers/base/test/device-link-class-test.c b/drivers/base/test/device-link-class-test.c
new file mode 100644
index 000000000000..7d52efb55aa5
--- /dev/null
+++ b/drivers/base/test/device-link-class-test.c
@@ -0,0 +1,479 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <kunit/platform_device.h>
+#include <kunit/resource.h>
+#include <kunit/test.h>
+
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/device/driver.h>
+#include <linux/fwnode.h>
+#include <linux/jiffies.h>
+#include <linux/kernfs.h>
+#include <linux/kthread.h>
+#include <linux/platform_device.h>
+#include <linux/sched/task.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+
+#define TEST_CLASS_NAME "device-link-test"
+#define TEST_DRIVER_NAME "device-link-test-consumer"
+
+enum test_class_action {
+ TEST_CLASS_NONE,
+ TEST_CLASS_SUPPLIER,
+ TEST_CLASS_SYNC_CONSUMER,
+ TEST_CLASS_OBSERVE,
+ TEST_CLASS_WAIT,
+ TEST_CLASS_UNBIND_RACE,
+};
+
+struct device_link_class_context {
+ enum test_class_action action;
+ struct device *endpoint;
+ struct device_link *link;
+ struct completion waiter_started;
+ struct completion waiter_done;
+ struct task_struct *waiter;
+ struct completion unbind_started;
+ struct completion unbind_done;
+ struct task_struct *unbinder;
+ bool waiter_completed_during_add;
+ bool unbind_completed_during_add;
+ bool waiting_for_supplier_seen;
+};
+
+struct test_class_device {
+ struct device dev;
+ struct fwnode_handle fwnode;
+ struct fwnode_handle missing_supplier;
+ bool added;
+};
+
+static struct class *test_class;
+static struct device_link_class_context *test_context;
+static atomic_t remove_count;
+static atomic_t sync_count;
+
+static int test_driver_probe(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static void test_driver_remove(struct platform_device *pdev)
+{
+ atomic_inc(&remove_count);
+}
+
+static void test_driver_sync_state(struct device *dev)
+{
+ atomic_inc(&sync_count);
+}
+
+static struct platform_driver test_driver = {
+ .probe = test_driver_probe,
+ .remove = test_driver_remove,
+ .driver = {
+ .name = TEST_DRIVER_NAME,
+ .sync_state = test_driver_sync_state,
+ },
+};
+
+static bool test_has_waiting_for_supplier(struct device *dev)
+{
+ struct kernfs_node *kn;
+
+ kn = sysfs_get_dirent(dev->kobj.sd, "waiting_for_supplier");
+ if (kn)
+ kernfs_put(kn);
+
+ return !!kn;
+}
+
+static int test_wait_for_probe(void *data)
+{
+ struct device_link_class_context *context = data;
+
+ complete(&context->waiter_started);
+ wait_for_device_probe();
+ complete(&context->waiter_done);
+
+ return 0;
+}
+
+static int test_unbind_supplier(void *data)
+{
+ struct device_link_class_context *context = data;
+
+ complete(&context->unbind_started);
+ device_release_driver(context->endpoint);
+ complete(&context->unbind_done);
+
+ return 0;
+}
+
+static int test_class_add(struct device *dev)
+{
+ struct device_link_class_context *context = test_context;
+
+ if (!context)
+ return 0;
+
+ switch (context->action) {
+ case TEST_CLASS_SUPPLIER:
+ context->link = device_link_add(context->endpoint, dev,
+ DL_FLAG_INFERRED |
+ DL_FLAG_AUTOPROBE_CONSUMER);
+ return context->link ? 0 : -ENOMEM;
+ case TEST_CLASS_SYNC_CONSUMER:
+ context->link = device_link_add(dev, context->endpoint,
+ DL_FLAG_INFERRED |
+ DL_FLAG_SYNC_STATE_ONLY);
+ return context->link ? 0 : -ENOMEM;
+ case TEST_CLASS_OBSERVE:
+ context->waiting_for_supplier_seen =
+ test_has_waiting_for_supplier(dev);
+ return 0;
+ case TEST_CLASS_WAIT:
+ context->waiter = kthread_create(test_wait_for_probe, context,
+ "device-link-wait");
+ if (IS_ERR(context->waiter))
+ return PTR_ERR(context->waiter);
+ get_task_struct(context->waiter);
+ wake_up_process(context->waiter);
+ wait_for_completion(&context->waiter_started);
+ context->waiter_completed_during_add =
+ wait_for_completion_timeout(&context->waiter_done,
+ msecs_to_jiffies(50));
+ return 0;
+ case TEST_CLASS_UNBIND_RACE:
+ context->link = device_link_add(dev, context->endpoint, 0);
+ if (!context->link)
+ return -ENOMEM;
+ context->unbinder = kthread_create(test_unbind_supplier, context,
+ "device-link-unbind");
+ if (IS_ERR(context->unbinder))
+ return PTR_ERR(context->unbinder);
+ get_task_struct(context->unbinder);
+ wake_up_process(context->unbinder);
+ wait_for_completion(&context->unbind_started);
+ context->unbind_completed_during_add =
+ wait_for_completion_timeout(&context->unbind_done,
+ msecs_to_jiffies(50));
+ return 0;
+ case TEST_CLASS_NONE:
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static struct class_interface test_class_interface = {
+ .add_dev = test_class_add,
+};
+
+static void test_unregister_device(void *data)
+{
+ device_unregister(data);
+}
+
+static void test_resume_sync_state(void *data)
+{
+ device_links_supplier_sync_state_resume();
+}
+
+static struct platform_device *
+test_register_platform_device(struct kunit *test)
+{
+ struct platform_device_info info = {
+ .name = TEST_DRIVER_NAME,
+ .id = PLATFORM_DEVID_AUTO,
+ };
+ struct platform_device *pdev;
+
+ pdev = kunit_platform_device_register_full(test, &info);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev);
+ KUNIT_ASSERT_EQ(test, pdev->dev.links.status, DL_DEV_DRIVER_BOUND);
+
+ return pdev;
+}
+
+static void test_class_device_release(struct device *dev)
+{
+ struct test_class_device *tdev =
+ container_of(dev, struct test_class_device, dev);
+
+ fwnode_links_purge(&tdev->fwnode);
+ fwnode_links_purge(&tdev->missing_supplier);
+ kfree(tdev);
+}
+
+static void test_class_device_cleanup(void *data)
+{
+ struct test_class_device *tdev = data;
+
+ if (tdev->added)
+ device_unregister(&tdev->dev);
+ else
+ put_device(&tdev->dev);
+}
+
+static struct test_class_device *
+test_class_device_alloc(struct kunit *test, const char *name)
+{
+ struct test_class_device *tdev;
+ int ret;
+
+ tdev = kzalloc_obj(*tdev);
+ KUNIT_ASSERT_NOT_NULL(test, tdev);
+
+ device_initialize(&tdev->dev);
+ fwnode_init(&tdev->fwnode, NULL);
+ fwnode_init(&tdev->missing_supplier, NULL);
+ tdev->dev.class = test_class;
+ tdev->dev.release = test_class_device_release;
+ device_set_node(&tdev->dev, &tdev->fwnode);
+ ret = dev_set_name(&tdev->dev, "%s", name);
+ if (ret) {
+ put_device(&tdev->dev);
+ KUNIT_FAIL(test, "failed to name class device: %d", ret);
+ return NULL;
+ }
+
+ ret = kunit_add_action_or_reset(test, test_class_device_cleanup, tdev);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ return tdev;
+}
+
+static void device_link_class_supplier_test(struct kunit *test)
+{
+ struct device_link_class_context *context = test->priv;
+ struct platform_device *consumer;
+ struct device *supplier;
+ int ret;
+
+ consumer = test_register_platform_device(test);
+ context->action = TEST_CLASS_SUPPLIER;
+ context->endpoint = &consumer->dev;
+
+ supplier = device_create(test_class, NULL, 0, NULL,
+ TEST_CLASS_NAME "-supplier");
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, supplier);
+ ret = kunit_add_action_or_reset(test, test_unregister_device, supplier);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ KUNIT_ASSERT_NOT_NULL(test, context->link);
+ KUNIT_EXPECT_EQ(test, supplier->links.status, DL_DEV_DRIVER_BOUND);
+ KUNIT_EXPECT_EQ(test, READ_ONCE(context->link->status), DL_STATE_ACTIVE);
+ KUNIT_EXPECT_TRUE(test, device_link_test(context->link,
+ DL_FLAG_AUTOPROBE_CONSUMER));
+ KUNIT_EXPECT_FALSE(test, device_link_test(context->link,
+ DL_FLAG_SYNC_STATE_ONLY));
+
+ kunit_release_action(test, test_unregister_device, supplier);
+ device_link_wait_removal();
+
+ KUNIT_EXPECT_EQ(test, atomic_read(&remove_count), 1);
+ KUNIT_EXPECT_EQ(test, consumer->dev.links.status, DL_DEV_NO_DRIVER);
+}
+
+static void device_link_class_consumer_sync_test(struct kunit *test)
+{
+ struct device_link_class_context *context = test->priv;
+ struct platform_device *supplier;
+ struct device *consumer;
+ int ret;
+
+ device_links_supplier_sync_state_pause();
+ ret = kunit_add_action_or_reset(test, test_resume_sync_state, NULL);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ supplier = test_register_platform_device(test);
+ context->action = TEST_CLASS_SYNC_CONSUMER;
+ context->endpoint = &supplier->dev;
+
+ consumer = device_create(test_class, NULL, 0, NULL,
+ TEST_CLASS_NAME "-consumer");
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, consumer);
+ ret = kunit_add_action_or_reset(test, test_unregister_device, consumer);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ KUNIT_ASSERT_NOT_NULL(test, context->link);
+ KUNIT_EXPECT_EQ(test, consumer->links.status, DL_DEV_DRIVER_BOUND);
+ KUNIT_EXPECT_TRUE(test, list_empty(&consumer->links.suppliers));
+ kunit_release_action(test, test_resume_sync_state, NULL);
+ KUNIT_EXPECT_EQ(test, atomic_read(&sync_count), 1);
+}
+
+static void device_link_class_existing_consumer_link_test(struct kunit *test)
+{
+ struct device_link_class_context *context = test->priv;
+ struct test_class_device *consumer;
+ struct platform_device *supplier;
+ struct device_link *link;
+ int ret;
+
+ supplier = test_register_platform_device(test);
+ consumer = test_class_device_alloc(test,
+ TEST_CLASS_NAME "-existing-link");
+ KUNIT_ASSERT_NOT_NULL(test, consumer);
+
+ link = device_link_add(&consumer->dev, &supplier->dev,
+ DL_FLAG_AUTOREMOVE_CONSUMER);
+ KUNIT_ASSERT_NOT_NULL(test, link);
+ KUNIT_ASSERT_EQ(test, READ_ONCE(link->status), DL_STATE_AVAILABLE);
+ ret = fwnode_link_add(&consumer->fwnode,
+ &consumer->missing_supplier, 0);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ context->action = TEST_CLASS_OBSERVE;
+ ret = device_add(&consumer->dev);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ consumer->added = true;
+
+ KUNIT_EXPECT_EQ(test, consumer->dev.links.status, DL_DEV_DRIVER_BOUND);
+ KUNIT_EXPECT_EQ(test, READ_ONCE(link->status), DL_STATE_ACTIVE);
+ KUNIT_EXPECT_TRUE(test, context->waiting_for_supplier_seen);
+ KUNIT_EXPECT_FALSE(test, test_has_waiting_for_supplier(&consumer->dev));
+}
+
+static void device_link_class_registration_wait_test(struct kunit *test)
+{
+ struct device_link_class_context *context = test->priv;
+ struct device *dev;
+ unsigned long timeout;
+ int ret;
+
+ wait_for_device_probe();
+ context->action = TEST_CLASS_WAIT;
+ dev = device_create(test_class, NULL, 0, NULL,
+ TEST_CLASS_NAME "-wait");
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+ ret = kunit_add_action_or_reset(test, test_unregister_device, dev);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ KUNIT_EXPECT_FALSE(test, context->waiter_completed_during_add);
+ timeout = wait_for_completion_timeout(&context->waiter_done,
+ msecs_to_jiffies(1000));
+ KUNIT_EXPECT_GT(test, timeout, 0UL);
+}
+
+static void device_link_class_supplier_unbind_race_test(struct kunit *test)
+{
+ struct device_link_class_context *context = test->priv;
+ struct platform_device *supplier;
+ struct device *consumer;
+ unsigned long timeout;
+ int ret;
+
+ supplier = test_register_platform_device(test);
+ context->action = TEST_CLASS_UNBIND_RACE;
+ context->endpoint = &supplier->dev;
+ consumer = device_create(test_class, NULL, 0, NULL,
+ TEST_CLASS_NAME "-unbind-race");
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, consumer);
+ ret = kunit_add_action_or_reset(test, test_unregister_device, consumer);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ KUNIT_ASSERT_NOT_NULL(test, context->link);
+ KUNIT_EXPECT_FALSE(test, context->unbind_completed_during_add);
+ timeout = wait_for_completion_timeout(&context->unbind_done,
+ msecs_to_jiffies(1000));
+ KUNIT_EXPECT_GT(test, timeout, 0UL);
+ KUNIT_EXPECT_EQ(test, atomic_read(&remove_count), 1);
+ KUNIT_EXPECT_EQ(test, supplier->dev.links.status, DL_DEV_NO_DRIVER);
+}
+
+static int device_link_class_test_init(struct kunit *test)
+{
+ struct device_link_class_context *context;
+
+ context = kunit_kzalloc(test, sizeof(*context), GFP_KERNEL);
+ if (!context)
+ return -ENOMEM;
+
+ init_completion(&context->waiter_started);
+ init_completion(&context->waiter_done);
+ init_completion(&context->unbind_started);
+ init_completion(&context->unbind_done);
+ test->priv = context;
+ test_context = context;
+ atomic_set(&remove_count, 0);
+ atomic_set(&sync_count, 0);
+
+ return 0;
+}
+
+static void device_link_class_test_exit(struct kunit *test)
+{
+ struct device_link_class_context *context = test->priv;
+
+ if (context->waiter && !IS_ERR(context->waiter)) {
+ kthread_stop(context->waiter);
+ put_task_struct(context->waiter);
+ }
+ if (context->unbinder && !IS_ERR(context->unbinder)) {
+ kthread_stop(context->unbinder);
+ put_task_struct(context->unbinder);
+ }
+ test_context = NULL;
+}
+
+static int device_link_class_suite_init(struct kunit_suite *suite)
+{
+ int ret;
+
+ test_class = class_create(TEST_CLASS_NAME);
+ if (IS_ERR(test_class))
+ return PTR_ERR(test_class);
+
+ ret = platform_driver_register(&test_driver);
+ if (ret)
+ goto destroy_class;
+
+ test_class_interface.class = test_class;
+ ret = class_interface_register(&test_class_interface);
+ if (ret)
+ goto unregister_driver;
+
+ return 0;
+
+unregister_driver:
+ platform_driver_unregister(&test_driver);
+destroy_class:
+ class_destroy(test_class);
+
+ return ret;
+}
+
+static void device_link_class_suite_exit(struct kunit_suite *suite)
+{
+ class_interface_unregister(&test_class_interface);
+ platform_driver_unregister(&test_driver);
+ class_destroy(test_class);
+ device_link_wait_removal();
+}
+
+static struct kunit_case device_link_class_test_cases[] = {
+ KUNIT_CASE(device_link_class_supplier_test),
+ KUNIT_CASE(device_link_class_consumer_sync_test),
+ KUNIT_CASE(device_link_class_existing_consumer_link_test),
+ KUNIT_CASE(device_link_class_registration_wait_test),
+ KUNIT_CASE(device_link_class_supplier_unbind_race_test),
+ {}
+};
+
+static struct kunit_suite device_link_class_test_suite = {
+ .name = "device-link-class",
+ .suite_init = device_link_class_suite_init,
+ .suite_exit = device_link_class_suite_exit,
+ .init = device_link_class_test_init,
+ .exit = device_link_class_test_exit,
+ .test_cases = device_link_class_test_cases,
+};
+
+kunit_test_suite(device_link_class_test_suite);
+
+MODULE_DESCRIPTION("KUnit tests for class device links");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/device.h b/include/linux/device.h
index aee79fd6b32b..3783744df86e 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -446,11 +446,16 @@ enum device_link_state {
#define DL_FLAG_CYCLE BIT(9)
/**
- * enum dl_dev_state - Device driver presence tracking information.
+ * enum dl_dev_state - Device availability tracking information.
* @DL_DEV_NO_DRIVER: There is no driver attached to the device.
* @DL_DEV_PROBING: A driver is probing.
* @DL_DEV_DRIVER_BOUND: The driver has been bound to the device.
* @DL_DEV_UNBINDING: The driver is unbinding from the device.
+ *
+ * Class devices which are not on a bus use the same states for their
+ * registration lifecycle. In that case, PROBING and DRIVER_BOUND mean that
+ * device_add() is in progress or has completed, respectively, and UNBINDING
+ * means that device_del() is in progress.
*/
enum dl_dev_state {
DL_DEV_NO_DRIVER = 0,
@@ -480,7 +485,7 @@ enum device_removable {
* @suppliers: List of links to supplier devices.
* @consumers: List of links to consumer devices.
* @defer_sync: Hook to global list of devices that have deferred sync_state.
- * @status: Driver status information.
+ * @status: Driver or class-device availability status.
*/
struct dev_links_info {
struct list_head suppliers;
--
2.53.0
^ permalink raw reply related [flat|nested] 9+ messages in thread