aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPreeti U Murthy <preeti@linux.vnet.ibm.com>2012-07-09 04:12:56 -0400
committerRafael J. Wysocki <rjw@sisk.pl>2012-07-10 15:34:49 -0400
commit8651f97bd951d0bb1c10fa24e3fa3455193f3548 (patch)
treea3bf8f8a4c67a092bbd50018b53f9de567acd687
parent25ac77613aa8fca131599705e3d7da2a0eaa06a0 (diff)
PM / cpuidle: System resume hang fix with cpuidle
On certain bios, resume hangs if cpus are allowed to enter idle states during suspend [1]. This was fixed in apci idle driver [2].But intel_idle driver does not have this fix. Thus instead of replicating the fix in both the idle drivers, or in more platform specific idle drivers if needed, the more general cpuidle infrastructure could handle this. A suspend callback in cpuidle_driver could handle this fix. But a cpuidle_driver provides only basic functionalities like platform idle state detection capability and mechanisms to support entry and exit into CPU idle states. All other cpuidle functions are found in the cpuidle generic infrastructure for good reason that all cpuidle drivers, irrepective of their platforms will support these functions. One option therefore would be to register a suspend callback in cpuidle which handles this fix. This could be called through a PM_SUSPEND_PREPARE notifier. But this is too generic a notfier for a driver to handle. Also, ideally the job of cpuidle is not to handle side effects of suspend. It should expose the interfaces which "handle cpuidle 'during' suspend" or any other operation, which the subsystems call during that respective operation. The fix demands that during suspend, no cpus should be allowed to enter deep C-states. The interface cpuidle_uninstall_idle_handler() in cpuidle ensures that. Not just that it also kicks all the cpus which are already in idle out of their idle states which was being done during cpu hotplug through a CPU_DYING_FROZEN callbacks. Now the question arises about when during suspend should cpuidle_uninstall_idle_handler() be called. Since we are dealing with drivers it seems best to call this function during dpm_suspend(). Delaying the call till dpm_suspend_noirq() does no harm, as long as it is before cpu_hotplug_begin() to avoid race conditions with cpu hotpulg operations. In dpm_suspend_noirq(), it would be wise to place this call before suspend_device_irqs() to avoid ugly interactions with the same. Ananlogously, during resume. References: [1] https://bugs.launchpad.net/ubuntu/+source/linux/+bug/674075. [2] http://marc.info/?l=linux-pm&m=133958534231884&w=2 Reported-and-tested-by: Dave Hansen <dave@linux.vnet.ibm.com> Signed-off-by: Preeti U Murthy <preeti@linux.vnet.ibm.com> Reviewed-by: Srivatsa S. Bhat <srivatsa.bhat@linux.vnet.ibm.com> Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
-rw-r--r--drivers/acpi/processor_idle.c30
-rw-r--r--drivers/base/power/main.c4
-rw-r--r--drivers/cpuidle/cpuidle.c16
-rw-r--r--include/linux/cpuidle.h4
4 files changed, 24 insertions, 30 deletions
diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c
index 47a8caa89dbe..d8366ee75716 100644
--- a/drivers/acpi/processor_idle.c
+++ b/drivers/acpi/processor_idle.c
@@ -221,10 +221,6 @@ static void lapic_timer_state_broadcast(struct acpi_processor *pr,
221 221
222#endif 222#endif
223 223
224/*
225 * Suspend / resume control
226 */
227static int acpi_idle_suspend;
228static u32 saved_bm_rld; 224static u32 saved_bm_rld;
229 225
230static void acpi_idle_bm_rld_save(void) 226static void acpi_idle_bm_rld_save(void)
@@ -243,21 +239,13 @@ static void acpi_idle_bm_rld_restore(void)
243 239
244int acpi_processor_suspend(struct acpi_device * device, pm_message_t state) 240int acpi_processor_suspend(struct acpi_device * device, pm_message_t state)
245{ 241{
246 if (acpi_idle_suspend == 1)
247 return 0;
248
249 acpi_idle_bm_rld_save(); 242 acpi_idle_bm_rld_save();
250 acpi_idle_suspend = 1;
251 return 0; 243 return 0;
252} 244}
253 245
254int acpi_processor_resume(struct acpi_device * device) 246int acpi_processor_resume(struct acpi_device * device)
255{ 247{
256 if (acpi_idle_suspend == 0)
257 return 0;
258
259 acpi_idle_bm_rld_restore(); 248 acpi_idle_bm_rld_restore();
260 acpi_idle_suspend = 0;
261 return 0; 249 return 0;
262} 250}
263 251
@@ -763,11 +751,6 @@ static int acpi_idle_enter_c1(struct cpuidle_device *dev,
763 751
764 local_irq_disable(); 752 local_irq_disable();
765 753
766 if (acpi_idle_suspend) {
767 local_irq_enable();
768 cpu_relax();
769 return -EBUSY;
770 }
771 754
772 lapic_timer_state_broadcast(pr, cx, 1); 755 lapic_timer_state_broadcast(pr, cx, 1);
773 kt1 = ktime_get_real(); 756 kt1 = ktime_get_real();
@@ -838,11 +821,6 @@ static int acpi_idle_enter_simple(struct cpuidle_device *dev,
838 821
839 local_irq_disable(); 822 local_irq_disable();
840 823
841 if (acpi_idle_suspend) {
842 local_irq_enable();
843 cpu_relax();
844 return -EBUSY;
845 }
846 824
847 if (cx->entry_method != ACPI_CSTATE_FFH) { 825 if (cx->entry_method != ACPI_CSTATE_FFH) {
848 current_thread_info()->status &= ~TS_POLLING; 826 current_thread_info()->status &= ~TS_POLLING;
@@ -928,8 +906,7 @@ static int acpi_idle_enter_bm(struct cpuidle_device *dev,
928 drv, drv->safe_state_index); 906 drv, drv->safe_state_index);
929 } else { 907 } else {
930 local_irq_disable(); 908 local_irq_disable();
931 if (!acpi_idle_suspend) 909 acpi_safe_halt();
932 acpi_safe_halt();
933 local_irq_enable(); 910 local_irq_enable();
934 return -EBUSY; 911 return -EBUSY;
935 } 912 }
@@ -937,11 +914,6 @@ static int acpi_idle_enter_bm(struct cpuidle_device *dev,
937 914
938 local_irq_disable(); 915 local_irq_disable();
939 916
940 if (acpi_idle_suspend) {
941 local_irq_enable();
942 cpu_relax();
943 return -EBUSY;
944 }
945 917
946 if (cx->entry_method != ACPI_CSTATE_FFH) { 918 if (cx->entry_method != ACPI_CSTATE_FFH) {
947 current_thread_info()->status &= ~TS_POLLING; 919 current_thread_info()->status &= ~TS_POLLING;
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
index 9cb845e49334..63048f79de5f 100644
--- a/drivers/base/power/main.c
+++ b/drivers/base/power/main.c
@@ -28,7 +28,7 @@
28#include <linux/sched.h> 28#include <linux/sched.h>
29#include <linux/async.h> 29#include <linux/async.h>
30#include <linux/suspend.h> 30#include <linux/suspend.h>
31 31#include <linux/cpuidle.h>
32#include "../base.h" 32#include "../base.h"
33#include "power.h" 33#include "power.h"
34 34
@@ -467,6 +467,7 @@ static void dpm_resume_noirq(pm_message_t state)
467 mutex_unlock(&dpm_list_mtx); 467 mutex_unlock(&dpm_list_mtx);
468 dpm_show_time(starttime, state, "noirq"); 468 dpm_show_time(starttime, state, "noirq");
469 resume_device_irqs(); 469 resume_device_irqs();
470 cpuidle_resume();
470} 471}
471 472
472/** 473/**
@@ -867,6 +868,7 @@ static int dpm_suspend_noirq(pm_message_t state)
867 ktime_t starttime = ktime_get(); 868 ktime_t starttime = ktime_get();
868 int error = 0; 869 int error = 0;
869 870
871 cpuidle_pause();
870 suspend_device_irqs(); 872 suspend_device_irqs();
871 mutex_lock(&dpm_list_mtx); 873 mutex_lock(&dpm_list_mtx);
872 while (!list_empty(&dpm_late_early_list)) { 874 while (!list_empty(&dpm_late_early_list)) {
diff --git a/drivers/cpuidle/cpuidle.c b/drivers/cpuidle/cpuidle.c
index 04e4b7674a47..efa9a2ca30e7 100644
--- a/drivers/cpuidle/cpuidle.c
+++ b/drivers/cpuidle/cpuidle.c
@@ -201,6 +201,22 @@ void cpuidle_resume_and_unlock(void)
201 201
202EXPORT_SYMBOL_GPL(cpuidle_resume_and_unlock); 202EXPORT_SYMBOL_GPL(cpuidle_resume_and_unlock);
203 203
204/* Currently used in suspend/resume path to suspend cpuidle */
205void cpuidle_pause(void)
206{
207 mutex_lock(&cpuidle_lock);
208 cpuidle_uninstall_idle_handler();
209 mutex_unlock(&cpuidle_lock);
210}
211
212/* Currently used in suspend/resume path to resume cpuidle */
213void cpuidle_resume(void)
214{
215 mutex_lock(&cpuidle_lock);
216 cpuidle_install_idle_handler();
217 mutex_unlock(&cpuidle_lock);
218}
219
204/** 220/**
205 * cpuidle_wrap_enter - performs timekeeping and irqen around enter function 221 * cpuidle_wrap_enter - performs timekeeping and irqen around enter function
206 * @dev: pointer to a valid cpuidle_device object 222 * @dev: pointer to a valid cpuidle_device object
diff --git a/include/linux/cpuidle.h b/include/linux/cpuidle.h
index 524bb6f3b6c4..ca6cdf55eb18 100644
--- a/include/linux/cpuidle.h
+++ b/include/linux/cpuidle.h
@@ -145,6 +145,8 @@ extern void cpuidle_unregister_device(struct cpuidle_device *dev);
145 145
146extern void cpuidle_pause_and_lock(void); 146extern void cpuidle_pause_and_lock(void);
147extern void cpuidle_resume_and_unlock(void); 147extern void cpuidle_resume_and_unlock(void);
148extern void cpuidle_pause(void);
149extern void cpuidle_resume(void);
148extern int cpuidle_enable_device(struct cpuidle_device *dev); 150extern int cpuidle_enable_device(struct cpuidle_device *dev);
149extern void cpuidle_disable_device(struct cpuidle_device *dev); 151extern void cpuidle_disable_device(struct cpuidle_device *dev);
150extern int cpuidle_wrap_enter(struct cpuidle_device *dev, 152extern int cpuidle_wrap_enter(struct cpuidle_device *dev,
@@ -168,6 +170,8 @@ static inline void cpuidle_unregister_device(struct cpuidle_device *dev) { }
168 170
169static inline void cpuidle_pause_and_lock(void) { } 171static inline void cpuidle_pause_and_lock(void) { }
170static inline void cpuidle_resume_and_unlock(void) { } 172static inline void cpuidle_resume_and_unlock(void) { }
173static inline void cpuidle_pause(void) { }
174static inline void cpuidle_resume(void) { }
171static inline int cpuidle_enable_device(struct cpuidle_device *dev) 175static inline int cpuidle_enable_device(struct cpuidle_device *dev)
172{return -ENODEV; } 176{return -ENODEV; }
173static inline void cpuidle_disable_device(struct cpuidle_device *dev) { } 177static inline void cpuidle_disable_device(struct cpuidle_device *dev) { }