Commit 7e73c5ae6e7991a6c01f6d096ff8afaef4458c36

Authored by Zhang Rui
Committed by Rafael J. Wysocki
1 parent fbadc58dd3

PM: Introduce suspend state PM_SUSPEND_FREEZE

PM_SUSPEND_FREEZE state is a general state that
does not need any platform specific support, it equals
frozen processes + suspended devices + idle processors.

Compared with PM_SUSPEND_MEMORY,
PM_SUSPEND_FREEZE saves less power
because the system is still in a running state.
PM_SUSPEND_FREEZE has less resume latency because it does not
touch BIOS, and the processors are in idle state.

Compared with RTPM/idle,
PM_SUSPEND_FREEZE saves more power as
1. the processor has longer sleep time because processes are frozen.
   The deeper c-state the processor supports, more power saving we can get.
2. PM_SUSPEND_FREEZE uses system suspend code path, thus we can get
   more power saving from the devices that does not have good RTPM support.

This state is useful for
1) platforms that do not have STR, or have a broken STR.
2) platforms that have an extremely low power idle state,
   which can be used to replace STR.

The following describes how PM_SUSPEND_FREEZE state works.
1. echo freeze > /sys/power/state
2. the processes are frozen.
3. all the devices are suspended.
4. all the processors are blocked by a wait queue
5. all the processors idles and enters (Deep) c-state.
6. an interrupt fires.
7. a processor is woken up and handles the irq.
8. if it is a general event,
   a) the irq handler runs and quites.
   b) goto step 4.
9. if it is a real wake event, say, power button pressing, keyboard touch, mouse moving,
   a) the irq handler runs and activate the wakeup source
   b) wakeup_source_activate() notifies the wait queue.
   c) system starts resuming from PM_SUSPEND_FREEZE
10. all the devices are resumed.
11. all the processes are unfrozen.
12. system is back to working.

Known Issue:
The wakeup of this new PM_SUSPEND_FREEZE state may behave differently
from the previous suspend state.
Take ACPI platform for example, there are some GPEs that only enabled
when the system is in sleep state, to wake the system backk from S3/S4.
But we are not touching these GPEs during transition to PM_SUSPEND_FREEZE.
This means we may lose some wake event.
But on the other hand, as we do not disable all the Interrupts during
PM_SUSPEND_FREEZE, we may get some extra "wakeup" Interrupts, that are
not available for S3/S4.

The patches has been tested on an old Sony laptop, and here are the results:

Average Power:
1. RPTM/idle for half an hour:
   14.8W, 12.6W, 14.1W, 12.5W, 14.4W, 13.2W, 12.9W
2. Freeze for half an hour:
   11W, 10.4W, 9.4W, 11.3W 10.5W
3. RTPM/idle for three hours:
   11.6W
4. Freeze for three hours:
   10W
5. Suspend to Memory:
   0.5~0.9W

Average Resume Latency:
1. RTPM/idle with a black screen: (From pressing keyboard to screen back)
   Less than 0.2s
2. Freeze: (From pressing power button to screen back)
   2.50s
3. Suspend to Memory: (From pressing power button to screen back)
   4.33s

>From the results, we can see that all the platforms should benefit from
this patch, even if it does not have Low Power S0.

Signed-off-by: Zhang Rui <rui.zhang@intel.com>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>

Showing 4 changed files with 68 additions and 15 deletions Side-by-side Diff

drivers/base/power/wakeup.c
... ... @@ -382,6 +382,12 @@
382 382 {
383 383 unsigned int cec;
384 384  
  385 + /*
  386 + * active wakeup source should bring the system
  387 + * out of PM_SUSPEND_FREEZE state
  388 + */
  389 + freeze_wake();
  390 +
385 391 ws->active = true;
386 392 ws->active_count++;
387 393 ws->last_time = ktime_get();
include/linux/suspend.h
... ... @@ -34,8 +34,10 @@
34 34 typedef int __bitwise suspend_state_t;
35 35  
36 36 #define PM_SUSPEND_ON ((__force suspend_state_t) 0)
37   -#define PM_SUSPEND_STANDBY ((__force suspend_state_t) 1)
  37 +#define PM_SUSPEND_FREEZE ((__force suspend_state_t) 1)
  38 +#define PM_SUSPEND_STANDBY ((__force suspend_state_t) 2)
38 39 #define PM_SUSPEND_MEM ((__force suspend_state_t) 3)
  40 +#define PM_SUSPEND_MIN PM_SUSPEND_FREEZE
39 41 #define PM_SUSPEND_MAX ((__force suspend_state_t) 4)
40 42  
41 43 enum suspend_stat_step {
... ... @@ -192,6 +194,7 @@
192 194 */
193 195 extern void suspend_set_ops(const struct platform_suspend_ops *ops);
194 196 extern int suspend_valid_only_mem(suspend_state_t state);
  197 +extern void freeze_wake(void);
195 198  
196 199 /**
197 200 * arch_suspend_disable_irqs - disable IRQs for suspend
... ... @@ -217,6 +220,7 @@
217 220  
218 221 static inline void suspend_set_ops(const struct platform_suspend_ops *ops) {}
219 222 static inline int pm_suspend(suspend_state_t state) { return -ENOSYS; }
  223 +static inline void freeze_wake(void) {}
220 224 #endif /* !CONFIG_SUSPEND */
221 225  
222 226 /* struct pbe is used for creating lists of pages that should be restored
... ... @@ -313,7 +313,7 @@
313 313 static suspend_state_t decode_state(const char *buf, size_t n)
314 314 {
315 315 #ifdef CONFIG_SUSPEND
316   - suspend_state_t state = PM_SUSPEND_STANDBY;
  316 + suspend_state_t state = PM_SUSPEND_MIN;
317 317 const char * const *s;
318 318 #endif
319 319 char *p;
kernel/power/suspend.c
... ... @@ -30,12 +30,38 @@
30 30 #include "power.h"
31 31  
32 32 const char *const pm_states[PM_SUSPEND_MAX] = {
  33 + [PM_SUSPEND_FREEZE] = "freeze",
33 34 [PM_SUSPEND_STANDBY] = "standby",
34 35 [PM_SUSPEND_MEM] = "mem",
35 36 };
36 37  
37 38 static const struct platform_suspend_ops *suspend_ops;
38 39  
  40 +static bool need_suspend_ops(suspend_state_t state)
  41 +{
  42 + return !!(state > PM_SUSPEND_FREEZE);
  43 +}
  44 +
  45 +static DECLARE_WAIT_QUEUE_HEAD(suspend_freeze_wait_head);
  46 +static bool suspend_freeze_wake;
  47 +
  48 +static void freeze_begin(void)
  49 +{
  50 + suspend_freeze_wake = false;
  51 +}
  52 +
  53 +static void freeze_enter(void)
  54 +{
  55 + wait_event(suspend_freeze_wait_head, suspend_freeze_wake);
  56 +}
  57 +
  58 +void freeze_wake(void)
  59 +{
  60 + suspend_freeze_wake = true;
  61 + wake_up(&suspend_freeze_wait_head);
  62 +}
  63 +EXPORT_SYMBOL_GPL(freeze_wake);
  64 +
39 65 /**
40 66 * suspend_set_ops - Set the global suspend method table.
41 67 * @ops: Suspend operations to use.
42 68  
... ... @@ -50,8 +76,11 @@
50 76  
51 77 bool valid_state(suspend_state_t state)
52 78 {
  79 + if (state == PM_SUSPEND_FREEZE)
  80 + return true;
53 81 /*
54   - * All states need lowlevel support and need to be valid to the lowlevel
  82 + * PM_SUSPEND_STANDBY and PM_SUSPEND_MEMORY states need lowlevel
  83 + * support and need to be valid to the lowlevel
55 84 * implementation, no valid callback implies that none are valid.
56 85 */
57 86 return suspend_ops && suspend_ops->valid && suspend_ops->valid(state);
58 87  
... ... @@ -89,11 +118,11 @@
89 118 * hibernation). Run suspend notifiers, allocate the "suspend" console and
90 119 * freeze processes.
91 120 */
92   -static int suspend_prepare(void)
  121 +static int suspend_prepare(suspend_state_t state)
93 122 {
94 123 int error;
95 124  
96   - if (!suspend_ops || !suspend_ops->enter)
  125 + if (need_suspend_ops(state) && (!suspend_ops || !suspend_ops->enter))
97 126 return -EPERM;
98 127  
99 128 pm_prepare_console();
... ... @@ -137,7 +166,7 @@
137 166 {
138 167 int error;
139 168  
140   - if (suspend_ops->prepare) {
  169 + if (need_suspend_ops(state) && suspend_ops->prepare) {
141 170 error = suspend_ops->prepare();
142 171 if (error)
143 172 goto Platform_finish;
144 173  
... ... @@ -149,12 +178,23 @@
149 178 goto Platform_finish;
150 179 }
151 180  
152   - if (suspend_ops->prepare_late) {
  181 + if (need_suspend_ops(state) && suspend_ops->prepare_late) {
153 182 error = suspend_ops->prepare_late();
154 183 if (error)
155 184 goto Platform_wake;
156 185 }
157 186  
  187 + /*
  188 + * PM_SUSPEND_FREEZE equals
  189 + * frozen processes + suspended devices + idle processors.
  190 + * Thus we should invoke freeze_enter() soon after
  191 + * all the devices are suspended.
  192 + */
  193 + if (state == PM_SUSPEND_FREEZE) {
  194 + freeze_enter();
  195 + goto Platform_wake;
  196 + }
  197 +
158 198 if (suspend_test(TEST_PLATFORM))
159 199 goto Platform_wake;
160 200  
161 201  
... ... @@ -182,13 +222,13 @@
182 222 enable_nonboot_cpus();
183 223  
184 224 Platform_wake:
185   - if (suspend_ops->wake)
  225 + if (need_suspend_ops(state) && suspend_ops->wake)
186 226 suspend_ops->wake();
187 227  
188 228 dpm_resume_start(PMSG_RESUME);
189 229  
190 230 Platform_finish:
191   - if (suspend_ops->finish)
  231 + if (need_suspend_ops(state) && suspend_ops->finish)
192 232 suspend_ops->finish();
193 233  
194 234 return error;
195 235  
... ... @@ -203,11 +243,11 @@
203 243 int error;
204 244 bool wakeup = false;
205 245  
206   - if (!suspend_ops)
  246 + if (need_suspend_ops(state) && !suspend_ops)
207 247 return -ENOSYS;
208 248  
209 249 trace_machine_suspend(state);
210   - if (suspend_ops->begin) {
  250 + if (need_suspend_ops(state) && suspend_ops->begin) {
211 251 error = suspend_ops->begin(state);
212 252 if (error)
213 253 goto Close;
... ... @@ -226,7 +266,7 @@
226 266  
227 267 do {
228 268 error = suspend_enter(state, &wakeup);
229   - } while (!error && !wakeup
  269 + } while (!error && !wakeup && need_suspend_ops(state)
230 270 && suspend_ops->suspend_again && suspend_ops->suspend_again());
231 271  
232 272 Resume_devices:
233 273  
... ... @@ -236,13 +276,13 @@
236 276 ftrace_start();
237 277 resume_console();
238 278 Close:
239   - if (suspend_ops->end)
  279 + if (need_suspend_ops(state) && suspend_ops->end)
240 280 suspend_ops->end();
241 281 trace_machine_suspend(PWR_EVENT_EXIT);
242 282 return error;
243 283  
244 284 Recover_platform:
245   - if (suspend_ops->recover)
  285 + if (need_suspend_ops(state) && suspend_ops->recover)
246 286 suspend_ops->recover();
247 287 goto Resume_devices;
248 288 }
249 289  
... ... @@ -278,12 +318,15 @@
278 318 if (!mutex_trylock(&pm_mutex))
279 319 return -EBUSY;
280 320  
  321 + if (state == PM_SUSPEND_FREEZE)
  322 + freeze_begin();
  323 +
281 324 printk(KERN_INFO "PM: Syncing filesystems ... ");
282 325 sys_sync();
283 326 printk("done.\n");
284 327  
285 328 pr_debug("PM: Preparing system for %s sleep\n", pm_states[state]);
286   - error = suspend_prepare();
  329 + error = suspend_prepare(state);
287 330 if (error)
288 331 goto Unlock;
289 332