2005-04-17 02:20:36 +04:00
/*
* linux / kernel / power / swsusp . c
*
2005-10-31 01:59:58 +03:00
* This file provides code to write suspend image to swap and read it back .
2005-04-17 02:20:36 +04:00
*
* Copyright ( C ) 1998 - 2001 Gabor Kuti < seasons @ fornax . hu >
2005-10-31 01:59:56 +03:00
* Copyright ( C ) 1998 , 2001 - 2005 Pavel Machek < pavel @ suse . cz >
2005-04-17 02:20:36 +04:00
*
* This file is released under the GPLv2 .
*
* I ' d like to thank the following people for their work :
2005-06-26 01:55:12 +04:00
*
2005-04-17 02:20:36 +04:00
* Pavel Machek < pavel @ ucw . cz > :
* Modifications , defectiveness pointing , being with me at the very beginning ,
* suspend to swap space , stop all tasks . Port to 2.4 .18 - ac and 2.5 .17 .
*
2005-06-26 01:55:12 +04:00
* Steve Doddi < dirk @ loth . demon . co . uk > :
2005-04-17 02:20:36 +04:00
* 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
*
2006-01-06 11:13:05 +03:00
* Rafael J . Wysocki < rjw @ sisk . pl >
2006-03-23 14:00:00 +03:00
* Reworked the freeing of memory and the handling of swap
2006-01-06 11:13:05 +03:00
*
2005-04-17 02:20:36 +04:00
* More state savers are welcome . Especially for the scsi layer . . .
*
* For TODOs , FIXMEs also look in Documentation / power / swsusp . txt
*/
# include <linux/mm.h>
# include <linux/suspend.h>
# include <linux/spinlock.h>
# include <linux/kernel.h>
# include <linux/major.h>
# include <linux/swap.h>
# include <linux/pm.h>
# include <linux/swapops.h>
# include <linux/bootmem.h>
# include <linux/syscalls.h>
# include <linux/highmem.h>
# include "power.h"
2006-01-06 11:15:56 +03:00
/*
2006-02-01 14:05:07 +03:00
* Preferred image size in bytes ( tunable via / sys / power / image_size ) .
2006-01-06 11:15:56 +03:00
* When it is set to N , swsusp will do its best to ensure the image
2006-02-01 14:05:07 +03:00
* size will not exceed N bytes , but if that is impossible , it will
2006-01-06 11:15:56 +03:00
* try to create the smallest image possible .
*/
2006-02-01 14:05:07 +03:00
unsigned long image_size = 500 * 1024 * 1024 ;
2006-01-06 11:15:56 +03:00
2006-03-23 13:59:59 +03:00
int in_suspend __nosavedata = 0 ;
2005-11-09 08:34:41 +03:00
# ifdef CONFIG_HIGHMEM
2006-01-06 11:13:46 +03:00
unsigned int count_highmem_pages ( void ) ;
2005-11-09 08:34:41 +03:00
int save_highmem ( void ) ;
int restore_highmem ( void ) ;
# else
static int save_highmem ( void ) { return 0 ; }
static int restore_highmem ( void ) { return 0 ; }
2006-01-06 11:13:46 +03:00
static unsigned int count_highmem_pages ( void ) { return 0 ; }
2005-11-09 08:34:41 +03:00
# endif
2005-04-17 02:20:36 +04:00
/**
2006-03-23 13:59:59 +03:00
* The following functions are used for tracing the allocated
* swap pages , so that they can be freed in case of an error .
2006-01-06 11:13:05 +03:00
*
2006-03-23 13:59:59 +03:00
* The functions operate on a linked bitmap structure defined
2006-03-23 14:00:00 +03:00
* in power . h
2005-04-17 02:20:36 +04:00
*/
2006-01-06 11:13:05 +03:00
2006-03-23 14:00:00 +03:00
void free_bitmap ( struct bitmap_page * bitmap )
2005-04-17 02:20:36 +04:00
{
2006-03-23 13:59:59 +03:00
struct bitmap_page * bp ;
2005-04-17 02:20:36 +04:00
2006-03-23 13:59:59 +03:00
while ( bitmap ) {
bp = bitmap - > next ;
free_page ( ( unsigned long ) bitmap ) ;
bitmap = bp ;
2006-01-06 11:13:05 +03:00
}
}
2006-03-23 14:00:00 +03:00
struct bitmap_page * alloc_bitmap ( unsigned int nr_bits )
2006-01-06 11:13:05 +03:00
{
2006-03-23 13:59:59 +03:00
struct bitmap_page * bitmap , * bp ;
unsigned int n ;
2006-01-06 11:13:05 +03:00
2006-03-23 13:59:59 +03:00
if ( ! nr_bits )
2006-01-06 11:13:05 +03:00
return NULL ;
2006-03-23 13:59:59 +03:00
bitmap = ( struct bitmap_page * ) get_zeroed_page ( GFP_KERNEL ) ;
bp = bitmap ;
for ( n = BITMAP_PAGE_BITS ; n < nr_bits ; n + = BITMAP_PAGE_BITS ) {
bp - > next = ( struct bitmap_page * ) get_zeroed_page ( GFP_KERNEL ) ;
bp = bp - > next ;
if ( ! bp ) {
free_bitmap ( bitmap ) ;
2006-01-06 11:13:05 +03:00
return NULL ;
}
2005-04-17 02:20:36 +04:00
}
2006-03-23 13:59:59 +03:00
return bitmap ;
2005-04-17 02:20:36 +04:00
}
2006-03-23 13:59:59 +03:00
static int bitmap_set ( struct bitmap_page * bitmap , unsigned long bit )
2005-04-17 02:20:36 +04:00
{
2006-03-23 13:59:59 +03:00
unsigned int n ;
n = BITMAP_PAGE_BITS ;
while ( bitmap & & n < = bit ) {
n + = BITMAP_PAGE_BITS ;
bitmap = bitmap - > next ;
}
if ( ! bitmap )
return - EINVAL ;
n - = BITMAP_PAGE_BITS ;
bit - = n ;
n = 0 ;
while ( bit > = BITS_PER_CHUNK ) {
bit - = BITS_PER_CHUNK ;
n + + ;
2006-01-06 11:13:05 +03:00
}
2006-03-23 13:59:59 +03:00
bitmap - > chunks [ n ] | = ( 1UL < < bit ) ;
return 0 ;
2006-01-06 11:13:05 +03:00
}
2005-04-17 02:20:36 +04:00
2006-03-23 14:00:00 +03:00
unsigned long alloc_swap_page ( int swap , struct bitmap_page * bitmap )
2006-01-06 11:13:05 +03:00
{
2006-03-23 13:59:59 +03:00
unsigned long offset ;
offset = swp_offset ( get_swap_page_of_type ( swap ) ) ;
if ( offset ) {
if ( bitmap_set ( bitmap , offset ) ) {
swap_free ( swp_entry ( swap , offset ) ) ;
offset = 0 ;
}
2006-01-06 11:13:05 +03:00
}
2006-03-23 13:59:59 +03:00
return offset ;
2006-01-06 11:13:05 +03:00
}
2005-04-17 02:20:36 +04:00
2006-03-23 14:00:00 +03:00
void free_all_swap_pages ( int swap , struct bitmap_page * bitmap )
2006-01-06 11:13:05 +03:00
{
2006-03-23 13:59:59 +03:00
unsigned int bit , n ;
unsigned long test ;
2006-01-06 11:13:05 +03:00
2006-03-23 13:59:59 +03:00
bit = 0 ;
while ( bitmap ) {
for ( n = 0 ; n < BITMAP_PAGE_CHUNKS ; n + + )
for ( test = 1UL ; test ; test < < = 1 ) {
if ( bitmap - > chunks [ n ] & test )
swap_free ( swp_entry ( swap , bit ) ) ;
bit + + ;
}
bitmap = bitmap - > next ;
2005-04-17 02:20:36 +04:00
}
2006-01-06 11:13:05 +03:00
}
2006-01-06 11:13:46 +03:00
/**
* swsusp_shrink_memory - Try to free as much memory as needed
*
* . . . but do not OOM - kill anyone
*
* Notice : all userland should be stopped before it is called , or
* livelock is possible .
*/
# define SHRINK_BITE 10000
int swsusp_shrink_memory ( void )
{
2006-01-06 11:15:22 +03:00
long size , tmp ;
2006-01-06 11:13:46 +03:00
struct zone * zone ;
unsigned long pages = 0 ;
unsigned int i = 0 ;
char * p = " - \\ |/ " ;
printk ( " Shrinking memory... " ) ;
do {
2006-01-06 11:15:22 +03:00
size = 2 * count_highmem_pages ( ) ;
size + = size / 50 + count_data_pages ( ) ;
size + = ( size + PBES_PER_PAGE - 1 ) / PBES_PER_PAGE +
2006-01-06 11:13:46 +03:00
PAGES_FOR_IO ;
2006-01-06 11:15:22 +03:00
tmp = size ;
2006-01-06 11:13:46 +03:00
for_each_zone ( zone )
if ( ! is_highmem ( zone ) )
tmp - = zone - > free_pages ;
if ( tmp > 0 ) {
tmp = shrink_all_memory ( SHRINK_BITE ) ;
if ( ! tmp )
return - ENOMEM ;
pages + = tmp ;
2006-02-01 14:05:07 +03:00
} else if ( size > image_size / PAGE_SIZE ) {
2006-01-06 11:15:22 +03:00
tmp = shrink_all_memory ( SHRINK_BITE ) ;
pages + = tmp ;
2006-01-06 11:13:46 +03:00
}
printk ( " \b %c " , p [ i + + % 4 ] ) ;
} while ( tmp > 0 ) ;
printk ( " \b done (%lu pages freed) \n " , pages ) ;
return 0 ;
}
2005-04-17 02:20:36 +04:00
int swsusp_suspend ( void )
{
int error ;
2005-11-09 08:34:41 +03:00
2005-04-17 02:20:36 +04:00
if ( ( error = arch_prepare_suspend ( ) ) )
return error ;
local_irq_disable ( ) ;
/* At this point, device_suspend() has been called, but *not*
* device_power_down ( ) . We * must * device_power_down ( ) now .
* Otherwise , drivers for some devices ( e . g . interrupt controllers )
* become desynchronized with the actual state of the hardware
* at resume time , and evil weirdness ensues .
*/
if ( ( error = device_power_down ( PMSG_FREEZE ) ) ) {
2005-09-04 02:57:05 +04:00
printk ( KERN_ERR " Some devices failed to power down, aborting suspend \n " ) ;
2005-11-09 08:34:41 +03:00
goto Enable_irqs ;
2005-04-17 02:20:36 +04:00
}
2005-07-08 04:56:44 +04:00
2005-11-09 08:34:41 +03:00
if ( ( error = save_highmem ( ) ) ) {
printk ( KERN_ERR " swsusp: Not enough free pages for highmem \n " ) ;
goto Restore_highmem ;
2005-07-08 04:56:44 +04:00
}
2005-04-17 02:20:36 +04:00
save_processor_state ( ) ;
if ( ( error = swsusp_arch_suspend ( ) ) )
2005-09-04 02:57:05 +04:00
printk ( KERN_ERR " Error %d suspending \n " , error ) ;
2005-04-17 02:20:36 +04:00
/* Restore control flow magically appears here */
restore_processor_state ( ) ;
2005-11-09 08:34:41 +03:00
Restore_highmem :
2005-04-17 02:20:36 +04:00
restore_highmem ( ) ;
device_power_up ( ) ;
2005-11-09 08:34:41 +03:00
Enable_irqs :
2005-04-17 02:20:36 +04:00
local_irq_enable ( ) ;
return error ;
}
int swsusp_resume ( void )
{
int error ;
local_irq_disable ( ) ;
if ( device_power_down ( PMSG_FREEZE ) )
printk ( KERN_ERR " Some devices failed to power down, very bad \n " ) ;
/* We'll ignore saved state, but this gets preempt count (etc) right */
save_processor_state ( ) ;
error = swsusp_arch_resume ( ) ;
/* Code below is only ever reached in case of failure. Otherwise
* execution continues at place where swsusp_arch_suspend was called
*/
BUG_ON ( ! error ) ;
2005-10-31 01:59:58 +03:00
/* The only reason why swsusp_arch_resume() can fail is memory being
* very tight , so we have to free it as soon as we can to avoid
* subsequent failures
*/
swsusp_free ( ) ;
2005-04-17 02:20:36 +04:00
restore_processor_state ( ) ;
restore_highmem ( ) ;
2005-09-07 02:16:27 +04:00
touch_softlockup_watchdog ( ) ;
2005-04-17 02:20:36 +04:00
device_power_up ( ) ;
local_irq_enable ( ) ;
return error ;
}