diff options
-rw-r--r-- | drivers/base/power/domain.c | 249 | ||||
-rw-r--r-- | include/linux/pm_domain.h | 10 |
2 files changed, 185 insertions, 74 deletions
diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 9a20d9302fcd..d06f3bb80b2e 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -13,6 +13,8 @@ #include <linux/pm_domain.h> #include <linux/slab.h> #include <linux/err.h> +#include <linux/sched.h> +#include <linux/suspend.h> #ifdef CONFIG_PM @@ -30,6 +32,34 @@ static void genpd_sd_counter_dec(struct generic_pm_domain *genpd) genpd->sd_count--; } +static void genpd_acquire_lock(struct generic_pm_domain *genpd) +{ + DEFINE_WAIT(wait); + + mutex_lock(&genpd->lock); + /* + * Wait for the domain to transition into either the active, + * or the power off state. + */ + for (;;) { + prepare_to_wait(&genpd->status_wait_queue, &wait, + TASK_UNINTERRUPTIBLE); + if (genpd->status != GPD_STATE_BUSY) + break; + mutex_unlock(&genpd->lock); + + schedule(); + + mutex_lock(&genpd->lock); + } + finish_wait(&genpd->status_wait_queue, &wait); +} + +static void genpd_release_lock(struct generic_pm_domain *genpd) +{ + mutex_unlock(&genpd->lock); +} + /** * pm_genpd_poweron - Restore power to a given PM domain and its parents. * @genpd: PM domain to power up. @@ -39,22 +69,50 @@ static void genpd_sd_counter_dec(struct generic_pm_domain *genpd) */ int pm_genpd_poweron(struct generic_pm_domain *genpd) { + struct generic_pm_domain *parent = genpd->parent; + DEFINE_WAIT(wait); int ret = 0; start: - if (genpd->parent) - mutex_lock(&genpd->parent->lock); - mutex_lock_nested(&genpd->lock, SINGLE_DEPTH_NESTING); + if (parent) { + mutex_lock(&parent->lock); + mutex_lock_nested(&genpd->lock, SINGLE_DEPTH_NESTING); + } else { + mutex_lock(&genpd->lock); + } + /* + * Wait for the domain to transition into either the active, + * or the power off state. + */ + for (;;) { + prepare_to_wait(&genpd->status_wait_queue, &wait, + TASK_UNINTERRUPTIBLE); + if (genpd->status != GPD_STATE_BUSY) + break; + mutex_unlock(&genpd->lock); + if (parent) + mutex_unlock(&parent->lock); + + schedule(); - if (!genpd->power_is_off + if (parent) { + mutex_lock(&parent->lock); + mutex_lock_nested(&genpd->lock, SINGLE_DEPTH_NESTING); + } else { + mutex_lock(&genpd->lock); + } + } + finish_wait(&genpd->status_wait_queue, &wait); + + if (genpd->status == GPD_STATE_ACTIVE || (genpd->prepared_count > 0 && genpd->suspend_power_off)) goto out; - if (genpd->parent && genpd->parent->power_is_off) { + if (parent && parent->status != GPD_STATE_ACTIVE) { mutex_unlock(&genpd->lock); - mutex_unlock(&genpd->parent->lock); + mutex_unlock(&parent->lock); - ret = pm_genpd_poweron(genpd->parent); + ret = pm_genpd_poweron(parent); if (ret) return ret; @@ -67,14 +125,14 @@ int pm_genpd_poweron(struct generic_pm_domain *genpd) goto out; } - genpd->power_is_off = false; - if (genpd->parent) - genpd->parent->sd_count++; + genpd->status = GPD_STATE_ACTIVE; + if (parent) + parent->sd_count++; out: mutex_unlock(&genpd->lock); - if (genpd->parent) - mutex_unlock(&genpd->parent->lock); + if (parent) + mutex_unlock(&parent->lock); return ret; } @@ -90,6 +148,7 @@ int pm_genpd_poweron(struct generic_pm_domain *genpd) */ static int __pm_genpd_save_device(struct dev_list_entry *dle, struct generic_pm_domain *genpd) + __releases(&genpd->lock) __acquires(&genpd->lock) { struct device *dev = dle->dev; struct device_driver *drv = dev->driver; @@ -98,6 +157,8 @@ static int __pm_genpd_save_device(struct dev_list_entry *dle, if (dle->need_restore) return 0; + mutex_unlock(&genpd->lock); + if (drv && drv->pm && drv->pm->runtime_suspend) { if (genpd->start_device) genpd->start_device(dev); @@ -108,6 +169,8 @@ static int __pm_genpd_save_device(struct dev_list_entry *dle, genpd->stop_device(dev); } + mutex_lock(&genpd->lock); + if (!ret) dle->need_restore = true; @@ -121,6 +184,7 @@ static int __pm_genpd_save_device(struct dev_list_entry *dle, */ static void __pm_genpd_restore_device(struct dev_list_entry *dle, struct generic_pm_domain *genpd) + __releases(&genpd->lock) __acquires(&genpd->lock) { struct device *dev = dle->dev; struct device_driver *drv = dev->driver; @@ -128,6 +192,8 @@ static void __pm_genpd_restore_device(struct dev_list_entry *dle, if (!dle->need_restore) return; + mutex_unlock(&genpd->lock); + if (drv && drv->pm && drv->pm->runtime_resume) { if (genpd->start_device) genpd->start_device(dev); @@ -138,6 +204,8 @@ static void __pm_genpd_restore_device(struct dev_list_entry *dle, genpd->stop_device(dev); } + mutex_lock(&genpd->lock); + dle->need_restore = false; } @@ -150,13 +218,14 @@ static void __pm_genpd_restore_device(struct dev_list_entry *dle, * the @genpd's devices' drivers and remove power from @genpd. */ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) + __releases(&genpd->lock) __acquires(&genpd->lock) { struct generic_pm_domain *parent; struct dev_list_entry *dle; unsigned int not_suspended; int ret; - if (genpd->power_is_off || genpd->prepared_count > 0) + if (genpd->status == GPD_STATE_POWER_OFF || genpd->prepared_count > 0) return 0; if (genpd->sd_count > 0) @@ -175,22 +244,36 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) return -EAGAIN; } + genpd->status = GPD_STATE_BUSY; + list_for_each_entry_reverse(dle, &genpd->dev_list, node) { ret = __pm_genpd_save_device(dle, genpd); if (ret) goto err_dev; } + mutex_unlock(&genpd->lock); + + parent = genpd->parent; + if (parent) { + genpd_acquire_lock(parent); + mutex_lock_nested(&genpd->lock, SINGLE_DEPTH_NESTING); + } else { + mutex_lock(&genpd->lock); + } + if (genpd->power_off) genpd->power_off(genpd); - genpd->power_is_off = true; + genpd->status = GPD_STATE_POWER_OFF; + wake_up_all(&genpd->status_wait_queue); - parent = genpd->parent; if (parent) { genpd_sd_counter_dec(parent); if (parent->sd_count == 0) queue_work(pm_wq, &parent->power_off_work); + + genpd_release_lock(parent); } return 0; @@ -199,6 +282,9 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) list_for_each_entry_continue(dle, &genpd->dev_list, node) __pm_genpd_restore_device(dle, genpd); + genpd->status = GPD_STATE_ACTIVE; + wake_up_all(&genpd->status_wait_queue); + return ret; } @@ -212,13 +298,9 @@ static void genpd_power_off_work_fn(struct work_struct *work) genpd = container_of(work, struct generic_pm_domain, power_off_work); - if (genpd->parent) - mutex_lock(&genpd->parent->lock); - mutex_lock_nested(&genpd->lock, SINGLE_DEPTH_NESTING); + genpd_acquire_lock(genpd); pm_genpd_poweroff(genpd); - mutex_unlock(&genpd->lock); - if (genpd->parent) - mutex_unlock(&genpd->parent->lock); + genpd_release_lock(genpd); } /** @@ -239,23 +321,17 @@ static int pm_genpd_runtime_suspend(struct device *dev) if (IS_ERR(genpd)) return -EINVAL; - if (genpd->parent) - mutex_lock(&genpd->parent->lock); - mutex_lock_nested(&genpd->lock, SINGLE_DEPTH_NESTING); - if (genpd->stop_device) { int ret = genpd->stop_device(dev); if (ret) - goto out; + return ret; } + + genpd_acquire_lock(genpd); genpd->in_progress++; pm_genpd_poweroff(genpd); genpd->in_progress--; - - out: - mutex_unlock(&genpd->lock); - if (genpd->parent) - mutex_unlock(&genpd->parent->lock); + genpd_release_lock(genpd); return 0; } @@ -276,9 +352,6 @@ static void __pm_genpd_runtime_resume(struct device *dev, break; } } - - if (genpd->start_device) - genpd->start_device(dev); } /** @@ -304,9 +377,15 @@ static int pm_genpd_runtime_resume(struct device *dev) if (ret) return ret; - mutex_lock(&genpd->lock); + genpd_acquire_lock(genpd); + genpd->status = GPD_STATE_BUSY; __pm_genpd_runtime_resume(dev, genpd); - mutex_unlock(&genpd->lock); + genpd->status = GPD_STATE_ACTIVE; + wake_up_all(&genpd->status_wait_queue); + genpd_release_lock(genpd); + + if (genpd->start_device) + genpd->start_device(dev); return 0; } @@ -339,7 +418,7 @@ static void pm_genpd_sync_poweroff(struct generic_pm_domain *genpd) { struct generic_pm_domain *parent = genpd->parent; - if (genpd->power_is_off) + if (genpd->status == GPD_STATE_POWER_OFF) return; if (genpd->suspended_count != genpd->device_count || genpd->sd_count > 0) @@ -348,7 +427,7 @@ static void pm_genpd_sync_poweroff(struct generic_pm_domain *genpd) if (genpd->power_off) genpd->power_off(genpd); - genpd->power_is_off = true; + genpd->status = GPD_STATE_POWER_OFF; if (parent) { genpd_sd_counter_dec(parent); pm_genpd_sync_poweroff(parent); @@ -375,32 +454,41 @@ static int pm_genpd_prepare(struct device *dev) if (IS_ERR(genpd)) return -EINVAL; - mutex_lock(&genpd->lock); + /* + * If a wakeup request is pending for the device, it should be woken up + * at this point and a system wakeup event should be reported if it's + * set up to wake up the system from sleep states. + */ + pm_runtime_get_noresume(dev); + if (pm_runtime_barrier(dev) && device_may_wakeup(dev)) + pm_wakeup_event(dev, 0); + + if (pm_wakeup_pending()) { + pm_runtime_put_sync(dev); + return -EBUSY; + } + + genpd_acquire_lock(genpd); if (genpd->prepared_count++ == 0) - genpd->suspend_power_off = genpd->power_is_off; + genpd->suspend_power_off = genpd->status == GPD_STATE_POWER_OFF; + + genpd_release_lock(genpd); if (genpd->suspend_power_off) { - mutex_unlock(&genpd->lock); + pm_runtime_put_noidle(dev); return 0; } /* - * If the device is in the (runtime) "suspended" state, call - * .start_device() for it, if defined. - */ - if (pm_runtime_suspended(dev)) - __pm_genpd_runtime_resume(dev, genpd); - - /* - * Do not check if runtime resume is pending at this point, because it - * has been taken care of already and if pm_genpd_poweron() ran at this - * point as a result of the check, it would deadlock. + * The PM domain must be in the GPD_STATE_ACTIVE state at this point, + * so pm_genpd_poweron() will return immediately, but if the device + * is suspended (e.g. it's been stopped by .stop_device()), we need + * to make it operational. */ + pm_runtime_resume(dev); __pm_runtime_disable(dev, false); - mutex_unlock(&genpd->lock); - ret = pm_generic_prepare(dev); if (ret) { mutex_lock(&genpd->lock); @@ -409,7 +497,10 @@ static int pm_genpd_prepare(struct device *dev) genpd->suspend_power_off = false; mutex_unlock(&genpd->lock); + pm_runtime_enable(dev); } + + pm_runtime_put_sync(dev); return ret; } @@ -726,7 +817,7 @@ static int pm_genpd_restore_noirq(struct device *dev) * guaranteed that this function will never run twice in parallel for * the same PM domain, so it is not necessary to use locking here. */ - genpd->power_is_off = true; + genpd->status = GPD_STATE_POWER_OFF; if (genpd->suspend_power_off) { /* * The boot kernel might put the domain into the power on state, @@ -836,9 +927,9 @@ int pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev) if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(dev)) return -EINVAL; - mutex_lock(&genpd->lock); + genpd_acquire_lock(genpd); - if (genpd->power_is_off) { + if (genpd->status == GPD_STATE_POWER_OFF) { ret = -EINVAL; goto out; } @@ -870,7 +961,7 @@ int pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev) spin_unlock_irq(&dev->power.lock); out: - mutex_unlock(&genpd->lock); + genpd_release_lock(genpd); return ret; } @@ -891,7 +982,7 @@ int pm_genpd_remove_device(struct generic_pm_domain *genpd, if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(dev)) return -EINVAL; - mutex_lock(&genpd->lock); + genpd_acquire_lock(genpd); if (genpd->prepared_count > 0) { ret = -EAGAIN; @@ -915,7 +1006,7 @@ int pm_genpd_remove_device(struct generic_pm_domain *genpd, } out: - mutex_unlock(&genpd->lock); + genpd_release_lock(genpd); return ret; } @@ -934,9 +1025,19 @@ int pm_genpd_add_subdomain(struct generic_pm_domain *genpd, if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(new_subdomain)) return -EINVAL; - mutex_lock(&genpd->lock); + start: + genpd_acquire_lock(genpd); + mutex_lock_nested(&new_subdomain->lock, SINGLE_DEPTH_NESTING); - if (genpd->power_is_off && !new_subdomain->power_is_off) { + if (new_subdomain->status != GPD_STATE_POWER_OFF + && new_subdomain->status != GPD_STATE_ACTIVE) { + mutex_unlock(&new_subdomain->lock); + genpd_release_lock(genpd); + goto start; + } + + if (genpd->status == GPD_STATE_POWER_OFF + && new_subdomain->status != GPD_STATE_POWER_OFF) { ret = -EINVAL; goto out; } @@ -948,17 +1049,14 @@ int pm_genpd_add_subdomain(struct generic_pm_domain *genpd, } } - mutex_lock_nested(&new_subdomain->lock, SINGLE_DEPTH_NESTING); - list_add_tail(&new_subdomain->sd_node, &genpd->sd_list); new_subdomain->parent = genpd; - if (!subdomain->power_is_off) + if (subdomain->status != GPD_STATE_POWER_OFF) genpd->sd_count++; - mutex_unlock(&new_subdomain->lock); - out: - mutex_unlock(&genpd->lock); + mutex_unlock(&new_subdomain->lock); + genpd_release_lock(genpd); return ret; } @@ -977,7 +1075,8 @@ int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd, if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(target)) return -EINVAL; - mutex_lock(&genpd->lock); + start: + genpd_acquire_lock(genpd); list_for_each_entry(subdomain, &genpd->sd_list, sd_node) { if (subdomain != target) @@ -985,9 +1084,16 @@ int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd, mutex_lock_nested(&subdomain->lock, SINGLE_DEPTH_NESTING); + if (subdomain->status != GPD_STATE_POWER_OFF + && subdomain->status != GPD_STATE_ACTIVE) { + mutex_unlock(&subdomain->lock); + genpd_release_lock(genpd); + goto start; + } + list_del(&subdomain->sd_node); subdomain->parent = NULL; - if (!subdomain->power_is_off) + if (subdomain->status != GPD_STATE_POWER_OFF) genpd_sd_counter_dec(genpd); mutex_unlock(&subdomain->lock); @@ -996,7 +1102,7 @@ int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd, break; } - mutex_unlock(&genpd->lock); + genpd_release_lock(genpd); return ret; } @@ -1022,7 +1128,8 @@ void pm_genpd_init(struct generic_pm_domain *genpd, INIT_WORK(&genpd->power_off_work, genpd_power_off_work_fn); genpd->in_progress = 0; genpd->sd_count = 0; - genpd->power_is_off = is_off; + genpd->status = is_off ? GPD_STATE_POWER_OFF : GPD_STATE_ACTIVE; + init_waitqueue_head(&genpd->status_wait_queue); genpd->device_count = 0; genpd->suspended_count = 0; genpd->domain.ops.runtime_suspend = pm_genpd_runtime_suspend; diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index 14fb0953fa47..c71457cb8a79 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -11,8 +11,11 @@ #include <linux/device.h> -#define GPD_IN_SUSPEND 1 -#define GPD_POWER_OFF 2 +enum gpd_status { + GPD_STATE_ACTIVE = 0, /* PM domain is active */ + GPD_STATE_BUSY, /* Something is happening to the PM domain */ + GPD_STATE_POWER_OFF, /* PM domain is off */ +}; struct dev_power_governor { bool (*power_down_ok)(struct dev_pm_domain *domain); @@ -29,7 +32,8 @@ struct generic_pm_domain { struct work_struct power_off_work; unsigned int in_progress; /* Number of devices being suspended now */ unsigned int sd_count; /* Number of subdomains with power "on" */ - bool power_is_off; /* Whether or not power has been removed */ + enum gpd_status status; /* Current state of the domain */ + wait_queue_head_t status_wait_queue; unsigned int device_count; /* Number of devices */ unsigned int suspended_count; /* System suspend device counter */ unsigned int prepared_count; /* Suspend counter of prepared devices */ |