diff options
Diffstat (limited to 'drivers/base/power/main.c')
-rw-r--r-- | drivers/base/power/main.c | 79 |
1 files changed, 79 insertions, 0 deletions
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 220ec3a3ca75..2c41a74d0eda 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -31,6 +31,9 @@ #include <trace/events/power.h> #include <linux/cpufreq.h> #include <linux/cpuidle.h> +#include <linux/timer.h> +#include <linux/wakeup_reason.h> + #include "../base.h" #include "power.h" @@ -56,6 +59,12 @@ struct suspend_stats suspend_stats; static DEFINE_MUTEX(dpm_list_mtx); static pm_message_t pm_transition; +struct dpm_watchdog { + struct device *dev; + struct task_struct *tsk; + struct timer_list timer; +}; + static int async_error; /** @@ -386,6 +395,56 @@ static int dpm_run_callback(pm_callback_t cb, struct device *dev, return error; } +/** + * dpm_wd_handler - Driver suspend / resume watchdog handler. + * + * Called when a driver has timed out suspending or resuming. + * There's not much we can do here to recover so BUG() out for + * a crash-dump + */ +static void dpm_wd_handler(unsigned long data) +{ + struct dpm_watchdog *wd = (void *)data; + struct device *dev = wd->dev; + struct task_struct *tsk = wd->tsk; + + dev_emerg(dev, "**** DPM device timeout ****\n"); + show_stack(tsk, NULL); + + BUG(); +} + +/** + * dpm_wd_set - Enable pm watchdog for given device. + * @wd: Watchdog. Must be allocated on the stack. + * @dev: Device to handle. + */ +static void dpm_wd_set(struct dpm_watchdog *wd, struct device *dev) +{ + struct timer_list *timer = &wd->timer; + + wd->dev = dev; + wd->tsk = get_current(); + + init_timer_on_stack(timer); + timer->expires = jiffies + HZ * 12; + timer->function = dpm_wd_handler; + timer->data = (unsigned long)wd; + add_timer(timer); +} + +/** + * dpm_wd_clear - Disable pm watchdog. + * @wd: Watchdog to disable. + */ +static void dpm_wd_clear(struct dpm_watchdog *wd) +{ + struct timer_list *timer = &wd->timer; + + del_timer_sync(timer); + destroy_timer_on_stack(timer); +} + /*------------------------- Resume routines -------------------------*/ /** @@ -572,6 +631,7 @@ static int device_resume(struct device *dev, pm_message_t state, bool async) pm_callback_t callback = NULL; char *info = NULL; int error = 0; + struct dpm_watchdog wd; TRACE_DEVICE(dev); TRACE_RESUME(0); @@ -587,6 +647,7 @@ static int device_resume(struct device *dev, pm_message_t state, bool async) * a resumed device, even if the device hasn't been completed yet. */ dev->power.is_prepared = false; + dpm_wd_set(&wd, dev); if (!dev->power.is_suspended) goto Unlock; @@ -638,6 +699,7 @@ static int device_resume(struct device *dev, pm_message_t state, bool async) Unlock: device_unlock(dev); + dpm_wd_clear(&wd); Complete: complete_all(&dev->power.completion); @@ -881,6 +943,7 @@ static int device_suspend_noirq(struct device *dev, pm_message_t state) static int dpm_suspend_noirq(pm_message_t state) { ktime_t starttime = ktime_get(); + char suspend_abort[MAX_SUSPEND_ABORT_LEN]; int error = 0; cpuidle_pause(); @@ -908,6 +971,9 @@ static int dpm_suspend_noirq(pm_message_t state) put_device(dev); if (pm_wakeup_pending()) { + pm_get_active_wakeup_sources(suspend_abort, + MAX_SUSPEND_ABORT_LEN); + log_suspend_abort_reason(suspend_abort); error = -EBUSY; break; } @@ -966,6 +1032,7 @@ static int device_suspend_late(struct device *dev, pm_message_t state) static int dpm_suspend_late(pm_message_t state) { ktime_t starttime = ktime_get(); + char suspend_abort[MAX_SUSPEND_ABORT_LEN]; int error = 0; mutex_lock(&dpm_list_mtx); @@ -991,6 +1058,9 @@ static int dpm_suspend_late(pm_message_t state) put_device(dev); if (pm_wakeup_pending()) { + pm_get_active_wakeup_sources(suspend_abort, + MAX_SUSPEND_ABORT_LEN); + log_suspend_abort_reason(suspend_abort); error = -EBUSY; break; } @@ -1057,6 +1127,8 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async) pm_callback_t callback = NULL; char *info = NULL; int error = 0; + struct dpm_watchdog wd; + char suspend_abort[MAX_SUSPEND_ABORT_LEN]; dpm_wait_for_children(dev, async); @@ -1073,12 +1145,17 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async) pm_wakeup_event(dev, 0); if (pm_wakeup_pending()) { + pm_get_active_wakeup_sources(suspend_abort, + MAX_SUSPEND_ABORT_LEN); + log_suspend_abort_reason(suspend_abort); async_error = -EBUSY; goto Complete; } if (dev->power.syscore) goto Complete; + + dpm_wd_set(&wd, dev); device_lock(dev); @@ -1135,6 +1212,8 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async) device_unlock(dev); + dpm_wd_clear(&wd); + Complete: complete_all(&dev->power.completion); if (error) |