main.c 10.9 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
	pm_prepare_console();

180 181 182 183
	error = pm_notifier_call_chain(PM_SUSPEND_PREPARE);
	if (error)
		goto Finish;

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();
203 204
 Finish:
	pm_notifier_call_chain(PM_POST_SUSPEND);
205
	pm_restore_console();
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
	device_pm_lock();
232 233
	arch_suspend_disable_irqs();
	BUG_ON(!irqs_disabled());
L
Linus Torvalds 已提交
234 235

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

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

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

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

260
	if (!suspend_ops)
261 262
		return -ENOSYS;

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

	if (suspend_test(TEST_DEVICES))
		goto Resume_devices;

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

	if (suspend_test(TEST_PLATFORM))
		goto Finish;

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

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

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




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

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

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

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

351
	if (!valid_state(state))
352
		return -ENODEV;
353

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

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

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

366 367 368
	if (suspend_test(TEST_FREEZER))
		goto Finish;

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

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


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

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

396
EXPORT_SYMBOL(pm_suspend);
L
Linus Torvalds 已提交
397

398 399
#endif /* CONFIG_SUSPEND */

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

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

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

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

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

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

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

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

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

power_attr(state);

470 471 472
#ifdef CONFIG_PM_TRACE
int pm_trace_enabled;

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

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

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

power_attr(pm_trace);
493
#endif /* CONFIG_PM_TRACE */
494 495 496

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

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


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

core_initcall(pm_init);