diff options
author | Srivatsa S. Bhat <srivatsa.bhat@linux.vnet.ibm.com> | 2011-11-02 19:59:25 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2012-01-12 14:35:46 -0500 |
commit | 4ae84455f98af3b3c3a23f8b6458fcefc9ff62bf (patch) | |
tree | 84c872ec590114cadc912adeb495878d50f9098b /kernel/cpu.c | |
parent | 81aaa36dde03db855fb233382c7667f98ac12659 (diff) |
PM / Sleep: Fix race between CPU hotplug and freezer
commit 79cfbdfa87e84992d509e6c1648a18e1d7e68c20 upstream.
The CPU hotplug notifications sent out by the _cpu_up() and _cpu_down()
functions depend on the value of the 'tasks_frozen' argument passed to them
(which indicates whether tasks have been frozen or not).
(Examples for such CPU hotplug notifications: CPU_ONLINE, CPU_ONLINE_FROZEN,
CPU_DEAD, CPU_DEAD_FROZEN).
Thus, it is essential that while the callbacks for those notifications are
running, the state of the system with respect to the tasks being frozen or
not remains unchanged, *throughout that duration*. Hence there is a need for
synchronizing the CPU hotplug code with the freezer subsystem.
Since the freezer is involved only in the Suspend/Hibernate call paths, this
patch hooks the CPU hotplug code to the suspend/hibernate notifiers
PM_[SUSPEND|HIBERNATE]_PREPARE and PM_POST_[SUSPEND|HIBERNATE] to prevent
the race between CPU hotplug and freezer, thus ensuring that CPU hotplug
notifications will always be run with the state of the system really being
what the notifications indicate, _throughout_ their execution time.
Signed-off-by: Srivatsa S. Bhat <srivatsa.bhat@linux.vnet.ibm.com>
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'kernel/cpu.c')
-rw-r--r-- | kernel/cpu.c | 74 |
1 files changed, 74 insertions, 0 deletions
diff --git a/kernel/cpu.c b/kernel/cpu.c index 12b7458f23b..aa39dd7a384 100644 --- a/kernel/cpu.c +++ b/kernel/cpu.c | |||
@@ -15,6 +15,7 @@ | |||
15 | #include <linux/stop_machine.h> | 15 | #include <linux/stop_machine.h> |
16 | #include <linux/mutex.h> | 16 | #include <linux/mutex.h> |
17 | #include <linux/gfp.h> | 17 | #include <linux/gfp.h> |
18 | #include <linux/suspend.h> | ||
18 | 19 | ||
19 | #ifdef CONFIG_SMP | 20 | #ifdef CONFIG_SMP |
20 | /* Serializes the updates to cpu_online_mask, cpu_present_mask */ | 21 | /* Serializes the updates to cpu_online_mask, cpu_present_mask */ |
@@ -476,6 +477,79 @@ static int alloc_frozen_cpus(void) | |||
476 | return 0; | 477 | return 0; |
477 | } | 478 | } |
478 | core_initcall(alloc_frozen_cpus); | 479 | core_initcall(alloc_frozen_cpus); |
480 | |||
481 | /* | ||
482 | * Prevent regular CPU hotplug from racing with the freezer, by disabling CPU | ||
483 | * hotplug when tasks are about to be frozen. Also, don't allow the freezer | ||
484 | * to continue until any currently running CPU hotplug operation gets | ||
485 | * completed. | ||
486 | * To modify the 'cpu_hotplug_disabled' flag, we need to acquire the | ||
487 | * 'cpu_add_remove_lock'. And this same lock is also taken by the regular | ||
488 | * CPU hotplug path and released only after it is complete. Thus, we | ||
489 | * (and hence the freezer) will block here until any currently running CPU | ||
490 | * hotplug operation gets completed. | ||
491 | */ | ||
492 | void cpu_hotplug_disable_before_freeze(void) | ||
493 | { | ||
494 | cpu_maps_update_begin(); | ||
495 | cpu_hotplug_disabled = 1; | ||
496 | cpu_maps_update_done(); | ||
497 | } | ||
498 | |||
499 | |||
500 | /* | ||
501 | * When tasks have been thawed, re-enable regular CPU hotplug (which had been | ||
502 | * disabled while beginning to freeze tasks). | ||
503 | */ | ||
504 | void cpu_hotplug_enable_after_thaw(void) | ||
505 | { | ||
506 | cpu_maps_update_begin(); | ||
507 | cpu_hotplug_disabled = 0; | ||
508 | cpu_maps_update_done(); | ||
509 | } | ||
510 | |||
511 | /* | ||
512 | * When callbacks for CPU hotplug notifications are being executed, we must | ||
513 | * ensure that the state of the system with respect to the tasks being frozen | ||
514 | * or not, as reported by the notification, remains unchanged *throughout the | ||
515 | * duration* of the execution of the callbacks. | ||
516 | * Hence we need to prevent the freezer from racing with regular CPU hotplug. | ||
517 | * | ||
518 | * This synchronization is implemented by mutually excluding regular CPU | ||
519 | * hotplug and Suspend/Hibernate call paths by hooking onto the Suspend/ | ||
520 | * Hibernate notifications. | ||
521 | */ | ||
522 | static int | ||
523 | cpu_hotplug_pm_callback(struct notifier_block *nb, | ||
524 | unsigned long action, void *ptr) | ||
525 | { | ||
526 | switch (action) { | ||
527 | |||
528 | case PM_SUSPEND_PREPARE: | ||
529 | case PM_HIBERNATION_PREPARE: | ||
530 | cpu_hotplug_disable_before_freeze(); | ||
531 | break; | ||
532 | |||
533 | case PM_POST_SUSPEND: | ||
534 | case PM_POST_HIBERNATION: | ||
535 | cpu_hotplug_enable_after_thaw(); | ||
536 | break; | ||
537 | |||
538 | default: | ||
539 | return NOTIFY_DONE; | ||
540 | } | ||
541 | |||
542 | return NOTIFY_OK; | ||
543 | } | ||
544 | |||
545 | |||
546 | int cpu_hotplug_pm_sync_init(void) | ||
547 | { | ||
548 | pm_notifier(cpu_hotplug_pm_callback, 0); | ||
549 | return 0; | ||
550 | } | ||
551 | core_initcall(cpu_hotplug_pm_sync_init); | ||
552 | |||
479 | #endif /* CONFIG_PM_SLEEP_SMP */ | 553 | #endif /* CONFIG_PM_SLEEP_SMP */ |
480 | 554 | ||
481 | /** | 555 | /** |