aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/base/power/main.c
diff options
context:
space:
mode:
authorBenoit Goby <benoit@android.com>2013-10-17 13:48:46 -0400
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>2013-10-18 07:33:08 -0400
commit70fea60d888d472ac044910fd0dc46b304419705 (patch)
treeb5e884b2384ce0122481069aaa116678ea52bfa2 /drivers/base/power/main.c
parent61e6cfa80de5760bbe406f4e815b7739205754d2 (diff)
PM / Sleep: Detect device suspend/resume lockup and log event
Rather than hard-lock the kernel, dump the suspend/resume thread stack and panic() to capture a message in pstore when a driver takes too long to suspend/resume. Default suspend/resume watchdog timeout is set to 12 seconds to be longer than the usbhid 10 second timeout, but could be changed at compile time. Exclude from the watchdog the time spent waiting for children that are resumed asynchronously and time every device, whether or not they resumed synchronously. This patch is targeted for mobile devices where a suspend/resume lockup could cause a system reboot. Information about failing device can be retrieved in subsequent boot session by mounting pstore and inspecting the log. Laptops with EFI-enabled pstore could also benefit from this feature. The hardware watchdog timer is likely suspended during this time and couldn't be relied upon. The soft-lockup detector would eventually tell that tasks are not scheduled, but would provide little context as to why. The patch hence uses system timer and assumes it is still active while the devices are suspended/resumed. This feature can be enabled/disabled during kernel configuration. This change is based on earlier work by San Mehat. Signed-off-by: Benoit Goby <benoit@android.com> Signed-off-by: Zoran Markovic <zoran.markovic@linaro.org> Acked-by: Ulf Hansson <ulf.hansson@linaro.org> Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Diffstat (limited to 'drivers/base/power/main.c')
-rw-r--r--drivers/base/power/main.c73
1 files changed, 73 insertions, 0 deletions
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
index 9f098a82cf04..ee039afe9078 100644
--- a/drivers/base/power/main.c
+++ b/drivers/base/power/main.c
@@ -30,6 +30,8 @@
30#include <linux/suspend.h> 30#include <linux/suspend.h>
31#include <trace/events/power.h> 31#include <trace/events/power.h>
32#include <linux/cpuidle.h> 32#include <linux/cpuidle.h>
33#include <linux/timer.h>
34
33#include "../base.h" 35#include "../base.h"
34#include "power.h" 36#include "power.h"
35 37
@@ -390,6 +392,71 @@ static int dpm_run_callback(pm_callback_t cb, struct device *dev,
390 return error; 392 return error;
391} 393}
392 394
395#ifdef CONFIG_DPM_WATCHDOG
396struct dpm_watchdog {
397 struct device *dev;
398 struct task_struct *tsk;
399 struct timer_list timer;
400};
401
402#define DECLARE_DPM_WATCHDOG_ON_STACK(wd) \
403 struct dpm_watchdog wd
404
405/**
406 * dpm_watchdog_handler - Driver suspend / resume watchdog handler.
407 * @data: Watchdog object address.
408 *
409 * Called when a driver has timed out suspending or resuming.
410 * There's not much we can do here to recover so panic() to
411 * capture a crash-dump in pstore.
412 */
413static void dpm_watchdog_handler(unsigned long data)
414{
415 struct dpm_watchdog *wd = (void *)data;
416
417 dev_emerg(wd->dev, "**** DPM device timeout ****\n");
418 show_stack(wd->tsk, NULL);
419 panic("%s %s: unrecoverable failure\n",
420 dev_driver_string(wd->dev), dev_name(wd->dev));
421}
422
423/**
424 * dpm_watchdog_set - Enable pm watchdog for given device.
425 * @wd: Watchdog. Must be allocated on the stack.
426 * @dev: Device to handle.
427 */
428static void dpm_watchdog_set(struct dpm_watchdog *wd, struct device *dev)
429{
430 struct timer_list *timer = &wd->timer;
431
432 wd->dev = dev;
433 wd->tsk = current;
434
435 init_timer_on_stack(timer);
436 /* use same timeout value for both suspend and resume */
437 timer->expires = jiffies + HZ * CONFIG_DPM_WATCHDOG_TIMEOUT;
438 timer->function = dpm_watchdog_handler;
439 timer->data = (unsigned long)wd;
440 add_timer(timer);
441}
442
443/**
444 * dpm_watchdog_clear - Disable suspend/resume watchdog.
445 * @wd: Watchdog to disable.
446 */
447static void dpm_watchdog_clear(struct dpm_watchdog *wd)
448{
449 struct timer_list *timer = &wd->timer;
450
451 del_timer_sync(timer);
452 destroy_timer_on_stack(timer);
453}
454#else
455#define DECLARE_DPM_WATCHDOG_ON_STACK(wd)
456#define dpm_watchdog_set(x, y)
457#define dpm_watchdog_clear(x)
458#endif
459
393/*------------------------- Resume routines -------------------------*/ 460/*------------------------- Resume routines -------------------------*/
394 461
395/** 462/**
@@ -576,6 +643,7 @@ static int device_resume(struct device *dev, pm_message_t state, bool async)
576 pm_callback_t callback = NULL; 643 pm_callback_t callback = NULL;
577 char *info = NULL; 644 char *info = NULL;
578 int error = 0; 645 int error = 0;
646 DECLARE_DPM_WATCHDOG_ON_STACK(wd);
579 647
580 TRACE_DEVICE(dev); 648 TRACE_DEVICE(dev);
581 TRACE_RESUME(0); 649 TRACE_RESUME(0);
@@ -584,6 +652,7 @@ static int device_resume(struct device *dev, pm_message_t state, bool async)
584 goto Complete; 652 goto Complete;
585 653
586 dpm_wait(dev->parent, async); 654 dpm_wait(dev->parent, async);
655 dpm_watchdog_set(&wd, dev);
587 device_lock(dev); 656 device_lock(dev);
588 657
589 /* 658 /*
@@ -642,6 +711,7 @@ static int device_resume(struct device *dev, pm_message_t state, bool async)
642 711
643 Unlock: 712 Unlock:
644 device_unlock(dev); 713 device_unlock(dev);
714 dpm_watchdog_clear(&wd);
645 715
646 Complete: 716 Complete:
647 complete_all(&dev->power.completion); 717 complete_all(&dev->power.completion);
@@ -1060,6 +1130,7 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async)
1060 pm_callback_t callback = NULL; 1130 pm_callback_t callback = NULL;
1061 char *info = NULL; 1131 char *info = NULL;
1062 int error = 0; 1132 int error = 0;
1133 DECLARE_DPM_WATCHDOG_ON_STACK(wd);
1063 1134
1064 dpm_wait_for_children(dev, async); 1135 dpm_wait_for_children(dev, async);
1065 1136
@@ -1083,6 +1154,7 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async)
1083 if (dev->power.syscore) 1154 if (dev->power.syscore)
1084 goto Complete; 1155 goto Complete;
1085 1156
1157 dpm_watchdog_set(&wd, dev);
1086 device_lock(dev); 1158 device_lock(dev);
1087 1159
1088 if (dev->pm_domain) { 1160 if (dev->pm_domain) {
@@ -1139,6 +1211,7 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async)
1139 } 1211 }
1140 1212
1141 device_unlock(dev); 1213 device_unlock(dev);
1214 dpm_watchdog_clear(&wd);
1142 1215
1143 Complete: 1216 Complete:
1144 complete_all(&dev->power.completion); 1217 complete_all(&dev->power.completion);