From 431d452af13720463dda498999b2e9a08729c03a Mon Sep 17 00:00:00 2001 From: Zhonghui Fu Date: Wed, 18 Mar 2015 15:54:27 +0100 Subject: PM / sleep: add pm-trace support for suspending phase Occasionally, the system can't come back up after suspend/resume due to problems of device suspending phase. This patch make PM_TRACE infrastructure cover device suspending phase of suspend/resume process, and the information in RTC can tell developers which device suspending function make system hang. Signed-off-by: Zhonghui Fu Signed-off-by: Rafael J. Wysocki --- drivers/base/power/main.c | 20 ++++++++++++++++---- drivers/base/power/trace.c | 6 +++--- 2 files changed, 19 insertions(+), 7 deletions(-) (limited to 'drivers/base/power') diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 9717d5f20139..3d874eca7104 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -23,7 +23,7 @@ #include #include #include -#include +#include #include #include #include @@ -1017,6 +1017,9 @@ static int __device_suspend_noirq(struct device *dev, pm_message_t state, bool a char *info = NULL; int error = 0; + TRACE_DEVICE(dev); + TRACE_SUSPEND(0); + if (async_error) goto Complete; @@ -1057,6 +1060,7 @@ static int __device_suspend_noirq(struct device *dev, pm_message_t state, bool a Complete: complete_all(&dev->power.completion); + TRACE_SUSPEND(error); return error; } @@ -1078,7 +1082,7 @@ static int device_suspend_noirq(struct device *dev) { reinit_completion(&dev->power.completion); - if (pm_async_enabled && dev->power.async_suspend) { + if (is_async(dev)) { get_device(dev); async_schedule(async_suspend_noirq, dev); return 0; @@ -1157,6 +1161,9 @@ static int __device_suspend_late(struct device *dev, pm_message_t state, bool as char *info = NULL; int error = 0; + TRACE_DEVICE(dev); + TRACE_SUSPEND(0); + __pm_runtime_disable(dev, false); if (async_error) @@ -1198,6 +1205,7 @@ static int __device_suspend_late(struct device *dev, pm_message_t state, bool as async_error = error; Complete: + TRACE_SUSPEND(error); complete_all(&dev->power.completion); return error; } @@ -1219,7 +1227,7 @@ static int device_suspend_late(struct device *dev) { reinit_completion(&dev->power.completion); - if (pm_async_enabled && dev->power.async_suspend) { + if (is_async(dev)) { get_device(dev); async_schedule(async_suspend_late, dev); return 0; @@ -1338,6 +1346,9 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async) int error = 0; DECLARE_DPM_WATCHDOG_ON_STACK(wd); + TRACE_DEVICE(dev); + TRACE_SUSPEND(0); + dpm_wait_for_children(dev, async); if (async_error) @@ -1444,6 +1455,7 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async) if (error) async_error = error; + TRACE_SUSPEND(error); return error; } @@ -1465,7 +1477,7 @@ static int device_suspend(struct device *dev) { reinit_completion(&dev->power.completion); - if (pm_async_enabled && dev->power.async_suspend) { + if (is_async(dev)) { get_device(dev); async_schedule(async_suspend, dev); return 0; diff --git a/drivers/base/power/trace.c b/drivers/base/power/trace.c index d94a1f5121cf..a311cfa4c5bd 100644 --- a/drivers/base/power/trace.c +++ b/drivers/base/power/trace.c @@ -7,7 +7,7 @@ * devices may be working. */ -#include +#include #include #include @@ -154,7 +154,7 @@ EXPORT_SYMBOL(set_trace_device); * it's not any guarantee, but it's a high _likelihood_ that * the match is valid). */ -void generate_resume_trace(const void *tracedata, unsigned int user) +void generate_pm_trace(const void *tracedata, unsigned int user) { unsigned short lineno = *(unsigned short *)tracedata; const char *file = *(const char **)(tracedata + 2); @@ -164,7 +164,7 @@ void generate_resume_trace(const void *tracedata, unsigned int user) file_hash_value = hash_string(lineno, file, FILEHASH); set_magic_time(user_hash_value, file_hash_value, dev_hash_value); } -EXPORT_SYMBOL(generate_resume_trace); +EXPORT_SYMBOL(generate_pm_trace); extern char __tracedata_start, __tracedata_end; static int show_file_hash(unsigned int value) -- cgit v1.2.3-55-g7522 From 632f7ce3f9b65039ac6c4f14ef91a0a1f3bdff59 Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 20 Mar 2015 15:55:12 +0100 Subject: PM / Domains: Sync runtime PM status with genpd after probe Buses which currently supports attaching devices to their PM domains, will invoke the dev_pm_domain_attach() API from their ->probe() callbacks. During the attach procedure, genpd power up the PM domain. In those scenarios where the bus/driver don't need to access its device during probe, it may leave it in runtime PM suspended state since that's also the default state. In that way, no notifications through the runtime PM callbacks will reach the PM domain during probe. For genpd, the consequence from the above scenario means the PM domain will remain powered. Therefore, implement the struct dev_pm_domain's ->sync() callback, which is invoked from driver core after the bus/driver has probed the device. It allows genpd to power off the PM domain if it's unused. Signed-off-by: Russell King [ Ulf: Updated patch according to updates in driver core ] Signed-off-by: Ulf Hansson Acked-by: Kevin Hilman Signed-off-by: Rafael J. Wysocki --- drivers/base/power/domain.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'drivers/base/power') diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 45937f88e77c..295ff7189504 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -2130,6 +2130,17 @@ static void genpd_dev_pm_detach(struct device *dev, bool power_off) genpd_queue_power_off_work(pd); } +static void genpd_dev_pm_sync(struct device *dev) +{ + struct generic_pm_domain *pd; + + pd = dev_to_genpd(dev); + if (IS_ERR(pd)) + return; + + genpd_queue_power_off_work(pd); +} + /** * genpd_dev_pm_attach - Attach a device to its PM domain using DT. * @dev: Device to attach. @@ -2196,6 +2207,7 @@ int genpd_dev_pm_attach(struct device *dev) } dev->pm_domain->detach = genpd_dev_pm_detach; + dev->pm_domain->sync = genpd_dev_pm_sync; pm_genpd_poweron(pd); return 0; -- cgit v1.2.3-55-g7522 From 6d7d5c3266aa946b2049d9fed02186c1a378621b Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 20 Mar 2015 17:20:28 +0000 Subject: PM / domains: quieten down generic pm domains PM domains are rather noisy; scheduling behaviour can cause callbacks to take longer, which causes them to spit out a warning-level message each time a callback takes a little longer than the previous time. There really isn't a need for this, except when debugging. Acked-by: Ulf Hansson Acked-by: Kevin Hilman Signed-off-by: Russell King Signed-off-by: Rafael J. Wysocki --- drivers/base/power/domain.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers/base/power') diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 295ff7189504..2e2e6af1f3fe 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -173,8 +173,8 @@ static int genpd_power_on(struct generic_pm_domain *genpd) genpd->power_on_latency_ns = elapsed_ns; genpd->max_off_time_changed = true; genpd_recalc_cpu_exit_latency(genpd); - pr_warn("%s: Power-%s latency exceeded, new value %lld ns\n", - genpd->name, "on", elapsed_ns); + pr_debug("%s: Power-%s latency exceeded, new value %lld ns\n", + genpd->name, "on", elapsed_ns); return ret; } @@ -199,8 +199,8 @@ static int genpd_power_off(struct generic_pm_domain *genpd) genpd->power_off_latency_ns = elapsed_ns; genpd->max_off_time_changed = true; - pr_warn("%s: Power-%s latency exceeded, new value %lld ns\n", - genpd->name, "off", elapsed_ns); + pr_debug("%s: Power-%s latency exceeded, new value %lld ns\n", + genpd->name, "off", elapsed_ns); return ret; } -- cgit v1.2.3-55-g7522 From 446d999c1c92cec996e759dc3c03110596e626f5 Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 20 Mar 2015 17:20:33 +0000 Subject: PM / domains: factor out code to get the generic PM domain from a struct device The PM domain code contains two methods to get the generic PM domain for a struct device. One is dev_to_genpd() which is only safe when we know for certain that the device has a generic PM domain attached. The other is coded into genpd_dev_pm_detach() which ensures that the PM domain in the struct device is a generic PM domain (and so is safer). This commit factors out the safer version, documents it, and hides the unsafe dev_to_genpd(). Signed-off-by: Russell King Signed-off-by: Rafael J. Wysocki --- drivers/base/power/domain.c | 46 +++++++++++++++++++++++++++++++-------------- include/linux/pm_domain.h | 6 +++--- 2 files changed, 35 insertions(+), 17 deletions(-) (limited to 'drivers/base/power') diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 2e2e6af1f3fe..da25c0630789 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -68,7 +68,36 @@ static struct generic_pm_domain *pm_genpd_lookup_name(const char *domain_name) return genpd; } -struct generic_pm_domain *dev_to_genpd(struct device *dev) +/* + * Get the generic PM domain for a particular struct device. + * This validates the struct device pointer, the PM domain pointer, + * and checks that the PM domain pointer is a real generic PM domain. + * Any failure results in NULL being returned. + */ +struct generic_pm_domain *pm_genpd_lookup_dev(struct device *dev) +{ + struct generic_pm_domain *genpd = NULL, *gpd; + + if (IS_ERR_OR_NULL(dev) || IS_ERR_OR_NULL(dev->pm_domain)) + return NULL; + + mutex_lock(&gpd_list_lock); + list_for_each_entry(gpd, &gpd_list, gpd_list_node) { + if (&gpd->domain == dev->pm_domain) { + genpd = gpd; + break; + } + } + mutex_unlock(&gpd_list_lock); + + return genpd; +} + +/* + * This should only be used where we are certain that the pm_domain + * attached to the device is a genpd domain. + */ +static struct generic_pm_domain *dev_to_genpd(struct device *dev) { if (IS_ERR_OR_NULL(dev->pm_domain)) return ERR_PTR(-EINVAL); @@ -2093,21 +2122,10 @@ EXPORT_SYMBOL_GPL(of_genpd_get_from_provider); */ static void genpd_dev_pm_detach(struct device *dev, bool power_off) { - struct generic_pm_domain *pd = NULL, *gpd; + struct generic_pm_domain *pd; int ret = 0; - if (!dev->pm_domain) - return; - - mutex_lock(&gpd_list_lock); - list_for_each_entry(gpd, &gpd_list, gpd_list_node) { - if (&gpd->domain == dev->pm_domain) { - pd = gpd; - break; - } - } - mutex_unlock(&gpd_list_lock); - + pd = pm_genpd_lookup_dev(dev); if (!pd) return; diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index 080e778118ba..681ccb053f72 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -127,7 +127,7 @@ static inline struct generic_pm_domain_data *dev_gpd_data(struct device *dev) return to_gpd_data(dev->power.subsys_data->domain_data); } -extern struct generic_pm_domain *dev_to_genpd(struct device *dev); +extern struct generic_pm_domain *pm_genpd_lookup_dev(struct device *dev); extern int __pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev, struct gpd_timing_data *td); @@ -163,9 +163,9 @@ static inline struct generic_pm_domain_data *dev_gpd_data(struct device *dev) { return ERR_PTR(-ENOSYS); } -static inline struct generic_pm_domain *dev_to_genpd(struct device *dev) +static inline struct generic_pm_domain *pm_genpd_lookup_dev(struct device *dev) { - return ERR_PTR(-ENOSYS); + return NULL; } static inline int __pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev, -- cgit v1.2.3-55-g7522 From df6a0d6f633d684ef62bd92038c01a1781894f85 Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 20 Mar 2015 17:20:38 +0000 Subject: PM / domains: avoid potential oops in pm_genpd_remove_device() pm_genpd_remove_device() tries hard to validate the generic PM domain passed to it, but the validation is not complete. dev->pm_domain contains a struct dev_pm_domain, which is the "base class" of generic PM domains. Other users of dev_pm_domains include stuff like vga_switheroo. Hence, a device could have a generic PM domain or a vga_switcheroo PM domain in dev->pm_domain. We need ot be certain that the PM domain is actually valid before we try to remove it. We can do this easily as we have a way to get the current validated generic PM domain for a struct device. This must match the generic PM domain being requested for removal. Convert the code to use this alternative validation method instead. Signed-off-by: Russell King Acked-by: Kevin Hilman Acked-by: Ulf Hansson Signed-off-by: Rafael J. Wysocki --- drivers/base/power/domain.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'drivers/base/power') diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index da25c0630789..2327613d4539 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -1542,9 +1542,7 @@ int pm_genpd_remove_device(struct generic_pm_domain *genpd, dev_dbg(dev, "%s()\n", __func__); - if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(dev) - || IS_ERR_OR_NULL(dev->pm_domain) - || pd_to_genpd(dev->pm_domain) != genpd) + if (!genpd || genpd != pm_genpd_lookup_dev(dev)) return -EINVAL; /* The above validation also means we have existing domain_data. */ -- cgit v1.2.3-55-g7522