aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/base/power/main.c
diff options
context:
space:
mode:
authorRafael J. Wysocki <rjw@sisk.pl>2010-01-23 16:23:32 -0500
committerRafael J. Wysocki <rjw@sisk.pl>2010-02-26 14:39:09 -0500
commit5af84b82701a96be4b033aaa51d86c72e2ded061 (patch)
treeac5751c7d2e9c17bf41dabdbeba964a05f09af18 /drivers/base/power/main.c
parent8cc6b39ff36b4bbce2d7471da088df122b0e9033 (diff)
PM: Asynchronous suspend and resume of devices
Theoretically, the total time of system sleep transitions (suspend to RAM, hibernation) can be reduced by running suspend and resume callbacks of device drivers in parallel with each other. However, there are dependencies between devices such that we're not allowed to suspend the parent of a device before suspending the device itself. Analogously, we're not allowed to resume a device before resuming its parent. The most straightforward way to take these dependencies into accout is to start the async threads used for suspending and resuming devices at the core level, so that async_schedule() is called for each suspend and resume callback supposed to be executed asynchronously. For this purpose, introduce a new device flag, power.async_suspend, used to mark the devices whose suspend and resume callbacks are to be executed asynchronously (ie. in parallel with the main suspend/resume thread and possibly in parallel with each other) and helper function device_enable_async_suspend() allowing one to set power.async_suspend for given device (power.async_suspend is unset by default for all devices). For each device with the power.async_suspend flag set the PM core will use async_schedule() to execute its suspend and resume callbacks. The async threads started for different devices as a result of calling async_schedule() are synchronized with each other and with the main suspend/resume thread with the help of completions, in the following way: (1) There is a completion, power.completion, for each device object. (2) Each device's completion is reset before calling async_schedule() for the device or, in the case of devices with the power.async_suspend flags unset, before executing the device's suspend and resume callbacks. (3) During suspend, right before running the bus type, device type and device class suspend callbacks for the device, the PM core waits for the completions of all the device's children to be completed. (4) During resume, right before running the bus type, device type and device class resume callbacks for the device, the PM core waits for the completion of the device's parent to be completed. (5) The PM core completes power.completion for each device right after the bus type, device type and device class suspend (or resume) callbacks executed for the device have returned. Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
Diffstat (limited to 'drivers/base/power/main.c')
-rw-r--r--drivers/base/power/main.c115
1 files changed, 109 insertions, 6 deletions
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
index 6ca5cdf63849..3b44c201ddad 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 @@
42LIST_HEAD(dpm_list); 43LIST_HEAD(dpm_list);
43 44
44static DEFINE_MUTEX(dpm_list_mtx); 45static DEFINE_MUTEX(dpm_list_mtx);
46static 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;
56void device_pm_init(struct device *dev) 58void 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 */
199static void dpm_wait(struct device *dev, bool async)
200{
201 if (!dev)
202 return;
203
204 if (async || dev->power.async_suspend)
205 wait_for_completion(&dev->power.completion);
206}
207
208static int dpm_wait_fn(struct device *dev, void *async_ptr)
209{
210 dpm_wait(dev, *((bool *)async_ptr));
211 return 0;
212}
213
214static 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.
@@ -466,17 +495,19 @@ static int legacy_resume(struct device *dev, int (*cb)(struct device *dev))
466} 495}
467 496
468/** 497/**
469 * device_resume - Execute "resume" callbacks for given device. 498 * __device_resume - Execute "resume" callbacks for given device.
470 * @dev: Device to handle. 499 * @dev: Device to handle.
471 * @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.
472 */ 502 */
473static int device_resume(struct device *dev, pm_message_t state) 503static int __device_resume(struct device *dev, pm_message_t state, bool async)
474{ 504{
475 int error = 0; 505 int error = 0;
476 506
477 TRACE_DEVICE(dev); 507 TRACE_DEVICE(dev);
478 TRACE_RESUME(0); 508 TRACE_RESUME(0);
479 509
510 dpm_wait(dev->parent, async);
480 down(&dev->sem); 511 down(&dev->sem);
481 512
482 if (dev->bus) { 513 if (dev->bus) {
@@ -511,11 +542,36 @@ static int device_resume(struct device *dev, pm_message_t state)
511 } 542 }
512 End: 543 End:
513 up(&dev->sem); 544 up(&dev->sem);
545 complete_all(&dev->power.completion);
514 546
515 TRACE_RESUME(error); 547 TRACE_RESUME(error);
516 return error; 548 return error;
517} 549}
518 550
551static void async_resume(void *data, async_cookie_t cookie)
552{
553 struct device *dev = (struct device *)data;
554 int error;
555
556 error = __device_resume(dev, pm_transition, true);
557 if (error)
558 pm_dev_err(dev, pm_transition, " async", error);
559 put_device(dev);
560}
561
562static int device_resume(struct device *dev)
563{
564 INIT_COMPLETION(dev->power.completion);
565
566 if (dev->power.async_suspend && !pm_trace_is_enabled()) {
567 get_device(dev);
568 async_schedule(async_resume, dev);
569 return 0;
570 }
571
572 return __device_resume(dev, pm_transition, false);
573}
574
519/** 575/**
520 * dpm_resume - Execute "resume" callbacks for non-sysdev devices. 576 * dpm_resume - Execute "resume" callbacks for non-sysdev devices.
521 * @state: PM transition of the system being carried out. 577 * @state: PM transition of the system being carried out.
@@ -530,6 +586,7 @@ static void dpm_resume(pm_message_t state)
530 586
531 INIT_LIST_HEAD(&list); 587 INIT_LIST_HEAD(&list);
532 mutex_lock(&dpm_list_mtx); 588 mutex_lock(&dpm_list_mtx);
589 pm_transition = state;
533 while (!list_empty(&dpm_list)) { 590 while (!list_empty(&dpm_list)) {
534 struct device *dev = to_device(dpm_list.next); 591 struct device *dev = to_device(dpm_list.next);
535 592
@@ -540,7 +597,7 @@ static void dpm_resume(pm_message_t state)
540 dev->power.status = DPM_RESUMING; 597 dev->power.status = DPM_RESUMING;
541 mutex_unlock(&dpm_list_mtx); 598 mutex_unlock(&dpm_list_mtx);
542 599
543 error = device_resume(dev, state); 600 error = device_resume(dev);
544 601
545 mutex_lock(&dpm_list_mtx); 602 mutex_lock(&dpm_list_mtx);
546 if (error) 603 if (error)
@@ -555,6 +612,7 @@ static void dpm_resume(pm_message_t state)
555 } 612 }
556 list_splice(&list, &dpm_list); 613 list_splice(&list, &dpm_list);
557 mutex_unlock(&dpm_list_mtx); 614 mutex_unlock(&dpm_list_mtx);
615 async_synchronize_full();
558 dpm_show_time(starttime, state, NULL); 616 dpm_show_time(starttime, state, NULL);
559} 617}
560 618
@@ -732,17 +790,24 @@ static int legacy_suspend(struct device *dev, pm_message_t state,
732 return error; 790 return error;
733} 791}
734 792
793static int async_error;
794
735/** 795/**
736 * device_suspend - Execute "suspend" callbacks for given device. 796 * device_suspend - Execute "suspend" callbacks for given device.
737 * @dev: Device to handle. 797 * @dev: Device to handle.
738 * @state: PM transition of the system being carried out. 798 * @state: PM transition of the system being carried out.
799 * @async: If true, the device is being suspended asynchronously.
739 */ 800 */
740static int device_suspend(struct device *dev, pm_message_t state) 801static int __device_suspend(struct device *dev, pm_message_t state, bool async)
741{ 802{
742 int error = 0; 803 int error = 0;
743 804
805 dpm_wait_for_children(dev, async);
744 down(&dev->sem); 806 down(&dev->sem);
745 807
808 if (async_error)
809 goto End;
810
746 if (dev->class) { 811 if (dev->class) {
747 if (dev->class->pm) { 812 if (dev->class->pm) {
748 pm_dev_dbg(dev, state, "class "); 813 pm_dev_dbg(dev, state, "class ");
@@ -773,12 +838,44 @@ static int device_suspend(struct device *dev, pm_message_t state)
773 error = legacy_suspend(dev, state, dev->bus->suspend); 838 error = legacy_suspend(dev, state, dev->bus->suspend);
774 } 839 }
775 } 840 }
841
842 if (!error)
843 dev->power.status = DPM_OFF;
844
776 End: 845 End:
777 up(&dev->sem); 846 up(&dev->sem);
847 complete_all(&dev->power.completion);
778 848
779 return error; 849 return error;
780} 850}
781 851
852static void async_suspend(void *data, async_cookie_t cookie)
853{
854 struct device *dev = (struct device *)data;
855 int error;
856
857 error = __device_suspend(dev, pm_transition, true);
858 if (error) {
859 pm_dev_err(dev, pm_transition, " async", error);
860 async_error = error;
861 }
862
863 put_device(dev);
864}
865
866static int device_suspend(struct device *dev)
867{
868 INIT_COMPLETION(dev->power.completion);
869
870 if (dev->power.async_suspend) {
871 get_device(dev);
872 async_schedule(async_suspend, dev);
873 return 0;
874 }
875
876 return __device_suspend(dev, pm_transition, false);
877}
878
782/** 879/**
783 * dpm_suspend - Execute "suspend" callbacks for all non-sysdev devices. 880 * dpm_suspend - Execute "suspend" callbacks for all non-sysdev devices.
784 * @state: PM transition of the system being carried out. 881 * @state: PM transition of the system being carried out.
@@ -791,13 +888,15 @@ static int dpm_suspend(pm_message_t state)
791 888
792 INIT_LIST_HEAD(&list); 889 INIT_LIST_HEAD(&list);
793 mutex_lock(&dpm_list_mtx); 890 mutex_lock(&dpm_list_mtx);
891 pm_transition = state;
892 async_error = 0;
794 while (!list_empty(&dpm_list)) { 893 while (!list_empty(&dpm_list)) {
795 struct device *dev = to_device(dpm_list.prev); 894 struct device *dev = to_device(dpm_list.prev);
796 895
797 get_device(dev); 896 get_device(dev);
798 mutex_unlock(&dpm_list_mtx); 897 mutex_unlock(&dpm_list_mtx);
799 898
800 error = device_suspend(dev, state); 899 error = device_suspend(dev);
801 900
802 mutex_lock(&dpm_list_mtx); 901 mutex_lock(&dpm_list_mtx);
803 if (error) { 902 if (error) {
@@ -805,13 +904,17 @@ static int dpm_suspend(pm_message_t state)
805 put_device(dev); 904 put_device(dev);
806 break; 905 break;
807 } 906 }
808 dev->power.status = DPM_OFF;
809 if (!list_empty(&dev->power.entry)) 907 if (!list_empty(&dev->power.entry))
810 list_move(&dev->power.entry, &list); 908 list_move(&dev->power.entry, &list);
811 put_device(dev); 909 put_device(dev);
910 if (async_error)
911 break;
812 } 912 }
813 list_splice(&list, dpm_list.prev); 913 list_splice(&list, dpm_list.prev);
814 mutex_unlock(&dpm_list_mtx); 914 mutex_unlock(&dpm_list_mtx);
915 async_synchronize_full();
916 if (!error)
917 error = async_error;
815 if (!error) 918 if (!error)
816 dpm_show_time(starttime, state, NULL); 919 dpm_show_time(starttime, state, NULL);
817 return error; 920 return error;