driver core: fw_devlink: Allow marking a fwnode link as being part of a cycle
authorSaravana Kannan <saravanak@google.com>
Tue, 7 Feb 2023 01:41:58 +0000 (17:41 -0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 8 Feb 2023 12:37:54 +0000 (13:37 +0100)
To improve detection and handling of dependency cycles, we need to be
able to mark fwnode links as being part of cycles. fwnode links marked
as being part of a cycle should not block their consumers from probing.

Fixes: 2de9d8e0d2fe ("driver core: fw_devlink: Improve handling of cyclic dependencies")
Signed-off-by: Saravana Kannan <saravanak@google.com>
Tested-by: Colin Foster <colin.foster@in-advantage.com>
Tested-by: Sudeep Holla <sudeep.holla@arm.com>
Tested-by: Douglas Anderson <dianders@chromium.org>
Tested-by: Geert Uytterhoeven <geert+renesas@glider.be>
Tested-by: Luca Weiss <luca.weiss@fairphone.com> # qcom/sm7225-fairphone-fp4
Link: https://lore.kernel.org/r/20230207014207.1678715-7-saravanak@google.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/base/core.c
include/linux/fwnode.h

index ac19752..6639f90 100644 (file)
@@ -76,13 +76,15 @@ static bool fw_devlink_best_effort;
  * are ignored and there is no reference counting.
  */
 static int __fwnode_link_add(struct fwnode_handle *con,
-                            struct fwnode_handle *sup)
+                            struct fwnode_handle *sup, u8 flags)
 {
        struct fwnode_link *link;
 
        list_for_each_entry(link, &sup->consumers, s_hook)
-               if (link->consumer == con)
+               if (link->consumer == con) {
+                       link->flags |= flags;
                        return 0;
+               }
 
        link = kzalloc(sizeof(*link), GFP_KERNEL);
        if (!link)
@@ -92,6 +94,7 @@ static int __fwnode_link_add(struct fwnode_handle *con,
        INIT_LIST_HEAD(&link->s_hook);
        link->consumer = con;
        INIT_LIST_HEAD(&link->c_hook);
+       link->flags = flags;
 
        list_add(&link->s_hook, &sup->consumers);
        list_add(&link->c_hook, &con->suppliers);
@@ -106,7 +109,7 @@ int fwnode_link_add(struct fwnode_handle *con, struct fwnode_handle *sup)
        int ret;
 
        mutex_lock(&fwnode_link_lock);
-       ret = __fwnode_link_add(con, sup);
+       ret = __fwnode_link_add(con, sup, 0);
        mutex_unlock(&fwnode_link_lock);
        return ret;
 }
@@ -127,6 +130,19 @@ static void __fwnode_link_del(struct fwnode_link *link)
 }
 
 /**
+ * __fwnode_link_cycle - Mark a fwnode link as being part of a cycle.
+ * @link: the fwnode_link to be marked
+ *
+ * The fwnode_link_lock needs to be held when this function is called.
+ */
+static void __fwnode_link_cycle(struct fwnode_link *link)
+{
+       pr_debug("%pfwf: Relaxing link with %pfwf\n",
+                link->consumer, link->supplier);
+       link->flags |= FWLINK_FLAG_CYCLE;
+}
+
+/**
  * fwnode_links_purge_suppliers - Delete all supplier links of fwnode_handle.
  * @fwnode: fwnode whose supplier links need to be deleted
  *
@@ -199,7 +215,7 @@ static void __fwnode_links_move_consumers(struct fwnode_handle *from,
        struct fwnode_link *link, *tmp;
 
        list_for_each_entry_safe(link, tmp, &from->consumers, s_hook) {
-               __fwnode_link_add(link->consumer, to);
+               __fwnode_link_add(link->consumer, to, link->flags);
                __fwnode_link_del(link);
        }
 }
@@ -1041,6 +1057,21 @@ static bool dev_is_best_effort(struct device *dev)
                (dev->fwnode && (dev->fwnode->flags & FWNODE_FLAG_BEST_EFFORT));
 }
 
+static struct fwnode_handle *fwnode_links_check_suppliers(
+                                               struct fwnode_handle *fwnode)
+{
+       struct fwnode_link *link;
+
+       if (!fwnode || fw_devlink_is_permissive())
+               return NULL;
+
+       list_for_each_entry(link, &fwnode->suppliers, c_hook)
+               if (!(link->flags & FWLINK_FLAG_CYCLE))
+                       return link->supplier;
+
+       return NULL;
+}
+
 /**
  * device_links_check_suppliers - Check presence of supplier drivers.
  * @dev: Consumer device.
@@ -1068,11 +1099,8 @@ int device_links_check_suppliers(struct device *dev)
         * probe.
         */
        mutex_lock(&fwnode_link_lock);
-       if (dev->fwnode && !list_empty(&dev->fwnode->suppliers) &&
-           !fw_devlink_is_permissive()) {
-               sup_fw = list_first_entry(&dev->fwnode->suppliers,
-                                         struct fwnode_link,
-                                         c_hook)->supplier;
+       sup_fw = fwnode_links_check_suppliers(dev->fwnode);
+       if (sup_fw) {
                if (!dev_is_best_effort(dev)) {
                        fwnode_ret = -EPROBE_DEFER;
                        dev_err_probe(dev, -EPROBE_DEFER,
@@ -1261,7 +1289,9 @@ static ssize_t waiting_for_supplier_show(struct device *dev,
        bool val;
 
        device_lock(dev);
-       val = !list_empty(&dev->fwnode->suppliers);
+       mutex_lock(&fwnode_link_lock);
+       val = !!fwnode_links_check_suppliers(dev->fwnode);
+       mutex_unlock(&fwnode_link_lock);
        device_unlock(dev);
        return sysfs_emit(buf, "%u\n", val);
 }
index 89b9bdf..fdf2ee0 100644 (file)
@@ -18,7 +18,7 @@ struct fwnode_operations;
 struct device;
 
 /*
- * fwnode link flags
+ * fwnode flags
  *
  * LINKS_ADDED:        The fwnode has already be parsed to add fwnode links.
  * NOT_DEVICE: The fwnode will never be populated as a struct device.
@@ -36,6 +36,7 @@ struct device;
 #define FWNODE_FLAG_INITIALIZED                        BIT(2)
 #define FWNODE_FLAG_NEEDS_CHILD_BOUND_ON_ADD   BIT(3)
 #define FWNODE_FLAG_BEST_EFFORT                        BIT(4)
+#define FWNODE_FLAG_VISITED                    BIT(5)
 
 struct fwnode_handle {
        struct fwnode_handle *secondary;
@@ -46,11 +47,19 @@ struct fwnode_handle {
        u8 flags;
 };
 
+/*
+ * fwnode link flags
+ *
+ * CYCLE:      The fwnode link is part of a cycle. Don't defer probe.
+ */
+#define FWLINK_FLAG_CYCLE                      BIT(0)
+
 struct fwnode_link {
        struct fwnode_handle *supplier;
        struct list_head s_hook;
        struct fwnode_handle *consumer;
        struct list_head c_hook;
+       u8 flags;
 };
 
 /**