2005-04-17 02:20:36 +04:00
/*
* kernel / power / disk . c - Suspend - to - disk support .
*
* Copyright ( c ) 2003 Patrick Mochel
* Copyright ( c ) 2003 Open Source Development Lab
* Copyright ( c ) 2004 Pavel Machek < pavel @ suse . cz >
*
* This file is released under the GPLv2 .
*
*/
# include <linux/suspend.h>
# include <linux/syscalls.h>
# include <linux/reboot.h>
# include <linux/string.h>
# include <linux/device.h>
# include <linux/delay.h>
# include <linux/fs.h>
2005-07-13 00:58:07 +04:00
# include <linux/mount.h>
2005-04-17 02:20:36 +04:00
# include "power.h"
extern suspend_disk_method_t pm_disk_mode ;
extern struct pm_ops * pm_ops ;
extern int swsusp_suspend ( void ) ;
extern int swsusp_write ( void ) ;
extern int swsusp_check ( void ) ;
extern int swsusp_read ( void ) ;
extern void swsusp_close ( void ) ;
extern int swsusp_resume ( void ) ;
extern int swsusp_free ( void ) ;
static int noresume = 0 ;
char resume_file [ 256 ] = CONFIG_PM_STD_PARTITION ;
dev_t swsusp_resume_device ;
/**
* power_down - Shut machine down for hibernate .
* @ mode : Suspend - to - disk mode
*
* Use the platform driver , if configured so , and return gracefully if it
* fails .
* Otherwise , try to power off and reboot . If they fail , halt the machine ,
* there ain ' t no turning back .
*/
static void power_down ( suspend_disk_method_t mode )
{
unsigned long flags ;
int error = 0 ;
local_irq_save ( flags ) ;
switch ( mode ) {
case PM_DISK_PLATFORM :
device_shutdown ( ) ;
error = pm_ops - > enter ( PM_SUSPEND_DISK ) ;
break ;
case PM_DISK_SHUTDOWN :
2005-07-26 22:01:17 +04:00
kernel_power_off ( ) ;
2005-04-17 02:20:36 +04:00
break ;
case PM_DISK_REBOOT :
2005-07-26 22:01:17 +04:00
kernel_restart ( NULL ) ;
2005-04-17 02:20:36 +04:00
break ;
}
2005-07-26 22:01:17 +04:00
kernel_halt ( ) ;
2005-04-17 02:20:36 +04:00
/* Valid image is on the disk, if we continue we risk serious data corruption
after resume . */
printk ( KERN_CRIT " Please power me down manually \n " ) ;
while ( 1 ) ;
}
static int in_suspend __nosavedata = 0 ;
/**
* free_some_memory - 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 )
{
unsigned int i = 0 ;
unsigned int tmp ;
unsigned long pages = 0 ;
char * p = " - \\ |/ " ;
printk ( " Freeing memory... " ) ;
while ( ( tmp = shrink_all_memory ( 10000 ) ) ) {
pages + = tmp ;
printk ( " \b %c " , p [ i ] ) ;
i + + ;
if ( i > 3 )
i = 0 ;
}
printk ( " \b done (%li pages freed) \n " , pages ) ;
}
static inline void platform_finish ( void )
{
if ( pm_disk_mode = = PM_DISK_PLATFORM ) {
if ( pm_ops & & pm_ops - > finish )
pm_ops - > finish ( PM_SUSPEND_DISK ) ;
}
}
static void finish ( void )
{
device_resume ( ) ;
platform_finish ( ) ;
thaw_processes ( ) ;
2005-06-26 01:55:06 +04:00
enable_nonboot_cpus ( ) ;
2005-04-17 02:20:36 +04:00
pm_restore_console ( ) ;
}
static int prepare_processes ( void )
{
int error ;
pm_prepare_console ( ) ;
sys_sync ( ) ;
2005-06-26 01:55:06 +04:00
disable_nonboot_cpus ( ) ;
2005-04-17 02:20:36 +04:00
if ( freeze_processes ( ) ) {
error = - EBUSY ;
2005-06-26 01:55:06 +04:00
goto thaw ;
2005-04-17 02:20:36 +04:00
}
if ( pm_disk_mode = = PM_DISK_PLATFORM ) {
if ( pm_ops & & pm_ops - > prepare ) {
if ( ( error = pm_ops - > prepare ( PM_SUSPEND_DISK ) ) )
2005-06-26 01:55:06 +04:00
goto thaw ;
2005-04-17 02:20:36 +04:00
}
}
/* Free memory before shutting down devices. */
free_some_memory ( ) ;
return 0 ;
2005-06-26 01:55:06 +04:00
thaw :
thaw_processes ( ) ;
enable_nonboot_cpus ( ) ;
pm_restore_console ( ) ;
return error ;
2005-04-17 02:20:36 +04:00
}
static void unprepare_processes ( void )
{
2005-06-26 01:55:06 +04:00
platform_finish ( ) ;
2005-04-17 02:20:36 +04:00
thaw_processes ( ) ;
2005-06-26 01:55:06 +04:00
enable_nonboot_cpus ( ) ;
2005-04-17 02:20:36 +04:00
pm_restore_console ( ) ;
}
static int prepare_devices ( void )
{
int error ;
2005-06-26 01:55:06 +04:00
if ( ( error = device_suspend ( PMSG_FREEZE ) ) )
2005-04-17 02:20:36 +04:00
printk ( " Some devices failed to suspend \n " ) ;
2005-06-26 01:55:06 +04:00
return error ;
2005-04-17 02:20:36 +04:00
}
/**
* pm_suspend_disk - The granpappy of power management .
*
* If we ' re going through the firmware , then get it over with quickly .
*
* If not , then call swsusp to do its thing , then figure out how
* to power down the system .
*/
int pm_suspend_disk ( void )
{
int error ;
error = prepare_processes ( ) ;
2005-06-26 01:55:06 +04:00
if ( error )
return error ;
error = prepare_devices ( ) ;
2005-04-17 02:20:36 +04:00
if ( error ) {
unprepare_processes ( ) ;
return error ;
}
pr_debug ( " PM: Attempting to suspend to disk. \n " ) ;
if ( pm_disk_mode = = PM_DISK_FIRMWARE )
return pm_ops - > enter ( PM_SUSPEND_DISK ) ;
pr_debug ( " PM: snapshotting memory. \n " ) ;
in_suspend = 1 ;
if ( ( error = swsusp_suspend ( ) ) )
goto Done ;
if ( in_suspend ) {
pr_debug ( " PM: writing image. \n " ) ;
error = swsusp_write ( ) ;
if ( ! error )
power_down ( pm_disk_mode ) ;
} else
pr_debug ( " PM: Image restored successfully. \n " ) ;
swsusp_free ( ) ;
Done :
finish ( ) ;
return error ;
}
/**
* software_resume - Resume from a saved image .
*
* Called as a late_initcall ( so all devices are discovered and
* initialized ) , we call swsusp to see if we have a saved image or not .
* If so , we quiesce devices , the restore the saved image . We will
* return above ( in pm_suspend_disk ( ) ) if everything goes well .
* Otherwise , we fail gracefully and return to the normally
* scheduled program .
*
*/
static int software_resume ( void )
{
int error ;
2005-07-08 04:56:43 +04:00
if ( ! swsusp_resume_device ) {
if ( ! strlen ( resume_file ) )
return - ENOENT ;
swsusp_resume_device = name_to_dev_t ( resume_file ) ;
pr_debug ( " swsusp: Resume From Partition %s \n " , resume_file ) ;
} else {
pr_debug ( " swsusp: Resume From Partition %d:%d \n " ,
MAJOR ( swsusp_resume_device ) , MINOR ( swsusp_resume_device ) ) ;
}
2005-04-17 02:20:36 +04:00
if ( noresume ) {
/**
* FIXME : If noresume is specified , we need to find the partition
* and reset it back to normal swap space .
*/
return 0 ;
}
pr_debug ( " PM: Checking swsusp image. \n " ) ;
if ( ( error = swsusp_check ( ) ) )
goto Done ;
pr_debug ( " PM: Preparing processes for restore. \n " ) ;
if ( ( error = prepare_processes ( ) ) ) {
swsusp_close ( ) ;
2005-06-26 01:55:06 +04:00
goto Done ;
2005-04-17 02:20:36 +04:00
}
pr_debug ( " PM: Reading swsusp image. \n " ) ;
if ( ( error = swsusp_read ( ) ) )
goto Cleanup ;
pr_debug ( " PM: Preparing devices for restore. \n " ) ;
if ( ( error = prepare_devices ( ) ) )
goto Free ;
mb ( ) ;
pr_debug ( " PM: Restoring saved image. \n " ) ;
swsusp_resume ( ) ;
pr_debug ( " PM: Restore failed, recovering.n " ) ;
finish ( ) ;
Free :
swsusp_free ( ) ;
Cleanup :
unprepare_processes ( ) ;
Done :
pr_debug ( " PM: Resume from disk failed. \n " ) ;
return 0 ;
}
late_initcall ( software_resume ) ;
static char * pm_disk_modes [ ] = {
[ PM_DISK_FIRMWARE ] = " firmware " ,
[ PM_DISK_PLATFORM ] = " platform " ,
[ PM_DISK_SHUTDOWN ] = " shutdown " ,
[ PM_DISK_REBOOT ] = " reboot " ,
} ;
/**
* disk - Control suspend - to - disk mode
*
* Suspend - to - disk can be handled in several ways . The greatest
* distinction is who writes memory to disk - the firmware or the OS .
* If the firmware does it , we assume that it also handles suspending
* the system .
* If the OS does it , then we have three options for putting the system
* to sleep - using the platform driver ( e . g . ACPI or other PM registers ) ,
* powering off the system or rebooting the system ( for testing ) .
*
* The system will support either ' firmware ' or ' platform ' , and that is
* known a priori ( and encoded in pm_ops ) . But , the user may choose
* ' shutdown ' or ' reboot ' as alternatives .
*
* show ( ) will display what the mode is currently set to .
* store ( ) will accept one of
*
* ' firmware '
* ' platform '
* ' shutdown '
* ' reboot '
*
* It will only change to ' firmware ' or ' platform ' if the system
* supports it ( as determined from pm_ops - > pm_disk_mode ) .
*/
static ssize_t disk_show ( struct subsystem * subsys , char * buf )
{
return sprintf ( buf , " %s \n " , pm_disk_modes [ pm_disk_mode ] ) ;
}
static ssize_t disk_store ( struct subsystem * s , const char * buf , size_t n )
{
int error = 0 ;
int i ;
int len ;
char * p ;
suspend_disk_method_t mode = 0 ;
p = memchr ( buf , ' \n ' , n ) ;
len = p ? p - buf : n ;
down ( & pm_sem ) ;
for ( i = PM_DISK_FIRMWARE ; i < PM_DISK_MAX ; i + + ) {
if ( ! strncmp ( buf , pm_disk_modes [ i ] , len ) ) {
mode = i ;
break ;
}
}
if ( mode ) {
if ( mode = = PM_DISK_SHUTDOWN | | mode = = PM_DISK_REBOOT )
pm_disk_mode = mode ;
else {
if ( pm_ops & & pm_ops - > enter & &
( mode = = pm_ops - > pm_disk_mode ) )
pm_disk_mode = mode ;
else
error = - EINVAL ;
}
} else
error = - EINVAL ;
pr_debug ( " PM: suspend-to-disk mode set to '%s' \n " ,
pm_disk_modes [ mode ] ) ;
up ( & pm_sem ) ;
return error ? error : n ;
}
power_attr ( disk ) ;
static ssize_t resume_show ( struct subsystem * subsys , char * buf )
{
return sprintf ( buf , " %d:%d \n " , MAJOR ( swsusp_resume_device ) ,
MINOR ( swsusp_resume_device ) ) ;
}
static ssize_t resume_store ( struct subsystem * subsys , const char * buf , size_t n )
{
int len ;
char * p ;
unsigned int maj , min ;
int error = - EINVAL ;
dev_t res ;
p = memchr ( buf , ' \n ' , n ) ;
len = p ? p - buf : n ;
if ( sscanf ( buf , " %u:%u " , & maj , & min ) = = 2 ) {
res = MKDEV ( maj , min ) ;
if ( maj = = MAJOR ( res ) & & min = = MINOR ( res ) ) {
swsusp_resume_device = res ;
printk ( " Attempting manual resume \n " ) ;
noresume = 0 ;
software_resume ( ) ;
}
}
return error > = 0 ? n : error ;
}
power_attr ( resume ) ;
static struct attribute * g [ ] = {
& disk_attr . attr ,
& resume_attr . attr ,
NULL ,
} ;
static struct attribute_group attr_group = {
. attrs = g ,
} ;
static int __init pm_disk_init ( void )
{
return sysfs_create_group ( & power_subsys . kset . kobj , & attr_group ) ;
}
core_initcall ( pm_disk_init ) ;
static int __init resume_setup ( char * str )
{
if ( noresume )
return 1 ;
strncpy ( resume_file , str , 255 ) ;
return 1 ;
}
static int __init noresume_setup ( char * str )
{
noresume = 1 ;
return 1 ;
}
__setup ( " noresume " , noresume_setup ) ;
__setup ( " resume= " , resume_setup ) ;