Merge tag 'pm-5.1-rc1-2' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael...
[muen/linux.git] / drivers / base / power / runtime.c
index a2d22e3..977db40 100644 (file)
@@ -292,11 +292,8 @@ static int rpm_get_suppliers(struct device *dev)
        list_for_each_entry_rcu(link, &dev->links.suppliers, c_node) {
                int retval;
 
        list_for_each_entry_rcu(link, &dev->links.suppliers, c_node) {
                int retval;
 
-               if (!(link->flags & DL_FLAG_PM_RUNTIME))
-                       continue;
-
-               if (READ_ONCE(link->status) == DL_STATE_SUPPLIER_UNBIND ||
-                   link->rpm_active)
+               if (!(link->flags & DL_FLAG_PM_RUNTIME) ||
+                   READ_ONCE(link->status) == DL_STATE_SUPPLIER_UNBIND)
                        continue;
 
                retval = pm_runtime_get_sync(link->supplier);
                        continue;
 
                retval = pm_runtime_get_sync(link->supplier);
@@ -305,7 +302,7 @@ static int rpm_get_suppliers(struct device *dev)
                        pm_runtime_put_noidle(link->supplier);
                        return retval;
                }
                        pm_runtime_put_noidle(link->supplier);
                        return retval;
                }
-               link->rpm_active = true;
+               refcount_inc(&link->rpm_active);
        }
        return 0;
 }
        }
        return 0;
 }
@@ -314,12 +311,13 @@ static void rpm_put_suppliers(struct device *dev)
 {
        struct device_link *link;
 
 {
        struct device_link *link;
 
-       list_for_each_entry_rcu(link, &dev->links.suppliers, c_node)
-               if (link->rpm_active &&
-                   READ_ONCE(link->status) != DL_STATE_SUPPLIER_UNBIND) {
+       list_for_each_entry_rcu(link, &dev->links.suppliers, c_node) {
+               if (READ_ONCE(link->status) == DL_STATE_SUPPLIER_UNBIND)
+                       continue;
+
+               while (refcount_dec_not_one(&link->rpm_active))
                        pm_runtime_put(link->supplier);
                        pm_runtime_put(link->supplier);
-                       link->rpm_active = false;
-               }
+       }
 }
 
 /**
 }
 
 /**
@@ -1124,24 +1122,57 @@ EXPORT_SYMBOL_GPL(pm_runtime_get_if_in_use);
  * and the device parent's counter of unsuspended children is modified to
  * reflect the new status.  If the new status is RPM_SUSPENDED, an idle
  * notification request for the parent is submitted.
  * and the device parent's counter of unsuspended children is modified to
  * reflect the new status.  If the new status is RPM_SUSPENDED, an idle
  * notification request for the parent is submitted.
+ *
+ * If @dev has any suppliers (as reflected by device links to them), and @status
+ * is RPM_ACTIVE, they will be activated upfront and if the activation of one
+ * of them fails, the status of @dev will be changed to RPM_SUSPENDED (instead
+ * of the @status value) and the suppliers will be deacticated on exit.  The
+ * error returned by the failing supplier activation will be returned in that
+ * case.
  */
 int __pm_runtime_set_status(struct device *dev, unsigned int status)
 {
        struct device *parent = dev->parent;
  */
 int __pm_runtime_set_status(struct device *dev, unsigned int status)
 {
        struct device *parent = dev->parent;
-       unsigned long flags;
        bool notify_parent = false;
        int error = 0;
 
        if (status != RPM_ACTIVE && status != RPM_SUSPENDED)
                return -EINVAL;
 
        bool notify_parent = false;
        int error = 0;
 
        if (status != RPM_ACTIVE && status != RPM_SUSPENDED)
                return -EINVAL;
 
-       spin_lock_irqsave(&dev->power.lock, flags);
+       spin_lock_irq(&dev->power.lock);
 
 
-       if (!dev->power.runtime_error && !dev->power.disable_depth) {
+       /*
+        * Prevent PM-runtime from being enabled for the device or return an
+        * error if it is enabled already and working.
+        */
+       if (dev->power.runtime_error || dev->power.disable_depth)
+               dev->power.disable_depth++;
+       else
                error = -EAGAIN;
                error = -EAGAIN;
-               goto out;
+
+       spin_unlock_irq(&dev->power.lock);
+
+       if (error)
+               return error;
+
+       /*
+        * If the new status is RPM_ACTIVE, the suppliers can be activated
+        * upfront regardless of the current status, because next time
+        * rpm_put_suppliers() runs, the rpm_active refcounts of the links
+        * involved will be dropped down to one anyway.
+        */
+       if (status == RPM_ACTIVE) {
+               int idx = device_links_read_lock();
+
+               error = rpm_get_suppliers(dev);
+               if (error)
+                       status = RPM_SUSPENDED;
+
+               device_links_read_unlock(idx);
        }
 
        }
 
+       spin_lock_irq(&dev->power.lock);
+
        if (dev->power.runtime_status == status || !parent)
                goto out_set;
 
        if (dev->power.runtime_status == status || !parent)
                goto out_set;
 
@@ -1169,19 +1200,33 @@ int __pm_runtime_set_status(struct device *dev, unsigned int status)
 
                spin_unlock(&parent->power.lock);
 
 
                spin_unlock(&parent->power.lock);
 
-               if (error)
+               if (error) {
+                       status = RPM_SUSPENDED;
                        goto out;
                        goto out;
+               }
        }
 
  out_set:
        __update_runtime_status(dev, status);
        }
 
  out_set:
        __update_runtime_status(dev, status);
-       dev->power.runtime_error = 0;
+       if (!error)
+               dev->power.runtime_error = 0;
+
  out:
  out:
-       spin_unlock_irqrestore(&dev->power.lock, flags);
+       spin_unlock_irq(&dev->power.lock);
 
        if (notify_parent)
                pm_request_idle(parent);
 
 
        if (notify_parent)
                pm_request_idle(parent);
 
+       if (status == RPM_SUSPENDED) {
+               int idx = device_links_read_lock();
+
+               rpm_put_suppliers(dev);
+
+               device_links_read_unlock(idx);
+       }
+
+       pm_runtime_enable(dev);
+
        return error;
 }
 EXPORT_SYMBOL_GPL(__pm_runtime_set_status);
        return error;
 }
 EXPORT_SYMBOL_GPL(__pm_runtime_set_status);
@@ -1579,7 +1624,7 @@ void pm_runtime_remove(struct device *dev)
  *
  * Check links from this device to any consumers and if any of them have active
  * runtime PM references to the device, drop the usage counter of the device
  *
  * Check links from this device to any consumers and if any of them have active
  * runtime PM references to the device, drop the usage counter of the device
- * (once per link).
+ * (as many times as needed).
  *
  * Links with the DL_FLAG_STATELESS flag set are ignored.
  *
  *
  * Links with the DL_FLAG_STATELESS flag set are ignored.
  *
@@ -1601,10 +1646,8 @@ void pm_runtime_clean_up_links(struct device *dev)
                if (link->flags & DL_FLAG_STATELESS)
                        continue;
 
                if (link->flags & DL_FLAG_STATELESS)
                        continue;
 
-               if (link->rpm_active) {
+               while (refcount_dec_not_one(&link->rpm_active))
                        pm_runtime_put_noidle(dev);
                        pm_runtime_put_noidle(dev);
-                       link->rpm_active = false;
-               }
        }
 
        device_links_read_unlock(idx);
        }
 
        device_links_read_unlock(idx);
@@ -1622,8 +1665,11 @@ void pm_runtime_get_suppliers(struct device *dev)
        idx = device_links_read_lock();
 
        list_for_each_entry_rcu(link, &dev->links.suppliers, c_node)
        idx = device_links_read_lock();
 
        list_for_each_entry_rcu(link, &dev->links.suppliers, c_node)
-               if (link->flags & DL_FLAG_PM_RUNTIME)
+               if (link->flags & DL_FLAG_PM_RUNTIME) {
+                       link->supplier_preactivated = true;
+                       refcount_inc(&link->rpm_active);
                        pm_runtime_get_sync(link->supplier);
                        pm_runtime_get_sync(link->supplier);
+               }
 
        device_links_read_unlock(idx);
 }
 
        device_links_read_unlock(idx);
 }
@@ -1640,8 +1686,11 @@ void pm_runtime_put_suppliers(struct device *dev)
        idx = device_links_read_lock();
 
        list_for_each_entry_rcu(link, &dev->links.suppliers, c_node)
        idx = device_links_read_lock();
 
        list_for_each_entry_rcu(link, &dev->links.suppliers, c_node)
-               if (link->flags & DL_FLAG_PM_RUNTIME)
-                       pm_runtime_put(link->supplier);
+               if (link->supplier_preactivated) {
+                       link->supplier_preactivated = false;
+                       if (refcount_dec_not_one(&link->rpm_active))
+                               pm_runtime_put(link->supplier);
+               }
 
        device_links_read_unlock(idx);
 }
 
        device_links_read_unlock(idx);
 }
@@ -1655,8 +1704,6 @@ void pm_runtime_new_link(struct device *dev)
 
 void pm_runtime_drop_link(struct device *dev)
 {
 
 void pm_runtime_drop_link(struct device *dev)
 {
-       rpm_put_suppliers(dev);
-
        spin_lock_irq(&dev->power.lock);
        WARN_ON(dev->power.links_count == 0);
        dev->power.links_count--;
        spin_lock_irq(&dev->power.lock);
        WARN_ON(dev->power.links_count == 0);
        dev->power.links_count--;