Skip to content

Commit 3810631

Browse files
committed
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 <[email protected]> Acked-by: Peter Zijlstra (Intel) <[email protected]>
1 parent 18320f2 commit 3810631

File tree

5 files changed

+96
-32
lines changed

5 files changed

+96
-32
lines changed

drivers/cpuidle/cpuidle.c

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <linux/ktime.h>
2020
#include <linux/hrtimer.h>
2121
#include <linux/module.h>
22+
#include <linux/suspend.h>
2223
#include <trace/events/power.h>
2324

2425
#include "cpuidle.h"
@@ -32,7 +33,6 @@ LIST_HEAD(cpuidle_detected_devices);
3233
static int enabled_devices;
3334
static int off __read_mostly;
3435
static int initialized __read_mostly;
35-
static bool use_deepest_state __read_mostly;
3636

3737
int cpuidle_disabled(void)
3838
{
@@ -66,24 +66,9 @@ int cpuidle_play_dead(void)
6666
}
6767

6868
/**
69-
* cpuidle_use_deepest_state - Enable/disable the "deepest idle" mode.
70-
* @enable: Whether enable or disable the feature.
71-
*
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-
*/
78-
void 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.
69+
* cpuidle_find_deepest_state - Find deepest state meeting specific conditions.
70+
* @drv: cpuidle driver for the given CPU.
71+
* @dev: cpuidle device for the given CPU.
8772
*/
8873
static int cpuidle_find_deepest_state(struct cpuidle_driver *drv,
8974
struct cpuidle_device *dev)
@@ -104,6 +89,27 @@ static int cpuidle_find_deepest_state(struct cpuidle_driver *drv,
10489
return ret;
10590
}
10691

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+
*/
97+
void 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+
107113
/**
108114
* cpuidle_enter_state - enter the state and update stats
109115
* @dev: cpuidle device for this cpu
@@ -166,9 +172,6 @@ int cpuidle_select(struct cpuidle_driver *drv, struct cpuidle_device *dev)
166172
if (!drv || !dev || !dev->enabled)
167173
return -EBUSY;
168174

169-
if (unlikely(use_deepest_state))
170-
return cpuidle_find_deepest_state(drv, dev);
171-
172175
return cpuidle_curr_governor->select(drv, dev);
173176
}
174177

@@ -200,7 +203,7 @@ int cpuidle_enter(struct cpuidle_driver *drv, struct cpuidle_device *dev,
200203
*/
201204
void cpuidle_reflect(struct cpuidle_device *dev, int index)
202205
{
203-
if (cpuidle_curr_governor->reflect && !unlikely(use_deepest_state))
206+
if (cpuidle_curr_governor->reflect)
204207
cpuidle_curr_governor->reflect(dev, index);
205208
}
206209

include/linux/cpuidle.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ extern void cpuidle_resume(void);
141141
extern int cpuidle_enable_device(struct cpuidle_device *dev);
142142
extern void cpuidle_disable_device(struct cpuidle_device *dev);
143143
extern int cpuidle_play_dead(void);
144-
extern void cpuidle_use_deepest_state(bool enable);
144+
extern void cpuidle_enter_freeze(void);
145145

146146
extern struct cpuidle_driver *cpuidle_get_cpu_driver(struct cpuidle_device *dev);
147147
#else
@@ -174,7 +174,7 @@ static inline int cpuidle_enable_device(struct cpuidle_device *dev)
174174
{return -ENODEV; }
175175
static inline void cpuidle_disable_device(struct cpuidle_device *dev) { }
176176
static inline int cpuidle_play_dead(void) {return -ENODEV; }
177-
static inline void cpuidle_use_deepest_state(bool enable) {}
177+
static inline void cpuidle_enter_freeze(void) { }
178178
static inline struct cpuidle_driver *cpuidle_get_cpu_driver(
179179
struct cpuidle_device *dev) {return NULL; }
180180
#endif

include/linux/suspend.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,21 @@ struct platform_freeze_ops {
201201
*/
202202
extern void suspend_set_ops(const struct platform_suspend_ops *ops);
203203
extern int suspend_valid_only_mem(suspend_state_t state);
204+
205+
/* Suspend-to-idle state machnine. */
206+
enum 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+
212+
extern enum freeze_state __read_mostly suspend_freeze_state;
213+
214+
static inline bool idle_should_freeze(void)
215+
{
216+
return unlikely(suspend_freeze_state == FREEZE_STATE_ENTER);
217+
}
218+
204219
extern void freeze_set_ops(const struct platform_freeze_ops *ops);
205220
extern void freeze_wake(void);
206221

@@ -228,6 +243,7 @@ extern int pm_suspend(suspend_state_t state);
228243

229244
static inline void suspend_set_ops(const struct platform_suspend_ops *ops) {}
230245
static inline int pm_suspend(suspend_state_t state) { return -ENOSYS; }
246+
static inline bool idle_should_freeze(void) { return false; }
231247
static inline void freeze_set_ops(const struct platform_freeze_ops *ops) {}
232248
static inline void freeze_wake(void) {}
233249
#endif /* !CONFIG_SUSPEND */

kernel/power/suspend.c

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ const char *pm_states[PM_SUSPEND_MAX];
3737
static const struct platform_suspend_ops *suspend_ops;
3838
static const struct platform_freeze_ops *freeze_ops;
3939
static DECLARE_WAIT_QUEUE_HEAD(suspend_freeze_wait_head);
40-
static bool suspend_freeze_wake;
40+
41+
enum freeze_state __read_mostly suspend_freeze_state;
42+
static DEFINE_SPINLOCK(suspend_freeze_lock);
4143

4244
void freeze_set_ops(const struct platform_freeze_ops *ops)
4345
{
@@ -48,22 +50,49 @@ void freeze_set_ops(const struct platform_freeze_ops *ops)
4850

4951
static void freeze_begin(void)
5052
{
51-
suspend_freeze_wake = false;
53+
suspend_freeze_state = FREEZE_STATE_NONE;
5254
}
5355

5456
static void freeze_enter(void)
5557
{
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();
5766
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+
5976
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);
6184
}
6285

6386
void freeze_wake(void)
6487
{
65-
suspend_freeze_wake = true;
66-
wake_up(&suspend_freeze_wait_head);
88+
unsigned long flags;
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);
6796
}
6897
EXPORT_SYMBOL_GPL(freeze_wake);
6998

kernel/sched/idle.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <linux/tick.h>
88
#include <linux/mm.h>
99
#include <linux/stackprotector.h>
10+
#include <linux/suspend.h>
1011

1112
#include <asm/tlb.h>
1213

@@ -104,6 +105,21 @@ static void cpuidle_idle_call(void)
104105
*/
105106
rcu_idle_enter();
106107

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+
107123
/*
108124
* Ask the cpuidle framework to choose a convenient idle state.
109125
* Fall back to the default arch idle method on errors.

0 commit comments

Comments
 (0)