diff options
Diffstat (limited to 'drivers/base/power/main.c')
-rw-r--r-- | drivers/base/power/main.c | 143 |
1 files changed, 132 insertions, 11 deletions
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index a5142bddef41..0e26a6f6fd48 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c | |||
@@ -25,6 +25,7 @@ | |||
25 | #include <linux/resume-trace.h> | 25 | #include <linux/resume-trace.h> |
26 | #include <linux/interrupt.h> | 26 | #include <linux/interrupt.h> |
27 | #include <linux/sched.h> | 27 | #include <linux/sched.h> |
28 | #include <linux/async.h> | ||
28 | 29 | ||
29 | #include "../base.h" | 30 | #include "../base.h" |
30 | #include "power.h" | 31 | #include "power.h" |
@@ -42,6 +43,7 @@ | |||
42 | LIST_HEAD(dpm_list); | 43 | LIST_HEAD(dpm_list); |
43 | 44 | ||
44 | static DEFINE_MUTEX(dpm_list_mtx); | 45 | static DEFINE_MUTEX(dpm_list_mtx); |
46 | static pm_message_t pm_transition; | ||
45 | 47 | ||
46 | /* | 48 | /* |
47 | * Set once the preparation of devices for a PM transition has started, reset | 49 | * Set once the preparation of devices for a PM transition has started, reset |
@@ -56,6 +58,7 @@ static bool transition_started; | |||
56 | void device_pm_init(struct device *dev) | 58 | void device_pm_init(struct device *dev) |
57 | { | 59 | { |
58 | dev->power.status = DPM_ON; | 60 | dev->power.status = DPM_ON; |
61 | init_completion(&dev->power.completion); | ||
59 | pm_runtime_init(dev); | 62 | pm_runtime_init(dev); |
60 | } | 63 | } |
61 | 64 | ||
@@ -111,6 +114,7 @@ void device_pm_remove(struct device *dev) | |||
111 | pr_debug("PM: Removing info for %s:%s\n", | 114 | pr_debug("PM: Removing info for %s:%s\n", |
112 | dev->bus ? dev->bus->name : "No Bus", | 115 | dev->bus ? dev->bus->name : "No Bus", |
113 | kobject_name(&dev->kobj)); | 116 | kobject_name(&dev->kobj)); |
117 | complete_all(&dev->power.completion); | ||
114 | mutex_lock(&dpm_list_mtx); | 118 | mutex_lock(&dpm_list_mtx); |
115 | list_del_init(&dev->power.entry); | 119 | list_del_init(&dev->power.entry); |
116 | mutex_unlock(&dpm_list_mtx); | 120 | mutex_unlock(&dpm_list_mtx); |
@@ -188,6 +192,31 @@ static void initcall_debug_report(struct device *dev, ktime_t calltime, | |||
188 | } | 192 | } |
189 | 193 | ||
190 | /** | 194 | /** |
195 | * dpm_wait - Wait for a PM operation to complete. | ||
196 | * @dev: Device to wait for. | ||
197 | * @async: If unset, wait only if the device's power.async_suspend flag is set. | ||
198 | */ | ||
199 | static void dpm_wait(struct device *dev, bool async) | ||
200 | { | ||
201 | if (!dev) | ||
202 | return; | ||
203 | |||
204 | if (async || (pm_async_enabled && dev->power.async_suspend)) | ||
205 | wait_for_completion(&dev->power.completion); | ||
206 | } | ||
207 | |||
208 | static int dpm_wait_fn(struct device *dev, void *async_ptr) | ||
209 | { | ||
210 | dpm_wait(dev, *((bool *)async_ptr)); | ||
211 | return 0; | ||
212 | } | ||
213 | |||
214 | static void dpm_wait_for_children(struct device *dev, bool async) | ||
215 | { | ||
216 | device_for_each_child(dev, &async, dpm_wait_fn); | ||
217 | } | ||
218 | |||
219 | /** | ||
191 | * pm_op - Execute the PM operation appropriate for given PM event. | 220 | * pm_op - Execute the PM operation appropriate for given PM event. |
192 | * @dev: Device to handle. | 221 | * @dev: Device to handle. |
193 | * @ops: PM operations to choose from. | 222 | * @ops: PM operations to choose from. |
@@ -271,8 +300,9 @@ static int pm_noirq_op(struct device *dev, | |||
271 | ktime_t calltime, delta, rettime; | 300 | ktime_t calltime, delta, rettime; |
272 | 301 | ||
273 | if (initcall_debug) { | 302 | if (initcall_debug) { |
274 | pr_info("calling %s_i+ @ %i\n", | 303 | pr_info("calling %s+ @ %i, parent: %s\n", |
275 | dev_name(dev), task_pid_nr(current)); | 304 | dev_name(dev), task_pid_nr(current), |
305 | dev->parent ? dev_name(dev->parent) : "none"); | ||
276 | calltime = ktime_get(); | 306 | calltime = ktime_get(); |
277 | } | 307 | } |
278 | 308 | ||
@@ -468,16 +498,20 @@ static int legacy_resume(struct device *dev, int (*cb)(struct device *dev)) | |||
468 | * device_resume - Execute "resume" callbacks for given device. | 498 | * device_resume - Execute "resume" callbacks for given device. |
469 | * @dev: Device to handle. | 499 | * @dev: Device to handle. |
470 | * @state: PM transition of the system being carried out. | 500 | * @state: PM transition of the system being carried out. |
501 | * @async: If true, the device is being resumed asynchronously. | ||
471 | */ | 502 | */ |
472 | static int device_resume(struct device *dev, pm_message_t state) | 503 | static int device_resume(struct device *dev, pm_message_t state, bool async) |
473 | { | 504 | { |
474 | int error = 0; | 505 | int error = 0; |
475 | 506 | ||
476 | TRACE_DEVICE(dev); | 507 | TRACE_DEVICE(dev); |
477 | TRACE_RESUME(0); | 508 | TRACE_RESUME(0); |
478 | 509 | ||
510 | dpm_wait(dev->parent, async); | ||
479 | down(&dev->sem); | 511 | down(&dev->sem); |
480 | 512 | ||
513 | dev->power.status = DPM_RESUMING; | ||
514 | |||
481 | if (dev->bus) { | 515 | if (dev->bus) { |
482 | if (dev->bus->pm) { | 516 | if (dev->bus->pm) { |
483 | pm_dev_dbg(dev, state, ""); | 517 | pm_dev_dbg(dev, state, ""); |
@@ -510,11 +544,29 @@ static int device_resume(struct device *dev, pm_message_t state) | |||
510 | } | 544 | } |
511 | End: | 545 | End: |
512 | up(&dev->sem); | 546 | up(&dev->sem); |
547 | complete_all(&dev->power.completion); | ||
513 | 548 | ||
514 | TRACE_RESUME(error); | 549 | TRACE_RESUME(error); |
515 | return error; | 550 | return error; |
516 | } | 551 | } |
517 | 552 | ||
553 | static void async_resume(void *data, async_cookie_t cookie) | ||
554 | { | ||
555 | struct device *dev = (struct device *)data; | ||
556 | int error; | ||
557 | |||
558 | error = device_resume(dev, pm_transition, true); | ||
559 | if (error) | ||
560 | pm_dev_err(dev, pm_transition, " async", error); | ||
561 | put_device(dev); | ||
562 | } | ||
563 | |||
564 | static bool is_async(struct device *dev) | ||
565 | { | ||
566 | return dev->power.async_suspend && pm_async_enabled | ||
567 | && !pm_trace_is_enabled(); | ||
568 | } | ||
569 | |||
518 | /** | 570 | /** |
519 | * dpm_resume - Execute "resume" callbacks for non-sysdev devices. | 571 | * dpm_resume - Execute "resume" callbacks for non-sysdev devices. |
520 | * @state: PM transition of the system being carried out. | 572 | * @state: PM transition of the system being carried out. |
@@ -525,21 +577,33 @@ static int device_resume(struct device *dev, pm_message_t state) | |||
525 | static void dpm_resume(pm_message_t state) | 577 | static void dpm_resume(pm_message_t state) |
526 | { | 578 | { |
527 | struct list_head list; | 579 | struct list_head list; |
580 | struct device *dev; | ||
528 | ktime_t starttime = ktime_get(); | 581 | ktime_t starttime = ktime_get(); |
529 | 582 | ||
530 | INIT_LIST_HEAD(&list); | 583 | INIT_LIST_HEAD(&list); |
531 | mutex_lock(&dpm_list_mtx); | 584 | mutex_lock(&dpm_list_mtx); |
532 | while (!list_empty(&dpm_list)) { | 585 | pm_transition = state; |
533 | struct device *dev = to_device(dpm_list.next); | 586 | |
587 | list_for_each_entry(dev, &dpm_list, power.entry) { | ||
588 | if (dev->power.status < DPM_OFF) | ||
589 | continue; | ||
590 | |||
591 | INIT_COMPLETION(dev->power.completion); | ||
592 | if (is_async(dev)) { | ||
593 | get_device(dev); | ||
594 | async_schedule(async_resume, dev); | ||
595 | } | ||
596 | } | ||
534 | 597 | ||
598 | while (!list_empty(&dpm_list)) { | ||
599 | dev = to_device(dpm_list.next); | ||
535 | get_device(dev); | 600 | get_device(dev); |
536 | if (dev->power.status >= DPM_OFF) { | 601 | if (dev->power.status >= DPM_OFF && !is_async(dev)) { |
537 | int error; | 602 | int error; |
538 | 603 | ||
539 | dev->power.status = DPM_RESUMING; | ||
540 | mutex_unlock(&dpm_list_mtx); | 604 | mutex_unlock(&dpm_list_mtx); |
541 | 605 | ||
542 | error = device_resume(dev, state); | 606 | error = device_resume(dev, state, false); |
543 | 607 | ||
544 | mutex_lock(&dpm_list_mtx); | 608 | mutex_lock(&dpm_list_mtx); |
545 | if (error) | 609 | if (error) |
@@ -554,6 +618,7 @@ static void dpm_resume(pm_message_t state) | |||
554 | } | 618 | } |
555 | list_splice(&list, &dpm_list); | 619 | list_splice(&list, &dpm_list); |
556 | mutex_unlock(&dpm_list_mtx); | 620 | mutex_unlock(&dpm_list_mtx); |
621 | async_synchronize_full(); | ||
557 | dpm_show_time(starttime, state, NULL); | 622 | dpm_show_time(starttime, state, NULL); |
558 | } | 623 | } |
559 | 624 | ||
@@ -731,17 +796,24 @@ static int legacy_suspend(struct device *dev, pm_message_t state, | |||
731 | return error; | 796 | return error; |
732 | } | 797 | } |
733 | 798 | ||
799 | static int async_error; | ||
800 | |||
734 | /** | 801 | /** |
735 | * device_suspend - Execute "suspend" callbacks for given device. | 802 | * device_suspend - Execute "suspend" callbacks for given device. |
736 | * @dev: Device to handle. | 803 | * @dev: Device to handle. |
737 | * @state: PM transition of the system being carried out. | 804 | * @state: PM transition of the system being carried out. |
805 | * @async: If true, the device is being suspended asynchronously. | ||
738 | */ | 806 | */ |
739 | static int device_suspend(struct device *dev, pm_message_t state) | 807 | static int __device_suspend(struct device *dev, pm_message_t state, bool async) |
740 | { | 808 | { |
741 | int error = 0; | 809 | int error = 0; |
742 | 810 | ||
811 | dpm_wait_for_children(dev, async); | ||
743 | down(&dev->sem); | 812 | down(&dev->sem); |
744 | 813 | ||
814 | if (async_error) | ||
815 | goto End; | ||
816 | |||
745 | if (dev->class) { | 817 | if (dev->class) { |
746 | if (dev->class->pm) { | 818 | if (dev->class->pm) { |
747 | pm_dev_dbg(dev, state, "class "); | 819 | pm_dev_dbg(dev, state, "class "); |
@@ -772,12 +844,44 @@ static int device_suspend(struct device *dev, pm_message_t state) | |||
772 | error = legacy_suspend(dev, state, dev->bus->suspend); | 844 | error = legacy_suspend(dev, state, dev->bus->suspend); |
773 | } | 845 | } |
774 | } | 846 | } |
847 | |||
848 | if (!error) | ||
849 | dev->power.status = DPM_OFF; | ||
850 | |||
775 | End: | 851 | End: |
776 | up(&dev->sem); | 852 | up(&dev->sem); |
853 | complete_all(&dev->power.completion); | ||
777 | 854 | ||
778 | return error; | 855 | return error; |
779 | } | 856 | } |
780 | 857 | ||
858 | static void async_suspend(void *data, async_cookie_t cookie) | ||
859 | { | ||
860 | struct device *dev = (struct device *)data; | ||
861 | int error; | ||
862 | |||
863 | error = __device_suspend(dev, pm_transition, true); | ||
864 | if (error) { | ||
865 | pm_dev_err(dev, pm_transition, " async", error); | ||
866 | async_error = error; | ||
867 | } | ||
868 | |||
869 | put_device(dev); | ||
870 | } | ||
871 | |||
872 | static int device_suspend(struct device *dev) | ||
873 | { | ||
874 | INIT_COMPLETION(dev->power.completion); | ||
875 | |||
876 | if (pm_async_enabled && dev->power.async_suspend) { | ||
877 | get_device(dev); | ||
878 | async_schedule(async_suspend, dev); | ||
879 | return 0; | ||
880 | } | ||
881 | |||
882 | return __device_suspend(dev, pm_transition, false); | ||
883 | } | ||
884 | |||
781 | /** | 885 | /** |
782 | * dpm_suspend - Execute "suspend" callbacks for all non-sysdev devices. | 886 | * dpm_suspend - Execute "suspend" callbacks for all non-sysdev devices. |
783 | * @state: PM transition of the system being carried out. | 887 | * @state: PM transition of the system being carried out. |
@@ -790,13 +894,15 @@ static int dpm_suspend(pm_message_t state) | |||
790 | 894 | ||
791 | INIT_LIST_HEAD(&list); | 895 | INIT_LIST_HEAD(&list); |
792 | mutex_lock(&dpm_list_mtx); | 896 | mutex_lock(&dpm_list_mtx); |
897 | pm_transition = state; | ||
898 | async_error = 0; | ||
793 | while (!list_empty(&dpm_list)) { | 899 | while (!list_empty(&dpm_list)) { |
794 | struct device *dev = to_device(dpm_list.prev); | 900 | struct device *dev = to_device(dpm_list.prev); |
795 | 901 | ||
796 | get_device(dev); | 902 | get_device(dev); |
797 | mutex_unlock(&dpm_list_mtx); | 903 | mutex_unlock(&dpm_list_mtx); |
798 | 904 | ||
799 | error = device_suspend(dev, state); | 905 | error = device_suspend(dev); |
800 | 906 | ||
801 | mutex_lock(&dpm_list_mtx); | 907 | mutex_lock(&dpm_list_mtx); |
802 | if (error) { | 908 | if (error) { |
@@ -804,13 +910,17 @@ static int dpm_suspend(pm_message_t state) | |||
804 | put_device(dev); | 910 | put_device(dev); |
805 | break; | 911 | break; |
806 | } | 912 | } |
807 | dev->power.status = DPM_OFF; | ||
808 | if (!list_empty(&dev->power.entry)) | 913 | if (!list_empty(&dev->power.entry)) |
809 | list_move(&dev->power.entry, &list); | 914 | list_move(&dev->power.entry, &list); |
810 | put_device(dev); | 915 | put_device(dev); |
916 | if (async_error) | ||
917 | break; | ||
811 | } | 918 | } |
812 | list_splice(&list, dpm_list.prev); | 919 | list_splice(&list, dpm_list.prev); |
813 | mutex_unlock(&dpm_list_mtx); | 920 | mutex_unlock(&dpm_list_mtx); |
921 | async_synchronize_full(); | ||
922 | if (!error) | ||
923 | error = async_error; | ||
814 | if (!error) | 924 | if (!error) |
815 | dpm_show_time(starttime, state, NULL); | 925 | dpm_show_time(starttime, state, NULL); |
816 | return error; | 926 | return error; |
@@ -936,3 +1046,14 @@ void __suspend_report_result(const char *function, void *fn, int ret) | |||
936 | printk(KERN_ERR "%s(): %pF returns %d\n", function, fn, ret); | 1046 | printk(KERN_ERR "%s(): %pF returns %d\n", function, fn, ret); |
937 | } | 1047 | } |
938 | EXPORT_SYMBOL_GPL(__suspend_report_result); | 1048 | EXPORT_SYMBOL_GPL(__suspend_report_result); |
1049 | |||
1050 | /** | ||
1051 | * device_pm_wait_for_dev - Wait for suspend/resume of a device to complete. | ||
1052 | * @dev: Device to wait for. | ||
1053 | * @subordinate: Device that needs to wait for @dev. | ||
1054 | */ | ||
1055 | void device_pm_wait_for_dev(struct device *subordinate, struct device *dev) | ||
1056 | { | ||
1057 | dpm_wait(dev, subordinate->power.async_suspend); | ||
1058 | } | ||
1059 | EXPORT_SYMBOL_GPL(device_pm_wait_for_dev); | ||