Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next
[linux-2.6-microblaze.git] / drivers / base / core.c
index 276c7e3..7fbd281 100644 (file)
@@ -178,10 +178,10 @@ void device_pm_move_to_tail(struct device *dev)
  * of the link.  If DL_FLAG_PM_RUNTIME is not set, DL_FLAG_RPM_ACTIVE will be
  * ignored.
  *
- * If the DL_FLAG_AUTOREMOVE is set, the link will be removed automatically
- * when the consumer device driver unbinds from it.  The combination of both
- * DL_FLAG_AUTOREMOVE and DL_FLAG_STATELESS set is invalid and will cause NULL
- * to be returned.
+ * If the DL_FLAG_AUTOREMOVE_CONSUMER is set, the link will be removed
+ * automatically when the consumer device driver unbinds from it.
+ * The combination of both DL_FLAG_AUTOREMOVE_CONSUMER and DL_FLAG_STATELESS
+ * set is invalid and will cause NULL to be returned.
  *
  * A side effect of the link creation is re-ordering of dpm_list and the
  * devices_kset list by moving the consumer device and all devices depending
@@ -198,7 +198,8 @@ struct device_link *device_link_add(struct device *consumer,
        struct device_link *link;
 
        if (!consumer || !supplier ||
-           ((flags & DL_FLAG_STATELESS) && (flags & DL_FLAG_AUTOREMOVE)))
+           ((flags & DL_FLAG_STATELESS) &&
+            (flags & DL_FLAG_AUTOREMOVE_CONSUMER)))
                return NULL;
 
        device_links_write_lock();
@@ -372,6 +373,36 @@ void device_link_del(struct device_link *link)
 }
 EXPORT_SYMBOL_GPL(device_link_del);
 
+/**
+ * device_link_remove - remove a link between two devices.
+ * @consumer: Consumer end of the link.
+ * @supplier: Supplier end of the link.
+ *
+ * The caller must ensure proper synchronization of this function with runtime
+ * PM.
+ */
+void device_link_remove(void *consumer, struct device *supplier)
+{
+       struct device_link *link;
+
+       if (WARN_ON(consumer == supplier))
+               return;
+
+       device_links_write_lock();
+       device_pm_lock();
+
+       list_for_each_entry(link, &supplier->links.consumers, s_node) {
+               if (link->consumer == consumer) {
+                       kref_put(&link->kref, __device_link_del);
+                       break;
+               }
+       }
+
+       device_pm_unlock();
+       device_links_write_unlock();
+}
+EXPORT_SYMBOL_GPL(device_link_remove);
+
 static void device_links_missing_supplier(struct device *dev)
 {
        struct device_link *link;
@@ -479,7 +510,7 @@ static void __device_links_no_driver(struct device *dev)
                if (link->flags & DL_FLAG_STATELESS)
                        continue;
 
-               if (link->flags & DL_FLAG_AUTOREMOVE)
+               if (link->flags & DL_FLAG_AUTOREMOVE_CONSUMER)
                        kref_put(&link->kref, __device_link_del);
                else if (link->status != DL_STATE_SUPPLIER_UNBIND)
                        WRITE_ONCE(link->status, DL_STATE_AVAILABLE);
@@ -515,8 +546,18 @@ void device_links_driver_cleanup(struct device *dev)
                if (link->flags & DL_FLAG_STATELESS)
                        continue;
 
-               WARN_ON(link->flags & DL_FLAG_AUTOREMOVE);
+               WARN_ON(link->flags & DL_FLAG_AUTOREMOVE_CONSUMER);
                WARN_ON(link->status != DL_STATE_SUPPLIER_UNBIND);
+
+               /*
+                * autoremove the links between this @dev and its consumer
+                * devices that are not active, i.e. where the link state
+                * has moved to DL_STATE_SUPPLIER_UNBIND.
+                */
+               if (link->status == DL_STATE_SUPPLIER_UNBIND &&
+                   link->flags & DL_FLAG_AUTOREMOVE_SUPPLIER)
+                       kref_put(&link->kref, __device_link_del);
+
                WRITE_ONCE(link->status, DL_STATE_DORMANT);
        }