main.c 7.75 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1
2
3
4
5
6
7
8
9
10
/*
 * kernel/power/main.c - PM subsystem core functionality.
 *
 * Copyright (c) 2003 Patrick Mochel
 * Copyright (c) 2003 Open Source Development Lab
 * 
 * This file is released under the GPLv2
 *
 */

11
#include <linux/module.h>
Linus Torvalds's avatar
Linus Torvalds committed
12
13
14
15
16
17
18
#include <linux/suspend.h>
#include <linux/kobject.h>
#include <linux/string.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/pm.h>
19
#include <linux/console.h>
20
#include <linux/cpu.h>
21
#include <linux/resume-trace.h>
22
#include <linux/freezer.h>
Christoph Lameter's avatar
Christoph Lameter committed
23
#include <linux/vmstat.h>
Linus Torvalds's avatar
Linus Torvalds committed
24
25
26

#include "power.h"

David Shaohua Li's avatar
David Shaohua Li committed
27
28
29
/*This is just an arbitrary number */
#define FREE_PAGE_NUMBER (100)

30
DEFINE_MUTEX(pm_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
31

32
struct pm_ops *pm_ops;
33
suspend_disk_method_t pm_disk_mode = PM_DISK_SHUTDOWN;
Linus Torvalds's avatar
Linus Torvalds committed
34
35
36
37
38
39
40
41

/**
 *	pm_set_ops - Set the global power method table. 
 *	@ops:	Pointer to ops structure.
 */

void pm_set_ops(struct pm_ops * ops)
{
42
	mutex_lock(&pm_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
43
	pm_ops = ops;
44
45
46
47
	if (ops && ops->pm_disk_mode != PM_DISK_INVALID) {
		pm_disk_mode = ops->pm_disk_mode;
	} else
		pm_disk_mode = PM_DISK_SHUTDOWN;
48
	mutex_unlock(&pm_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
49
50
}

51
52
53
54
55
56
57
58
59
60
61
62
63
/**
 * pm_valid_only_mem - generic memory-only valid callback
 *
 * pm_ops 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 pm_valid_only_mem(suspend_state_t state)
{
	return state == PM_SUSPEND_MEM;
}


64
65
66
67
68
static inline void pm_finish(suspend_state_t state)
{
	if (pm_ops->finish)
		pm_ops->finish(state);
}
Linus Torvalds's avatar
Linus Torvalds committed
69
70
71
72
73
74
75
76
77
78
79
80

/**
 *	suspend_prepare - Do prep work before entering low-power state.
 *	@state:		State we're entering.
 *
 *	This is common code that is called for each state that we're 
 *	entering. Allocate a console, stop all processes, then make sure
 *	the platform can enter the requested state.
 */

static int suspend_prepare(suspend_state_t state)
{
81
	int error;
David Shaohua Li's avatar
David Shaohua Li committed
82
	unsigned int free_pages;
Linus Torvalds's avatar
Linus Torvalds committed
83
84
85
86
87
88
89
90
91
92
93

	if (!pm_ops || !pm_ops->enter)
		return -EPERM;

	pm_prepare_console();

	if (freeze_processes()) {
		error = -EAGAIN;
		goto Thaw;
	}

Christoph Lameter's avatar
Christoph Lameter committed
94
95
	if ((free_pages = global_page_state(NR_FREE_PAGES))
			< FREE_PAGE_NUMBER) {
David Shaohua Li's avatar
David Shaohua Li committed
96
97
98
99
100
101
102
103
104
		pr_debug("PM: free some memory\n");
		shrink_all_memory(FREE_PAGE_NUMBER - free_pages);
		if (nr_free_pages() < FREE_PAGE_NUMBER) {
			error = -ENOMEM;
			printk(KERN_ERR "PM: No enough memory\n");
			goto Thaw;
		}
	}

Linus Torvalds's avatar
Linus Torvalds committed
105
106
107
108
109
	if (pm_ops->prepare) {
		if ((error = pm_ops->prepare(state)))
			goto Thaw;
	}

110
	suspend_console();
111
112
	error = device_suspend(PMSG_SUSPEND);
	if (error) {
Linus Torvalds's avatar
Linus Torvalds committed
113
		printk(KERN_ERR "Some devices failed to suspend\n");
114
		goto Resume_devices;
Linus Torvalds's avatar
Linus Torvalds committed
115
	}
116
117
118
119
120
121
122
123
124
	error = disable_nonboot_cpus();
	if (!error)
		return 0;

	enable_nonboot_cpus();
 Resume_devices:
	pm_finish(state);
	device_resume();
	resume_console();
Linus Torvalds's avatar
Linus Torvalds committed
125
126
127
128
129
130
 Thaw:
	thaw_processes();
	pm_restore_console();
	return error;
}

131
132
133
134
135
136
137
138
139
140
141
/* 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();
}
Linus Torvalds's avatar
Linus Torvalds committed
142

143
int suspend_enter(suspend_state_t state)
Linus Torvalds's avatar
Linus Torvalds committed
144
145
146
{
	int error = 0;

147
148
	arch_suspend_disable_irqs();
	BUG_ON(!irqs_disabled());
Linus Torvalds's avatar
Linus Torvalds committed
149
150
151
152
153
154
155
156

	if ((error = device_power_down(PMSG_SUSPEND))) {
		printk(KERN_ERR "Some devices failed to power down\n");
		goto Done;
	}
	error = pm_ops->enter(state);
	device_power_up();
 Done:
157
158
	arch_suspend_enable_irqs();
	BUG_ON(irqs_disabled());
Linus Torvalds's avatar
Linus Torvalds committed
159
160
161
162
163
164
165
166
167
168
169
170
171
172
	return error;
}


/**
 *	suspend_finish - Do final work before exiting suspend sequence.
 *	@state:		State we're coming out of.
 *
 *	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(suspend_state_t state)
{
173
174
	enable_nonboot_cpus();
	pm_finish(state);
Linus Torvalds's avatar
Linus Torvalds committed
175
	device_resume();
176
	resume_console();
Linus Torvalds's avatar
Linus Torvalds committed
177
178
179
180
181
182
183
	thaw_processes();
	pm_restore_console();
}




184
static const char * const pm_states[PM_SUSPEND_MAX] = {
Linus Torvalds's avatar
Linus Torvalds committed
185
186
	[PM_SUSPEND_STANDBY]	= "standby",
	[PM_SUSPEND_MEM]	= "mem",
187
#ifdef CONFIG_SOFTWARE_SUSPEND
Linus Torvalds's avatar
Linus Torvalds committed
188
	[PM_SUSPEND_DISK]	= "disk",
189
#endif
Linus Torvalds's avatar
Linus Torvalds committed
190
191
};

192
193
194
195
196
197
198
static inline int valid_state(suspend_state_t state)
{
	/* Suspend-to-disk does not really need low-level support.
	 * It can work with reboot if needed. */
	if (state == PM_SUSPEND_DISK)
		return 1;

199
200
	/* all other states need lowlevel support and need to be
	 * valid to the lowlevel implementation, no valid callback
201
202
	 * implies that none are valid. */
	if (!pm_ops || !pm_ops->valid || !pm_ops->valid(state))
203
204
205
206
		return 0;
	return 1;
}

Linus Torvalds's avatar
Linus Torvalds committed
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222

/**
 *	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).
 */

static int enter_state(suspend_state_t state)
{
	int error;

223
	if (!valid_state(state))
224
		return -ENODEV;
225
	if (!mutex_trylock(&pm_mutex))
Linus Torvalds's avatar
Linus Torvalds committed
226
227
228
229
230
231
232
		return -EBUSY;

	if (state == PM_SUSPEND_DISK) {
		error = pm_suspend_disk();
		goto Unlock;
	}

233
	pr_debug("PM: Preparing system for %s sleep\n", pm_states[state]);
Linus Torvalds's avatar
Linus Torvalds committed
234
235
236
	if ((error = suspend_prepare(state)))
		goto Unlock;

237
	pr_debug("PM: Entering %s sleep\n", pm_states[state]);
Linus Torvalds's avatar
Linus Torvalds committed
238
239
	error = suspend_enter(state);

240
	pr_debug("PM: Finishing wakeup.\n");
Linus Torvalds's avatar
Linus Torvalds committed
241
242
	suspend_finish(state);
 Unlock:
243
	mutex_unlock(&pm_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
	return error;
}

/*
 * This is main interface to the outside world. It needs to be
 * called from process context.
 */
int software_suspend(void)
{
	return enter_state(PM_SUSPEND_DISK);
}


/**
 *	pm_suspend - Externally visible function for suspending system.
 *	@state:		Enumarted 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)
{
Alexey Starikovskiy's avatar
Alexey Starikovskiy committed
267
	if (state > PM_SUSPEND_ON && state <= PM_SUSPEND_MAX)
Linus Torvalds's avatar
Linus Torvalds committed
268
269
270
271
		return enter_state(state);
	return -EINVAL;
}

272
EXPORT_SYMBOL(pm_suspend);
Linus Torvalds's avatar
Linus Torvalds committed
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293

decl_subsys(power,NULL,NULL);


/**
 *	state - control system power state.
 *
 *	show() returns what states are supported, which is hard-coded to
 *	'standby' (Power-On Suspend), 'mem' (Suspend-to-RAM), and
 *	'disk' (Suspend-to-Disk).
 *
 *	store() accepts one of those strings, translates it into the 
 *	proper enumerated value, and initiates a suspend transition.
 */

static ssize_t state_show(struct subsystem * subsys, char * buf)
{
	int i;
	char * s = buf;

	for (i = 0; i < PM_SUSPEND_MAX; i++) {
294
295
		if (pm_states[i] && valid_state(i))
			s += sprintf(s,"%s ", pm_states[i]);
Linus Torvalds's avatar
Linus Torvalds committed
296
297
298
299
300
301
302
303
	}
	s += sprintf(s,"\n");
	return (s - buf);
}

static ssize_t state_store(struct subsystem * subsys, const char * buf, size_t n)
{
	suspend_state_t state = PM_SUSPEND_STANDBY;
304
	const char * const *s;
Linus Torvalds's avatar
Linus Torvalds committed
305
306
307
308
309
310
311
312
313
314
315
	char *p;
	int error;
	int len;

	p = memchr(buf, '\n', n);
	len = p ? p - buf : n;

	for (s = &pm_states[state]; state < PM_SUSPEND_MAX; s++, state++) {
		if (*s && !strncmp(buf, *s, len))
			break;
	}
316
	if (state < PM_SUSPEND_MAX && *s)
Linus Torvalds's avatar
Linus Torvalds committed
317
318
319
320
321
322
323
324
		error = enter_state(state);
	else
		error = -EINVAL;
	return error ? error : n;
}

power_attr(state);

325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
#ifdef CONFIG_PM_TRACE
int pm_trace_enabled;

static ssize_t pm_trace_show(struct subsystem * subsys, char * buf)
{
	return sprintf(buf, "%d\n", pm_trace_enabled);
}

static ssize_t
pm_trace_store(struct subsystem * subsys, const char * buf, size_t n)
{
	int val;

	if (sscanf(buf, "%d", &val) == 1) {
		pm_trace_enabled = !!val;
		return n;
	}
	return -EINVAL;
}

power_attr(pm_trace);

static struct attribute * g[] = {
	&state_attr.attr,
	&pm_trace_attr.attr,
	NULL,
};
#else
Linus Torvalds's avatar
Linus Torvalds committed
353
354
355
356
static struct attribute * g[] = {
	&state_attr.attr,
	NULL,
};
357
#endif /* CONFIG_PM_TRACE */
Linus Torvalds's avatar
Linus Torvalds committed
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372

static struct attribute_group attr_group = {
	.attrs = g,
};


static int __init pm_init(void)
{
	int error = subsystem_register(&power_subsys);
	if (!error)
		error = sysfs_create_group(&power_subsys.kset.kobj,&attr_group);
	return error;
}

core_initcall(pm_init);