Blame view

kernel/power/suspend.c 7.38 KB
a9d705236   Rafael J. Wysocki   PM: Separate susp...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  /*
   * kernel/power/suspend.c - Suspend to RAM and standby functionality.
   *
   * Copyright (c) 2003 Patrick Mochel
   * Copyright (c) 2003 Open Source Development Lab
   * Copyright (c) 2009 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc.
   *
   * This file is released under the GPLv2.
   */
  
  #include <linux/string.h>
  #include <linux/delay.h>
  #include <linux/errno.h>
  #include <linux/init.h>
74da1ff71   Paul Gortmaker   kernel: fix sever...
15
  #include <linux/kmod.h>
a9d705236   Rafael J. Wysocki   PM: Separate susp...
16
17
18
  #include <linux/console.h>
  #include <linux/cpu.h>
  #include <linux/syscalls.h>
5a0e3ad6a   Tejun Heo   include cleanup: ...
19
  #include <linux/gfp.h>
dd4c4f17d   Matthew Garrett   suspend: Move NVS...
20
21
22
23
24
  #include <linux/io.h>
  #include <linux/kernel.h>
  #include <linux/list.h>
  #include <linux/mm.h>
  #include <linux/slab.h>
6e5fdeedc   Paul Gortmaker   kernel: Fix files...
25
  #include <linux/export.h>
dd4c4f17d   Matthew Garrett   suspend: Move NVS...
26
  #include <linux/suspend.h>
40dc166cb   Rafael J. Wysocki   PM / Core: Introd...
27
  #include <linux/syscore_ops.h>
938cfed18   Jean Pihet   perf: Add calls t...
28
  #include <trace/events/power.h>
a9d705236   Rafael J. Wysocki   PM: Separate susp...
29
30
31
32
33
34
35
  
  #include "power.h"
  
  const char *const pm_states[PM_SUSPEND_MAX] = {
  	[PM_SUSPEND_STANDBY]	= "standby",
  	[PM_SUSPEND_MEM]	= "mem",
  };
2f55ac072   Lionel Debroux   suspend: constify...
36
  static const struct platform_suspend_ops *suspend_ops;
a9d705236   Rafael J. Wysocki   PM: Separate susp...
37
38
39
40
41
  
  /**
   *	suspend_set_ops - Set the global suspend method table.
   *	@ops:	Pointer to ops structure.
   */
2f55ac072   Lionel Debroux   suspend: constify...
42
  void suspend_set_ops(const struct platform_suspend_ops *ops)
a9d705236   Rafael J. Wysocki   PM: Separate susp...
43
  {
bcda53faf   Srivatsa S. Bhat   PM / Sleep: Repla...
44
  	lock_system_sleep();
a9d705236   Rafael J. Wysocki   PM: Separate susp...
45
  	suspend_ops = ops;
bcda53faf   Srivatsa S. Bhat   PM / Sleep: Repla...
46
  	unlock_system_sleep();
a9d705236   Rafael J. Wysocki   PM: Separate susp...
47
  }
a5e4fd878   Kevin Hilman   PM / Suspend: Exp...
48
  EXPORT_SYMBOL_GPL(suspend_set_ops);
a9d705236   Rafael J. Wysocki   PM: Separate susp...
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
  
  bool valid_state(suspend_state_t state)
  {
  	/*
  	 * All states need lowlevel support and need to be valid to the lowlevel
  	 * implementation, no valid callback implies that none are valid.
  	 */
  	return suspend_ops && suspend_ops->valid && suspend_ops->valid(state);
  }
  
  /**
   * suspend_valid_only_mem - generic memory-only valid callback
   *
   * Platform drivers that implement mem suspend only and only need
   * to check for that in their .valid callback can use this instead
   * of rolling their own .valid callback.
   */
  int suspend_valid_only_mem(suspend_state_t state)
  {
  	return state == PM_SUSPEND_MEM;
  }
a5e4fd878   Kevin Hilman   PM / Suspend: Exp...
70
  EXPORT_SYMBOL_GPL(suspend_valid_only_mem);
a9d705236   Rafael J. Wysocki   PM: Separate susp...
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
  
  static int suspend_test(int level)
  {
  #ifdef CONFIG_PM_DEBUG
  	if (pm_test_level == level) {
  		printk(KERN_INFO "suspend debug: Waiting for 5 seconds.
  ");
  		mdelay(5000);
  		return 1;
  	}
  #endif /* !CONFIG_PM_DEBUG */
  	return 0;
  }
  
  /**
   *	suspend_prepare - Do prep work before entering low-power state.
   *
   *	This is common code that is called for each state that we're entering.
   *	Run suspend notifiers, allocate a console and stop all processes.
   */
  static int suspend_prepare(void)
  {
  	int error;
  
  	if (!suspend_ops || !suspend_ops->enter)
  		return -EPERM;
  
  	pm_prepare_console();
  
  	error = pm_notifier_call_chain(PM_SUSPEND_PREPARE);
  	if (error)
  		goto Finish;
  
  	error = usermodehelper_disable();
  	if (error)
  		goto Finish;
  
  	error = suspend_freeze_processes();
03afed8bc   Tejun Heo   freezer: clean up...
109
  	if (!error)
a9d705236   Rafael J. Wysocki   PM: Separate susp...
110
  		return 0;
03afed8bc   Tejun Heo   freezer: clean up...
111
112
  	suspend_stats.failed_freeze++;
  	dpm_save_failed_step(SUSPEND_FREEZE);
a9d705236   Rafael J. Wysocki   PM: Separate susp...
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
  	usermodehelper_enable();
   Finish:
  	pm_notifier_call_chain(PM_POST_SUSPEND);
  	pm_restore_console();
  	return error;
  }
  
  /* default implementation */
  void __attribute__ ((weak)) arch_suspend_disable_irqs(void)
  {
  	local_irq_disable();
  }
  
  /* default implementation */
  void __attribute__ ((weak)) arch_suspend_enable_irqs(void)
  {
  	local_irq_enable();
  }
  
  /**
3b5fe8525   MyungJoo Ham   PM / Suspend: Add...
133
134
135
   * suspend_enter - enter the desired system sleep state.
   * @state: State to enter
   * @wakeup: Returns information that suspend should not be entered again.
a9d705236   Rafael J. Wysocki   PM: Separate susp...
136
   *
3b5fe8525   MyungJoo Ham   PM / Suspend: Add...
137
   * This function should be called after devices have been suspended.
a9d705236   Rafael J. Wysocki   PM: Separate susp...
138
   */
3b5fe8525   MyungJoo Ham   PM / Suspend: Add...
139
  static int suspend_enter(suspend_state_t state, bool *wakeup)
a9d705236   Rafael J. Wysocki   PM: Separate susp...
140
141
142
143
144
145
  {
  	int error;
  
  	if (suspend_ops->prepare) {
  		error = suspend_ops->prepare();
  		if (error)
ce4410116   Rafael J. Wysocki   PM / Suspend: Fix...
146
  			goto Platform_finish;
a9d705236   Rafael J. Wysocki   PM: Separate susp...
147
148
149
150
151
152
  	}
  
  	error = dpm_suspend_noirq(PMSG_SUSPEND);
  	if (error) {
  		printk(KERN_ERR "PM: Some devices failed to power down
  ");
ce4410116   Rafael J. Wysocki   PM / Suspend: Fix...
153
  		goto Platform_finish;
a9d705236   Rafael J. Wysocki   PM: Separate susp...
154
155
156
157
158
  	}
  
  	if (suspend_ops->prepare_late) {
  		error = suspend_ops->prepare_late();
  		if (error)
ce4410116   Rafael J. Wysocki   PM / Suspend: Fix...
159
  			goto Platform_wake;
a9d705236   Rafael J. Wysocki   PM: Separate susp...
160
161
162
163
164
165
166
167
168
169
170
  	}
  
  	if (suspend_test(TEST_PLATFORM))
  		goto Platform_wake;
  
  	error = disable_nonboot_cpus();
  	if (error || suspend_test(TEST_CPUS))
  		goto Enable_cpus;
  
  	arch_suspend_disable_irqs();
  	BUG_ON(!irqs_disabled());
2e711c04d   Rafael J. Wysocki   PM: Remove sysdev...
171
  	error = syscore_suspend();
a9d705236   Rafael J. Wysocki   PM: Separate susp...
172
  	if (!error) {
3b5fe8525   MyungJoo Ham   PM / Suspend: Add...
173
174
  		*wakeup = pm_wakeup_pending();
  		if (!(suspend_test(TEST_CORE) || *wakeup)) {
a9d705236   Rafael J. Wysocki   PM: Separate susp...
175
  			error = suspend_ops->enter(state);
c125e96f0   Rafael J. Wysocki   PM: Make it possi...
176
177
  			events_check_enabled = false;
  		}
40dc166cb   Rafael J. Wysocki   PM / Core: Introd...
178
  		syscore_resume();
a9d705236   Rafael J. Wysocki   PM: Separate susp...
179
180
181
182
183
184
185
186
187
188
189
  	}
  
  	arch_suspend_enable_irqs();
  	BUG_ON(irqs_disabled());
  
   Enable_cpus:
  	enable_nonboot_cpus();
  
   Platform_wake:
  	if (suspend_ops->wake)
  		suspend_ops->wake();
a9d705236   Rafael J. Wysocki   PM: Separate susp...
190
  	dpm_resume_noirq(PMSG_RESUME);
ce4410116   Rafael J. Wysocki   PM / Suspend: Fix...
191
   Platform_finish:
a9d705236   Rafael J. Wysocki   PM: Separate susp...
192
193
194
195
196
197
198
199
200
201
202
203
204
205
  	if (suspend_ops->finish)
  		suspend_ops->finish();
  
  	return error;
  }
  
  /**
   *	suspend_devices_and_enter - suspend devices and enter the desired system
   *				    sleep state.
   *	@state:		  state to enter
   */
  int suspend_devices_and_enter(suspend_state_t state)
  {
  	int error;
3b5fe8525   MyungJoo Ham   PM / Suspend: Add...
206
  	bool wakeup = false;
a9d705236   Rafael J. Wysocki   PM: Separate susp...
207
208
209
  
  	if (!suspend_ops)
  		return -ENOSYS;
938cfed18   Jean Pihet   perf: Add calls t...
210
  	trace_machine_suspend(state);
a9d705236   Rafael J. Wysocki   PM: Separate susp...
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
  	if (suspend_ops->begin) {
  		error = suspend_ops->begin(state);
  		if (error)
  			goto Close;
  	}
  	suspend_console();
  	suspend_test_start();
  	error = dpm_suspend_start(PMSG_SUSPEND);
  	if (error) {
  		printk(KERN_ERR "PM: Some devices failed to suspend
  ");
  		goto Recover_platform;
  	}
  	suspend_test_finish("suspend devices");
  	if (suspend_test(TEST_DEVICES))
  		goto Recover_platform;
3b5fe8525   MyungJoo Ham   PM / Suspend: Add...
227
228
229
230
  	do {
  		error = suspend_enter(state, &wakeup);
  	} while (!error && !wakeup
  		&& suspend_ops->suspend_again && suspend_ops->suspend_again());
a9d705236   Rafael J. Wysocki   PM: Separate susp...
231
232
233
234
235
236
237
238
239
  
   Resume_devices:
  	suspend_test_start();
  	dpm_resume_end(PMSG_RESUME);
  	suspend_test_finish("resume devices");
  	resume_console();
   Close:
  	if (suspend_ops->end)
  		suspend_ops->end();
938cfed18   Jean Pihet   perf: Add calls t...
240
  	trace_machine_suspend(PWR_EVENT_EXIT);
a9d705236   Rafael J. Wysocki   PM: Separate susp...
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
  	return error;
  
   Recover_platform:
  	if (suspend_ops->recover)
  		suspend_ops->recover();
  	goto Resume_devices;
  }
  
  /**
   *	suspend_finish - Do final work before exiting suspend sequence.
   *
   *	Call platform code to clean up, restart processes, and free the
   *	console that we've allocated. This is not called for suspend-to-disk.
   */
  static void suspend_finish(void)
  {
  	suspend_thaw_processes();
  	usermodehelper_enable();
  	pm_notifier_call_chain(PM_POST_SUSPEND);
  	pm_restore_console();
  }
  
  /**
   *	enter_state - Do common work of entering low-power state.
   *	@state:		pm_state structure for state we're entering.
   *
   *	Make sure we're the only ones trying to enter a sleep state. Fail
   *	if someone has beat us to it, since we don't want anything weird to
   *	happen when we wake up.
   *	Then, do the setup for suspend, enter the state, and cleaup (after
   *	we've woken up).
   */
  int enter_state(suspend_state_t state)
  {
  	int error;
  
  	if (!valid_state(state))
  		return -ENODEV;
  
  	if (!mutex_trylock(&pm_mutex))
  		return -EBUSY;
  
  	printk(KERN_INFO "PM: Syncing filesystems ... ");
  	sys_sync();
  	printk("done.
  ");
  
  	pr_debug("PM: Preparing system for %s sleep
  ", pm_states[state]);
  	error = suspend_prepare();
  	if (error)
  		goto Unlock;
  
  	if (suspend_test(TEST_FREEZER))
  		goto Finish;
  
  	pr_debug("PM: Entering %s sleep
  ", pm_states[state]);
87186475a   Rafael J. Wysocki   PM: Fix warning i...
299
  	pm_restrict_gfp_mask();
a9d705236   Rafael J. Wysocki   PM: Separate susp...
300
  	error = suspend_devices_and_enter(state);
87186475a   Rafael J. Wysocki   PM: Fix warning i...
301
  	pm_restore_gfp_mask();
a9d705236   Rafael J. Wysocki   PM: Separate susp...
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
  
   Finish:
  	pr_debug("PM: Finishing wakeup.
  ");
  	suspend_finish();
   Unlock:
  	mutex_unlock(&pm_mutex);
  	return error;
  }
  
  /**
   *	pm_suspend - Externally visible function for suspending system.
   *	@state:		Enumerated value of state to enter.
   *
   *	Determine whether or not value is within range, get state
   *	structure, and enter (above).
   */
  int pm_suspend(suspend_state_t state)
  {
2a77c46de   ShuoX Liu   PM / Suspend: Add...
321
  	int ret;
528f7ce6e   Dan Carpenter   PM / Suspend: Off...
322
  	if (state > PM_SUSPEND_ON && state < PM_SUSPEND_MAX) {
2a77c46de   ShuoX Liu   PM / Suspend: Add...
323
324
325
326
327
328
329
330
  		ret = enter_state(state);
  		if (ret) {
  			suspend_stats.fail++;
  			dpm_save_failed_errno(ret);
  		} else
  			suspend_stats.success++;
  		return ret;
  	}
a9d705236   Rafael J. Wysocki   PM: Separate susp...
331
332
333
  	return -EINVAL;
  }
  EXPORT_SYMBOL(pm_suspend);