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-09-23 08:43:46 +04:00
# include <linux/pm.h>
2005-07-13 00:58:07 +04:00
2005-04-17 02:20:36 +04:00
# include "power.h"
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 )
{
int error = 0 ;
switch ( mode ) {
case PM_DISK_PLATFORM :
2005-12-01 12:29:00 +03:00
kernel_shutdown_prepare ( SYSTEM_SUSPEND_DISK ) ;
2005-04-17 02:20:36 +04:00
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 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 int prepare_processes ( void )
{
int error ;
pm_prepare_console ( ) ;
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
}
/* Free memory before shutting down devices. */
2006-01-06 11:13:46 +03:00
if ( ! ( error = swsusp_shrink_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 ( ) ;
}
/**
* 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 ;
2005-04-17 02:20:36 +04:00
2005-09-04 02:57:05 +04:00
error = device_suspend ( PMSG_FREEZE ) ;
2005-04-17 02:20:36 +04:00
if ( error ) {
2005-09-04 02:57:05 +04:00
printk ( " Some devices failed to suspend \n " ) ;
2005-04-17 02:20:36 +04:00
unprepare_processes ( ) ;
return error ;
}
pr_debug ( " PM: snapshotting memory. \n " ) ;
in_suspend = 1 ;
if ( ( error = swsusp_suspend ( ) ) )
goto Done ;
if ( in_suspend ) {
2005-10-31 02:00:01 +03:00
device_resume ( ) ;
2005-04-17 02:20:36 +04:00
pr_debug ( " PM: writing image. \n " ) ;
2006-03-23 13:59:59 +03:00
error = swsusp_write ( ) ;
2005-04-17 02:20:36 +04:00
if ( ! error )
power_down ( pm_disk_mode ) ;
2005-09-04 02:57:05 +04:00
else {
swsusp_free ( ) ;
unprepare_processes ( ) ;
return error ;
}
2005-04-17 02:20:36 +04:00
} else
pr_debug ( " PM: Image restored successfully. \n " ) ;
2005-09-04 02:57:05 +04:00
2005-04-17 02:20:36 +04:00
swsusp_free ( ) ;
Done :
2005-09-04 02:57:05 +04:00
device_resume ( ) ;
unprepare_processes ( ) ;
2005-04-17 02:20:36 +04:00
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-09-04 02:57:04 +04:00
down ( & pm_sem ) ;
2005-07-08 04:56:43 +04:00
if ( ! swsusp_resume_device ) {
2005-09-04 02:57:04 +04:00
if ( ! strlen ( resume_file ) ) {
up ( & pm_sem ) ;
2005-07-08 04:56:43 +04:00
return - ENOENT ;
2005-09-04 02:57:04 +04:00
}
2005-07-08 04:56:43 +04:00
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 .
*/
2005-09-04 02:57:04 +04:00
up ( & pm_sem ) ;
2005-04-17 02:20:36 +04:00
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 " ) ;
2006-03-23 13:59:59 +03:00
if ( ( error = swsusp_read ( ) ) ) {
2005-10-31 01:59:58 +03:00
swsusp_free ( ) ;
goto Thaw ;
}
2005-04-17 02:20:36 +04:00
pr_debug ( " PM: Preparing devices for restore. \n " ) ;
2005-09-04 02:57:05 +04:00
if ( ( error = device_suspend ( PMSG_FREEZE ) ) ) {
printk ( " Some devices failed to suspend \n " ) ;
2005-10-31 01:59:58 +03:00
swsusp_free ( ) ;
goto Thaw ;
2005-09-04 02:57:05 +04:00
}
2005-04-17 02:20:36 +04:00
mb ( ) ;
pr_debug ( " PM: Restoring saved image. \n " ) ;
swsusp_resume ( ) ;
pr_debug ( " PM: Restore failed, recovering.n " ) ;
2005-09-04 02:57:05 +04:00
device_resume ( ) ;
2005-10-31 01:59:58 +03:00
Thaw :
2005-04-17 02:20:36 +04:00
unprepare_processes ( ) ;
Done :
2005-09-04 02:57:04 +04:00
/* For success case, the suspend path will release the lock */
up ( & pm_sem ) ;
2005-04-17 02:20:36 +04:00
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 ) ) ;
}
2006-01-06 11:09:50 +03:00
static ssize_t resume_store ( struct subsystem * subsys , const char * buf , size_t n )
2005-04-17 02:20:36 +04:00
{
unsigned int maj , min ;
dev_t res ;
2006-01-06 11:09:50 +03:00
int ret = - EINVAL ;
2005-04-17 02:20:36 +04:00
2006-01-06 11:09:50 +03:00
if ( sscanf ( buf , " %u:%u " , & maj , & min ) ! = 2 )
goto out ;
2005-04-17 02:20:36 +04:00
2006-01-06 11:09:50 +03:00
res = MKDEV ( maj , min ) ;
if ( maj ! = MAJOR ( res ) | | min ! = MINOR ( res ) )
goto out ;
2005-04-17 02:20:36 +04:00
2006-01-06 11:09:50 +03:00
down ( & pm_sem ) ;
swsusp_resume_device = res ;
up ( & pm_sem ) ;
printk ( " Attempting manual resume \n " ) ;
noresume = 0 ;
software_resume ( ) ;
ret = n ;
out :
return ret ;
2005-04-17 02:20:36 +04:00
}
power_attr ( resume ) ;
2006-01-06 11:15:56 +03:00
static ssize_t image_size_show ( struct subsystem * subsys , char * buf )
{
2006-02-01 14:05:07 +03:00
return sprintf ( buf , " %lu \n " , image_size ) ;
2006-01-06 11:15:56 +03:00
}
static ssize_t image_size_store ( struct subsystem * subsys , const char * buf , size_t n )
{
2006-02-01 14:05:07 +03:00
unsigned long size ;
2006-01-06 11:15:56 +03:00
2006-02-01 14:05:07 +03:00
if ( sscanf ( buf , " %lu " , & size ) = = 1 ) {
2006-01-06 11:15:56 +03:00
image_size = size ;
return n ;
}
return - EINVAL ;
}
power_attr ( image_size ) ;
2005-04-17 02:20:36 +04:00
static struct attribute * g [ ] = {
& disk_attr . attr ,
& resume_attr . attr ,
2006-01-06 11:15:56 +03:00
& image_size_attr . attr ,
2005-04-17 02:20:36 +04:00
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 ) ;