Skip to content

Commit 5af84b8

Browse files
committed
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>
1 parent 8cc6b39 commit 5af84b8

File tree

4 files changed

+125
-6
lines changed

4 files changed

+125
-6
lines changed

drivers/base/power/main.c

Lines changed: 109 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <linux/resume-trace.h>
2626
#include <linux/interrupt.h>
2727
#include <linux/sched.h>
28+
#include <linux/async.h>
2829

2930
#include "../base.h"
3031
#include "power.h"
@@ -42,6 +43,7 @@
4243
LIST_HEAD(dpm_list);
4344

4445
static DEFINE_MUTEX(dpm_list_mtx);
46+
static pm_message_t pm_transition;
4547

4648
/*
4749
* Set once the preparation of devices for a PM transition has started, reset
@@ -56,6 +58,7 @@ static bool transition_started;
5658
void device_pm_init(struct device *dev)
5759
{
5860
dev->power.status = DPM_ON;
61+
init_completion(&dev->power.completion);
5962
pm_runtime_init(dev);
6063
}
6164

@@ -111,6 +114,7 @@ void device_pm_remove(struct device *dev)
111114
pr_debug("PM: Removing info for %s:%s\n",
112115
dev->bus ? dev->bus->name : "No Bus",
113116
kobject_name(&dev->kobj));
117+
complete_all(&dev->power.completion);
114118
mutex_lock(&dpm_list_mtx);
115119
list_del_init(&dev->power.entry);
116120
mutex_unlock(&dpm_list_mtx);
@@ -187,6 +191,31 @@ static void initcall_debug_report(struct device *dev, ktime_t calltime,
187191
}
188192
}
189193

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 || 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+
190219
/**
191220
* pm_op - Execute the PM operation appropriate for given PM event.
192221
* @dev: Device to handle.
@@ -466,17 +495,19 @@ static int legacy_resume(struct device *dev, int (*cb)(struct device *dev))
466495
}
467496

468497
/**
469-
* device_resume - Execute "resume" callbacks for given device.
498+
* __device_resume - Execute "resume" callbacks for given device.
470499
* @dev: Device to handle.
471500
* @state: PM transition of the system being carried out.
501+
* @async: If true, the device is being resumed asynchronously.
472502
*/
473-
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)
474504
{
475505
int error = 0;
476506

477507
TRACE_DEVICE(dev);
478508
TRACE_RESUME(0);
479509

510+
dpm_wait(dev->parent, async);
480511
down(&dev->sem);
481512

482513
if (dev->bus) {
@@ -511,11 +542,36 @@ static int device_resume(struct device *dev, pm_message_t state)
511542
}
512543
End:
513544
up(&dev->sem);
545+
complete_all(&dev->power.completion);
514546

515547
TRACE_RESUME(error);
516548
return error;
517549
}
518550

551+
static 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+
562+
static 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+
519575
/**
520576
* dpm_resume - Execute "resume" callbacks for non-sysdev devices.
521577
* @state: PM transition of the system being carried out.
@@ -530,6 +586,7 @@ static void dpm_resume(pm_message_t state)
530586

531587
INIT_LIST_HEAD(&list);
532588
mutex_lock(&dpm_list_mtx);
589+
pm_transition = state;
533590
while (!list_empty(&dpm_list)) {
534591
struct device *dev = to_device(dpm_list.next);
535592

@@ -540,7 +597,7 @@ static void dpm_resume(pm_message_t state)
540597
dev->power.status = DPM_RESUMING;
541598
mutex_unlock(&dpm_list_mtx);
542599

543-
error = device_resume(dev, state);
600+
error = device_resume(dev);
544601

545602
mutex_lock(&dpm_list_mtx);
546603
if (error)
@@ -555,6 +612,7 @@ static void dpm_resume(pm_message_t state)
555612
}
556613
list_splice(&list, &dpm_list);
557614
mutex_unlock(&dpm_list_mtx);
615+
async_synchronize_full();
558616
dpm_show_time(starttime, state, NULL);
559617
}
560618

@@ -732,17 +790,24 @@ static int legacy_suspend(struct device *dev, pm_message_t state,
732790
return error;
733791
}
734792

793+
static int async_error;
794+
735795
/**
736796
* device_suspend - Execute "suspend" callbacks for given device.
737797
* @dev: Device to handle.
738798
* @state: PM transition of the system being carried out.
799+
* @async: If true, the device is being suspended asynchronously.
739800
*/
740-
static int device_suspend(struct device *dev, pm_message_t state)
801+
static int __device_suspend(struct device *dev, pm_message_t state, bool async)
741802
{
742803
int error = 0;
743804

805+
dpm_wait_for_children(dev, async);
744806
down(&dev->sem);
745807

808+
if (async_error)
809+
goto End;
810+
746811
if (dev->class) {
747812
if (dev->class->pm) {
748813
pm_dev_dbg(dev, state, "class ");
@@ -773,12 +838,44 @@ static int device_suspend(struct device *dev, pm_message_t state)
773838
error = legacy_suspend(dev, state, dev->bus->suspend);
774839
}
775840
}
841+
842+
if (!error)
843+
dev->power.status = DPM_OFF;
844+
776845
End:
777846
up(&dev->sem);
847+
complete_all(&dev->power.completion);
778848

779849
return error;
780850
}
781851

852+
static 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+
866+
static 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+
782879
/**
783880
* dpm_suspend - Execute "suspend" callbacks for all non-sysdev devices.
784881
* @state: PM transition of the system being carried out.
@@ -791,27 +888,33 @@ static int dpm_suspend(pm_message_t state)
791888

792889
INIT_LIST_HEAD(&list);
793890
mutex_lock(&dpm_list_mtx);
891+
pm_transition = state;
892+
async_error = 0;
794893
while (!list_empty(&dpm_list)) {
795894
struct device *dev = to_device(dpm_list.prev);
796895

797896
get_device(dev);
798897
mutex_unlock(&dpm_list_mtx);
799898

800-
error = device_suspend(dev, state);
899+
error = device_suspend(dev);
801900

802901
mutex_lock(&dpm_list_mtx);
803902
if (error) {
804903
pm_dev_err(dev, state, "", error);
805904
put_device(dev);
806905
break;
807906
}
808-
dev->power.status = DPM_OFF;
809907
if (!list_empty(&dev->power.entry))
810908
list_move(&dev->power.entry, &list);
811909
put_device(dev);
910+
if (async_error)
911+
break;
812912
}
813913
list_splice(&list, dpm_list.prev);
814914
mutex_unlock(&dpm_list_mtx);
915+
async_synchronize_full();
916+
if (!error)
917+
error = async_error;
815918
if (!error)
816919
dpm_show_time(starttime, state, NULL);
817920
return error;

include/linux/device.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,12 @@ static inline int device_is_registered(struct device *dev)
472472
return dev->kobj.state_in_sysfs;
473473
}
474474

475+
static inline void device_enable_async_suspend(struct device *dev)
476+
{
477+
if (dev->power.status == DPM_ON)
478+
dev->power.async_suspend = true;
479+
}
480+
475481
void driver_init(void);
476482

477483
/*

include/linux/pm.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include <linux/spinlock.h>
2727
#include <linux/wait.h>
2828
#include <linux/timer.h>
29+
#include <linux/completion.h>
2930

3031
/*
3132
* Callbacks for platform drivers to implement.
@@ -412,9 +413,11 @@ struct dev_pm_info {
412413
pm_message_t power_state;
413414
unsigned int can_wakeup:1;
414415
unsigned int should_wakeup:1;
416+
unsigned async_suspend:1;
415417
enum dpm_state status; /* Owned by the PM core */
416418
#ifdef CONFIG_PM_SLEEP
417419
struct list_head entry;
420+
struct completion completion;
418421
#endif
419422
#ifdef CONFIG_PM_RUNTIME
420423
struct timer_list suspend_timer;

include/linux/resume-trace.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66

77
extern int pm_trace_enabled;
88

9+
static inline int pm_trace_is_enabled(void)
10+
{
11+
return pm_trace_enabled;
12+
}
13+
914
struct device;
1015
extern void set_trace_device(struct device *);
1116
extern void generate_resume_trace(const void *tracedata, unsigned int user);
@@ -17,6 +22,8 @@ extern void generate_resume_trace(const void *tracedata, unsigned int user);
1722

1823
#else
1924

25+
static inline int pm_trace_is_enabled(void) { return 0; }
26+
2027
#define TRACE_DEVICE(dev) do { } while (0)
2128
#define TRACE_RESUME(dev) do { } while (0)
2229

0 commit comments

Comments
 (0)