PM/Domain: fix delayed power-off race
Andrew Bresticker [Tue, 14 Jan 2014 00:29:43 +0000 (16:29 -0800)]
Commit 5ffea6f ["CHROMIUM: PM/domains: add delayed power off capability"]
introduced the ability to defer the power-off of a power domain and
used a PM notifier to flush any delayed power-off work for a power
domain. The PM notifiers, however, run before tasks are frozen and
outstanding runtime PM requests are flushed.  A runtime resume may
run during this time scheduling a delayed power-off which may not run
until after pm_genpd_prepare(), where the state of the domain at suspend
is recorded. As a result, a device which expected to be powered off
across suspend may be powered up unexpectedly during resume because
pm_genpd_prepare() recorded the state of the device as on at suspend.
Fix this race by flushing delayed power-off work in pm_genpd_prepare()
instead.

Bug 1426717

Change-Id: Ia3f83304eaa5eb8a6396175eaa98192a392692ad
Signed-off-by: Andrew Bresticker <abrestic@chromium.org>
Signed-off-by: Mark Zhang <markz@nvidia.com>
Signed-off-by: Jeetesh Burman <jburman@nvidia.com>
Reviewed-on: http://git-master/r/355390
Tested-by: Jeetesh Burman <jburman@nvidia.com>
Reviewed-on: http://git-master/r/364897
Reviewed-by: Arto Merilainen <amerilainen@nvidia.com>
Reviewed-by: Shridhar Rasal <srasal@nvidia.com>
GVS: Gerrit_Virtual_Submit
Reviewed-by: Winnie Hsu <whsu@nvidia.com>

drivers/base/power/domain.c
include/linux/pm_domain.h

index 7175c0d..afa48f3 100644 (file)
@@ -1110,6 +1110,15 @@ static int pm_genpd_prepare(struct device *dev)
        if (resume_needed(dev, genpd))
                pm_runtime_resume(dev);
 
+       /* If a delayed power-off for the domain is pending, turn it off now */
+       if (genpd->power_off_delay) {
+               if (delayed_work_pending(&genpd->power_off_delayed_work)) {
+                       cancel_delayed_work_sync(
+                               &genpd->power_off_delayed_work);
+                       __pm_genpd_poweroff(genpd);
+               }
+       }
+
        genpd_acquire_lock(genpd);
 
        if (genpd->prepared_count++ == 0) {
@@ -2317,38 +2326,6 @@ static int pm_genpd_default_thaw(struct device *dev)
        return cb ? cb(dev) : pm_generic_thaw(dev);
 }
 
-#ifdef CONFIG_PM_RUNTIME
-static int pm_genpd_suspend_notifier(struct notifier_block *notifier,
-       unsigned long pm_event, void *unused)
-{
-       struct generic_pm_domain *genpd = container_of(notifier,
-               struct generic_pm_domain, system_suspend_notifier);
-
-       if (!genpd)
-               return NOTIFY_DONE;
-
-       switch (pm_event) {
-       case PM_SUSPEND_PREPARE:
-               if (genpd->power_off_delay) {
-                       /* if a domain has scheduled a delayed work */
-                       if (delayed_work_pending(
-                               &genpd->power_off_delayed_work)) {
-
-                               /* cancel it now */
-                               cancel_delayed_work_sync(
-                                       &genpd->power_off_delayed_work);
-
-                               /* call its power off */
-                               __pm_genpd_poweroff(genpd);
-                       }
-               }
-               return NOTIFY_OK;
-       }
-
-       return NOTIFY_DONE;
-}
-#endif
-
 #else /* !CONFIG_PM_SLEEP */
 
 #define pm_genpd_default_suspend       NULL
@@ -2427,11 +2404,6 @@ void pm_genpd_init(struct generic_pm_domain *genpd,
        genpd->dev_ops.freeze_late = pm_genpd_default_freeze_late;
        genpd->dev_ops.thaw_early = pm_genpd_default_thaw_early;
        genpd->dev_ops.thaw = pm_genpd_default_thaw;
-#if defined(CONFIG_PM_SLEEP) && defined(CONFIG_PM_RUNTIME)
-       genpd->system_suspend_notifier.notifier_call =
-               pm_genpd_suspend_notifier;
-       register_pm_notifier(&genpd->system_suspend_notifier);
-#endif
        mutex_lock(&gpd_list_lock);
        list_add(&genpd->gpd_list_node, &gpd_list);
        mutex_unlock(&gpd_list_lock);
index e796787..4c622b5 100644 (file)
@@ -87,9 +87,6 @@ struct generic_pm_domain {
        unsigned long power_on_jiffies;
        unsigned long power_off_jiffies;
        unsigned long accounting_timestamp;
-#ifdef CONFIG_PM_SLEEP
-       struct notifier_block system_suspend_notifier;
-#endif
 };
 
 static inline struct generic_pm_domain *pd_to_genpd(struct dev_pm_domain *pd)