swsusp.c 28.6 KB
Newer Older
Pavel Machek's avatar
Pavel Machek committed
1
/*
Pavel Machek's avatar
Pavel Machek committed
2
 * linux/kernel/suspend.c
Pavel Machek's avatar
Pavel Machek committed
3 4 5 6 7 8 9 10 11 12 13
 *
 * This file is to realize architecture-independent
 * machine suspend feature using pretty near only high-level routines
 *
 * Copyright (C) 1998-2001 Gabor Kuti <seasons@fornax.hu>
 * Copyright (C) 1998,2001,2002 Pavel Machek <pavel@suse.cz>
 *
 * I'd like to thank the following people for their work:
 * 
 * Pavel Machek <pavel@ucw.cz>:
 * Modifications, defectiveness pointing, being with me at the very beginning,
Pavel Machek's avatar
Pavel Machek committed
14
 * suspend to swap space, stop all tasks. Port to 2.4.18-ac and 2.5.17.
Pavel Machek's avatar
Pavel Machek committed
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
 *
 * Steve Doddi <dirk@loth.demon.co.uk>: 
 * Support the possibility of hardware state restoring.
 *
 * Raph <grey.havens@earthling.net>:
 * Support for preserving states of network devices and virtual console
 * (including X and svgatextmode)
 *
 * Kurt Garloff <garloff@suse.de>:
 * Straightened the critical function in order to prevent compilers from
 * playing tricks with local variables.
 *
 * Andreas Mohr <a.mohr@mailto.de>
 *
 * Alex Badea <vampire@go.ro>:
 * Fixed runaway init
 *
 * More state savers are welcome. Especially for the scsi layer...
 *
 * For TODOs,FIXMEs also look in Documentation/swsusp.txt
 */

#include <linux/module.h>
#include <linux/mm.h>
#include <linux/suspend.h>
#include <linux/smp_lock.h>
#include <linux/file.h>
#include <linux/utsname.h>
#include <linux/version.h>
#include <linux/delay.h>
#include <linux/reboot.h>
#include <linux/bitops.h>
47
#include <linux/vt_kern.h>
Pavel Machek's avatar
Pavel Machek committed
48 49 50 51 52 53 54 55 56
#include <linux/kbd_kern.h>
#include <linux/keyboard.h>
#include <linux/spinlock.h>
#include <linux/genhd.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/swap.h>
#include <linux/pm.h>
#include <linux/device.h>
Pavel Machek's avatar
Pavel Machek committed
57
#include <linux/buffer_head.h>
Pavel Machek's avatar
Pavel Machek committed
58 59
#include <linux/swapops.h>
#include <linux/bootmem.h>
Pavel Machek's avatar
Pavel Machek committed
60 61 62 63 64 65

#include <asm/uaccess.h>
#include <asm/mmu_context.h>
#include <asm/pgtable.h>
#include <asm/io.h>

66 67
#include "power.h"

68
extern long sys_sync(void);
69

70
unsigned char software_suspend_enabled = 1;
Pavel Machek's avatar
Pavel Machek committed
71

72 73 74
#define __ADDRESS(x)  ((unsigned long) phys_to_virt(x))
#define ADDRESS(x) __ADDRESS((x) << PAGE_SHIFT)
#define ADDRESS2(x) __ADDRESS(__pa(x))		/* Needed for x86-64 where some pages are in memory twice */
Pavel Machek's avatar
Pavel Machek committed
75 76 77 78 79 80 81 82 83 84 85 86 87

/* References to section boundaries */
extern char __nosave_begin, __nosave_end;

extern int is_head_of_free_region(struct page *);

/* Locks */
spinlock_t suspend_pagedir_lock __nosavedata = SPIN_LOCK_UNLOCKED;

/* Variables to be preserved over suspend */
static int pagedir_order_check;
static int nr_copy_pages_check;

88
static char resume_file[256];			/* For resume= kernel option */
Alexander Viro's avatar
Alexander Viro committed
89
static dev_t resume_device;
Pavel Machek's avatar
Pavel Machek committed
90
/* Local variables that should not be affected by save */
91
unsigned int nr_copy_pages __nosavedata = 0;
Pavel Machek's avatar
Pavel Machek committed
92

93
static int pm_suspend_state;
Pavel Machek's avatar
Pavel Machek committed
94 95 96 97 98 99 100

/* Suspend pagedir is allocated before final copy, therefore it
   must be freed after resume 

   Warning: this is evil. There are actually two pagedirs at time of
   resume. One is "pagedir_save", which is empty frame allocated at
   time of suspend, that must be freed. Second is "pagedir_nosave", 
Pavel Machek's avatar
Pavel Machek committed
101
   allocated at time of resume, that travels through memory not to
Pavel Machek's avatar
Pavel Machek committed
102 103
   collide with anything.
 */
104
suspend_pagedir_t *pagedir_nosave __nosavedata = NULL;
105
static suspend_pagedir_t *pagedir_save;
Pavel Machek's avatar
Pavel Machek committed
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
static int pagedir_order __nosavedata = 0;

struct link {
	char dummy[PAGE_SIZE - sizeof(swp_entry_t)];
	swp_entry_t next;
};

union diskpage {
	union swap_header swh;
	struct link link;
	struct suspend_header sh;
};

/*
 * XXX: We try to keep some more pages free so that I/O operations succeed
 * without paging. Might this be more?
 */
#define PAGES_FOR_IO	512

125 126
static const char name_suspend[] = "Suspend Machine: ";
static const char name_resume[] = "Resume Machine: ";
Pavel Machek's avatar
Pavel Machek committed
127 128 129 130

/*
 * Debug
 */
131
#define	DEBUG_DEFAULT
Pavel Machek's avatar
Pavel Machek committed
132 133
#undef	DEBUG_PROCESS
#undef	DEBUG_SLOW
134
#define TEST_SWSUSP 0		/* Set to 1 to reboot instead of halt machine after suspension */
Pavel Machek's avatar
Pavel Machek committed
135 136

#ifdef DEBUG_DEFAULT
137
# define PRINTK(f, a...)       printk(f, ## a)
Pavel Machek's avatar
Pavel Machek committed
138
#else
139
# define PRINTK(f, a...)
Pavel Machek's avatar
Pavel Machek committed
140
#endif
141

Pavel Machek's avatar
Pavel Machek committed
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
#ifdef DEBUG_SLOW
#define MDELAY(a) mdelay(a)
#else
#define MDELAY(a)
#endif

/*
 * Saving part...
 */

static __inline__ int fill_suspend_header(struct suspend_header *sh)
{
	memset((char *)sh, 0, sizeof(*sh));

	sh->version_code = LINUX_VERSION_CODE;
	sh->num_physpages = num_physpages;
	strncpy(sh->machine, system_utsname.machine, 8);
	strncpy(sh->version, system_utsname.version, 20);
160 161
	/* FIXME: Is this bogus? --RR */
	sh->num_cpus = num_online_cpus();
Pavel Machek's avatar
Pavel Machek committed
162
	sh->page_size = PAGE_SIZE;
163
	sh->suspend_pagedir = pagedir_nosave;
164
	BUG_ON (pagedir_save != pagedir_nosave);
Pavel Machek's avatar
Pavel Machek committed
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
	sh->num_pbes = nr_copy_pages;
	/* TODO: needed? mounted fs' last mounted date comparison
	 * [so they haven't been mounted since last suspend.
	 * Maybe it isn't.] [we'd need to do this for _all_ fs-es]
	 */
	return 0;
}

/* We memorize in swapfile_used what swap devices are used for suspension */
#define SWAPFILE_UNUSED    0
#define SWAPFILE_SUSPEND   1	/* This is the suspending device */
#define SWAPFILE_IGNORED   2	/* Those are other swap devices ignored for suspension */

static unsigned short swapfile_used[MAX_SWAPFILES];
static unsigned short root_swap;
#define MARK_SWAP_SUSPEND 0
#define MARK_SWAP_RESUME 2

static void mark_swapfiles(swp_entry_t prev, int mode)
{
	swp_entry_t entry;
	union diskpage *cur;
187 188
	struct page *page;

Pavel Machek's avatar
Pavel Machek committed
189 190 191
	if (root_swap == 0xFFFF)  /* ignored */
		return;

192 193
	page = alloc_page(GFP_ATOMIC);
	if (!page)
Pavel Machek's avatar
Pavel Machek committed
194
		panic("Out of memory in mark_swapfiles");
195
	cur = page_address(page);
Pavel Machek's avatar
Pavel Machek committed
196
	/* XXX: this is dirty hack to get first page of swap file */
197
	entry = swp_entry(root_swap, 0);
198
	rw_swap_page_sync(READ, entry, page);
Pavel Machek's avatar
Pavel Machek committed
199 200

	if (mode == MARK_SWAP_RESUME) {
201
	  	if (!memcmp("S1",cur->swh.magic.magic,2))
Pavel Machek's avatar
Pavel Machek committed
202
		  	memcpy(cur->swh.magic.magic,"SWAP-SPACE",10);
203
		else if (!memcmp("S2",cur->swh.magic.magic,2))
Pavel Machek's avatar
Pavel Machek committed
204 205 206 207 208
			memcpy(cur->swh.magic.magic,"SWAPSPACE2",10);
		else printk("%sUnable to find suspended-data signature (%.10s - misspelled?\n", 
		      	name_resume, cur->swh.magic.magic);
	} else {
	  	if ((!memcmp("SWAP-SPACE",cur->swh.magic.magic,10)))
209
		  	memcpy(cur->swh.magic.magic,"S1SUSP....",10);
Pavel Machek's avatar
Pavel Machek committed
210
		else if ((!memcmp("SWAPSPACE2",cur->swh.magic.magic,10)))
211
			memcpy(cur->swh.magic.magic,"S2SUSP....",10);
Pavel Machek's avatar
Pavel Machek committed
212 213
		else panic("\nSwapspace is not swapspace (%.10s)\n", cur->swh.magic.magic);
		cur->link.next = prev; /* prev is the first/last swap page of the resume area */
214
		/* link.next lies *no more* in last 4/8 bytes of magic */
Pavel Machek's avatar
Pavel Machek committed
215
	}
216 217
	rw_swap_page_sync(WRITE, entry, page);
	__free_page(page);
Pavel Machek's avatar
Pavel Machek committed
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
}

static void read_swapfiles(void) /* This is called before saving image */
{
	int i, len;
	
	len=strlen(resume_file);
	root_swap = 0xFFFF;
	
	swap_list_lock();
	for(i=0; i<MAX_SWAPFILES; i++) {
		if (swap_info[i].flags == 0) {
			swapfile_used[i]=SWAPFILE_UNUSED;
		} else {
			if(!len) {
	    			printk(KERN_WARNING "resume= option should be used to set suspend device" );
				if(root_swap == 0xFFFF) {
					swapfile_used[i] = SWAPFILE_SUSPEND;
					root_swap = i;
				} else
					swapfile_used[i] = SWAPFILE_IGNORED;				  
			} else {
	  			/* we ignore all swap devices that are not the resume_file */
				if (1) {
// FIXME				if(resume_device == swap_info[i].swap_device) {
					swapfile_used[i] = SWAPFILE_SUSPEND;
					root_swap = i;
				} else {
#if 0
247
					printk( "Resume: device %s (%x != %x) ignored\n", swap_info[i].swap_file->d_name.name, swap_info[i].swap_device, resume_device );				  
Pavel Machek's avatar
Pavel Machek committed
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
#endif
				  	swapfile_used[i] = SWAPFILE_IGNORED;
				}
			}
		}
	}
	swap_list_unlock();
}

static void lock_swapdevices(void) /* This is called after saving image so modification
				      will be lost after resume... and that's what we want. */
{
	int i;

	swap_list_lock();
	for(i = 0; i< MAX_SWAPFILES; i++)
		if(swapfile_used[i] == SWAPFILE_IGNORED) {
			swap_info[i].flags ^= 0xFF; /* we make the device unusable. A new call to
						       lock_swapdevices can unlock the devices. */
		}
	swap_list_unlock();
}

static int write_suspend_image(void)
{
	int i;
	swp_entry_t entry, prev = { 0 };
	int nr_pgdir_pages = SUSPEND_PD_PAGES(nr_copy_pages);
276
	union diskpage *cur,  *buffer = (union diskpage *)get_zeroed_page(GFP_ATOMIC);
Pavel Machek's avatar
Pavel Machek committed
277
	unsigned long address;
278
	struct page *page;
Pavel Machek's avatar
Pavel Machek committed
279

280
	printk( "Writing data to swap (%d pages): ", nr_copy_pages );
Pavel Machek's avatar
Pavel Machek committed
281 282
	for (i=0; i<nr_copy_pages; i++) {
		if (!(i%100))
283
			printk( "." );
Pavel Machek's avatar
Pavel Machek committed
284 285 286
		if (!(entry = get_swap_page()).val)
			panic("\nNot enough swapspace when writing data" );
		
287
		if (swapfile_used[swp_type(entry)] != SWAPFILE_SUSPEND)
Pavel Machek's avatar
Pavel Machek committed
288 289 290
			panic("\nPage %d: not enough swapspace on suspend device", i );
	    
		address = (pagedir_nosave+i)->address;
291 292
		page = virt_to_page(address);
		rw_swap_page_sync(WRITE, entry, page);
Pavel Machek's avatar
Pavel Machek committed
293 294
		(pagedir_nosave+i)->swap_address = entry;
	}
295 296
	printk( "|\n" );
	printk( "Writing pagedir (%d pages): ", nr_pgdir_pages);
Pavel Machek's avatar
Pavel Machek committed
297 298
	for (i=0; i<nr_pgdir_pages; i++) {
		cur = (union diskpage *)((char *) pagedir_nosave)+i;
299 300
		BUG_ON ((char *) cur != (((char *) pagedir_nosave) + i*PAGE_SIZE));
		printk( "." );
Pavel Machek's avatar
Pavel Machek committed
301 302 303 304 305 306 307
		if (!(entry = get_swap_page()).val) {
			printk(KERN_CRIT "Not enough swapspace when writing pgdir\n" );
			panic("Don't know how to recover");
			free_page((unsigned long) buffer);
			return -ENOSPC;
		}

308
		if(swapfile_used[swp_type(entry)] != SWAPFILE_SUSPEND)
309 310 311 312
			panic("\nNot enough swapspace for pagedir on suspend device" );

		BUG_ON (sizeof(swp_entry_t) != sizeof(long));
		BUG_ON (PAGE_SIZE % sizeof(struct pbe));
Pavel Machek's avatar
Pavel Machek committed
313 314

		cur->link.next = prev;				
315 316
		page = virt_to_page((unsigned long)cur);
		rw_swap_page_sync(WRITE, entry, page);
Pavel Machek's avatar
Pavel Machek committed
317 318
		prev = entry;
	}
319 320 321
	printk("H");
	BUG_ON (sizeof(struct suspend_header) > PAGE_SIZE-sizeof(swp_entry_t));
	BUG_ON (sizeof(union diskpage) != PAGE_SIZE);
Pavel Machek's avatar
Pavel Machek committed
322 323
	if (!(entry = get_swap_page()).val)
		panic( "\nNot enough swapspace when writing header" );
324 325
	if (swapfile_used[swp_type(entry)] != SWAPFILE_SUSPEND)
		panic("\nNot enough swapspace for header on suspend device" );
Pavel Machek's avatar
Pavel Machek committed
326 327

	cur = (void *) buffer;
328
	if (fill_suspend_header(&cur->sh))
Pavel Machek's avatar
Pavel Machek committed
329 330 331 332
		panic("\nOut of memory while writing header");
		
	cur->link.next = prev;

333 334
	page = virt_to_page((unsigned long)cur);
	rw_swap_page_sync(WRITE, entry, page);
Pavel Machek's avatar
Pavel Machek committed
335 336
	prev = entry;

337
	printk( "S" );
Pavel Machek's avatar
Pavel Machek committed
338
	mark_swapfiles(prev, MARK_SWAP_SUSPEND);
339
	printk( "|\n" );
Pavel Machek's avatar
Pavel Machek committed
340 341 342 343 344 345 346 347 348 349 350

	MDELAY(1000);
	free_page((unsigned long) buffer);
	return 0;
}

/* if pagedir_p != NULL it also copies the counted pages */
static int count_and_copy_data_pages(struct pbe *pagedir_p)
{
	int chunk_size;
	int nr_copy_pages = 0;
351 352
	int pfn;
	struct page *page;
353
	
Pavel Machek's avatar
Pavel Machek committed
354
	BUG_ON (max_pfn != num_physpages);
355

Pavel Machek's avatar
Pavel Machek committed
356
	for (pfn = 0; pfn < max_pfn; pfn++) {
357
		page = pfn_to_page(pfn);
358

359 360
		if (!PageReserved(page)) {
			if (PageNosave(page))
Pavel Machek's avatar
Pavel Machek committed
361 362
				continue;

363 364
			if ((chunk_size=is_head_of_free_region(page))!=0) {
				pfn += chunk_size - 1;
Pavel Machek's avatar
Pavel Machek committed
365 366
				continue;
			}
367 368
		} else if (PageReserved(page)) {
			BUG_ON (PageNosave(page));
Pavel Machek's avatar
Pavel Machek committed
369 370 371 372

			/*
			 * Just copy whole code segment. Hopefully it is not that big.
			 */
373 374 375
			if ((ADDRESS(pfn) >= (unsigned long) ADDRESS2(&__nosave_begin)) && 
			    (ADDRESS(pfn) <  (unsigned long) ADDRESS2(&__nosave_end))) {
				PRINTK("[nosave %lx]", ADDRESS(pfn));
Pavel Machek's avatar
Pavel Machek committed
376 377 378 379
				continue;
			}
			/* Hmm, perhaps copying all reserved pages is not too healthy as they may contain 
			   critical bios data? */
380
		} else	BUG();
Pavel Machek's avatar
Pavel Machek committed
381 382

		nr_copy_pages++;
383
		if (pagedir_p) {
384
			pagedir_p->orig_address = ADDRESS(pfn);
385
			copy_page((void *) pagedir_p->address, (void *) pagedir_p->orig_address);
Pavel Machek's avatar
Pavel Machek committed
386 387 388 389 390 391 392 393
			pagedir_p++;
		}
	}
	return nr_copy_pages;
}

static void free_suspend_pagedir(unsigned long this_pagedir)
{
394 395
	struct page *page;
	int pfn;
Pavel Machek's avatar
Pavel Machek committed
396 397 398
	unsigned long this_pagedir_end = this_pagedir +
		(PAGE_SIZE << pagedir_order);

399 400
	for(pfn = 0; pfn < num_physpages; pfn++) {
		page = pfn_to_page(pfn);
401
		if (!TestClearPageNosave(page))
Pavel Machek's avatar
Pavel Machek committed
402 403
			continue;

404
		if (ADDRESS(pfn) >= this_pagedir && ADDRESS(pfn) < this_pagedir_end)
Pavel Machek's avatar
Pavel Machek committed
405 406
			continue; /* old pagedir gets freed in one */
		
407
		free_page(ADDRESS(pfn));
Pavel Machek's avatar
Pavel Machek committed
408 409 410 411 412 413 414 415 416 417 418 419 420
	}
	free_pages(this_pagedir, pagedir_order);
}

static suspend_pagedir_t *create_suspend_pagedir(int nr_copy_pages)
{
	int i;
	suspend_pagedir_t *pagedir;
	struct pbe *p;
	struct page *page;

	pagedir_order = get_bitmask_order(SUSPEND_PD_PAGES(nr_copy_pages));

421
	p = pagedir = (suspend_pagedir_t *)__get_free_pages(GFP_ATOMIC | __GFP_COLD, pagedir_order);
Pavel Machek's avatar
Pavel Machek committed
422 423 424 425 426 427 428 429
	if(!pagedir)
		return NULL;

	page = virt_to_page(pagedir);
	for(i=0; i < 1<<pagedir_order; i++)
		SetPageNosave(page++);
		
	while(nr_copy_pages--) {
430
		p->address = get_zeroed_page(GFP_ATOMIC | __GFP_COLD);
Pavel Machek's avatar
Pavel Machek committed
431 432 433 434 435 436 437 438 439 440 441 442 443
		if(!p->address) {
			free_suspend_pagedir((unsigned long) pagedir);
			return NULL;
		}
		SetPageNosave(virt_to_page(p->address));
		p->orig_address = 0;
		p++;
	}
	return pagedir;
}

static int prepare_suspend_processes(void)
{
Pavel Machek's avatar
Pavel Machek committed
444
	sys_sync();	/* Syncing needs pdflushd, so do it before stopping processes */
Pavel Machek's avatar
Pavel Machek committed
445
	if (freeze_processes()) {
446
		printk( KERN_ERR "Suspend failed: Not all processes stopped!\n" );
Pavel Machek's avatar
Pavel Machek committed
447 448 449 450 451 452 453 454 455 456 457 458 459
		thaw_processes();
		return 1;
	}
	return 0;
}

/*
 * Try to free as much memory as possible, but do not OOM-kill anyone
 *
 * Notice: all userland should be stopped at this point, or livelock is possible.
 */
static void free_some_memory(void)
{
460
	printk("Freeing memory: ");
461
	while (shrink_all_memory(10000))
Pavel Machek's avatar
Pavel Machek committed
462
		printk(".");
463
	printk("|\n");
Pavel Machek's avatar
Pavel Machek committed
464 465 466 467 468 469
}

/* Make disk drivers accept operations, again */
static void drivers_unsuspend(void)
{
	device_resume(RESUME_RESTORE_STATE);
470
	device_resume(RESUME_ENABLE);
Pavel Machek's avatar
Pavel Machek committed
471 472 473 474 475
}

/* Called from process context */
static int drivers_suspend(void)
{
476 477 478 479 480 481 482
	if (device_suspend(4, SUSPEND_NOTIFY))
		return -EIO;
	if (device_suspend(4, SUSPEND_SAVE_STATE)) {
		device_resume(RESUME_RESTORE_STATE);
		return -EIO;
	}
	if (!pm_suspend_state) {
Pavel Machek's avatar
Pavel Machek committed
483 484
		if(pm_send_all(PM_SUSPEND,(void *)3)) {
			printk(KERN_WARNING "Problem while sending suspend event\n");
485
			return -EIO;
Pavel Machek's avatar
Pavel Machek committed
486 487 488 489
		}
		pm_suspend_state=1;
	} else
		printk(KERN_WARNING "PM suspend state already raised\n");
490
	device_suspend(4, SUSPEND_DISABLE);
Pavel Machek's avatar
Pavel Machek committed
491
	  
492
	return 0;
Pavel Machek's avatar
Pavel Machek committed
493 494 495 496 497 498 499
}

#define RESUME_PHASE1 1 /* Called from interrupts disabled */
#define RESUME_PHASE2 2 /* Called with interrupts enabled */
#define RESUME_ALL_PHASES (RESUME_PHASE1 | RESUME_PHASE2)
static void drivers_resume(int flags)
{
500 501
	if (flags & RESUME_PHASE1) {
		device_resume(RESUME_RESTORE_STATE);
502
		device_resume(RESUME_ENABLE);
503 504
	}
  	if (flags & RESUME_PHASE2) {
505
		if (pm_suspend_state) {
Pavel Machek's avatar
Pavel Machek committed
506 507 508 509 510 511 512 513 514 515 516 517
			if(pm_send_all(PM_RESUME,(void *)0))
				printk(KERN_WARNING "Problem while sending resume event\n");
			pm_suspend_state=0;
		} else
			printk(KERN_WARNING "PM suspend state wasn't raised\n");

#ifdef SUSPEND_CONSOLE
		update_screen(fg_console);	/* Hmm, is this the problem? */
#endif
	}
}

518
static int suspend_prepare_image(void)
Pavel Machek's avatar
Pavel Machek committed
519 520 521 522
{
	struct sysinfo i;
	unsigned int nr_needed_pages = 0;

523 524
	drain_local_pages();

Pavel Machek's avatar
Pavel Machek committed
525
	pagedir_nosave = NULL;
526
	printk( "/critical section: Counting pages to copy" );
Pavel Machek's avatar
Pavel Machek committed
527 528 529
	nr_copy_pages = count_and_copy_data_pages(NULL);
	nr_needed_pages = nr_copy_pages + PAGES_FOR_IO;
	
530
	printk(" (pages needed: %d+%d=%d free: %d)\n",nr_copy_pages,PAGES_FOR_IO,nr_needed_pages,nr_free_pages());
Pavel Machek's avatar
Pavel Machek committed
531 532 533
	if(nr_free_pages() < nr_needed_pages) {
		printk(KERN_CRIT "%sCouldn't get enough free pages, on %d pages short\n",
		       name_suspend, nr_needed_pages-nr_free_pages());
Pavel Machek's avatar
Pavel Machek committed
534
		root_swap = 0xFFFF;
Pavel Machek's avatar
Pavel Machek committed
535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555
		return 1;
	}
	si_swapinfo(&i);	/* FIXME: si_swapinfo(&i) returns all swap devices information.
				   We should only consider resume_device. */
	if (i.freeswap < nr_needed_pages)  {
		printk(KERN_CRIT "%sThere's not enough swap space available, on %ld pages short\n",
		       name_suspend, nr_needed_pages-i.freeswap);
		return 1;
	}

	PRINTK( "Alloc pagedir\n" ); 
	pagedir_save = pagedir_nosave = create_suspend_pagedir(nr_copy_pages);
	if(!pagedir_nosave) {
		/* Shouldn't happen */
		printk(KERN_CRIT "%sCouldn't allocate enough pages\n",name_suspend);
		panic("Really should not happen");
		return 1;
	}
	nr_copy_pages_check = nr_copy_pages;
	pagedir_order_check = pagedir_order;

556
	drain_local_pages();	/* During allocating of suspend pagedir, new cold pages may appear. Kill them */
Pavel Machek's avatar
Pavel Machek committed
557
	if (nr_copy_pages != count_and_copy_data_pages(pagedir_nosave))	/* copy */
558
		BUG();
Pavel Machek's avatar
Pavel Machek committed
559 560 561 562 563 564

	/*
	 * End of critical section. From now on, we can write to memory,
	 * but we should not touch disk. This specially means we must _not_
	 * touch swap space! Except we must write out our image of course.
	 */
565

566
	printk( "critical section/: done (%d pages copied)\n", nr_copy_pages );
567 568 569 570 571 572
	return 0;
}

static void suspend_save_image(void)
{
	drivers_unsuspend();
Pavel Machek's avatar
Pavel Machek committed
573 574 575 576 577 578 579 580 581 582 583 584

	lock_swapdevices();
	write_suspend_image();
	lock_swapdevices();	/* This will unlock ignored swap devices since writing is finished */

	/* It is important _NOT_ to umount filesystems at this point. We want
	 * them synced (in case something goes wrong) but we DO not want to mark
	 * filesystem clean: it is not. (And it does not matter, if we resume
	 * correctly, we'll mark system clean, anyway.)
	 */
}

585
static void suspend_power_down(void)
Pavel Machek's avatar
Pavel Machek committed
586
{
587
	extern int C_A_D;
Pavel Machek's avatar
Pavel Machek committed
588
	C_A_D = 0;
589
	printk(KERN_EMERG "%s%s Trying to power down.\n", name_suspend, TEST_SWSUSP ? "Disable TEST_SWSUSP. NOT ": "");
Pavel Machek's avatar
Pavel Machek committed
590
#ifdef CONFIG_VT
591
	PRINTK(KERN_EMERG "shift_state: %04x\n", shift_state);
Pavel Machek's avatar
Pavel Machek committed
592 593 594 595 596
	mdelay(1000);
	if (TEST_SWSUSP ^ (!!(shift_state & (1 << KG_CTRL))))
		machine_restart(NULL);
	else
#endif
Pavel Machek's avatar
Pavel Machek committed
597 598
	{
		device_shutdown();
Pavel Machek's avatar
Pavel Machek committed
599
		machine_power_off();
Pavel Machek's avatar
Pavel Machek committed
600
	}
Pavel Machek's avatar
Pavel Machek committed
601 602 603 604 605 606 607 608 609 610 611

	printk(KERN_EMERG "%sProbably not capable for powerdown. System halted.\n", name_suspend);
	machine_halt();
	while (1);
	/* NOTREACHED */
}

/*
 * Magic happens here
 */

612
void do_magic_resume_1(void)
Pavel Machek's avatar
Pavel Machek committed
613 614 615 616 617
{
	barrier();
	mb();
	spin_lock_irq(&suspend_pagedir_lock);	/* Done to disable interrupts */ 

618
	PRINTK( "Waiting for DMAs to settle down...\n");
Pavel Machek's avatar
Pavel Machek committed
619 620 621 622 623
	mdelay(1000);	/* We do not want some readahead with DMA to corrupt our memory, right?
			   Do it with disabled interrupts for best effect. That way, if some
			   driver scheduled DMA, we have good chance for DMA to finish ;-). */
}

624
void do_magic_resume_2(void)
Pavel Machek's avatar
Pavel Machek committed
625
{
626 627
	BUG_ON (nr_copy_pages_check != nr_copy_pages);
	BUG_ON (pagedir_order_check != pagedir_order);
Pavel Machek's avatar
Pavel Machek committed
628

629 630
	__flush_tlb_global();		/* Even mappings of "global" things (vmalloc) need to be fixed */

631
	PRINTK( "Freeing prev allocated pagedir\n" );
632
	free_suspend_pagedir((unsigned long) pagedir_save);
Pavel Machek's avatar
Pavel Machek committed
633
	spin_unlock_irq(&suspend_pagedir_lock);
634
	drivers_resume(RESUME_ALL_PHASES);
Pavel Machek's avatar
Pavel Machek committed
635 636 637 638 639 640 641 642 643 644

	PRINTK( "Fixing swap signatures... " );
	mark_swapfiles(((swp_entry_t) {0}), MARK_SWAP_RESUME);
	PRINTK( "ok\n" );

#ifdef SUSPEND_CONSOLE
	update_screen(fg_console);	/* Hmm, is this the problem? */
#endif
}

645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662
/* do_magic() is implemented in arch/?/kernel/suspend_asm.S, and basically does:

	if (!resume) {
		do_magic_suspend_1();
		save_processor_state();
		SAVE_REGISTERS
		do_magic_suspend_2();
		return;
	}
	GO_TO_SWAPPER_PAGE_TABLES
	do_magic_resume_1();
	COPY_PAGES_BACK
	RESTORE_REGISTERS
	restore_processor_state();
	do_magic_resume_2();

 */

663
void do_magic_suspend_1(void)
Pavel Machek's avatar
Pavel Machek committed
664 665 666
{
	mb();
	barrier();
667
	BUG_ON(in_atomic());
Pavel Machek's avatar
Pavel Machek committed
668 669 670
	spin_lock_irq(&suspend_pagedir_lock);
}

671
void do_magic_suspend_2(void)
Pavel Machek's avatar
Pavel Machek committed
672
{
673
	int is_problem;
Pavel Machek's avatar
Pavel Machek committed
674
	read_swapfiles();
675 676 677 678 679
	is_problem = suspend_prepare_image();
	spin_unlock_irq(&suspend_pagedir_lock);
	if (!is_problem) {
		kernel_fpu_end();	/* save_processor_state() does kernel_fpu_begin, and we need to revert it in order to pass in_atomic() checks */
		BUG_ON(in_atomic());
680
		suspend_save_image();
Pavel Machek's avatar
Pavel Machek committed
681
		suspend_power_down();	/* FIXME: if suspend_power_down is commented out, console is lost after few suspends ?! */
682
	}
Pavel Machek's avatar
Pavel Machek committed
683

684
	printk(KERN_EMERG "%sSuspend failed, trying to recover...\n", name_suspend);
Pavel Machek's avatar
Pavel Machek committed
685 686 687 688 689 690 691 692 693 694 695 696
	MDELAY(1000); /* So user can wait and report us messages if armageddon comes :-) */

	barrier();
	mb();
	spin_lock_irq(&suspend_pagedir_lock);	/* Done to disable interrupts */ 
	mdelay(1000);

	free_pages((unsigned long) pagedir_nosave, pagedir_order);
	spin_unlock_irq(&suspend_pagedir_lock);
	mark_swapfiles(((swp_entry_t) {0}), MARK_SWAP_RESUME);
}

697
static int do_software_suspend(void)
Pavel Machek's avatar
Pavel Machek committed
698 699
{
	arch_prepare_suspend();
700
	if (pm_prepare_console())
701
		printk( "%sCan't allocate a console... proceeding\n", name_suspend);
Pavel Machek's avatar
Pavel Machek committed
702
	if (!prepare_suspend_processes()) {
703 704 705 706 707

		/* At this point, all user processes and "dangerous"
                   kernel threads are stopped. Free some memory, as we
                   need half of memory free. */

Pavel Machek's avatar
Pavel Machek committed
708 709
		free_some_memory();
		
710 711
		/* No need to invalidate any vfsmnt list -- 
		 * they will be valid after resume, anyway.
Pavel Machek's avatar
Pavel Machek committed
712
		 */
713
		blk_run_queues();
714 715

		/* Save state of all device drivers, and stop them. */		   
716
		if (drivers_suspend()==0)
717 718 719 720 721 722 723 724 725 726
			/* If stopping device drivers worked, we proceed basically into
			 * suspend_save_image.
			 *
			 * do_magic(0) returns after system is resumed.
			 *
			 * do_magic() copies all "used" memory to "free" memory, then
			 * unsuspends all device drivers, and writes memory to disk
			 * using normal kernel mechanism.
			 */
			do_magic(0);
Pavel Machek's avatar
Pavel Machek committed
727
		thaw_processes();
Pavel Machek's avatar
Pavel Machek committed
728 729 730
	}
	software_suspend_enabled = 1;
	MDELAY(1000);
731
	pm_restore_console();
732
	return 0;
Pavel Machek's avatar
Pavel Machek committed
733 734
}

735 736 737 738 739 740

/**
 *	software_suspend - initiate suspend-to-swap transition.
 *
 *	This is main interface to the outside world. It needs to be
 *	called from process context.
Pavel Machek's avatar
Pavel Machek committed
741
 */
742 743

int software_suspend(void)
Pavel Machek's avatar
Pavel Machek committed
744 745
{
	if(!software_suspend_enabled)
746 747
		return -EINVAL;

748 749 750 751 752
	if (num_online_cpus() > 1) {
		printk(KERN_WARNING "swsusp does not support SMP.\n");	
		return -EPERM;
	}

753 754 755 756
#if defined (CONFIG_HIGHMEM) || defined (COFNIG_DISCONTIGMEM)
	printk("swsusp is not supported with high- or discontig-mem.\n");
	return -EPERM;
#endif
Pavel Machek's avatar
Pavel Machek committed
757 758

	software_suspend_enabled = 0;
759
	might_sleep();
760
	return do_software_suspend();
Pavel Machek's avatar
Pavel Machek committed
761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777
}

/* More restore stuff */

/* FIXME: Why not memcpy(to, from, 1<<pagedir_order*PAGE_SIZE)? */
static void copy_pagedir(suspend_pagedir_t *to, suspend_pagedir_t *from)
{
	int i;
	char *topointer=(char *)to, *frompointer=(char *)from;

	for(i=0; i < 1 << pagedir_order; i++) {
		copy_page(topointer, frompointer);
		topointer += PAGE_SIZE;
		frompointer += PAGE_SIZE;
	}
}

Pavel Machek's avatar
Pavel Machek committed
778
#define does_collide(addr) does_collide_order(pagedir_nosave, addr, 0)
Pavel Machek's avatar
Pavel Machek committed
779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808

/*
 * Returns true if given address/order collides with any orig_address 
 */
static int does_collide_order(suspend_pagedir_t *pagedir, unsigned long addr,
		int order)
{
	int i;
	unsigned long addre = addr + (PAGE_SIZE<<order);
	
	for(i=0; i < nr_copy_pages; i++)
		if((pagedir+i)->orig_address >= addr &&
			(pagedir+i)->orig_address < addre)
			return 1;

	return 0;
}

/*
 * We check here that pagedir & pages it points to won't collide with pages
 * where we're going to restore from the loaded pages later
 */
static int check_pagedir(void)
{
	int i;

	for(i=0; i < nr_copy_pages; i++) {
		unsigned long addr;

		do {
809
			addr = get_zeroed_page(GFP_ATOMIC);
Pavel Machek's avatar
Pavel Machek committed
810 811 812 813 814 815 816 817 818 819 820
			if(!addr)
				return -ENOMEM;
		} while (does_collide(addr));

		(pagedir_nosave+i)->address = addr;
	}
	return 0;
}

static int relocate_pagedir(void)
{
Pavel Machek's avatar
Pavel Machek committed
821 822 823 824
	/*
	 * We have to avoid recursion (not to overflow kernel stack),
	 * and that's why code looks pretty cryptic 
	 */
Pavel Machek's avatar
Pavel Machek committed
825 826 827 828
	suspend_pagedir_t *new_pagedir, *old_pagedir = pagedir_nosave;
	void **eaten_memory = NULL;
	void **c = eaten_memory, *m, *f;

829 830
	printk("Relocating pagedir");

Pavel Machek's avatar
Pavel Machek committed
831
	if(!does_collide_order(old_pagedir, (unsigned long)old_pagedir, pagedir_order)) {
832
		printk("not necessary\n");
Pavel Machek's avatar
Pavel Machek committed
833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859
		return 0;
	}

	while ((m = (void *) __get_free_pages(GFP_ATOMIC, pagedir_order))) {
		memset(m, 0, PAGE_SIZE);
		if (!does_collide_order(old_pagedir, (unsigned long)m, pagedir_order))
			break;
		eaten_memory = m;
		printk( "." ); 
		*eaten_memory = c;
		c = eaten_memory;
	}

	if (!m)
		return -ENOMEM;

	pagedir_nosave = new_pagedir = m;
	copy_pagedir(new_pagedir, old_pagedir);

	c = eaten_memory;
	while(c) {
		printk(":");
		f = *c;
		c = *c;
		if (f)
			free_pages((unsigned long)f, pagedir_order);
	}
860
	printk("|\n");
Pavel Machek's avatar
Pavel Machek committed
861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884
	return 0;
}

/*
 * Sanity check if this image makes sense with this kernel/swap context
 * I really don't think that it's foolproof but more than nothing..
 */

static int sanity_check_failed(char *reason)
{
	printk(KERN_ERR "%s%s\n",name_resume,reason);
	return -EPERM;
}

static int sanity_check(struct suspend_header *sh)
{
	if(sh->version_code != LINUX_VERSION_CODE)
		return sanity_check_failed("Incorrect kernel version");
	if(sh->num_physpages != num_physpages)
		return sanity_check_failed("Incorrect memory size");
	if(strncmp(sh->machine, system_utsname.machine, 8))
		return sanity_check_failed("Incorrect machine type");
	if(strncmp(sh->version, system_utsname.version, 20))
		return sanity_check_failed("Incorrect version");
885
	if(sh->num_cpus != num_online_cpus())
Pavel Machek's avatar
Pavel Machek committed
886 887 888 889 890 891
		return sanity_check_failed("Incorrect number of cpus");
	if(sh->page_size != PAGE_SIZE)
		return sanity_check_failed("Incorrect PAGE_SIZE");
	return 0;
}

892
static int bdev_read_page(struct block_device *bdev, long pos, void *buf)
Pavel Machek's avatar
Pavel Machek committed
893 894
{
	struct buffer_head *bh;
895
	BUG_ON (pos%PAGE_SIZE);
Pavel Machek's avatar
Pavel Machek committed
896 897 898 899 900 901 902 903 904 905
	bh = __bread(bdev, pos/PAGE_SIZE, PAGE_SIZE);
	if (!bh || (!bh->b_data)) {
		return -1;
	}
	memcpy(buf, bh->b_data, PAGE_SIZE);	/* FIXME: may need kmap() */
	BUG_ON(!buffer_uptodate(bh));
	brelse(bh);
	return 0;
} 

906
extern dev_t __init name_to_dev_t(const char *line);
Pavel Machek's avatar
Pavel Machek committed
907

908
static int __read_suspend_image(struct block_device *bdev, union diskpage *cur)
Pavel Machek's avatar
Pavel Machek committed
909 910
{
	swp_entry_t next;
911
	int i, nr_pgdir_pages;
Pavel Machek's avatar
Pavel Machek committed
912 913 914

#define PREPARENEXT \
	{	next = cur->link.next; \
915
		next.val = swp_offset(next) * PAGE_SIZE; \
Pavel Machek's avatar
Pavel Machek committed
916 917
        }

918
	if (bdev_read_page(bdev, 0, cur)) return -EIO;
Pavel Machek's avatar
Pavel Machek committed
919 920 921 922

	if ((!memcmp("SWAP-SPACE",cur->swh.magic.magic,10)) ||
	    (!memcmp("SWAPSPACE2",cur->swh.magic.magic,10))) {
		printk(KERN_ERR "%sThis is normal swap space\n", name_resume );
923
		return -EINVAL;
Pavel Machek's avatar
Pavel Machek committed
924 925 926 927
	}

	PREPARENEXT; /* We have to read next position before we overwrite it */

928
	if (!memcmp("S1",cur->swh.magic.magic,2))
Pavel Machek's avatar
Pavel Machek committed
929
		memcpy(cur->swh.magic.magic,"SWAP-SPACE",10);
930
	else if (!memcmp("S2",cur->swh.magic.magic,2))
Pavel Machek's avatar
Pavel Machek committed
931 932
		memcpy(cur->swh.magic.magic,"SWAPSPACE2",10);
	else {
933
		printk("swsusp: %s: Unable to find suspended-data signature (%.10s - misspelled?\n", 
Pavel Machek's avatar
Pavel Machek committed
934
			name_resume, cur->swh.magic.magic);
935
		return -EFAULT;
Pavel Machek's avatar
Pavel Machek committed
936
	}
937

Pavel Machek's avatar
Pavel Machek committed
938 939 940
	printk( "%sSignature found, resuming\n", name_resume );
	MDELAY(1000);

941 942 943
	if (bdev_read_page(bdev, next.val, cur)) return -EIO;
	if (sanity_check(&cur->sh)) 	/* Is this same machine? */	
		return -EPERM;
Pavel Machek's avatar
Pavel Machek committed
944 945 946 947 948 949 950 951
	PREPARENEXT;

	pagedir_save = cur->sh.suspend_pagedir;
	nr_copy_pages = cur->sh.num_pbes;
	nr_pgdir_pages = SUSPEND_PD_PAGES(nr_copy_pages);
	pagedir_order = get_bitmask_order(nr_pgdir_pages);

	pagedir_nosave = (suspend_pagedir_t *)__get_free_pages(GFP_ATOMIC, pagedir_order);
952 953
	if (!pagedir_nosave)
		return -ENOMEM;
Pavel Machek's avatar
Pavel Machek committed
954

955
	PRINTK( "%sReading pagedir, ", name_resume );
Pavel Machek's avatar
Pavel Machek committed
956 957 958

	/* We get pages in reverse order of saving! */
	for (i=nr_pgdir_pages-1; i>=0; i--) {
959
		BUG_ON (!next.val);
Pavel Machek's avatar
Pavel Machek committed
960
		cur = (union diskpage *)((char *) pagedir_nosave)+i;
961
		if (bdev_read_page(bdev, next.val, cur)) return -EIO;
Pavel Machek's avatar
Pavel Machek committed
962 963
		PREPARENEXT;
	}
964
	BUG_ON (next.val);
Pavel Machek's avatar
Pavel Machek committed
965

966 967 968 969
	if (relocate_pagedir())
		return -ENOMEM;
	if (check_pagedir())
		return -ENOMEM;
Pavel Machek's avatar
Pavel Machek committed
970

971
	printk( "Reading image data (%d pages): ", nr_copy_pages );
Pavel Machek's avatar
Pavel Machek committed
972 973 974
	for(i=0; i < nr_copy_pages; i++) {
		swp_entry_t swap_address = (pagedir_nosave+i)->swap_address;
		if (!(i%100))
975
			printk( "." );
Pavel Machek's avatar
Pavel Machek committed
976 977
		/* You do not need to check for overlaps...
		   ... check_pagedir already did this work */
978 979
		if (bdev_read_page(bdev, swp_offset(swap_address) * PAGE_SIZE, (char *)((pagedir_nosave+i)->address)))
			return -EIO;
Pavel Machek's avatar
Pavel Machek committed
980
	}
981 982 983
	printk( "|\n" );
	return 0;
}
Pavel Machek's avatar
Pavel Machek committed
984

985
static int read_suspend_image(const char * specialfile)
986 987 988 989
{
	union diskpage *cur;
	unsigned long scratch_page = 0;
	int error;
990
	char b[BDEVNAME_SIZE];
991

Alexander Viro's avatar
Alexander Viro committed
992
	resume_device = name_to_dev_t(specialfile);
993
	scratch_page = get_zeroed_page(GFP_ATOMIC);
994 995 996
	cur = (void *) scratch_page;
	if (cur) {
		struct block_device *bdev;
997 998
		printk("Resuming from device %s\n",
				__bdevname(resume_device, b));
Alexander Viro's avatar
Alexander Viro committed
999 1000 1001 1002 1003
		bdev = open_by_devnum(resume_device, FMODE_READ, BDEV_RAW);
		if (IS_ERR(bdev)) {
			error = PTR_ERR(bdev);
		} else {
			set_blocksize(bdev, PAGE_SIZE);
1004
			error = __read_suspend_image(bdev, cur);
Alexander Viro's avatar
Alexander Viro committed
1005
			blkdev_put(bdev, BDEV_RAW);
1006 1007 1008 1009 1010
		}
	} else error = -ENOMEM;

	if (scratch_page)
		free_page(scratch_page);
Pavel Machek's avatar
Pavel Machek committed
1011 1012
	switch (error) {
		case 0:
1013
			PRINTK("Reading resume file was successful\n");
Pavel Machek's avatar
Pavel Machek committed
1014 1015 1016 1017 1018 1019 1020 1021
			break;
		case -EINVAL:
			break;
		case -EIO:
			printk( "%sI/O error\n", name_resume);
			break;
		case -ENOENT:
			printk( "%s%s: No such file or directory\n", name_resume, specialfile);
1022 1023 1024
			break;
		case -ENOMEM:
			printk( "%sNot enough memory\n", name_resume);
Pavel Machek's avatar
Pavel Machek committed
1025 1026 1027 1028 1029 1030 1031 1032
			break;
		default:
			printk( "%sError %d resuming\n", name_resume, error );
	}
	MDELAY(1000);
	return error;
}

1033 1034 1035 1036 1037
/**
 *	software_resume - Check and load saved image from swap.
 *
 *	Defined as a late_initcall, so it gets called after all devices
 *	have been probed and initialized, but before we've mounted anything.
Pavel Machek's avatar
Pavel Machek committed
1038 1039
 */

1040
static int software_resume(void)
Pavel Machek's avatar
Pavel Machek committed
1041
{
1042 1043
	if (!strlen(resume_file))
		return 0;
Pavel Machek's avatar
Pavel Machek committed
1044

1045 1046
	if (pm_prepare_console())
		printk("swsusp: Can't allocate a console... proceeding\n");
Pavel Machek's avatar
Pavel Machek committed
1047

1048 1049 1050
	printk("swsusp: %s\n", name_resume );

	MDELAY(1000);
Pavel Machek's avatar
Pavel Machek committed
1051

1052 1053
	printk("swsusp: resuming from %s\n", resume_file);
	if (read_suspend_image(resume_file))
Pavel Machek's avatar
Pavel Machek committed
1054 1055
		goto read_failure;
	do_magic(1);
1056
	printk("swsusp: Resume failed. Continuing.\n");
Pavel Machek's avatar
Pavel Machek committed
1057 1058

read_failure:
1059
	pm_restore_console();
1060
	return -EFAULT;
Pavel Machek's avatar
Pavel Machek committed
1061 1062
}

1063 1064
late_initcall(software_resume);

Pavel Machek's avatar
Pavel Machek committed
1065 1066 1067 1068 1069 1070
static int __init resume_setup(char *str)
{
	strncpy( resume_file, str, 255 );
	return 1;
}

1071
static int __init noresume_setup(char *str)
Pavel Machek's avatar
Pavel Machek committed
1072
{
1073
	resume_file[0] = '\0';
Pavel Machek's avatar
Pavel Machek committed
1074 1075 1076
	return 1;
}

1077
__setup("noresume", noresume_setup);
Pavel Machek's avatar
Pavel Machek committed
1078 1079 1080 1081
__setup("resume=", resume_setup);

EXPORT_SYMBOL(software_suspend);
EXPORT_SYMBOL(software_suspend_enabled);