main.c 10.8 KB
Newer Older
L
Linus Torvalds 已提交
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>
L
Linus Torvalds 已提交
12 13 14 15 16 17
#include <linux/suspend.h>
#include <linux/kobject.h>
#include <linux/string.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/init.h>
18
#include <linux/console.h>
19
#include <linux/cpu.h>
20
#include <linux/resume-trace.h>
21
#include <linux/freezer.h>
C
Christoph Lameter 已提交
22
#include <linux/vmstat.h>
23
#include <linux/syscalls.h>
L
Linus Torvalds 已提交
24 25 26

#include "power.h"

27
DEFINE_MUTEX(pm_mutex);
L
Linus Torvalds 已提交
28

29 30 31
unsigned int pm_flags;
EXPORT_SYMBOL(pm_flags);

32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
#ifdef CONFIG_PM_SLEEP

/* Routines for PM-transition notifications */

static BLOCKING_NOTIFIER_HEAD(pm_chain_head);

int register_pm_notifier(struct notifier_block *nb)
{
	return blocking_notifier_chain_register(&pm_chain_head, nb);
}
EXPORT_SYMBOL_GPL(register_pm_notifier);

int unregister_pm_notifier(struct notifier_block *nb)
{
	return blocking_notifier_chain_unregister(&pm_chain_head, nb);
}
EXPORT_SYMBOL_GPL(unregister_pm_notifier);

int pm_notifier_call_chain(unsigned long val)
{
	return (blocking_notifier_call_chain(&pm_chain_head, val, NULL)
			== NOTIFY_BAD) ? -EINVAL : 0;
}

56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
#ifdef CONFIG_PM_DEBUG
int pm_test_level = TEST_NONE;

static int suspend_test(int level)
{
	if (pm_test_level == level) {
		printk(KERN_INFO "suspend debug: Waiting for 5 seconds.\n");
		mdelay(5000);
		return 1;
	}
	return 0;
}

static const char * const pm_tests[__TEST_AFTER_LAST] = {
	[TEST_NONE] = "none",
	[TEST_CORE] = "core",
	[TEST_CPUS] = "processors",
	[TEST_PLATFORM] = "platform",
	[TEST_DEVICES] = "devices",
	[TEST_FREEZER] = "freezer",
};

78 79
static ssize_t pm_test_show(struct kobject *kobj, struct kobj_attribute *attr,
				char *buf)
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
{
	char *s = buf;
	int level;

	for (level = TEST_FIRST; level <= TEST_MAX; level++)
		if (pm_tests[level]) {
			if (level == pm_test_level)
				s += sprintf(s, "[%s] ", pm_tests[level]);
			else
				s += sprintf(s, "%s ", pm_tests[level]);
		}

	if (s != buf)
		/* convert the last space to a newline */
		*(s-1) = '\n';

	return (s - buf);
}

99 100
static ssize_t pm_test_store(struct kobject *kobj, struct kobj_attribute *attr,
				const char *buf, size_t n)
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
{
	const char * const *s;
	int level;
	char *p;
	int len;
	int error = -EINVAL;

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

	mutex_lock(&pm_mutex);

	level = TEST_FIRST;
	for (s = &pm_tests[level]; level <= TEST_MAX; s++, level++)
		if (*s && len == strlen(*s) && !strncmp(buf, *s, len)) {
			pm_test_level = level;
			error = 0;
			break;
		}

	mutex_unlock(&pm_mutex);

	return error ? error : n;
}

power_attr(pm_test);
#else /* !CONFIG_PM_DEBUG */
static inline int suspend_test(int level) { return 0; }
#endif /* !CONFIG_PM_DEBUG */

131
#endif /* CONFIG_PM_SLEEP */
132

133 134 135 136 137
#ifdef CONFIG_SUSPEND

/* This is just an arbitrary number */
#define FREE_PAGE_NUMBER (100)

R
Rafael J. Wysocki 已提交
138
static struct platform_suspend_ops *suspend_ops;
L
Linus Torvalds 已提交
139 140

/**
141
 *	suspend_set_ops - Set the global suspend method table.
L
Linus Torvalds 已提交
142 143 144
 *	@ops:	Pointer to ops structure.
 */

145
void suspend_set_ops(struct platform_suspend_ops *ops)
L
Linus Torvalds 已提交
146
{
147
	mutex_lock(&pm_mutex);
148
	suspend_ops = ops;
149
	mutex_unlock(&pm_mutex);
L
Linus Torvalds 已提交
150 151
}

152
/**
153
 * suspend_valid_only_mem - generic memory-only valid callback
154
 *
155
 * Platform drivers that implement mem suspend only and only need
156 157 158
 * to check for that in their .valid callback can use this instead
 * of rolling their own .valid callback.
 */
159
int suspend_valid_only_mem(suspend_state_t state)
160 161 162 163
{
	return state == PM_SUSPEND_MEM;
}

L
Linus Torvalds 已提交
164 165 166
/**
 *	suspend_prepare - Do prep work before entering low-power state.
 *
167 168
 *	This is common code that is called for each state that we're entering.
 *	Run suspend notifiers, allocate a console and stop all processes.
L
Linus Torvalds 已提交
169
 */
170
static int suspend_prepare(void)
L
Linus Torvalds 已提交
171
{
172
	int error;
D
David Shaohua Li 已提交
173
	unsigned int free_pages;
L
Linus Torvalds 已提交
174

175
	if (!suspend_ops || !suspend_ops->enter)
L
Linus Torvalds 已提交
176 177
		return -EPERM;

178 179 180 181
	error = pm_notifier_call_chain(PM_SUSPEND_PREPARE);
	if (error)
		goto Finish;

L
Linus Torvalds 已提交
182 183
	pm_prepare_console();

184
	if (suspend_freeze_processes()) {
L
Linus Torvalds 已提交
185 186 187 188
		error = -EAGAIN;
		goto Thaw;
	}

189 190
	free_pages = global_page_state(NR_FREE_PAGES);
	if (free_pages < FREE_PAGE_NUMBER) {
D
David Shaohua Li 已提交
191 192 193 194 195 196 197
		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");
		}
	}
198 199 200
	if (!error)
		return 0;

L
Linus Torvalds 已提交
201
 Thaw:
202
	suspend_thaw_processes();
L
Linus Torvalds 已提交
203
	pm_restore_console();
204 205
 Finish:
	pm_notifier_call_chain(PM_POST_SUSPEND);
L
Linus Torvalds 已提交
206 207 208
	return error;
}

209 210 211 212 213 214 215 216 217 218 219
/* 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();
}
L
Linus Torvalds 已提交
220

221 222 223 224 225 226
/**
 *	suspend_enter - enter the desired system sleep state.
 *	@state:		state to enter
 *
 *	This function should be called after devices have been suspended.
 */
227
static int suspend_enter(suspend_state_t state)
L
Linus Torvalds 已提交
228 229 230
{
	int error = 0;

231 232
	arch_suspend_disable_irqs();
	BUG_ON(!irqs_disabled());
L
Linus Torvalds 已提交
233 234

	if ((error = device_power_down(PMSG_SUSPEND))) {
235
		printk(KERN_ERR "PM: Some devices failed to power down\n");
L
Linus Torvalds 已提交
236 237
		goto Done;
	}
238 239 240 241

	if (!suspend_test(TEST_CORE))
		error = suspend_ops->enter(state);

L
Linus Torvalds 已提交
242 243
	device_power_up();
 Done:
244 245
	arch_suspend_enable_irqs();
	BUG_ON(irqs_disabled());
L
Linus Torvalds 已提交
246 247 248
	return error;
}

249
/**
250 251
 *	suspend_devices_and_enter - suspend devices and enter the desired system
 *				    sleep state.
252 253 254 255 256 257
 *	@state:		  state to enter
 */
int suspend_devices_and_enter(suspend_state_t state)
{
	int error;

258
	if (!suspend_ops)
259 260
		return -ENOSYS;

261 262
	if (suspend_ops->begin) {
		error = suspend_ops->begin(state);
263
		if (error)
264
			goto Close;
265 266 267 268
	}
	suspend_console();
	error = device_suspend(PMSG_SUSPEND);
	if (error) {
269
		printk(KERN_ERR "PM: Some devices failed to suspend\n");
270 271
		goto Resume_console;
	}
272 273 274 275

	if (suspend_test(TEST_DEVICES))
		goto Resume_devices;

276
	if (suspend_ops->prepare) {
277
		error = suspend_ops->prepare();
278 279 280
		if (error)
			goto Resume_devices;
	}
281 282 283 284

	if (suspend_test(TEST_PLATFORM))
		goto Finish;

285
	error = disable_nonboot_cpus();
286
	if (!error && !suspend_test(TEST_CPUS))
287 288 289
		suspend_enter(state);

	enable_nonboot_cpus();
290
 Finish:
291 292
	if (suspend_ops->finish)
		suspend_ops->finish();
293 294 295 296
 Resume_devices:
	device_resume();
 Resume_console:
	resume_console();
297 298 299
 Close:
	if (suspend_ops->end)
		suspend_ops->end();
300 301
	return error;
}
L
Linus Torvalds 已提交
302 303 304 305 306 307 308

/**
 *	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.
 */
309
static void suspend_finish(void)
L
Linus Torvalds 已提交
310
{
311
	suspend_thaw_processes();
L
Linus Torvalds 已提交
312
	pm_restore_console();
313
	pm_notifier_call_chain(PM_POST_SUSPEND);
L
Linus Torvalds 已提交
314 315 316 317 318
}




319
static const char * const pm_states[PM_SUSPEND_MAX] = {
L
Linus Torvalds 已提交
320 321 322 323
	[PM_SUSPEND_STANDBY]	= "standby",
	[PM_SUSPEND_MEM]	= "mem",
};

324 325
static inline int valid_state(suspend_state_t state)
{
326 327
	/* All states need lowlevel support and need to be valid
	 * to the lowlevel implementation, no valid callback
328
	 * implies that none are valid. */
329
	if (!suspend_ops || !suspend_ops->valid || !suspend_ops->valid(state))
330 331 332 333
		return 0;
	return 1;
}

L
Linus Torvalds 已提交
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348

/**
 *	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;

349
	if (!valid_state(state))
350
		return -ENODEV;
351

352
	if (!mutex_trylock(&pm_mutex))
L
Linus Torvalds 已提交
353 354
		return -EBUSY;

355
	printk(KERN_INFO "PM: Syncing filesystems ... ");
356 357 358
	sys_sync();
	printk("done.\n");

359
	pr_debug("PM: Preparing system for %s sleep\n", pm_states[state]);
360 361
	error = suspend_prepare();
	if (error)
L
Linus Torvalds 已提交
362 363
		goto Unlock;

364 365 366
	if (suspend_test(TEST_FREEZER))
		goto Finish;

367
	pr_debug("PM: Entering %s sleep\n", pm_states[state]);
368
	error = suspend_devices_and_enter(state);
L
Linus Torvalds 已提交
369

370
 Finish:
371
	pr_debug("PM: Finishing wakeup.\n");
372
	suspend_finish();
L
Linus Torvalds 已提交
373
 Unlock:
374
	mutex_unlock(&pm_mutex);
L
Linus Torvalds 已提交
375 376 377 378 379 380
	return error;
}


/**
 *	pm_suspend - Externally visible function for suspending system.
381
 *	@state:		Enumerated value of state to enter.
L
Linus Torvalds 已提交
382 383 384 385 386 387 388
 *
 *	Determine whether or not value is within range, get state 
 *	structure, and enter (above).
 */

int pm_suspend(suspend_state_t state)
{
A
Alexey Starikovskiy 已提交
389
	if (state > PM_SUSPEND_ON && state <= PM_SUSPEND_MAX)
L
Linus Torvalds 已提交
390 391 392 393
		return enter_state(state);
	return -EINVAL;
}

394
EXPORT_SYMBOL(pm_suspend);
L
Linus Torvalds 已提交
395

396 397
#endif /* CONFIG_SUSPEND */

398
struct kobject *power_kobj;
L
Linus Torvalds 已提交
399 400 401 402 403 404 405 406 407 408 409 410

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

411 412
static ssize_t state_show(struct kobject *kobj, struct kobj_attribute *attr,
			  char *buf)
L
Linus Torvalds 已提交
413
{
414 415
	char *s = buf;
#ifdef CONFIG_SUSPEND
L
Linus Torvalds 已提交
416 417 418
	int i;

	for (i = 0; i < PM_SUSPEND_MAX; i++) {
419 420
		if (pm_states[i] && valid_state(i))
			s += sprintf(s,"%s ", pm_states[i]);
L
Linus Torvalds 已提交
421
	}
422
#endif
423
#ifdef CONFIG_HIBERNATION
424 425 426 427 428 429
	s += sprintf(s, "%s\n", "disk");
#else
	if (s != buf)
		/* convert the last space to a newline */
		*(s-1) = '\n';
#endif
L
Linus Torvalds 已提交
430 431 432
	return (s - buf);
}

433 434
static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
			   const char *buf, size_t n)
L
Linus Torvalds 已提交
435
{
436
#ifdef CONFIG_SUSPEND
L
Linus Torvalds 已提交
437
	suspend_state_t state = PM_SUSPEND_STANDBY;
438
	const char * const *s;
439
#endif
L
Linus Torvalds 已提交
440 441
	char *p;
	int len;
442
	int error = -EINVAL;
L
Linus Torvalds 已提交
443 444 445 446

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

447
	/* First, check if we are requested to hibernate */
R
Rafael J. Wysocki 已提交
448
	if (len == 4 && !strncmp(buf, "disk", len)) {
449
		error = hibernate();
450
  goto Exit;
451 452
	}

453
#ifdef CONFIG_SUSPEND
L
Linus Torvalds 已提交
454
	for (s = &pm_states[state]; state < PM_SUSPEND_MAX; s++, state++) {
R
Rafael J. Wysocki 已提交
455
		if (*s && len == strlen(*s) && !strncmp(buf, *s, len))
L
Linus Torvalds 已提交
456 457
			break;
	}
458
	if (state < PM_SUSPEND_MAX && *s)
L
Linus Torvalds 已提交
459
		error = enter_state(state);
460 461 462
#endif

 Exit:
L
Linus Torvalds 已提交
463 464 465 466 467
	return error ? error : n;
}

power_attr(state);

468 469 470
#ifdef CONFIG_PM_TRACE
int pm_trace_enabled;

471 472
static ssize_t pm_trace_show(struct kobject *kobj, struct kobj_attribute *attr,
			     char *buf)
473 474 475 476 477
{
	return sprintf(buf, "%d\n", pm_trace_enabled);
}

static ssize_t
478 479
pm_trace_store(struct kobject *kobj, struct kobj_attribute *attr,
	       const char *buf, size_t n)
480 481 482 483 484 485 486 487 488 489 490
{
	int val;

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

power_attr(pm_trace);
491
#endif /* CONFIG_PM_TRACE */
492 493 494

static struct attribute * g[] = {
	&state_attr.attr,
495
#ifdef CONFIG_PM_TRACE
496
	&pm_trace_attr.attr,
497
#endif
498
#if defined(CONFIG_PM_SLEEP) && defined(CONFIG_PM_DEBUG)
499 500
	&pm_test_attr.attr,
#endif
501 502
	NULL,
};
L
Linus Torvalds 已提交
503 504 505 506 507 508 509 510

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


static int __init pm_init(void)
{
511 512
	power_kobj = kobject_create_and_add("power", NULL);
	if (!power_kobj)
513
		return -ENOMEM;
514
	return sysfs_create_group(power_kobj, &attr_group);
L
Linus Torvalds 已提交
515 516 517
}

core_initcall(pm_init);