aboutsummaryrefslogtreecommitdiff
path: root/drivers/base
diff options
context:
space:
mode:
authorBenoit Goby <benoit@android.com>2009-08-13 09:40:42 -0700
committerArve Hjønnevåg <arve@android.com>2013-07-01 13:34:57 -0700
commit2c3e239f2c102292d10c3131075e23b01b8e2fcd (patch)
tree0bf1ccd98070c71fc6d5eca4c834bcce1fd13573 /drivers/base
parent859cc17a0027e11614de33f3c5e9df9701a83c10 (diff)
drivers: power: Add watchdog timer to catch drivers which lockup during suspend.
Rather than hard-lock the kernel, dump the suspend thread stack and BUG() when a driver takes too long to suspend. The timeout is set to 12 seconds to be longer than the usbhid 10 second timeout. Exclude from the watchdog the time spent waiting for children that are resumed asynchronously and time every device, whether or not they resumed synchronously. Change-Id: Ifd211c06b104860c2fee6eecfe0d61774aa4508a Original-author: San Mehat <san@google.com> Signed-off-by: Benoit Goby <benoit@android.com>
Diffstat (limited to 'drivers/base')
-rw-r--r--drivers/base/power/main.c45
1 files changed, 45 insertions, 0 deletions
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
index 5a9b6569dd7..70312e7cacb 100644
--- a/drivers/base/power/main.c
+++ b/drivers/base/power/main.c
@@ -29,6 +29,8 @@
#include <linux/async.h>
#include <linux/suspend.h>
#include <linux/cpuidle.h>
+#include <linux/timer.h>
+
#include "../base.h"
#include "power.h"
@@ -54,6 +56,12 @@ struct suspend_stats suspend_stats;
static DEFINE_MUTEX(dpm_list_mtx);
static pm_message_t pm_transition;
+static void dpm_drv_timeout(unsigned long data);
+struct dpm_drv_wd_data {
+ struct device *dev;
+ struct task_struct *tsk;
+};
+
static int async_error;
/**
@@ -663,6 +671,30 @@ static bool is_async(struct device *dev)
}
/**
+ * dpm_drv_timeout - Driver suspend / resume watchdog handler
+ * @data: struct device which timed out
+ *
+ * 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_drv_timeout(unsigned long data)
+{
+ struct dpm_drv_wd_data *wd_data = (void *)data;
+ struct device *dev = wd_data->dev;
+ struct task_struct *tsk = wd_data->tsk;
+
+ printk(KERN_EMERG "**** DPM device timeout: %s (%s)\n", dev_name(dev),
+ (dev->driver ? dev->driver->name : "no driver"));
+
+ printk(KERN_EMERG "dpm suspend stack:\n");
+ show_stack(tsk, NULL);
+
+ BUG();
+}
+
+/**
* dpm_resume - Execute "resume" callbacks for non-sysdev devices.
* @state: PM transition of the system being carried out.
*
@@ -1053,6 +1085,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 timer_list timer;
+ struct dpm_drv_wd_data data;
dpm_wait_for_children(dev, async);
@@ -1075,6 +1109,14 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async)
if (dev->power.syscore)
goto Complete;
+
+ data.dev = dev;
+ data.tsk = get_current();
+ init_timer_on_stack(&timer);
+ timer.expires = jiffies + HZ * 12;
+ timer.function = dpm_drv_timeout;
+ timer.data = (unsigned long)&data;
+ add_timer(&timer);
device_lock(dev);
@@ -1131,6 +1173,9 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async)
device_unlock(dev);
+ del_timer_sync(&timer);
+ destroy_timer_on_stack(&timer);
+
Complete:
complete_all(&dev->power.completion);
if (error)