drivers: power: Add watchdog timer to catch drivers which lockup during suspend.
[firefly-linux-kernel-4.4.55.git] / drivers / base / power / main.c
index 1710c26ba097d363873dfd8318ff1d949aeb4a86..df539322d9f5a2197a0371847f802f62af4ce3a0 100644 (file)
@@ -59,6 +59,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;
 
 static char *pm_verb(int event)
@@ -829,6 +835,30 @@ static void async_resume(void *data, async_cookie_t cookie)
        put_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.
@@ -1347,6 +1377,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;
        DECLARE_DPM_WATCHDOG_ON_STACK(wd);
 
        TRACE_DEVICE(dev);
@@ -1373,6 +1405,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);
 
        if (dev->power.direct_complete) {
                if (pm_runtime_status_suspended(dev)) {
@@ -1453,6 +1493,9 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async)
        device_unlock(dev);
        dpm_watchdog_clear(&wd);
 
+       del_timer_sync(&timer);
+       destroy_timer_on_stack(&timer);
+
  Complete:
        complete_all(&dev->power.completion);
        if (error)