| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * kernel/power/suspend.c - Suspend to RAM and standby functionality. |
|---|
| 3 | 4 | * |
|---|
| 4 | 5 | * Copyright (c) 2003 Patrick Mochel |
|---|
| 5 | 6 | * Copyright (c) 2003 Open Source Development Lab |
|---|
| 6 | 7 | * Copyright (c) 2009 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc. |
|---|
| 7 | | - * |
|---|
| 8 | | - * This file is released under the GPLv2. |
|---|
| 9 | 8 | */ |
|---|
| 10 | 9 | |
|---|
| 11 | 10 | #define pr_fmt(fmt) "PM: " fmt |
|---|
| .. | .. |
|---|
| 17 | 16 | #include <linux/console.h> |
|---|
| 18 | 17 | #include <linux/cpu.h> |
|---|
| 19 | 18 | #include <linux/cpuidle.h> |
|---|
| 20 | | -#include <linux/syscalls.h> |
|---|
| 21 | 19 | #include <linux/gfp.h> |
|---|
| 22 | 20 | #include <linux/io.h> |
|---|
| 23 | 21 | #include <linux/kernel.h> |
|---|
| .. | .. |
|---|
| 46 | 44 | [PM_SUSPEND_TO_IDLE] = "s2idle", |
|---|
| 47 | 45 | [PM_SUSPEND_STANDBY] = "shallow", |
|---|
| 48 | 46 | [PM_SUSPEND_MEM] = "deep", |
|---|
| 49 | | - [PM_SUSPEND_MEM_LITE] = "lite", |
|---|
| 50 | | - [PM_SUSPEND_MEM_ULTRA] = "ultra", |
|---|
| 51 | 47 | }; |
|---|
| 52 | 48 | const char *mem_sleep_states[PM_SUSPEND_MAX]; |
|---|
| 53 | 49 | |
|---|
| .. | .. |
|---|
| 66 | 62 | enum s2idle_states __read_mostly s2idle_state; |
|---|
| 67 | 63 | static DEFINE_RAW_SPINLOCK(s2idle_lock); |
|---|
| 68 | 64 | |
|---|
| 69 | | -bool pm_suspend_via_s2idle(void) |
|---|
| 65 | +/** |
|---|
| 66 | + * pm_suspend_default_s2idle - Check if suspend-to-idle is the default suspend. |
|---|
| 67 | + * |
|---|
| 68 | + * Return 'true' if suspend-to-idle has been selected as the default system |
|---|
| 69 | + * suspend method. |
|---|
| 70 | + */ |
|---|
| 71 | +bool pm_suspend_default_s2idle(void) |
|---|
| 70 | 72 | { |
|---|
| 71 | 73 | return mem_sleep_current == PM_SUSPEND_TO_IDLE; |
|---|
| 72 | 74 | } |
|---|
| 73 | | -EXPORT_SYMBOL_GPL(pm_suspend_via_s2idle); |
|---|
| 75 | +EXPORT_SYMBOL_GPL(pm_suspend_default_s2idle); |
|---|
| 74 | 76 | |
|---|
| 75 | 77 | void s2idle_set_ops(const struct platform_s2idle_ops *ops) |
|---|
| 76 | 78 | { |
|---|
| .. | .. |
|---|
| 78 | 80 | s2idle_ops = ops; |
|---|
| 79 | 81 | unlock_system_sleep(); |
|---|
| 80 | 82 | } |
|---|
| 81 | | -EXPORT_SYMBOL_GPL(s2idle_set_ops); |
|---|
| 82 | 83 | |
|---|
| 83 | 84 | static void s2idle_begin(void) |
|---|
| 84 | 85 | { |
|---|
| .. | .. |
|---|
| 121 | 122 | { |
|---|
| 122 | 123 | pm_pr_dbg("suspend-to-idle\n"); |
|---|
| 123 | 124 | |
|---|
| 125 | + /* |
|---|
| 126 | + * Suspend-to-idle equals: |
|---|
| 127 | + * frozen processes + suspended devices + idle processors. |
|---|
| 128 | + * Thus s2idle_enter() should be called right after all devices have |
|---|
| 129 | + * been suspended. |
|---|
| 130 | + * |
|---|
| 131 | + * Wakeups during the noirq suspend of devices may be spurious, so try |
|---|
| 132 | + * to avoid them upfront. |
|---|
| 133 | + */ |
|---|
| 124 | 134 | for (;;) { |
|---|
| 125 | | - int error; |
|---|
| 126 | | - |
|---|
| 127 | | - dpm_noirq_begin(); |
|---|
| 128 | | - |
|---|
| 129 | | - /* |
|---|
| 130 | | - * Suspend-to-idle equals |
|---|
| 131 | | - * frozen processes + suspended devices + idle processors. |
|---|
| 132 | | - * Thus s2idle_enter() should be called right after |
|---|
| 133 | | - * all devices have been suspended. |
|---|
| 134 | | - * |
|---|
| 135 | | - * Wakeups during the noirq suspend of devices may be spurious, |
|---|
| 136 | | - * so prevent them from terminating the loop right away. |
|---|
| 137 | | - */ |
|---|
| 138 | | - error = dpm_noirq_suspend_devices(PMSG_SUSPEND); |
|---|
| 139 | | - if (!error) |
|---|
| 140 | | - s2idle_enter(); |
|---|
| 141 | | - else if (error == -EBUSY && pm_wakeup_pending()) |
|---|
| 142 | | - error = 0; |
|---|
| 143 | | - |
|---|
| 144 | | - if (!error && s2idle_ops && s2idle_ops->wake) |
|---|
| 145 | | - s2idle_ops->wake(); |
|---|
| 146 | | - |
|---|
| 147 | | - dpm_noirq_resume_devices(PMSG_RESUME); |
|---|
| 148 | | - |
|---|
| 149 | | - dpm_noirq_end(); |
|---|
| 150 | | - |
|---|
| 151 | | - if (error) |
|---|
| 135 | + if (s2idle_ops && s2idle_ops->wake) { |
|---|
| 136 | + if (s2idle_ops->wake()) |
|---|
| 137 | + break; |
|---|
| 138 | + } else if (pm_wakeup_pending()) { |
|---|
| 152 | 139 | break; |
|---|
| 140 | + } |
|---|
| 153 | 141 | |
|---|
| 154 | | - if (s2idle_ops && s2idle_ops->sync) |
|---|
| 155 | | - s2idle_ops->sync(); |
|---|
| 156 | | - |
|---|
| 157 | | - if (pm_wakeup_pending()) |
|---|
| 158 | | - break; |
|---|
| 159 | | - |
|---|
| 160 | | - pm_wakeup_clear(false); |
|---|
| 161 | 142 | clear_wakeup_reasons(); |
|---|
| 143 | + s2idle_enter(); |
|---|
| 162 | 144 | } |
|---|
| 163 | 145 | |
|---|
| 164 | 146 | pm_pr_dbg("resume from suspend-to-idle\n"); |
|---|
| .. | .. |
|---|
| 232 | 214 | } |
|---|
| 233 | 215 | if (valid_state(PM_SUSPEND_MEM)) { |
|---|
| 234 | 216 | mem_sleep_states[PM_SUSPEND_MEM] = mem_sleep_labels[PM_SUSPEND_MEM]; |
|---|
| 235 | | - mem_sleep_states[PM_SUSPEND_MEM_LITE] = mem_sleep_labels[PM_SUSPEND_MEM_LITE]; |
|---|
| 236 | | - mem_sleep_states[PM_SUSPEND_MEM_ULTRA] = mem_sleep_labels[PM_SUSPEND_MEM_ULTRA]; |
|---|
| 237 | 217 | if (mem_sleep_default >= PM_SUSPEND_MEM) |
|---|
| 238 | 218 | mem_sleep_current = PM_SUSPEND_MEM; |
|---|
| 239 | 219 | } |
|---|
| .. | .. |
|---|
| 274 | 254 | |
|---|
| 275 | 255 | static int platform_suspend_prepare_noirq(suspend_state_t state) |
|---|
| 276 | 256 | { |
|---|
| 277 | | - return state != PM_SUSPEND_TO_IDLE && suspend_ops->prepare_late ? |
|---|
| 278 | | - suspend_ops->prepare_late() : 0; |
|---|
| 257 | + if (state == PM_SUSPEND_TO_IDLE) |
|---|
| 258 | + return s2idle_ops && s2idle_ops->prepare_late ? |
|---|
| 259 | + s2idle_ops->prepare_late() : 0; |
|---|
| 260 | + |
|---|
| 261 | + return suspend_ops->prepare_late ? suspend_ops->prepare_late() : 0; |
|---|
| 279 | 262 | } |
|---|
| 280 | 263 | |
|---|
| 281 | 264 | static void platform_resume_noirq(suspend_state_t state) |
|---|
| 282 | 265 | { |
|---|
| 283 | | - if (state != PM_SUSPEND_TO_IDLE && suspend_ops->wake) |
|---|
| 266 | + if (state == PM_SUSPEND_TO_IDLE) { |
|---|
| 267 | + if (s2idle_ops && s2idle_ops->restore_early) |
|---|
| 268 | + s2idle_ops->restore_early(); |
|---|
| 269 | + } else if (suspend_ops->wake) { |
|---|
| 284 | 270 | suspend_ops->wake(); |
|---|
| 271 | + } |
|---|
| 285 | 272 | } |
|---|
| 286 | 273 | |
|---|
| 287 | 274 | static void platform_resume_early(suspend_state_t state) |
|---|
| .. | .. |
|---|
| 355 | 342 | */ |
|---|
| 356 | 343 | static int suspend_prepare(suspend_state_t state) |
|---|
| 357 | 344 | { |
|---|
| 358 | | - int error, nr_calls = 0; |
|---|
| 345 | + int error; |
|---|
| 359 | 346 | |
|---|
| 360 | 347 | if (!sleep_state_supported(state)) |
|---|
| 361 | 348 | return -EPERM; |
|---|
| 362 | 349 | |
|---|
| 363 | 350 | pm_prepare_console(); |
|---|
| 364 | 351 | |
|---|
| 365 | | - error = __pm_notifier_call_chain(PM_SUSPEND_PREPARE, -1, &nr_calls); |
|---|
| 366 | | - if (error) { |
|---|
| 367 | | - nr_calls--; |
|---|
| 368 | | - goto Finish; |
|---|
| 369 | | - } |
|---|
| 352 | + error = pm_notifier_call_chain_robust(PM_SUSPEND_PREPARE, PM_POST_SUSPEND); |
|---|
| 353 | + if (error) |
|---|
| 354 | + goto Restore; |
|---|
| 370 | 355 | |
|---|
| 371 | 356 | trace_suspend_resume(TPS("freeze_processes"), 0, true); |
|---|
| 372 | 357 | error = suspend_freeze_processes(); |
|---|
| .. | .. |
|---|
| 377 | 362 | log_suspend_abort_reason("One or more tasks refusing to freeze"); |
|---|
| 378 | 363 | suspend_stats.failed_freeze++; |
|---|
| 379 | 364 | dpm_save_failed_step(SUSPEND_FREEZE); |
|---|
| 380 | | - Finish: |
|---|
| 381 | | - __pm_notifier_call_chain(PM_POST_SUSPEND, nr_calls, NULL); |
|---|
| 365 | + pm_notifier_call_chain(PM_POST_SUSPEND); |
|---|
| 366 | + Restore: |
|---|
| 382 | 367 | pm_restore_console(); |
|---|
| 383 | 368 | return error; |
|---|
| 384 | 369 | } |
|---|
| .. | .. |
|---|
| 423 | 408 | if (error) |
|---|
| 424 | 409 | goto Devices_early_resume; |
|---|
| 425 | 410 | |
|---|
| 426 | | - if (state == PM_SUSPEND_TO_IDLE && pm_test_level != TEST_PLATFORM) { |
|---|
| 427 | | - s2idle_loop(); |
|---|
| 428 | | - goto Platform_early_resume; |
|---|
| 429 | | - } |
|---|
| 430 | | - |
|---|
| 431 | 411 | error = dpm_suspend_noirq(PMSG_SUSPEND); |
|---|
| 432 | 412 | if (error) { |
|---|
| 433 | 413 | last_dev = suspend_stats.last_failed_dev + REC_FAILED_NUM - 1; |
|---|
| .. | .. |
|---|
| 444 | 424 | if (suspend_test(TEST_PLATFORM)) |
|---|
| 445 | 425 | goto Platform_wake; |
|---|
| 446 | 426 | |
|---|
| 447 | | - error = disable_nonboot_cpus(); |
|---|
| 427 | + if (state == PM_SUSPEND_TO_IDLE) { |
|---|
| 428 | + s2idle_loop(); |
|---|
| 429 | + goto Platform_wake; |
|---|
| 430 | + } |
|---|
| 431 | + |
|---|
| 432 | + error = suspend_disable_secondary_cpus(); |
|---|
| 448 | 433 | if (error || suspend_test(TEST_CPUS)) { |
|---|
| 449 | 434 | log_suspend_abort_reason("Disabling non-boot cpus failed"); |
|---|
| 450 | 435 | goto Enable_cpus; |
|---|
| .. | .. |
|---|
| 476 | 461 | BUG_ON(irqs_disabled()); |
|---|
| 477 | 462 | |
|---|
| 478 | 463 | Enable_cpus: |
|---|
| 479 | | - enable_nonboot_cpus(); |
|---|
| 464 | + suspend_enable_secondary_cpus(); |
|---|
| 480 | 465 | |
|---|
| 481 | 466 | Platform_wake: |
|---|
| 482 | 467 | platform_resume_noirq(state); |
|---|
| .. | .. |
|---|
| 506 | 491 | return -ENOSYS; |
|---|
| 507 | 492 | |
|---|
| 508 | 493 | pm_suspend_target_state = state; |
|---|
| 494 | + |
|---|
| 495 | + if (state == PM_SUSPEND_TO_IDLE) |
|---|
| 496 | + pm_set_suspend_no_platform(); |
|---|
| 509 | 497 | |
|---|
| 510 | 498 | error = platform_suspend_begin(state); |
|---|
| 511 | 499 | if (error) |
|---|
| .. | .. |
|---|
| 588 | 576 | if (state == PM_SUSPEND_TO_IDLE) |
|---|
| 589 | 577 | s2idle_begin(); |
|---|
| 590 | 578 | |
|---|
| 591 | | -#ifndef CONFIG_SUSPEND_SKIP_SYNC |
|---|
| 592 | | - trace_suspend_resume(TPS("sync_filesystems"), 0, true); |
|---|
| 593 | | - pr_info("Syncing filesystems ... "); |
|---|
| 594 | | - ksys_sync(); |
|---|
| 595 | | - pr_cont("done.\n"); |
|---|
| 596 | | - trace_suspend_resume(TPS("sync_filesystems"), 0, false); |
|---|
| 597 | | -#endif |
|---|
| 579 | + if (sync_on_suspend_enabled) { |
|---|
| 580 | + trace_suspend_resume(TPS("sync_filesystems"), 0, true); |
|---|
| 581 | + ksys_sync_helper(); |
|---|
| 582 | + trace_suspend_resume(TPS("sync_filesystems"), 0, false); |
|---|
| 583 | + } |
|---|
| 598 | 584 | |
|---|
| 599 | 585 | pm_pr_dbg("Preparing system for sleep (%s)\n", mem_sleep_labels[state]); |
|---|
| 600 | 586 | pm_suspend_clear_flags(); |
|---|
| .. | .. |
|---|
| 635 | 621 | return -EINVAL; |
|---|
| 636 | 622 | |
|---|
| 637 | 623 | pr_info("suspend entry (%s)\n", mem_sleep_labels[state]); |
|---|
| 638 | | - |
|---|
| 639 | | - if (state == PM_SUSPEND_MEM_LITE || state == PM_SUSPEND_MEM_ULTRA) |
|---|
| 640 | | - state = PM_SUSPEND_MEM; |
|---|
| 641 | | - |
|---|
| 642 | 624 | error = enter_state(state); |
|---|
| 643 | 625 | if (error) { |
|---|
| 644 | 626 | suspend_stats.fail++; |
|---|