PM / Domains: Fix unsafe iteration over modified list of domains
[sfrench/cifs-2.6.git] / drivers / base / power / domain.c
index e697dec9d25bf585175a5ee569097f849f9669c6..e342408cfb8d29028a3f3bd17fb8b50197da308b 100644 (file)
@@ -121,17 +121,23 @@ static const struct genpd_lock_ops genpd_spin_ops = {
 #define genpd_lock_interruptible(p)    p->lock_ops->lock_interruptible(p)
 #define genpd_unlock(p)                        p->lock_ops->unlock(p)
 
+#define genpd_status_on(genpd)         (genpd->status == GPD_STATE_ACTIVE)
 #define genpd_is_irq_safe(genpd)       (genpd->flags & GENPD_FLAG_IRQ_SAFE)
+#define genpd_is_always_on(genpd)      (genpd->flags & GENPD_FLAG_ALWAYS_ON)
 
 static inline bool irq_safe_dev_in_no_sleep_domain(struct device *dev,
-               struct generic_pm_domain *genpd)
+               const struct generic_pm_domain *genpd)
 {
        bool ret;
 
        ret = pm_runtime_is_irq_safe(dev) && !genpd_is_irq_safe(genpd);
 
-       /* Warn once if IRQ safe dev in no sleep domain */
-       if (ret)
+       /*
+        * Warn once if an IRQ safe device is attached to a no sleep domain, as
+        * to indicate a suboptimal configuration for PM. For an always on
+        * domain this isn't case, thus don't warn.
+        */
+       if (ret && !genpd_is_always_on(genpd))
                dev_warn_once(dev, "PM domain %s will not be powered off\n",
                                genpd->name);
 
@@ -175,12 +181,14 @@ static struct generic_pm_domain *dev_to_genpd(struct device *dev)
        return pd_to_genpd(dev->pm_domain);
 }
 
-static int genpd_stop_dev(struct generic_pm_domain *genpd, struct device *dev)
+static int genpd_stop_dev(const struct generic_pm_domain *genpd,
+                         struct device *dev)
 {
        return GENPD_DEV_CALLBACK(genpd, int, stop, dev);
 }
 
-static int genpd_start_dev(struct generic_pm_domain *genpd, struct device *dev)
+static int genpd_start_dev(const struct generic_pm_domain *genpd,
+                          struct device *dev)
 {
        return GENPD_DEV_CALLBACK(genpd, int, start, dev);
 }
@@ -296,11 +304,15 @@ static int genpd_power_off(struct generic_pm_domain *genpd, bool one_dev_on,
         * (1) The domain is already in the "power off" state.
         * (2) System suspend is in progress.
         */
-       if (genpd->status == GPD_STATE_POWER_OFF
-           || genpd->prepared_count > 0)
+       if (!genpd_status_on(genpd) || genpd->prepared_count > 0)
                return 0;
 
-       if (atomic_read(&genpd->sd_count) > 0)
+       /*
+        * Abort power off for the PM domain in the following situations:
+        * (1) The domain is configured as always on.
+        * (2) When the domain has a subdomain being powered on.
+        */
+       if (genpd_is_always_on(genpd) || atomic_read(&genpd->sd_count) > 0)
                return -EBUSY;
 
        list_for_each_entry(pdd, &genpd->dev_list, list_node) {
@@ -373,7 +385,7 @@ static int genpd_power_on(struct generic_pm_domain *genpd, unsigned int depth)
        struct gpd_link *link;
        int ret = 0;
 
-       if (genpd->status == GPD_STATE_ACTIVE)
+       if (genpd_status_on(genpd))
                return 0;
 
        /*
@@ -433,7 +445,7 @@ static int genpd_dev_pm_qos_notifier(struct notifier_block *nb,
 
                pdd = dev->power.subsys_data ?
                                dev->power.subsys_data->domain_data : NULL;
-               if (pdd && pdd->dev) {
+               if (pdd) {
                        to_gpd_data(pdd)->td.constraint_changed = true;
                        genpd = dev_to_genpd(dev);
                } else {
@@ -728,7 +740,7 @@ static bool pm_genpd_present(const struct generic_pm_domain *genpd)
 
 #ifdef CONFIG_PM_SLEEP
 
-static bool genpd_dev_active_wakeup(struct generic_pm_domain *genpd,
+static bool genpd_dev_active_wakeup(const struct generic_pm_domain *genpd,
                                    struct device *dev)
 {
        return GENPD_DEV_CALLBACK(genpd, bool, active_wakeup, dev);
@@ -752,7 +764,7 @@ static void genpd_sync_power_off(struct generic_pm_domain *genpd, bool use_lock,
 {
        struct gpd_link *link;
 
-       if (genpd->status == GPD_STATE_POWER_OFF)
+       if (!genpd_status_on(genpd) || genpd_is_always_on(genpd))
                return;
 
        if (genpd->suspended_count != genpd->device_count
@@ -761,7 +773,8 @@ static void genpd_sync_power_off(struct generic_pm_domain *genpd, bool use_lock,
 
        /* Choose the deepest state when suspending */
        genpd->state_idx = genpd->state_count - 1;
-       _genpd_power_off(genpd, false);
+       if (_genpd_power_off(genpd, false))
+               return;
 
        genpd->status = GPD_STATE_POWER_OFF;
 
@@ -793,7 +806,7 @@ static void genpd_sync_power_on(struct generic_pm_domain *genpd, bool use_lock,
 {
        struct gpd_link *link;
 
-       if (genpd->status == GPD_STATE_ACTIVE)
+       if (genpd_status_on(genpd))
                return;
 
        list_for_each_entry(link, &genpd->slave_links, slave_node) {
@@ -829,7 +842,8 @@ static void genpd_sync_power_on(struct generic_pm_domain *genpd, bool use_lock,
  * signal remote wakeup from the system's working state as needed by runtime PM.
  * Return 'true' in either of the above cases.
  */
-static bool resume_needed(struct device *dev, struct generic_pm_domain *genpd)
+static bool resume_needed(struct device *dev,
+                         const struct generic_pm_domain *genpd)
 {
        bool active_wakeup;
 
@@ -888,19 +902,19 @@ static int pm_genpd_prepare(struct device *dev)
 }
 
 /**
- * pm_genpd_suspend_noirq - Completion of suspend of device in an I/O PM domain.
+ * genpd_finish_suspend - Completion of suspend or hibernation of device in an
+ *   I/O pm domain.
  * @dev: Device to suspend.
+ * @poweroff: Specifies if this is a poweroff_noirq or suspend_noirq callback.
  *
  * Stop the device and remove power from the domain if all devices in it have
  * been stopped.
  */
-static int pm_genpd_suspend_noirq(struct device *dev)
+static int genpd_finish_suspend(struct device *dev, bool poweroff)
 {
        struct generic_pm_domain *genpd;
        int ret;
 
-       dev_dbg(dev, "%s()\n", __func__);
-
        genpd = dev_to_genpd(dev);
        if (IS_ERR(genpd))
                return -EINVAL;
@@ -908,6 +922,13 @@ static int pm_genpd_suspend_noirq(struct device *dev)
        if (dev->power.wakeup_path && genpd_dev_active_wakeup(genpd, dev))
                return 0;
 
+       if (poweroff)
+               ret = pm_generic_poweroff_noirq(dev);
+       else
+               ret = pm_generic_suspend_noirq(dev);
+       if (ret)
+               return ret;
+
        if (genpd->dev_ops.stop && genpd->dev_ops.start) {
                ret = pm_runtime_force_suspend(dev);
                if (ret)
@@ -922,6 +943,20 @@ static int pm_genpd_suspend_noirq(struct device *dev)
        return 0;
 }
 
+/**
+ * pm_genpd_suspend_noirq - Completion of suspend of device in an I/O PM domain.
+ * @dev: Device to suspend.
+ *
+ * Stop the device and remove power from the domain if all devices in it have
+ * been stopped.
+ */
+static int pm_genpd_suspend_noirq(struct device *dev)
+{
+       dev_dbg(dev, "%s()\n", __func__);
+
+       return genpd_finish_suspend(dev, false);
+}
+
 /**
  * pm_genpd_resume_noirq - Start of resume of device in an I/O PM domain.
  * @dev: Device to resume.
@@ -950,6 +985,10 @@ static int pm_genpd_resume_noirq(struct device *dev)
        if (genpd->dev_ops.stop && genpd->dev_ops.start)
                ret = pm_runtime_force_resume(dev);
 
+       ret = pm_generic_resume_noirq(dev);
+       if (ret)
+               return ret;
+
        return ret;
 }
 
@@ -964,7 +1003,7 @@ static int pm_genpd_resume_noirq(struct device *dev)
  */
 static int pm_genpd_freeze_noirq(struct device *dev)
 {
-       struct generic_pm_domain *genpd;
+       const struct generic_pm_domain *genpd;
        int ret = 0;
 
        dev_dbg(dev, "%s()\n", __func__);
@@ -973,6 +1012,10 @@ static int pm_genpd_freeze_noirq(struct device *dev)
        if (IS_ERR(genpd))
                return -EINVAL;
 
+       ret = pm_generic_freeze_noirq(dev);
+       if (ret)
+               return ret;
+
        if (genpd->dev_ops.stop && genpd->dev_ops.start)
                ret = pm_runtime_force_suspend(dev);
 
@@ -988,7 +1031,7 @@ static int pm_genpd_freeze_noirq(struct device *dev)
  */
 static int pm_genpd_thaw_noirq(struct device *dev)
 {
-       struct generic_pm_domain *genpd;
+       const struct generic_pm_domain *genpd;
        int ret = 0;
 
        dev_dbg(dev, "%s()\n", __func__);
@@ -997,10 +1040,28 @@ static int pm_genpd_thaw_noirq(struct device *dev)
        if (IS_ERR(genpd))
                return -EINVAL;
 
-       if (genpd->dev_ops.stop && genpd->dev_ops.start)
+       if (genpd->dev_ops.stop && genpd->dev_ops.start) {
                ret = pm_runtime_force_resume(dev);
+               if (ret)
+                       return ret;
+       }
 
-       return ret;
+       return pm_generic_thaw_noirq(dev);
+}
+
+/**
+ * pm_genpd_poweroff_noirq - Completion of hibernation of device in an
+ *   I/O PM domain.
+ * @dev: Device to poweroff.
+ *
+ * Stop the device and remove power from the domain if all devices in it have
+ * been stopped.
+ */
+static int pm_genpd_poweroff_noirq(struct device *dev)
+{
+       dev_dbg(dev, "%s()\n", __func__);
+
+       return genpd_finish_suspend(dev, true);
 }
 
 /**
@@ -1037,10 +1098,13 @@ static int pm_genpd_restore_noirq(struct device *dev)
        genpd_sync_power_on(genpd, true, 0);
        genpd_unlock(genpd);
 
-       if (genpd->dev_ops.stop && genpd->dev_ops.start)
+       if (genpd->dev_ops.stop && genpd->dev_ops.start) {
                ret = pm_runtime_force_resume(dev);
+               if (ret)
+                       return ret;
+       }
 
-       return ret;
+       return pm_generic_restore_noirq(dev);
 }
 
 /**
@@ -1084,8 +1148,8 @@ static void genpd_syscore_switch(struct device *dev, bool suspend)
 {
        struct generic_pm_domain *genpd;
 
-       genpd = dev_to_genpd(dev);
-       if (!pm_genpd_present(genpd))
+       genpd = genpd_lookup_dev(dev);
+       if (!genpd)
                return;
 
        if (suspend) {
@@ -1329,8 +1393,7 @@ static int genpd_add_subdomain(struct generic_pm_domain *genpd,
        genpd_lock(subdomain);
        genpd_lock_nested(genpd, SINGLE_DEPTH_NESTING);
 
-       if (genpd->status == GPD_STATE_POWER_OFF
-           &&  subdomain->status != GPD_STATE_POWER_OFF) {
+       if (!genpd_status_on(genpd) && genpd_status_on(subdomain)) {
                ret = -EINVAL;
                goto out;
        }
@@ -1346,7 +1409,7 @@ static int genpd_add_subdomain(struct generic_pm_domain *genpd,
        list_add_tail(&link->master_node, &genpd->master_links);
        link->slave = subdomain;
        list_add_tail(&link->slave_node, &subdomain->slave_links);
-       if (subdomain->status != GPD_STATE_POWER_OFF)
+       if (genpd_status_on(subdomain))
                genpd_sd_counter_inc(genpd);
 
  out:
@@ -1383,7 +1446,7 @@ EXPORT_SYMBOL_GPL(pm_genpd_add_subdomain);
 int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd,
                              struct generic_pm_domain *subdomain)
 {
-       struct gpd_link *link;
+       struct gpd_link *l, *link;
        int ret = -EINVAL;
 
        if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(subdomain))
@@ -1399,14 +1462,14 @@ int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd,
                goto out;
        }
 
-       list_for_each_entry(link, &genpd->master_links, master_node) {
+       list_for_each_entry_safe(link, l, &genpd->master_links, master_node) {
                if (link->slave != subdomain)
                        continue;
 
                list_del(&link->master_node);
                list_del(&link->slave_node);
                kfree(link);
-               if (subdomain->status != GPD_STATE_POWER_OFF)
+               if (genpd_status_on(subdomain))
                        genpd_sd_counter_dec(genpd);
 
                ret = 0;
@@ -1483,7 +1546,7 @@ int pm_genpd_init(struct generic_pm_domain *genpd,
        genpd->domain.ops.resume_noirq = pm_genpd_resume_noirq;
        genpd->domain.ops.freeze_noirq = pm_genpd_freeze_noirq;
        genpd->domain.ops.thaw_noirq = pm_genpd_thaw_noirq;
-       genpd->domain.ops.poweroff_noirq = pm_genpd_suspend_noirq;
+       genpd->domain.ops.poweroff_noirq = pm_genpd_poweroff_noirq;
        genpd->domain.ops.restore_noirq = pm_genpd_restore_noirq;
        genpd->domain.ops.complete = pm_genpd_complete;
 
@@ -1492,6 +1555,10 @@ int pm_genpd_init(struct generic_pm_domain *genpd,
                genpd->dev_ops.start = pm_clk_resume;
        }
 
+       /* Always-on domains must be powered on at initialization. */
+       if (genpd_is_always_on(genpd) && !genpd_status_on(genpd))
+               return -EINVAL;
+
        /* Use only one "off" state if there were no states declared */
        if (genpd->state_count == 0) {
                ret = genpd_set_default_power_state(genpd);
@@ -1700,12 +1767,12 @@ int of_genpd_add_provider_simple(struct device_node *np,
 
        mutex_lock(&gpd_list_lock);
 
-       if (pm_genpd_present(genpd))
+       if (pm_genpd_present(genpd)) {
                ret = genpd_add_provider(np, genpd_xlate_simple, genpd);
-
-       if (!ret) {
-               genpd->provider = &np->fwnode;
-               genpd->has_provider = true;
+               if (!ret) {
+                       genpd->provider = &np->fwnode;
+                       genpd->has_provider = true;
+               }
        }
 
        mutex_unlock(&gpd_list_lock);
@@ -1768,12 +1835,12 @@ EXPORT_SYMBOL_GPL(of_genpd_add_provider_onecell);
  */
 void of_genpd_del_provider(struct device_node *np)
 {
-       struct of_genpd_provider *cp;
+       struct of_genpd_provider *cp, *tmp;
        struct generic_pm_domain *gpd;
 
        mutex_lock(&gpd_list_lock);
        mutex_lock(&of_genpd_mutex);
-       list_for_each_entry(cp, &of_genpd_providers, link) {
+       list_for_each_entry_safe(cp, tmp, &of_genpd_providers, link) {
                if (cp->node == np) {
                        /*
                         * For each PM domain associated with the
@@ -1913,14 +1980,14 @@ EXPORT_SYMBOL_GPL(of_genpd_add_subdomain);
  */
 struct generic_pm_domain *of_genpd_remove_last(struct device_node *np)
 {
-       struct generic_pm_domain *gpd, *genpd = ERR_PTR(-ENOENT);
+       struct generic_pm_domain *gpd, *tmp, *genpd = ERR_PTR(-ENOENT);
        int ret;
 
        if (IS_ERR_OR_NULL(np))
                return ERR_PTR(-EINVAL);
 
        mutex_lock(&gpd_list_lock);
-       list_for_each_entry(gpd, &gpd_list, gpd_list_node) {
+       list_for_each_entry_safe(gpd, tmp, &gpd_list, gpd_list_node) {
                if (gpd->provider == &np->fwnode) {
                        ret = genpd_remove(gpd);
                        genpd = ret ? ERR_PTR(ret) : gpd;
@@ -2079,11 +2146,6 @@ static int genpd_parse_state(struct genpd_power_state *genpd_state,
        int err;
        u32 residency;
        u32 entry_latency, exit_latency;
-       const struct of_device_id *match_id;
-
-       match_id = of_match_node(idle_state_match, state_node);
-       if (!match_id)
-               return -EINVAL;
 
        err = of_property_read_u32(state_node, "entry-latency-us",
                                                &entry_latency);
@@ -2132,6 +2194,7 @@ int of_genpd_parse_idle_states(struct device_node *dn,
        int err, ret;
        int count;
        struct of_phandle_iterator it;
+       const struct of_device_id *match_id;
 
        count = of_count_phandle_with_args(dn, "domain-idle-states", NULL);
        if (count <= 0)
@@ -2144,6 +2207,9 @@ int of_genpd_parse_idle_states(struct device_node *dn,
        /* Loop over the phandles until all the requested entry is found */
        of_for_each_phandle(&it, err, dn, "domain-idle-states", NULL, 0) {
                np = it.node;
+               match_id = of_match_node(idle_state_match, np);
+               if (!match_id)
+                       continue;
                ret = genpd_parse_state(&st[i++], np);
                if (ret) {
                        pr_err
@@ -2155,8 +2221,11 @@ int of_genpd_parse_idle_states(struct device_node *dn,
                }
        }
 
-       *n = count;
-       *states = st;
+       *n = i;
+       if (!i)
+               kfree(st);
+       else
+               *states = st;
 
        return 0;
 }
@@ -2221,7 +2290,7 @@ static int pm_genpd_summary_one(struct seq_file *s,
 
        if (WARN_ON(genpd->status >= ARRAY_SIZE(status_lookup)))
                goto exit;
-       if (genpd->status == GPD_STATE_POWER_OFF)
+       if (!genpd_status_on(genpd))
                snprintf(state, sizeof(state), "%s-%u",
                         status_lookup[genpd->status], genpd->state_idx);
        else