diff options
Diffstat (limited to 'drivers/base/power/main.c')
-rw-r--r-- | drivers/base/power/main.c | 80 |
1 files changed, 78 insertions, 2 deletions
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 10c3510d72a9..e3219dfd736c 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c | |||
@@ -31,6 +31,8 @@ | |||
31 | #include <trace/events/power.h> | 31 | #include <trace/events/power.h> |
32 | #include <linux/cpufreq.h> | 32 | #include <linux/cpufreq.h> |
33 | #include <linux/cpuidle.h> | 33 | #include <linux/cpuidle.h> |
34 | #include <linux/timer.h> | ||
35 | |||
34 | #include "../base.h" | 36 | #include "../base.h" |
35 | #include "power.h" | 37 | #include "power.h" |
36 | 38 | ||
@@ -391,6 +393,71 @@ static int dpm_run_callback(pm_callback_t cb, struct device *dev, | |||
391 | return error; | 393 | return error; |
392 | } | 394 | } |
393 | 395 | ||
396 | #ifdef CONFIG_DPM_WATCHDOG | ||
397 | struct dpm_watchdog { | ||
398 | struct device *dev; | ||
399 | struct task_struct *tsk; | ||
400 | struct timer_list timer; | ||
401 | }; | ||
402 | |||
403 | #define DECLARE_DPM_WATCHDOG_ON_STACK(wd) \ | ||
404 | struct dpm_watchdog wd | ||
405 | |||
406 | /** | ||
407 | * dpm_watchdog_handler - Driver suspend / resume watchdog handler. | ||
408 | * @data: Watchdog object address. | ||
409 | * | ||
410 | * Called when a driver has timed out suspending or resuming. | ||
411 | * There's not much we can do here to recover so panic() to | ||
412 | * capture a crash-dump in pstore. | ||
413 | */ | ||
414 | static void dpm_watchdog_handler(unsigned long data) | ||
415 | { | ||
416 | struct dpm_watchdog *wd = (void *)data; | ||
417 | |||
418 | dev_emerg(wd->dev, "**** DPM device timeout ****\n"); | ||
419 | show_stack(wd->tsk, NULL); | ||
420 | panic("%s %s: unrecoverable failure\n", | ||
421 | dev_driver_string(wd->dev), dev_name(wd->dev)); | ||
422 | } | ||
423 | |||
424 | /** | ||
425 | * dpm_watchdog_set - Enable pm watchdog for given device. | ||
426 | * @wd: Watchdog. Must be allocated on the stack. | ||
427 | * @dev: Device to handle. | ||
428 | */ | ||
429 | static void dpm_watchdog_set(struct dpm_watchdog *wd, struct device *dev) | ||
430 | { | ||
431 | struct timer_list *timer = &wd->timer; | ||
432 | |||
433 | wd->dev = dev; | ||
434 | wd->tsk = current; | ||
435 | |||
436 | init_timer_on_stack(timer); | ||
437 | /* use same timeout value for both suspend and resume */ | ||
438 | timer->expires = jiffies + HZ * CONFIG_DPM_WATCHDOG_TIMEOUT; | ||
439 | timer->function = dpm_watchdog_handler; | ||
440 | timer->data = (unsigned long)wd; | ||
441 | add_timer(timer); | ||
442 | } | ||
443 | |||
444 | /** | ||
445 | * dpm_watchdog_clear - Disable suspend/resume watchdog. | ||
446 | * @wd: Watchdog to disable. | ||
447 | */ | ||
448 | static void dpm_watchdog_clear(struct dpm_watchdog *wd) | ||
449 | { | ||
450 | struct timer_list *timer = &wd->timer; | ||
451 | |||
452 | del_timer_sync(timer); | ||
453 | destroy_timer_on_stack(timer); | ||
454 | } | ||
455 | #else | ||
456 | #define DECLARE_DPM_WATCHDOG_ON_STACK(wd) | ||
457 | #define dpm_watchdog_set(x, y) | ||
458 | #define dpm_watchdog_clear(x) | ||
459 | #endif | ||
460 | |||
394 | /*------------------------- Resume routines -------------------------*/ | 461 | /*------------------------- Resume routines -------------------------*/ |
395 | 462 | ||
396 | /** | 463 | /** |
@@ -578,6 +645,7 @@ static int device_resume(struct device *dev, pm_message_t state, bool async) | |||
578 | pm_callback_t callback = NULL; | 645 | pm_callback_t callback = NULL; |
579 | char *info = NULL; | 646 | char *info = NULL; |
580 | int error = 0; | 647 | int error = 0; |
648 | DECLARE_DPM_WATCHDOG_ON_STACK(wd); | ||
581 | 649 | ||
582 | TRACE_DEVICE(dev); | 650 | TRACE_DEVICE(dev); |
583 | TRACE_RESUME(0); | 651 | TRACE_RESUME(0); |
@@ -586,6 +654,7 @@ static int device_resume(struct device *dev, pm_message_t state, bool async) | |||
586 | goto Complete; | 654 | goto Complete; |
587 | 655 | ||
588 | dpm_wait(dev->parent, async); | 656 | dpm_wait(dev->parent, async); |
657 | dpm_watchdog_set(&wd, dev); | ||
589 | device_lock(dev); | 658 | device_lock(dev); |
590 | 659 | ||
591 | /* | 660 | /* |
@@ -644,6 +713,7 @@ static int device_resume(struct device *dev, pm_message_t state, bool async) | |||
644 | 713 | ||
645 | Unlock: | 714 | Unlock: |
646 | device_unlock(dev); | 715 | device_unlock(dev); |
716 | dpm_watchdog_clear(&wd); | ||
647 | 717 | ||
648 | Complete: | 718 | Complete: |
649 | complete_all(&dev->power.completion); | 719 | complete_all(&dev->power.completion); |
@@ -689,7 +759,7 @@ void dpm_resume(pm_message_t state) | |||
689 | async_error = 0; | 759 | async_error = 0; |
690 | 760 | ||
691 | list_for_each_entry(dev, &dpm_suspended_list, power.entry) { | 761 | list_for_each_entry(dev, &dpm_suspended_list, power.entry) { |
692 | INIT_COMPLETION(dev->power.completion); | 762 | reinit_completion(&dev->power.completion); |
693 | if (is_async(dev)) { | 763 | if (is_async(dev)) { |
694 | get_device(dev); | 764 | get_device(dev); |
695 | async_schedule(async_resume, dev); | 765 | async_schedule(async_resume, dev); |
@@ -1063,6 +1133,7 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async) | |||
1063 | pm_callback_t callback = NULL; | 1133 | pm_callback_t callback = NULL; |
1064 | char *info = NULL; | 1134 | char *info = NULL; |
1065 | int error = 0; | 1135 | int error = 0; |
1136 | DECLARE_DPM_WATCHDOG_ON_STACK(wd); | ||
1066 | 1137 | ||
1067 | dpm_wait_for_children(dev, async); | 1138 | dpm_wait_for_children(dev, async); |
1068 | 1139 | ||
@@ -1086,6 +1157,7 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async) | |||
1086 | if (dev->power.syscore) | 1157 | if (dev->power.syscore) |
1087 | goto Complete; | 1158 | goto Complete; |
1088 | 1159 | ||
1160 | dpm_watchdog_set(&wd, dev); | ||
1089 | device_lock(dev); | 1161 | device_lock(dev); |
1090 | 1162 | ||
1091 | if (dev->pm_domain) { | 1163 | if (dev->pm_domain) { |
@@ -1142,6 +1214,7 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async) | |||
1142 | } | 1214 | } |
1143 | 1215 | ||
1144 | device_unlock(dev); | 1216 | device_unlock(dev); |
1217 | dpm_watchdog_clear(&wd); | ||
1145 | 1218 | ||
1146 | Complete: | 1219 | Complete: |
1147 | complete_all(&dev->power.completion); | 1220 | complete_all(&dev->power.completion); |
@@ -1167,7 +1240,7 @@ static void async_suspend(void *data, async_cookie_t cookie) | |||
1167 | 1240 | ||
1168 | static int device_suspend(struct device *dev) | 1241 | static int device_suspend(struct device *dev) |
1169 | { | 1242 | { |
1170 | INIT_COMPLETION(dev->power.completion); | 1243 | reinit_completion(&dev->power.completion); |
1171 | 1244 | ||
1172 | if (pm_async_enabled && dev->power.async_suspend) { | 1245 | if (pm_async_enabled && dev->power.async_suspend) { |
1173 | get_device(dev); | 1246 | get_device(dev); |
@@ -1280,6 +1353,9 @@ static int device_prepare(struct device *dev, pm_message_t state) | |||
1280 | 1353 | ||
1281 | device_unlock(dev); | 1354 | device_unlock(dev); |
1282 | 1355 | ||
1356 | if (error) | ||
1357 | pm_runtime_put(dev); | ||
1358 | |||
1283 | return error; | 1359 | return error; |
1284 | } | 1360 | } |
1285 | 1361 | ||