driver core: Handle cycles in device links created by fw_devlink
[linux-2.6-microblaze.git] / drivers / base / core.c
index 25e08e5..4cc0303 100644 (file)
@@ -229,7 +229,8 @@ int device_is_dependent(struct device *dev, void *target)
                return ret;
 
        list_for_each_entry(link, &dev->links.consumers, s_node) {
-               if (link->flags == (DL_FLAG_SYNC_STATE_ONLY | DL_FLAG_MANAGED))
+               if ((link->flags & ~DL_FLAG_INFERRED) ==
+                   (DL_FLAG_SYNC_STATE_ONLY | DL_FLAG_MANAGED))
                        continue;
 
                if (link->consumer == target)
@@ -302,7 +303,8 @@ static int device_reorder_to_tail(struct device *dev, void *not_used)
 
        device_for_each_child(dev, NULL, device_reorder_to_tail);
        list_for_each_entry(link, &dev->links.consumers, s_node) {
-               if (link->flags == (DL_FLAG_SYNC_STATE_ONLY | DL_FLAG_MANAGED))
+               if ((link->flags & ~DL_FLAG_INFERRED) ==
+                   (DL_FLAG_SYNC_STATE_ONLY | DL_FLAG_MANAGED))
                        continue;
                device_reorder_to_tail(link->consumer, NULL);
        }
@@ -546,7 +548,8 @@ postcore_initcall(devlink_class_init);
 #define DL_MANAGED_LINK_FLAGS (DL_FLAG_AUTOREMOVE_CONSUMER | \
                               DL_FLAG_AUTOREMOVE_SUPPLIER | \
                               DL_FLAG_AUTOPROBE_CONSUMER  | \
-                              DL_FLAG_SYNC_STATE_ONLY)
+                              DL_FLAG_SYNC_STATE_ONLY | \
+                              DL_FLAG_INFERRED)
 
 #define DL_ADD_VALID_FLAGS (DL_MANAGED_LINK_FLAGS | DL_FLAG_STATELESS | \
                            DL_FLAG_PM_RUNTIME | DL_FLAG_RPM_ACTIVE)
@@ -615,7 +618,7 @@ struct device_link *device_link_add(struct device *consumer,
        if (!consumer || !supplier || flags & ~DL_ADD_VALID_FLAGS ||
            (flags & DL_FLAG_STATELESS && flags & DL_MANAGED_LINK_FLAGS) ||
            (flags & DL_FLAG_SYNC_STATE_ONLY &&
-            flags != DL_FLAG_SYNC_STATE_ONLY) ||
+            (flags & ~DL_FLAG_INFERRED) != DL_FLAG_SYNC_STATE_ONLY) ||
            (flags & DL_FLAG_AUTOPROBE_CONSUMER &&
             flags & (DL_FLAG_AUTOREMOVE_CONSUMER |
                      DL_FLAG_AUTOREMOVE_SUPPLIER)))
@@ -671,6 +674,10 @@ struct device_link *device_link_add(struct device *consumer,
                if (link->consumer != consumer)
                        continue;
 
+               if (link->flags & DL_FLAG_INFERRED &&
+                   !(flags & DL_FLAG_INFERRED))
+                       link->flags &= ~DL_FLAG_INFERRED;
+
                if (flags & DL_FLAG_PM_RUNTIME) {
                        if (!(link->flags & DL_FLAG_PM_RUNTIME)) {
                                pm_runtime_new_link(consumer);
@@ -929,6 +936,10 @@ int device_links_check_suppliers(struct device *dev)
        mutex_lock(&fwnode_link_lock);
        if (dev->fwnode && !list_empty(&dev->fwnode->suppliers) &&
            !fw_devlink_is_permissive()) {
+               dev_dbg(dev, "probe deferral - wait for supplier %pfwP\n",
+                       list_first_entry(&dev->fwnode->suppliers,
+                       struct fwnode_link,
+                       c_hook)->supplier);
                mutex_unlock(&fwnode_link_lock);
                return -EPROBE_DEFER;
        }
@@ -943,6 +954,8 @@ int device_links_check_suppliers(struct device *dev)
                if (link->status != DL_STATE_AVAILABLE &&
                    !(link->flags & DL_FLAG_SYNC_STATE_ONLY)) {
                        device_links_missing_supplier(dev);
+                       dev_dbg(dev, "probe deferral - supplier %s not ready\n",
+                               dev_name(link->supplier));
                        ret = -EPROBE_DEFER;
                        break;
                }
@@ -1437,7 +1450,14 @@ static void device_links_purge(struct device *dev)
        device_links_write_unlock();
 }
 
-static u32 fw_devlink_flags = DL_FLAG_SYNC_STATE_ONLY;
+#define FW_DEVLINK_FLAGS_PERMISSIVE    (DL_FLAG_INFERRED | \
+                                        DL_FLAG_SYNC_STATE_ONLY)
+#define FW_DEVLINK_FLAGS_ON            (DL_FLAG_INFERRED | \
+                                        DL_FLAG_AUTOPROBE_CONSUMER)
+#define FW_DEVLINK_FLAGS_RPM           (FW_DEVLINK_FLAGS_ON | \
+                                        DL_FLAG_PM_RUNTIME)
+
+static u32 fw_devlink_flags = FW_DEVLINK_FLAGS_PERMISSIVE;
 static int __init fw_devlink_setup(char *arg)
 {
        if (!arg)
@@ -1446,12 +1466,11 @@ static int __init fw_devlink_setup(char *arg)
        if (strcmp(arg, "off") == 0) {
                fw_devlink_flags = 0;
        } else if (strcmp(arg, "permissive") == 0) {
-               fw_devlink_flags = DL_FLAG_SYNC_STATE_ONLY;
+               fw_devlink_flags = FW_DEVLINK_FLAGS_PERMISSIVE;
        } else if (strcmp(arg, "on") == 0) {
-               fw_devlink_flags = DL_FLAG_AUTOPROBE_CONSUMER;
+               fw_devlink_flags = FW_DEVLINK_FLAGS_ON;
        } else if (strcmp(arg, "rpm") == 0) {
-               fw_devlink_flags = DL_FLAG_AUTOPROBE_CONSUMER |
-                                  DL_FLAG_PM_RUNTIME;
+               fw_devlink_flags = FW_DEVLINK_FLAGS_RPM;
        }
        return 0;
 }
@@ -1464,7 +1483,7 @@ u32 fw_devlink_get_flags(void)
 
 static bool fw_devlink_is_permissive(void)
 {
-       return fw_devlink_flags == DL_FLAG_SYNC_STATE_ONLY;
+       return fw_devlink_flags == FW_DEVLINK_FLAGS_PERMISSIVE;
 }
 
 static void fw_devlink_parse_fwnode(struct fwnode_handle *fwnode)
@@ -1486,6 +1505,53 @@ static void fw_devlink_parse_fwtree(struct fwnode_handle *fwnode)
                fw_devlink_parse_fwtree(child);
 }
 
+/**
+ * fw_devlink_relax_cycle - Convert cyclic links to SYNC_STATE_ONLY links
+ * @con: Device to check dependencies for.
+ * @sup: Device to check against.
+ *
+ * Check if @sup depends on @con or any device dependent on it (its child or
+ * its consumer etc).  When such a cyclic dependency is found, convert all
+ * device links created solely by fw_devlink into SYNC_STATE_ONLY device links.
+ * This is the equivalent of doing fw_devlink=permissive just between the
+ * devices in the cycle. We need to do this because, at this point, fw_devlink
+ * can't tell which of these dependencies is not a real dependency.
+ *
+ * Return 1 if a cycle is found. Otherwise, return 0.
+ */
+int fw_devlink_relax_cycle(struct device *con, void *sup)
+{
+       struct device_link *link;
+       int ret;
+
+       if (con == sup)
+               return 1;
+
+       ret = device_for_each_child(con, sup, fw_devlink_relax_cycle);
+       if (ret)
+               return ret;
+
+       list_for_each_entry(link, &con->links.consumers, s_node) {
+               if ((link->flags & ~DL_FLAG_INFERRED) ==
+                   (DL_FLAG_SYNC_STATE_ONLY | DL_FLAG_MANAGED))
+                       continue;
+
+               if (!fw_devlink_relax_cycle(link->consumer, sup))
+                       continue;
+
+               ret = 1;
+
+               if (!(link->flags & DL_FLAG_INFERRED))
+                       continue;
+
+               pm_runtime_drop_link(link);
+               link->flags = DL_FLAG_MANAGED | FW_DEVLINK_FLAGS_PERMISSIVE;
+               dev_dbg(link->consumer, "Relaxing link with %s\n",
+                       dev_name(link->supplier));
+       }
+       return ret;
+}
+
 /**
  * fw_devlink_create_devlink - Create a device link from a consumer to fwnode
  * @con - Consumer device for the device link
@@ -1517,8 +1583,17 @@ static int fw_devlink_create_devlink(struct device *con,
                 * If this fails, it is due to cycles in device links.  Just
                 * give up on this link and treat it as invalid.
                 */
-               if (!device_link_add(con, sup_dev, flags))
+               if (!device_link_add(con, sup_dev, flags) &&
+                   !(flags & DL_FLAG_SYNC_STATE_ONLY)) {
+                       dev_info(con, "Fixing up cyclic dependency with %s\n",
+                                dev_name(sup_dev));
+                       device_links_write_lock();
+                       fw_devlink_relax_cycle(con, sup_dev);
+                       device_links_write_unlock();
+                       device_link_add(con, sup_dev,
+                                       FW_DEVLINK_FLAGS_PERMISSIVE);
                        ret = -EINVAL;
+               }
 
                goto out;
        }
@@ -1611,7 +1686,7 @@ static void __fw_devlink_link_to_consumers(struct device *dev)
                                con_dev = NULL;
                        } else {
                                own_link = false;
-                               dl_flags = DL_FLAG_SYNC_STATE_ONLY;
+                               dl_flags = FW_DEVLINK_FLAGS_PERMISSIVE;
                        }
                }
 
@@ -1666,7 +1741,7 @@ static void __fw_devlink_link_to_suppliers(struct device *dev,
        if (own_link)
                dl_flags = fw_devlink_get_flags();
        else
-               dl_flags = DL_FLAG_SYNC_STATE_ONLY;
+               dl_flags = FW_DEVLINK_FLAGS_PERMISSIVE;
 
        list_for_each_entry_safe(link, tmp, &fwnode->suppliers, c_hook) {
                int ret;