aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRafael J. Wysocki <rafael.j.wysocki@intel.com>2015-02-12 17:33:15 -0500
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>2015-02-13 17:49:36 -0500
commit3810631332465d967ba5e27ea2c7dff2c9afac6c (patch)
tree9000632cbf95f7d3f1e03640195708b3146c64dc
parent18320f2a6871aaf2522f793fee4a67eccf5e131a (diff)
PM / sleep: Re-implement suspend-to-idle handling
In preparation for adding support for quiescing timers in the final stage of suspend-to-idle transitions, rework the freeze_enter() function making the system wait on a wakeup event, the freeze_wake() function terminating the suspend-to-idle loop and the mechanism by which deep idle states are entered during suspend-to-idle. First of all, introduce a simple state machine for suspend-to-idle and make the code in question use it. Second, prevent freeze_enter() from losing wakeup events due to race conditions and ensure that the number of online CPUs won't change while it is being executed. In addition to that, make it force all of the CPUs re-enter the idle loop in case they are in idle states already (so they can enter deeper idle states if possible). Next, drop cpuidle_use_deepest_state() and replace use_deepest_state checks in cpuidle_select() and cpuidle_reflect() with a single suspend-to-idle state check in cpuidle_idle_call(). Finally, introduce cpuidle_enter_freeze() that will simply find the deepest idle state available to the given CPU and enter it using cpuidle_enter(). Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com> Acked-by: Peter Zijlstra (Intel) <peterz@infradead.org>
-rw-r--r--drivers/cpuidle/cpuidle.c49
-rw-r--r--include/linux/cpuidle.h4
-rw-r--r--include/linux/suspend.h16
-rw-r--r--kernel/power/suspend.c43
-rw-r--r--kernel/sched/idle.c16
5 files changed, 96 insertions, 32 deletions
diff --git a/drivers/cpuidle/cpuidle.c b/drivers/cpuidle/cpuidle.c
index 125150dc6e81..23a8d6cc8d30 100644
--- a/drivers/cpuidle/cpuidle.c
+++ b/drivers/cpuidle/cpuidle.c
@@ -19,6 +19,7 @@
19#include <linux/ktime.h> 19#include <linux/ktime.h>
20#include <linux/hrtimer.h> 20#include <linux/hrtimer.h>
21#include <linux/module.h> 21#include <linux/module.h>
22#include <linux/suspend.h>
22#include <trace/events/power.h> 23#include <trace/events/power.h>
23 24
24#include "cpuidle.h" 25#include "cpuidle.h"
@@ -32,7 +33,6 @@ LIST_HEAD(cpuidle_detected_devices);
32static int enabled_devices; 33static int enabled_devices;
33static int off __read_mostly; 34static int off __read_mostly;
34static int initialized __read_mostly; 35static int initialized __read_mostly;
35static bool use_deepest_state __read_mostly;
36 36
37int cpuidle_disabled(void) 37int cpuidle_disabled(void)
38{ 38{
@@ -66,24 +66,9 @@ int cpuidle_play_dead(void)
66} 66}
67 67
68/** 68/**
69 * cpuidle_use_deepest_state - Enable/disable the "deepest idle" mode. 69 * cpuidle_find_deepest_state - Find deepest state meeting specific conditions.
70 * @enable: Whether enable or disable the feature. 70 * @drv: cpuidle driver for the given CPU.
71 * 71 * @dev: cpuidle device for the given CPU.
72 * If the "deepest idle" mode is enabled, cpuidle will ignore the governor and
73 * always use the state with the greatest exit latency (out of the states that
74 * are not disabled).
75 *
76 * This function can only be called after cpuidle_pause() to avoid races.
77 */
78void cpuidle_use_deepest_state(bool enable)
79{
80 use_deepest_state = enable;
81}
82
83/**
84 * cpuidle_find_deepest_state - Find the state of the greatest exit latency.
85 * @drv: cpuidle driver for a given CPU.
86 * @dev: cpuidle device for a given CPU.
87 */ 72 */
88static int cpuidle_find_deepest_state(struct cpuidle_driver *drv, 73static int cpuidle_find_deepest_state(struct cpuidle_driver *drv,
89 struct cpuidle_device *dev) 74 struct cpuidle_device *dev)
@@ -105,6 +90,27 @@ static int cpuidle_find_deepest_state(struct cpuidle_driver *drv,
105} 90}
106 91
107/** 92/**
93 * cpuidle_enter_freeze - Enter an idle state suitable for suspend-to-idle.
94 *
95 * Find the deepest state available and enter it.
96 */
97void cpuidle_enter_freeze(void)
98{
99 struct cpuidle_device *dev = __this_cpu_read(cpuidle_devices);
100 struct cpuidle_driver *drv = cpuidle_get_cpu_driver(dev);
101 int index;
102
103 index = cpuidle_find_deepest_state(drv, dev);
104 if (index >= 0)
105 cpuidle_enter(drv, dev, index);
106 else
107 arch_cpu_idle();
108
109 /* Interrupts are enabled again here. */
110 local_irq_disable();
111}
112
113/**
108 * cpuidle_enter_state - enter the state and update stats 114 * cpuidle_enter_state - enter the state and update stats
109 * @dev: cpuidle device for this cpu 115 * @dev: cpuidle device for this cpu
110 * @drv: cpuidle driver for this cpu 116 * @drv: cpuidle driver for this cpu
@@ -166,9 +172,6 @@ int cpuidle_select(struct cpuidle_driver *drv, struct cpuidle_device *dev)
166 if (!drv || !dev || !dev->enabled) 172 if (!drv || !dev || !dev->enabled)
167 return -EBUSY; 173 return -EBUSY;
168 174
169 if (unlikely(use_deepest_state))
170 return cpuidle_find_deepest_state(drv, dev);
171
172 return cpuidle_curr_governor->select(drv, dev); 175 return cpuidle_curr_governor->select(drv, dev);
173} 176}
174 177
@@ -200,7 +203,7 @@ int cpuidle_enter(struct cpuidle_driver *drv, struct cpuidle_device *dev,
200 */ 203 */
201void cpuidle_reflect(struct cpuidle_device *dev, int index) 204void cpuidle_reflect(struct cpuidle_device *dev, int index)
202{ 205{
203 if (cpuidle_curr_governor->reflect && !unlikely(use_deepest_state)) 206 if (cpuidle_curr_governor->reflect)
204 cpuidle_curr_governor->reflect(dev, index); 207 cpuidle_curr_governor->reflect(dev, index);
205} 208}
206 209
diff --git a/include/linux/cpuidle.h b/include/linux/cpuidle.h
index ab70f3bc44ad..f63aabf4ee90 100644
--- a/include/linux/cpuidle.h
+++ b/include/linux/cpuidle.h
@@ -141,7 +141,7 @@ extern void cpuidle_resume(void);
141extern int cpuidle_enable_device(struct cpuidle_device *dev); 141extern int cpuidle_enable_device(struct cpuidle_device *dev);
142extern void cpuidle_disable_device(struct cpuidle_device *dev); 142extern void cpuidle_disable_device(struct cpuidle_device *dev);
143extern int cpuidle_play_dead(void); 143extern int cpuidle_play_dead(void);
144extern void cpuidle_use_deepest_state(bool enable); 144extern void cpuidle_enter_freeze(void);
145 145
146extern struct cpuidle_driver *cpuidle_get_cpu_driver(struct cpuidle_device *dev); 146extern struct cpuidle_driver *cpuidle_get_cpu_driver(struct cpuidle_device *dev);
147#else 147#else
@@ -174,7 +174,7 @@ static inline int cpuidle_enable_device(struct cpuidle_device *dev)
174{return -ENODEV; } 174{return -ENODEV; }
175static inline void cpuidle_disable_device(struct cpuidle_device *dev) { } 175static inline void cpuidle_disable_device(struct cpuidle_device *dev) { }
176static inline int cpuidle_play_dead(void) {return -ENODEV; } 176static inline int cpuidle_play_dead(void) {return -ENODEV; }
177static inline void cpuidle_use_deepest_state(bool enable) {} 177static inline void cpuidle_enter_freeze(void) { }
178static inline struct cpuidle_driver *cpuidle_get_cpu_driver( 178static inline struct cpuidle_driver *cpuidle_get_cpu_driver(
179 struct cpuidle_device *dev) {return NULL; } 179 struct cpuidle_device *dev) {return NULL; }
180#endif 180#endif
diff --git a/include/linux/suspend.h b/include/linux/suspend.h
index 3388c1b6f7d8..5efe743ce1e8 100644
--- a/include/linux/suspend.h
+++ b/include/linux/suspend.h
@@ -201,6 +201,21 @@ struct platform_freeze_ops {
201 */ 201 */
202extern void suspend_set_ops(const struct platform_suspend_ops *ops); 202extern void suspend_set_ops(const struct platform_suspend_ops *ops);
203extern int suspend_valid_only_mem(suspend_state_t state); 203extern int suspend_valid_only_mem(suspend_state_t state);
204
205/* Suspend-to-idle state machnine. */
206enum freeze_state {
207 FREEZE_STATE_NONE, /* Not suspended/suspending. */
208 FREEZE_STATE_ENTER, /* Enter suspend-to-idle. */
209 FREEZE_STATE_WAKE, /* Wake up from suspend-to-idle. */
210};
211
212extern enum freeze_state __read_mostly suspend_freeze_state;
213
214static inline bool idle_should_freeze(void)
215{
216 return unlikely(suspend_freeze_state == FREEZE_STATE_ENTER);
217}
218
204extern void freeze_set_ops(const struct platform_freeze_ops *ops); 219extern void freeze_set_ops(const struct platform_freeze_ops *ops);
205extern void freeze_wake(void); 220extern void freeze_wake(void);
206 221
@@ -228,6 +243,7 @@ extern int pm_suspend(suspend_state_t state);
228 243
229static inline void suspend_set_ops(const struct platform_suspend_ops *ops) {} 244static inline void suspend_set_ops(const struct platform_suspend_ops *ops) {}
230static inline int pm_suspend(suspend_state_t state) { return -ENOSYS; } 245static inline int pm_suspend(suspend_state_t state) { return -ENOSYS; }
246static inline bool idle_should_freeze(void) { return false; }
231static inline void freeze_set_ops(const struct platform_freeze_ops *ops) {} 247static inline void freeze_set_ops(const struct platform_freeze_ops *ops) {}
232static inline void freeze_wake(void) {} 248static inline void freeze_wake(void) {}
233#endif /* !CONFIG_SUSPEND */ 249#endif /* !CONFIG_SUSPEND */
diff --git a/kernel/power/suspend.c b/kernel/power/suspend.c
index c347e3ce3a55..b7d6b3a721b1 100644
--- a/kernel/power/suspend.c
+++ b/kernel/power/suspend.c
@@ -37,7 +37,9 @@ const char *pm_states[PM_SUSPEND_MAX];
37static const struct platform_suspend_ops *suspend_ops; 37static const struct platform_suspend_ops *suspend_ops;
38static const struct platform_freeze_ops *freeze_ops; 38static const struct platform_freeze_ops *freeze_ops;
39static DECLARE_WAIT_QUEUE_HEAD(suspend_freeze_wait_head); 39static DECLARE_WAIT_QUEUE_HEAD(suspend_freeze_wait_head);
40static bool suspend_freeze_wake; 40
41enum freeze_state __read_mostly suspend_freeze_state;
42static DEFINE_SPINLOCK(suspend_freeze_lock);
41 43
42void freeze_set_ops(const struct platform_freeze_ops *ops) 44void freeze_set_ops(const struct platform_freeze_ops *ops)
43{ 45{
@@ -48,22 +50,49 @@ void freeze_set_ops(const struct platform_freeze_ops *ops)
48 50
49static void freeze_begin(void) 51static void freeze_begin(void)
50{ 52{
51 suspend_freeze_wake = false; 53 suspend_freeze_state = FREEZE_STATE_NONE;
52} 54}
53 55
54static void freeze_enter(void) 56static void freeze_enter(void)
55{ 57{
56 cpuidle_use_deepest_state(true); 58 spin_lock_irq(&suspend_freeze_lock);
59 if (pm_wakeup_pending())
60 goto out;
61
62 suspend_freeze_state = FREEZE_STATE_ENTER;
63 spin_unlock_irq(&suspend_freeze_lock);
64
65 get_online_cpus();
57 cpuidle_resume(); 66 cpuidle_resume();
58 wait_event(suspend_freeze_wait_head, suspend_freeze_wake); 67
68 /* Push all the CPUs into the idle loop. */
69 wake_up_all_idle_cpus();
70 pr_debug("PM: suspend-to-idle\n");
71 /* Make the current CPU wait so it can enter the idle loop too. */
72 wait_event(suspend_freeze_wait_head,
73 suspend_freeze_state == FREEZE_STATE_WAKE);
74 pr_debug("PM: resume from suspend-to-idle\n");
75
59 cpuidle_pause(); 76 cpuidle_pause();
60 cpuidle_use_deepest_state(false); 77 put_online_cpus();
78
79 spin_lock_irq(&suspend_freeze_lock);
80
81 out:
82 suspend_freeze_state = FREEZE_STATE_NONE;
83 spin_unlock_irq(&suspend_freeze_lock);
61} 84}
62 85
63void freeze_wake(void) 86void freeze_wake(void)
64{ 87{
65 suspend_freeze_wake = true; 88 unsigned long flags;
66 wake_up(&suspend_freeze_wait_head); 89
90 spin_lock_irqsave(&suspend_freeze_lock, flags);
91 if (suspend_freeze_state > FREEZE_STATE_NONE) {
92 suspend_freeze_state = FREEZE_STATE_WAKE;
93 wake_up(&suspend_freeze_wait_head);
94 }
95 spin_unlock_irqrestore(&suspend_freeze_lock, flags);
67} 96}
68EXPORT_SYMBOL_GPL(freeze_wake); 97EXPORT_SYMBOL_GPL(freeze_wake);
69 98
diff --git a/kernel/sched/idle.c b/kernel/sched/idle.c
index aaf1c1d5cf5d..94b2d7b88a27 100644
--- a/kernel/sched/idle.c
+++ b/kernel/sched/idle.c
@@ -7,6 +7,7 @@
7#include <linux/tick.h> 7#include <linux/tick.h>
8#include <linux/mm.h> 8#include <linux/mm.h>
9#include <linux/stackprotector.h> 9#include <linux/stackprotector.h>
10#include <linux/suspend.h>
10 11
11#include <asm/tlb.h> 12#include <asm/tlb.h>
12 13
@@ -105,6 +106,21 @@ static void cpuidle_idle_call(void)
105 rcu_idle_enter(); 106 rcu_idle_enter();
106 107
107 /* 108 /*
109 * Suspend-to-idle ("freeze") is a system state in which all user space
110 * has been frozen, all I/O devices have been suspended and the only
111 * activity happens here and in iterrupts (if any). In that case bypass
112 * the cpuidle governor and go stratight for the deepest idle state
113 * available. Possibly also suspend the local tick and the entire
114 * timekeeping to prevent timer interrupts from kicking us out of idle
115 * until a proper wakeup interrupt happens.
116 */
117 if (idle_should_freeze()) {
118 cpuidle_enter_freeze();
119 local_irq_enable();
120 goto exit_idle;
121 }
122
123 /*
108 * Ask the cpuidle framework to choose a convenient idle state. 124 * Ask the cpuidle framework to choose a convenient idle state.
109 * Fall back to the default arch idle method on errors. 125 * Fall back to the default arch idle method on errors.
110 */ 126 */