2005-04-16 15:20:36 -07:00
/*
* linux / kernel / power / swsusp . c
*
2005-10-30 14:59:58 -08:00
* This file provides code to write suspend image to swap and read it back .
2005-04-16 15:20:36 -07:00
*
* Copyright ( C ) 1998 - 2001 Gabor Kuti < seasons @ fornax . hu >
2005-10-30 14:59:56 -08:00
* Copyright ( C ) 1998 , 2001 - 2005 Pavel Machek < pavel @ suse . cz >
2005-04-16 15:20:36 -07:00
*
* This file is released under the GPLv2 .
*
* I ' d like to thank the following people for their work :
2005-06-25 14:55:12 -07:00
*
2005-04-16 15:20:36 -07: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-25 14:55:12 -07:00
* Steve Doddi < dirk @ loth . demon . co . uk > :
2005-04-16 15:20:36 -07: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 00:13:05 -08:00
* Rafael J . Wysocki < rjw @ sisk . pl >
* Added the swap map data structure and reworked the handling of swap
*
2005-04-16 15:20:36 -07:00
* More state savers are welcome . Especially for the scsi layer . . .
*
* For TODOs , FIXMEs also look in Documentation / power / 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/bitops.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>
# include <linux/buffer_head.h>
# include <linux/swapops.h>
# include <linux/bootmem.h>
# include <linux/syscalls.h>
# include <linux/highmem.h>
# include <linux/bio.h>
# include <asm/uaccess.h>
# include <asm/mmu_context.h>
# include <asm/pgtable.h>
# include <asm/tlbflush.h>
# include <asm/io.h>
# include "power.h"
2006-01-06 00:15:56 -08:00
/*
2006-02-01 03:05:07 -08:00
* Preferred image size in bytes ( tunable via / sys / power / image_size ) .
2006-01-06 00:15:56 -08:00
* When it is set to N , swsusp will do its best to ensure the image
2006-02-01 03:05:07 -08:00
* size will not exceed N bytes , but if that is impossible , it will
2006-01-06 00:15:56 -08:00
* try to create the smallest image possible .
*/
2006-02-01 03:05:07 -08:00
unsigned long image_size = 500 * 1024 * 1024 ;
2006-01-06 00:15:56 -08:00
2005-11-08 21:34:41 -08:00
# ifdef CONFIG_HIGHMEM
2006-01-06 00:13:46 -08:00
unsigned int count_highmem_pages ( void ) ;
2005-11-08 21:34:41 -08: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 00:13:46 -08:00
static unsigned int count_highmem_pages ( void ) { return 0 ; }
2005-11-08 21:34:41 -08:00
# endif
2005-04-16 15:20:36 -07:00
extern char resume_file [ ] ;
# define SWSUSP_SIG "S1SUSPEND"
static struct swsusp_header {
2006-01-06 00:12:24 -08:00
char reserved [ PAGE_SIZE - 20 - sizeof ( swp_entry_t ) ] ;
2006-01-06 00:17:58 -08:00
swp_entry_t image ;
2005-04-16 15:20:36 -07:00
char orig_sig [ 10 ] ;
char sig [ 10 ] ;
} __attribute__ ( ( packed , aligned ( PAGE_SIZE ) ) ) swsusp_header ;
static struct swsusp_info swsusp_info ;
/*
* Saving part . . .
*/
2006-01-06 00:17:16 -08:00
static unsigned short root_swap = 0xffff ;
2005-04-16 15:20:36 -07:00
2006-01-06 00:17:58 -08:00
static int mark_swapfiles ( swp_entry_t start )
2005-04-16 15:20:36 -07:00
{
int error ;
2005-06-25 14:55:12 -07:00
rw_swap_page_sync ( READ ,
2005-04-16 15:20:36 -07:00
swp_entry ( root_swap , 0 ) ,
virt_to_page ( ( unsigned long ) & swsusp_header ) ) ;
if ( ! memcmp ( " SWAP-SPACE " , swsusp_header . sig , 10 ) | |
! memcmp ( " SWAPSPACE2 " , swsusp_header . sig , 10 ) ) {
memcpy ( swsusp_header . orig_sig , swsusp_header . sig , 10 ) ;
memcpy ( swsusp_header . sig , SWSUSP_SIG , 10 ) ;
2006-01-06 00:17:58 -08:00
swsusp_header . image = start ;
2005-06-25 14:55:12 -07:00
error = rw_swap_page_sync ( WRITE ,
2005-04-16 15:20:36 -07:00
swp_entry ( root_swap , 0 ) ,
virt_to_page ( ( unsigned long )
& swsusp_header ) ) ;
} else {
pr_debug ( " swsusp: Partition is not swap space. \n " ) ;
error = - ENODEV ;
}
return error ;
}
/*
* Check whether the swap device is the specified resume
* device , irrespective of whether they are specified by
* identical names .
*
* ( Thus , device inode aliasing is allowed . You can say / dev / hda4
* instead of / dev / ide / host0 / bus0 / target0 / lun0 / part4 [ if using devfs ]
* and they ' ll be considered the same device . This is * necessary * for
* devfs , since the resume code can only recognize the form / dev / hda4 ,
* but the suspend code would see the long name . )
*/
2006-01-06 00:17:16 -08:00
static inline int is_resume_device ( const struct swap_info_struct * swap_info )
2005-04-16 15:20:36 -07:00
{
struct file * file = swap_info - > swap_file ;
struct inode * inode = file - > f_dentry - > d_inode ;
return S_ISBLK ( inode - > i_mode ) & &
swsusp_resume_device = = MKDEV ( imajor ( inode ) , iminor ( inode ) ) ;
}
static int swsusp_swap_check ( void ) /* This is called before saving image */
{
int i ;
2005-09-03 15:54:42 -07:00
spin_lock ( & swap_lock ) ;
2006-01-06 00:17:16 -08:00
for ( i = 0 ; i < MAX_SWAPFILES ; i + + ) {
if ( ! ( swap_info [ i ] . flags & SWP_WRITEOK ) )
continue ;
2006-02-17 13:52:51 -08:00
if ( ! swsusp_resume_device | | is_resume_device ( swap_info + i ) ) {
2006-01-06 00:17:16 -08:00
spin_unlock ( & swap_lock ) ;
root_swap = i ;
return 0 ;
2005-04-16 15:20:36 -07:00
}
2006-01-06 00:17:16 -08:00
}
2005-09-03 15:54:42 -07:00
spin_unlock ( & swap_lock ) ;
2006-01-06 00:17:16 -08:00
return - ENODEV ;
2005-04-16 15:20:36 -07:00
}
/**
2005-09-22 21:44:11 -07:00
* write_page - Write one page to a fresh swap location .
2005-04-16 15:20:36 -07:00
* @ addr : Address we ' re writing .
* @ loc : Place to store the entry we used .
*
* Allocate a new swap entry and ' sync ' it . Note we discard - EIO
2005-06-25 14:55:12 -07:00
* errors . That is an artifact left over from swsusp . It did not
2005-04-16 15:20:36 -07:00
* check the return of rw_swap_page_sync ( ) at all , since most pages
* written back to swap would return - EIO .
* This is a partial improvement , since we will at least return other
* errors , though we need to eventually fix the damn code .
*/
2005-11-07 00:58:40 -08:00
static int write_page ( unsigned long addr , swp_entry_t * loc )
2005-04-16 15:20:36 -07:00
{
swp_entry_t entry ;
2006-01-06 00:17:16 -08:00
int error = - ENOSPC ;
2005-04-16 15:20:36 -07:00
2006-01-06 00:17:16 -08:00
entry = get_swap_page_of_type ( root_swap ) ;
if ( swp_offset ( entry ) ) {
error = rw_swap_page_sync ( WRITE , entry , virt_to_page ( addr ) ) ;
if ( ! error | | error = = - EIO )
2005-04-16 15:20:36 -07:00
* loc = entry ;
2006-01-06 00:17:16 -08:00
}
2005-04-16 15:20:36 -07:00
return error ;
}
/**
2006-01-06 00:13:05 -08:00
* Swap map - handling functions
*
* The swap map is a data structure used for keeping track of each page
* written to the swap . It consists of many swap_map_page structures
* that contain each an array of MAP_PAGE_SIZE swap entries .
* These structures are linked together with the help of either the
* . next ( in memory ) or the . next_swap ( in swap ) member .
2005-04-16 15:20:36 -07:00
*
2006-01-06 00:13:05 -08:00
* The swap map is created during suspend . At that time we need to keep
* it in memory , because we have to free all of the allocated swap
* entries if an error occurs . The memory needed is preallocated
* so that we know in advance if there ' s enough of it .
*
* The first swap_map_page structure is filled with the swap entries that
* correspond to the first MAP_PAGE_SIZE data pages written to swap and
* so on . After the all of the data pages have been written , the order
* of the swap_map_page structures in the map is reversed so that they
* can be read from swap in the original order . This causes the data
* pages to be loaded in exactly the same order in which they have been
* saved .
*
* During resume we only need to use one swap_map_page structure
* at a time , which means that we only need to use two memory pages for
* reading the image - one for reading the swap_map_page structures
* and the second for reading the data pages from swap .
2005-04-16 15:20:36 -07:00
*/
2006-01-06 00:13:05 -08:00
# define MAP_PAGE_SIZE ((PAGE_SIZE - sizeof(swp_entry_t) - sizeof(void *)) \
/ sizeof ( swp_entry_t ) )
struct swap_map_page {
swp_entry_t entries [ MAP_PAGE_SIZE ] ;
swp_entry_t next_swap ;
struct swap_map_page * next ;
} ;
static inline void free_swap_map ( struct swap_map_page * swap_map )
2005-04-16 15:20:36 -07:00
{
2006-01-06 00:13:05 -08:00
struct swap_map_page * swp ;
2005-04-16 15:20:36 -07:00
2006-01-06 00:13:05 -08:00
while ( swap_map ) {
swp = swap_map - > next ;
free_page ( ( unsigned long ) swap_map ) ;
swap_map = swp ;
}
}
static struct swap_map_page * alloc_swap_map ( unsigned int nr_pages )
{
struct swap_map_page * swap_map , * swp ;
unsigned n = 0 ;
if ( ! nr_pages )
return NULL ;
pr_debug ( " alloc_swap_map(): nr_pages = %d \n " , nr_pages ) ;
swap_map = ( struct swap_map_page * ) get_zeroed_page ( GFP_ATOMIC ) ;
swp = swap_map ;
for ( n = MAP_PAGE_SIZE ; n < nr_pages ; n + = MAP_PAGE_SIZE ) {
swp - > next = ( struct swap_map_page * ) get_zeroed_page ( GFP_ATOMIC ) ;
swp = swp - > next ;
if ( ! swp ) {
free_swap_map ( swap_map ) ;
return NULL ;
}
2005-04-16 15:20:36 -07:00
}
2006-01-06 00:13:05 -08:00
return swap_map ;
2005-04-16 15:20:36 -07:00
}
/**
2006-01-06 00:13:05 -08:00
* reverse_swap_map - reverse the order of pages in the swap map
* @ swap_map
2005-04-16 15:20:36 -07:00
*/
2006-01-06 00:13:05 -08:00
static inline struct swap_map_page * reverse_swap_map ( struct swap_map_page * swap_map )
2005-04-16 15:20:36 -07:00
{
2006-01-06 00:13:05 -08:00
struct swap_map_page * prev , * next ;
prev = NULL ;
while ( swap_map ) {
next = swap_map - > next ;
swap_map - > next = prev ;
prev = swap_map ;
swap_map = next ;
}
return prev ;
}
2005-04-16 15:20:36 -07:00
2006-01-06 00:13:05 -08:00
/**
* free_swap_map_entries - free the swap entries allocated to store
* the swap map @ swap_map ( this is only called in case of an error )
*/
static inline void free_swap_map_entries ( struct swap_map_page * swap_map )
{
while ( swap_map ) {
if ( swap_map - > next_swap . val )
swap_free ( swap_map - > next_swap ) ;
swap_map = swap_map - > next ;
}
}
2005-04-16 15:20:36 -07:00
2006-01-06 00:13:05 -08:00
/**
* save_swap_map - save the swap map used for tracing the data pages
* stored in the swap
*/
static int save_swap_map ( struct swap_map_page * swap_map , swp_entry_t * start )
{
swp_entry_t entry = ( swp_entry_t ) { 0 } ;
int error ;
while ( swap_map ) {
swap_map - > next_swap = entry ;
if ( ( error = write_page ( ( unsigned long ) swap_map , & entry ) ) )
2005-04-16 15:20:36 -07:00
return error ;
2006-01-06 00:13:05 -08:00
swap_map = swap_map - > next ;
2005-04-16 15:20:36 -07:00
}
2006-01-06 00:13:05 -08:00
* start = entry ;
return 0 ;
}
/**
* free_image_entries - free the swap entries allocated to store
* the image data pages ( this is only called in case of an error )
*/
static inline void free_image_entries ( struct swap_map_page * swp )
{
unsigned k ;
while ( swp ) {
for ( k = 0 ; k < MAP_PAGE_SIZE ; k + + )
if ( swp - > entries [ k ] . val )
swap_free ( swp - > entries [ k ] ) ;
swp = swp - > next ;
}
}
/**
* The swap_map_handle structure is used for handling the swap map in
* a file - alike way
*/
struct swap_map_handle {
struct swap_map_page * cur ;
unsigned int k ;
} ;
static inline void init_swap_map_handle ( struct swap_map_handle * handle ,
struct swap_map_page * map )
{
handle - > cur = map ;
handle - > k = 0 ;
}
static inline int swap_map_write_page ( struct swap_map_handle * handle ,
unsigned long addr )
{
int error ;
error = write_page ( addr , handle - > cur - > entries + handle - > k ) ;
if ( error )
return error ;
if ( + + handle - > k > = MAP_PAGE_SIZE ) {
handle - > cur = handle - > cur - > next ;
handle - > k = 0 ;
}
return 0 ;
}
/**
* save_image_data - save the data pages pointed to by the PBEs
* from the list @ pblist using the swap map handle @ handle
* ( assume there are @ nr_pages data pages to save )
*/
static int save_image_data ( struct pbe * pblist ,
struct swap_map_handle * handle ,
unsigned int nr_pages )
{
unsigned int m ;
struct pbe * p ;
int error = 0 ;
printk ( " Saving image data pages (%u pages) ... " , nr_pages ) ;
m = nr_pages / 100 ;
if ( ! m )
m = 1 ;
nr_pages = 0 ;
for_each_pbe ( p , pblist ) {
error = swap_map_write_page ( handle , p - > address ) ;
if ( error )
break ;
if ( ! ( nr_pages % m ) )
printk ( " \b \b \b \b %3d%% " , nr_pages / m ) ;
nr_pages + + ;
}
if ( ! error )
printk ( " \b \b \b \b done \n " ) ;
2005-04-16 15:20:36 -07:00
return error ;
}
static void dump_info ( void )
{
pr_debug ( " swsusp: Version: %u \n " , swsusp_info . version_code ) ;
pr_debug ( " swsusp: Num Pages: %ld \n " , swsusp_info . num_physpages ) ;
pr_debug ( " swsusp: UTS Sys: %s \n " , swsusp_info . uts . sysname ) ;
pr_debug ( " swsusp: UTS Node: %s \n " , swsusp_info . uts . nodename ) ;
pr_debug ( " swsusp: UTS Release: %s \n " , swsusp_info . uts . release ) ;
pr_debug ( " swsusp: UTS Version: %s \n " , swsusp_info . uts . version ) ;
pr_debug ( " swsusp: UTS Machine: %s \n " , swsusp_info . uts . machine ) ;
pr_debug ( " swsusp: UTS Domain: %s \n " , swsusp_info . uts . domainname ) ;
pr_debug ( " swsusp: CPUs: %d \n " , swsusp_info . cpus ) ;
pr_debug ( " swsusp: Image: %ld Pages \n " , swsusp_info . image_pages ) ;
2006-01-06 00:13:05 -08:00
pr_debug ( " swsusp: Total: %ld Pages \n " , swsusp_info . pages ) ;
2005-04-16 15:20:36 -07:00
}
2006-01-06 00:13:05 -08:00
static void init_header ( unsigned int nr_pages )
2005-04-16 15:20:36 -07:00
{
memset ( & swsusp_info , 0 , sizeof ( swsusp_info ) ) ;
swsusp_info . version_code = LINUX_VERSION_CODE ;
swsusp_info . num_physpages = num_physpages ;
memcpy ( & swsusp_info . uts , & system_utsname , sizeof ( system_utsname ) ) ;
swsusp_info . cpus = num_online_cpus ( ) ;
2006-01-06 00:13:05 -08:00
swsusp_info . image_pages = nr_pages ;
swsusp_info . pages = nr_pages +
2006-01-06 00:17:58 -08:00
( ( nr_pages * sizeof ( long ) + PAGE_SIZE - 1 ) > > PAGE_SHIFT ) + 1 ;
2005-04-16 15:20:36 -07:00
}
/**
2006-01-06 00:13:05 -08:00
* pack_orig_addresses - the . orig_address fields of the PBEs from the
* list starting at @ pbe are stored in the array @ buf [ ] ( 1 page )
2005-04-16 15:20:36 -07:00
*/
2006-01-06 00:13:05 -08:00
static inline struct pbe * pack_orig_addresses ( unsigned long * buf ,
struct pbe * pbe )
2005-04-16 15:20:36 -07:00
{
2006-01-06 00:13:05 -08:00
int j ;
2005-04-16 15:20:36 -07:00
2006-01-06 00:13:05 -08:00
for ( j = 0 ; j < PAGE_SIZE / sizeof ( long ) & & pbe ; j + + ) {
buf [ j ] = pbe - > orig_address ;
pbe = pbe - > next ;
}
if ( ! pbe )
for ( ; j < PAGE_SIZE / sizeof ( long ) ; j + + )
buf [ j ] = 0 ;
return pbe ;
2005-04-16 15:20:36 -07:00
}
/**
2006-01-06 00:13:05 -08:00
* save_image_metadata - save the . orig_address fields of the PBEs
* from the list @ pblist using the swap map handle @ handle
2005-04-16 15:20:36 -07:00
*/
2006-01-06 00:13:05 -08:00
static int save_image_metadata ( struct pbe * pblist ,
struct swap_map_handle * handle )
2005-04-16 15:20:36 -07:00
{
2006-01-06 00:13:05 -08:00
unsigned long * buf ;
2005-11-07 00:58:40 -08:00
unsigned int n = 0 ;
2006-01-06 00:13:05 -08:00
struct pbe * p ;
int error = 0 ;
2005-04-16 15:20:36 -07:00
2006-01-06 00:13:05 -08:00
printk ( " Saving image metadata ... " ) ;
buf = ( unsigned long * ) get_zeroed_page ( GFP_ATOMIC ) ;
if ( ! buf )
return - ENOMEM ;
p = pblist ;
while ( p ) {
p = pack_orig_addresses ( buf , p ) ;
error = swap_map_write_page ( handle , ( unsigned long ) buf ) ;
if ( error )
break ;
n + + ;
2005-04-16 15:20:36 -07:00
}
2006-01-06 00:13:05 -08:00
free_page ( ( unsigned long ) buf ) ;
if ( ! error )
printk ( " done (%u pages saved) \n " , n ) ;
2005-04-16 15:20:36 -07:00
return error ;
}
2005-11-08 21:34:41 -08:00
/**
* enough_swap - Make sure we have enough swap to save the image .
*
* Returns TRUE or FALSE after checking the total amount of swap
2006-01-06 00:17:16 -08:00
* space avaiable from the resume partition .
2005-11-08 21:34:41 -08:00
*/
static int enough_swap ( unsigned int nr_pages )
{
2006-01-06 00:17:16 -08:00
unsigned int free_swap = swap_info [ root_swap ] . pages -
swap_info [ root_swap ] . inuse_pages ;
2005-11-08 21:34:41 -08:00
2006-01-06 00:17:16 -08:00
pr_debug ( " swsusp: free swap pages: %u \n " , free_swap ) ;
return free_swap > ( nr_pages + PAGES_FOR_IO +
2005-11-08 21:34:41 -08:00
( nr_pages + PBES_PER_PAGE - 1 ) / PBES_PER_PAGE ) ;
}
2005-04-16 15:20:36 -07:00
/**
2006-01-06 00:17:16 -08:00
* swsusp_write - Write entire image and metadata .
*
* 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 . )
2005-04-16 15:20:36 -07:00
*/
2006-01-06 00:17:16 -08:00
int swsusp_write ( struct pbe * pblist , unsigned int nr_pages )
2005-04-16 15:20:36 -07:00
{
2006-01-06 00:13:05 -08:00
struct swap_map_page * swap_map ;
struct swap_map_handle handle ;
2006-01-06 00:17:58 -08:00
swp_entry_t start ;
2005-04-16 15:20:36 -07:00
int error ;
2006-01-06 00:17:16 -08:00
if ( ( error = swsusp_swap_check ( ) ) ) {
printk ( KERN_ERR " swsusp: Cannot find swap device, try swapon -a. \n " ) ;
return error ;
}
2006-01-06 00:13:05 -08:00
if ( ! enough_swap ( nr_pages ) ) {
2005-11-08 21:34:41 -08:00
printk ( KERN_ERR " swsusp: Not enough free swap \n " ) ;
return - ENOSPC ;
}
2006-01-06 00:13:05 -08:00
init_header ( nr_pages ) ;
swap_map = alloc_swap_map ( swsusp_info . pages ) ;
if ( ! swap_map )
return - ENOMEM ;
init_swap_map_handle ( & handle , swap_map ) ;
2005-04-16 15:20:36 -07:00
2006-01-06 00:17:58 -08:00
error = swap_map_write_page ( & handle , ( unsigned long ) & swsusp_info ) ;
if ( ! error )
error = save_image_metadata ( pblist , & handle ) ;
2006-01-06 00:13:05 -08:00
if ( ! error )
error = save_image_data ( pblist , & handle , nr_pages ) ;
if ( error )
goto Free_image_entries ;
2005-04-16 15:20:36 -07:00
2006-01-06 00:13:05 -08:00
swap_map = reverse_swap_map ( swap_map ) ;
2006-01-06 00:17:58 -08:00
error = save_swap_map ( swap_map , & start ) ;
2006-01-06 00:13:05 -08:00
if ( error )
goto Free_map_entries ;
2006-01-06 00:17:58 -08:00
dump_info ( ) ;
printk ( " S " ) ;
error = mark_swapfiles ( start ) ;
printk ( " | \n " ) ;
2006-01-06 00:13:05 -08:00
if ( error )
goto Free_map_entries ;
Free_swap_map :
free_swap_map ( swap_map ) ;
2005-04-16 15:20:36 -07:00
return error ;
2006-01-06 00:13:05 -08:00
Free_map_entries :
free_swap_map_entries ( swap_map ) ;
Free_image_entries :
free_image_entries ( swap_map ) ;
goto Free_swap_map ;
2005-04-16 15:20:36 -07:00
}
2006-01-06 00:13:46 -08: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 00:15:22 -08:00
long size , tmp ;
2006-01-06 00:13:46 -08:00
struct zone * zone ;
unsigned long pages = 0 ;
unsigned int i = 0 ;
char * p = " - \\ |/ " ;
printk ( " Shrinking memory... " ) ;
do {
2006-01-06 00:15:22 -08:00
size = 2 * count_highmem_pages ( ) ;
size + = size / 50 + count_data_pages ( ) ;
size + = ( size + PBES_PER_PAGE - 1 ) / PBES_PER_PAGE +
2006-01-06 00:13:46 -08:00
PAGES_FOR_IO ;
2006-01-06 00:15:22 -08:00
tmp = size ;
2006-01-06 00:13:46 -08: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 03:05:07 -08:00
} else if ( size > image_size / PAGE_SIZE ) {
2006-01-06 00:15:22 -08:00
tmp = shrink_all_memory ( SHRINK_BITE ) ;
pages + = tmp ;
2006-01-06 00:13:46 -08:00
}
printk ( " \b %c " , p [ i + + % 4 ] ) ;
} while ( tmp > 0 ) ;
printk ( " \b done (%lu pages freed) \n " , pages ) ;
return 0 ;
}
2005-04-16 15:20:36 -07:00
int swsusp_suspend ( void )
{
int error ;
2005-11-08 21:34:41 -08:00
2005-04-16 15:20:36 -07: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-03 15:57:05 -07:00
printk ( KERN_ERR " Some devices failed to power down, aborting suspend \n " ) ;
2005-11-08 21:34:41 -08:00
goto Enable_irqs ;
2005-04-16 15:20:36 -07:00
}
2005-07-07 17:56:44 -07:00
2005-11-08 21:34:41 -08:00
if ( ( error = save_highmem ( ) ) ) {
printk ( KERN_ERR " swsusp: Not enough free pages for highmem \n " ) ;
goto Restore_highmem ;
2005-07-07 17:56:44 -07:00
}
2005-04-16 15:20:36 -07:00
save_processor_state ( ) ;
if ( ( error = swsusp_arch_suspend ( ) ) )
2005-09-03 15:57:05 -07:00
printk ( KERN_ERR " Error %d suspending \n " , error ) ;
2005-04-16 15:20:36 -07:00
/* Restore control flow magically appears here */
restore_processor_state ( ) ;
2005-11-08 21:34:41 -08:00
Restore_highmem :
2005-04-16 15:20:36 -07:00
restore_highmem ( ) ;
device_power_up ( ) ;
2005-11-08 21:34:41 -08:00
Enable_irqs :
2005-04-16 15:20:36 -07: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-30 14:59:58 -08: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-16 15:20:36 -07:00
restore_processor_state ( ) ;
restore_highmem ( ) ;
2005-09-06 15:16:27 -07:00
touch_softlockup_watchdog ( ) ;
2005-04-16 15:20:36 -07:00
device_power_up ( ) ;
local_irq_enable ( ) ;
return error ;
}
/**
2005-11-08 21:34:40 -08:00
* mark_unsafe_pages - mark the pages that cannot be used for storing
* the image during resume , because they conflict with the pages that
* had been used before suspend
2005-04-16 15:20:36 -07:00
*/
2005-11-08 21:34:40 -08:00
static void mark_unsafe_pages ( struct pbe * pblist )
2005-04-16 15:20:36 -07:00
{
struct zone * zone ;
unsigned long zone_pfn ;
2005-11-08 21:34:40 -08:00
struct pbe * p ;
2005-04-16 15:20:36 -07:00
if ( ! pblist ) /* a sanity check */
2005-11-08 21:34:40 -08:00
return ;
2005-04-16 15:20:36 -07:00
2005-10-30 14:59:58 -08:00
/* Clear page flags */
2005-06-25 14:55:12 -07:00
for_each_zone ( zone ) {
2005-11-08 21:34:40 -08:00
for ( zone_pfn = 0 ; zone_pfn < zone - > spanned_pages ; + + zone_pfn )
if ( pfn_valid ( zone_pfn + zone - > zone_start_pfn ) )
ClearPageNosaveFree ( pfn_to_page ( zone_pfn +
2005-04-16 15:20:36 -07:00
zone - > zone_start_pfn ) ) ;
}
2005-10-30 14:59:58 -08:00
/* Mark orig addresses */
2005-04-16 15:20:36 -07:00
for_each_pbe ( p , pblist )
2005-10-30 14:59:58 -08:00
SetPageNosaveFree ( virt_to_page ( p - > orig_address ) ) ;
2005-04-16 15:20:36 -07:00
2005-11-08 21:34:40 -08:00
}
2005-04-16 15:20:36 -07:00
2005-11-08 21:34:40 -08:00
static void copy_page_backup_list ( struct pbe * dst , struct pbe * src )
{
/* We assume both lists contain the same number of elements */
while ( src ) {
dst - > orig_address = src - > orig_address ;
dst = dst - > next ;
src = src - > next ;
2005-10-30 14:59:58 -08:00
}
2005-04-16 15:20:36 -07:00
}
2005-05-01 08:59:25 -07:00
/*
2005-04-16 15:20:36 -07:00
* Using bio to read from swap .
* This code requires a bit more work than just using buffer heads
* but , it is the recommended way for 2.5 / 2.6 .
* The following are to signal the beginning and end of I / O . Bios
* finish asynchronously , while we want them to happen synchronously .
* A simple atomic_t , and a wait loop take care of this problem .
*/
static atomic_t io_done = ATOMIC_INIT ( 0 ) ;
2005-11-07 00:58:40 -08:00
static int end_io ( struct bio * bio , unsigned int num , int err )
2005-04-16 15:20:36 -07:00
{
if ( ! test_bit ( BIO_UPTODATE , & bio - > bi_flags ) )
panic ( " I/O error reading memory image " ) ;
atomic_set ( & io_done , 0 ) ;
return 0 ;
}
2005-11-07 00:58:40 -08:00
static struct block_device * resume_bdev ;
2005-04-16 15:20:36 -07:00
/**
* submit - submit BIO request .
* @ rw : READ or WRITE .
* @ off physical offset of page .
* @ page : page we ' re reading or writing .
*
* Straight from the textbook - allocate and initialize the bio .
* If we ' re writing , make sure the page is marked as dirty .
* Then submit it and wait .
*/
2005-11-07 00:58:40 -08:00
static int submit ( int rw , pgoff_t page_off , void * page )
2005-04-16 15:20:36 -07:00
{
int error = 0 ;
2005-11-07 00:58:40 -08:00
struct bio * bio ;
2005-04-16 15:20:36 -07:00
bio = bio_alloc ( GFP_ATOMIC , 1 ) ;
if ( ! bio )
return - ENOMEM ;
bio - > bi_sector = page_off * ( PAGE_SIZE > > 9 ) ;
bio - > bi_bdev = resume_bdev ;
bio - > bi_end_io = end_io ;
if ( bio_add_page ( bio , virt_to_page ( page ) , PAGE_SIZE , 0 ) < PAGE_SIZE ) {
printk ( " swsusp: ERROR: adding page to bio at %ld \n " , page_off ) ;
error = - EFAULT ;
goto Done ;
}
atomic_set ( & io_done , 1 ) ;
submit_bio ( rw | ( 1 < < BIO_RW_SYNC ) , bio ) ;
while ( atomic_read ( & io_done ) )
yield ( ) ;
2006-02-07 12:58:22 -08:00
if ( rw = = READ )
bio_set_pages_dirty ( bio ) ;
2005-04-16 15:20:36 -07:00
Done :
bio_put ( bio ) ;
return error ;
}
2005-11-07 00:58:40 -08:00
static int bio_read_page ( pgoff_t page_off , void * page )
2005-04-16 15:20:36 -07:00
{
return submit ( READ , page_off , page ) ;
}
2005-11-07 00:58:40 -08:00
static int bio_write_page ( pgoff_t page_off , void * page )
2005-04-16 15:20:36 -07:00
{
return submit ( WRITE , page_off , page ) ;
}
2006-01-06 00:13:05 -08:00
/**
* The following functions allow us to read data using a swap map
* in a file - alike way
*/
static inline void release_swap_map_reader ( struct swap_map_handle * handle )
{
if ( handle - > cur )
free_page ( ( unsigned long ) handle - > cur ) ;
handle - > cur = NULL ;
}
static inline int get_swap_map_reader ( struct swap_map_handle * handle ,
swp_entry_t start )
{
int error ;
if ( ! swp_offset ( start ) )
return - EINVAL ;
handle - > cur = ( struct swap_map_page * ) get_zeroed_page ( GFP_ATOMIC ) ;
if ( ! handle - > cur )
return - ENOMEM ;
error = bio_read_page ( swp_offset ( start ) , handle - > cur ) ;
if ( error ) {
release_swap_map_reader ( handle ) ;
return error ;
}
handle - > k = 0 ;
return 0 ;
}
static inline int swap_map_read_page ( struct swap_map_handle * handle , void * buf )
{
unsigned long offset ;
int error ;
if ( ! handle - > cur )
return - EINVAL ;
offset = swp_offset ( handle - > cur - > entries [ handle - > k ] ) ;
if ( ! offset )
return - EINVAL ;
error = bio_read_page ( offset , buf ) ;
if ( error )
return error ;
if ( + + handle - > k > = MAP_PAGE_SIZE ) {
handle - > k = 0 ;
offset = swp_offset ( handle - > cur - > next_swap ) ;
if ( ! offset )
release_swap_map_reader ( handle ) ;
else
error = bio_read_page ( offset , handle - > cur ) ;
}
return error ;
}
2006-01-06 00:17:58 -08:00
static int check_header ( void )
2005-04-16 15:20:36 -07:00
{
2006-01-06 00:17:58 -08:00
char * reason = NULL ;
2005-04-16 15:20:36 -07:00
dump_info ( ) ;
2005-07-07 17:56:44 -07:00
if ( swsusp_info . version_code ! = LINUX_VERSION_CODE )
2006-01-06 00:17:58 -08:00
reason = " kernel version " ;
2005-07-07 17:56:44 -07:00
if ( swsusp_info . num_physpages ! = num_physpages )
2006-01-06 00:17:58 -08:00
reason = " memory size " ;
2005-04-16 15:20:36 -07:00
if ( strcmp ( swsusp_info . uts . sysname , system_utsname . sysname ) )
2006-01-06 00:17:58 -08:00
reason = " system type " ;
2005-04-16 15:20:36 -07:00
if ( strcmp ( swsusp_info . uts . release , system_utsname . release ) )
2006-01-06 00:17:58 -08:00
reason = " kernel release " ;
2005-04-16 15:20:36 -07:00
if ( strcmp ( swsusp_info . uts . version , system_utsname . version ) )
2006-01-06 00:17:58 -08:00
reason = " version " ;
2005-04-16 15:20:36 -07:00
if ( strcmp ( swsusp_info . uts . machine , system_utsname . machine ) )
2006-01-06 00:17:58 -08:00
reason = " machine " ;
if ( reason ) {
printk ( KERN_ERR " swsusp: Resume mismatch: %s \n " , reason ) ;
2005-04-16 15:20:36 -07:00
return - EPERM ;
}
2006-01-06 00:17:58 -08:00
return 0 ;
2005-04-16 15:20:36 -07:00
}
/**
2006-01-06 00:13:05 -08:00
* load_image_data - load the image data using the swap map handle
* @ handle and store them using the page backup list @ pblist
* ( assume there are @ nr_pages pages to load )
2005-04-16 15:20:36 -07:00
*/
2006-01-06 00:13:05 -08:00
static int load_image_data ( struct pbe * pblist ,
struct swap_map_handle * handle ,
unsigned int nr_pages )
2005-04-16 15:20:36 -07:00
{
2006-01-06 00:13:05 -08:00
int error ;
unsigned int m ;
2005-11-07 00:58:40 -08:00
struct pbe * p ;
2005-04-16 15:20:36 -07:00
2006-01-06 00:13:05 -08:00
if ( ! pblist )
return - EINVAL ;
printk ( " Loading image data pages (%u pages) ... " , nr_pages ) ;
m = nr_pages / 100 ;
if ( ! m )
m = 1 ;
nr_pages = 0 ;
p = pblist ;
while ( p ) {
error = swap_map_read_page ( handle , ( void * ) p - > address ) ;
if ( error )
break ;
p = p - > next ;
if ( ! ( nr_pages % m ) )
printk ( " \b \b \b \b %3d%% " , nr_pages / m ) ;
nr_pages + + ;
2005-04-16 15:20:36 -07:00
}
2006-01-06 00:13:05 -08:00
if ( ! error )
printk ( " \b \b \b \b done \n " ) ;
2005-04-16 15:20:36 -07:00
return error ;
}
/**
2006-01-06 00:13:05 -08:00
* unpack_orig_addresses - copy the elements of @ buf [ ] ( 1 page ) to
* the PBEs in the list starting at @ pbe
2005-04-16 15:20:36 -07:00
*/
2006-01-06 00:13:05 -08:00
static inline struct pbe * unpack_orig_addresses ( unsigned long * buf ,
struct pbe * pbe )
2005-04-16 15:20:36 -07:00
{
2006-01-06 00:13:05 -08:00
int j ;
2005-04-16 15:20:36 -07:00
2006-01-06 00:13:05 -08:00
for ( j = 0 ; j < PAGE_SIZE / sizeof ( long ) & & pbe ; j + + ) {
pbe - > orig_address = buf [ j ] ;
pbe = pbe - > next ;
}
return pbe ;
}
2005-04-16 15:20:36 -07:00
2006-01-06 00:13:05 -08:00
/**
* load_image_metadata - load the image metadata using the swap map
* handle @ handle and put them into the PBEs in the list @ pblist
*/
2005-04-16 15:20:36 -07:00
2006-01-06 00:13:05 -08:00
static int load_image_metadata ( struct pbe * pblist , struct swap_map_handle * handle )
{
struct pbe * p ;
unsigned long * buf ;
unsigned int n = 0 ;
int error = 0 ;
2005-04-16 15:20:36 -07:00
2006-01-06 00:13:05 -08:00
printk ( " Loading image metadata ... " ) ;
buf = ( unsigned long * ) get_zeroed_page ( GFP_ATOMIC ) ;
if ( ! buf )
return - ENOMEM ;
p = pblist ;
while ( p ) {
error = swap_map_read_page ( handle , buf ) ;
2005-04-16 15:20:36 -07:00
if ( error )
break ;
2006-01-06 00:13:05 -08:00
p = unpack_orig_addresses ( buf , p ) ;
n + + ;
2005-04-16 15:20:36 -07:00
}
2006-01-06 00:13:05 -08:00
free_page ( ( unsigned long ) buf ) ;
2005-10-30 14:59:58 -08:00
if ( ! error )
2006-01-06 00:13:05 -08:00
printk ( " done (%u pages loaded) \n " , n ) ;
2005-04-16 15:20:36 -07:00
return error ;
}
2006-01-06 00:17:58 -08:00
int swsusp_read ( struct pbe * * pblist_ptr )
2005-04-16 15:20:36 -07:00
{
2006-01-06 00:17:58 -08:00
int error ;
2006-01-06 00:13:05 -08:00
struct pbe * p , * pblist ;
struct swap_map_handle handle ;
2006-01-06 00:17:58 -08:00
unsigned int nr_pages ;
2005-04-16 15:20:36 -07:00
2006-01-06 00:17:58 -08:00
if ( IS_ERR ( resume_bdev ) ) {
pr_debug ( " swsusp: block device not initialised \n " ) ;
return PTR_ERR ( resume_bdev ) ;
}
error = get_swap_map_reader ( & handle , swsusp_header . image ) ;
if ( ! error )
error = swap_map_read_page ( & handle , & swsusp_info ) ;
if ( ! error )
error = check_header ( ) ;
if ( error )
return error ;
nr_pages = swsusp_info . image_pages ;
2006-01-06 00:13:05 -08:00
p = alloc_pagedir ( nr_pages , GFP_ATOMIC , 0 ) ;
if ( ! p )
2005-04-16 15:20:36 -07:00
return - ENOMEM ;
2006-01-06 00:13:05 -08:00
error = load_image_metadata ( p , & handle ) ;
if ( ! error ) {
mark_unsafe_pages ( p ) ;
pblist = alloc_pagedir ( nr_pages , GFP_ATOMIC , 1 ) ;
if ( pblist )
copy_page_backup_list ( pblist , p ) ;
free_pagedir ( p ) ;
if ( ! pblist )
error = - ENOMEM ;
/* Allocate memory for the image and read the data from swap */
if ( ! error )
error = alloc_data_pages ( pblist , GFP_ATOMIC , 1 ) ;
2006-01-06 00:13:46 -08:00
if ( ! error ) {
release_eaten_pages ( ) ;
2006-01-06 00:13:05 -08:00
error = load_image_data ( pblist , & handle , nr_pages ) ;
2006-01-06 00:13:46 -08:00
}
2006-01-06 00:13:05 -08:00
if ( ! error )
* pblist_ptr = pblist ;
2005-11-08 21:34:40 -08:00
}
2006-01-06 00:13:05 -08:00
release_swap_map_reader ( & handle ) ;
2006-01-06 00:17:58 -08:00
blkdev_put ( resume_bdev ) ;
if ( ! error )
pr_debug ( " swsusp: Reading resume file was successful \n " ) ;
else
pr_debug ( " swsusp: Error %d resuming \n " , error ) ;
2005-04-16 15:20:36 -07:00
return error ;
}
/**
2006-01-06 00:17:58 -08:00
* swsusp_check - Check for swsusp signature in the resume device
2005-04-16 15:20:36 -07:00
*/
int swsusp_check ( void )
{
int error ;
resume_bdev = open_by_devnum ( swsusp_resume_device , FMODE_READ ) ;
if ( ! IS_ERR ( resume_bdev ) ) {
set_blocksize ( resume_bdev , PAGE_SIZE ) ;
2006-01-06 00:17:58 -08:00
memset ( & swsusp_header , 0 , sizeof ( swsusp_header ) ) ;
if ( ( error = bio_read_page ( 0 , & swsusp_header ) ) )
return error ;
if ( ! memcmp ( SWSUSP_SIG , swsusp_header . sig , 10 ) ) {
memcpy ( swsusp_header . sig , swsusp_header . orig_sig , 10 ) ;
/* Reset swap signature now */
error = bio_write_page ( 0 , & swsusp_header ) ;
} else {
return - EINVAL ;
}
2005-04-16 15:20:36 -07:00
if ( error )
2006-01-06 00:17:58 -08:00
blkdev_put ( resume_bdev ) ;
else
pr_debug ( " swsusp: Signature found, resuming \n " ) ;
} else {
2005-04-16 15:20:36 -07:00
error = PTR_ERR ( resume_bdev ) ;
}
2006-01-06 00:17:58 -08:00
if ( error )
pr_debug ( " swsusp: Error %d check for resume file \n " , error ) ;
2005-04-16 15:20:36 -07:00
return error ;
}
/**
* swsusp_close - close swap device .
*/
void swsusp_close ( void )
{
if ( IS_ERR ( resume_bdev ) ) {
pr_debug ( " swsusp: block device not initialised \n " ) ;
return ;
}
blkdev_put ( resume_bdev ) ;
}