driver core: Add device link support for SYNC_STATE_ONLY flag
authorSaravana Kannan <saravanak@google.com>
Mon, 28 Oct 2019 22:00:22 +0000 (15:00 -0700)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sat, 2 Nov 2019 17:05:17 +0000 (18:05 +0100)
Parent devices might need to create "proxy" device links from themselves
to supplier devices to make sure the supplier devices don't get a
sync_state() before the child consumer devices get a chance to add
device links to the supplier devices.

However, the parent device has no real dependency on the supplier device
and probing, suspend/resume or runtime PM don't need to be affected by
the supplier device.  To capture these cases, create a SYNC_STATE_ONLY
device link flag that only affects sync_state() behavior and doesn't
affect probing, suspend/resume or runtime PM.

Signed-off-by: Saravana Kannan <saravanak@google.com>
Link: https://lore.kernel.org/r/20191028220027.251605-2-saravanak@google.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/base/core.c
include/linux/device.h

index 7ea665a..17ed054 100644 (file)
@@ -131,6 +131,9 @@ static 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))
+                       continue;
+
                if (link->consumer == target)
                        return 1;
 
@@ -200,8 +203,11 @@ static int device_reorder_to_tail(struct device *dev, void *not_used)
                device_pm_move_last(dev);
 
        device_for_each_child(dev, NULL, device_reorder_to_tail);
-       list_for_each_entry(link, &dev->links.consumers, s_node)
+       list_for_each_entry(link, &dev->links.consumers, s_node) {
+               if (link->flags == (DL_FLAG_SYNC_STATE_ONLY | DL_FLAG_MANAGED))
+                       continue;
                device_reorder_to_tail(link->consumer, NULL);
+       }
 
        return 0;
 }
@@ -228,7 +234,8 @@ void device_pm_move_to_tail(struct device *dev)
 
 #define DL_MANAGED_LINK_FLAGS (DL_FLAG_AUTOREMOVE_CONSUMER | \
                               DL_FLAG_AUTOREMOVE_SUPPLIER | \
-                              DL_FLAG_AUTOPROBE_CONSUMER)
+                              DL_FLAG_AUTOPROBE_CONSUMER  | \
+                              DL_FLAG_SYNC_STATE_ONLY)
 
 #define DL_ADD_VALID_FLAGS (DL_MANAGED_LINK_FLAGS | DL_FLAG_STATELESS | \
                            DL_FLAG_PM_RUNTIME | DL_FLAG_RPM_ACTIVE)
@@ -296,6 +303,8 @@ 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_AUTOPROBE_CONSUMER &&
             flags & (DL_FLAG_AUTOREMOVE_CONSUMER |
                      DL_FLAG_AUTOREMOVE_SUPPLIER)))
@@ -316,11 +325,14 @@ struct device_link *device_link_add(struct device *consumer,
 
        /*
         * If the supplier has not been fully registered yet or there is a
-        * reverse dependency between the consumer and the supplier already in
-        * the graph, return NULL.
+        * reverse (non-SYNC_STATE_ONLY) dependency between the consumer and
+        * the supplier already in the graph, return NULL. If the link is a
+        * SYNC_STATE_ONLY link, we don't check for reverse dependencies
+        * because it only affects sync_state() callbacks.
         */
        if (!device_pm_initialized(supplier)
-           || device_is_dependent(consumer, supplier)) {
+           || (!(flags & DL_FLAG_SYNC_STATE_ONLY) &&
+                 device_is_dependent(consumer, supplier))) {
                link = NULL;
                goto out;
        }
@@ -347,9 +359,14 @@ struct device_link *device_link_add(struct device *consumer,
                }
 
                if (flags & DL_FLAG_STATELESS) {
-                       link->flags |= DL_FLAG_STATELESS;
                        kref_get(&link->kref);
-                       goto out;
+                       if (link->flags & DL_FLAG_SYNC_STATE_ONLY &&
+                           !(link->flags & DL_FLAG_STATELESS)) {
+                               link->flags |= DL_FLAG_STATELESS;
+                               goto reorder;
+                       } else {
+                               goto out;
+                       }
                }
 
                /*
@@ -371,6 +388,12 @@ struct device_link *device_link_add(struct device *consumer,
                        link->flags |= DL_FLAG_MANAGED;
                        device_link_init_status(link, consumer, supplier);
                }
+               if (link->flags & DL_FLAG_SYNC_STATE_ONLY &&
+                   !(flags & DL_FLAG_SYNC_STATE_ONLY)) {
+                       link->flags &= ~DL_FLAG_SYNC_STATE_ONLY;
+                       goto reorder;
+               }
+
                goto out;
        }
 
@@ -410,6 +433,13 @@ struct device_link *device_link_add(struct device *consumer,
            flags & DL_FLAG_PM_RUNTIME)
                pm_runtime_resume(supplier);
 
+       if (flags & DL_FLAG_SYNC_STATE_ONLY) {
+               dev_dbg(consumer,
+                       "Linked as a sync state only consumer to %s\n",
+                       dev_name(supplier));
+               goto out;
+       }
+reorder:
        /*
         * Move the consumer and all of the devices depending on it to the end
         * of dpm_list and the devices_kset list.
@@ -635,7 +665,8 @@ int device_links_check_suppliers(struct device *dev)
        device_links_write_lock();
 
        list_for_each_entry(link, &dev->links.suppliers, c_node) {
-               if (!(link->flags & DL_FLAG_MANAGED))
+               if (!(link->flags & DL_FLAG_MANAGED) ||
+                   link->flags & DL_FLAG_SYNC_STATE_ONLY)
                        continue;
 
                if (link->status != DL_STATE_AVAILABLE) {
@@ -949,7 +980,8 @@ void device_links_unbind_consumers(struct device *dev)
        list_for_each_entry(link, &dev->links.consumers, s_node) {
                enum device_link_state status;
 
-               if (!(link->flags & DL_FLAG_MANAGED))
+               if (!(link->flags & DL_FLAG_MANAGED) ||
+                   link->flags & DL_FLAG_SYNC_STATE_ONLY)
                        continue;
 
                status = link->status;
index 6978bb4..82890e1 100644 (file)
@@ -1096,6 +1096,7 @@ enum device_link_state {
  * AUTOREMOVE_SUPPLIER: Remove the link automatically on supplier driver unbind.
  * AUTOPROBE_CONSUMER: Probe consumer driver automatically after supplier binds.
  * MANAGED: The core tracks presence of supplier/consumer drivers (internal).
+ * SYNC_STATE_ONLY: Link only affects sync_state() behavior.
  */
 #define DL_FLAG_STATELESS              BIT(0)
 #define DL_FLAG_AUTOREMOVE_CONSUMER    BIT(1)
@@ -1104,6 +1105,7 @@ enum device_link_state {
 #define DL_FLAG_AUTOREMOVE_SUPPLIER    BIT(4)
 #define DL_FLAG_AUTOPROBE_CONSUMER     BIT(5)
 #define DL_FLAG_MANAGED                        BIT(6)
+#define DL_FLAG_SYNC_STATE_ONLY                BIT(7)
 
 /**
  * struct device_link - Device link representation.