2005-04-17 02:20:36 +04:00
/*
* kernel / power / main . c - PM subsystem core functionality .
*
* Copyright ( c ) 2003 Patrick Mochel
* Copyright ( c ) 2003 Open Source Development Lab
*
* This file is released under the GPLv2
*
*/
2006-12-07 07:36:06 +03:00
# include <linux/module.h>
2005-04-17 02:20:36 +04:00
# include <linux/suspend.h>
# include <linux/kobject.h>
# include <linux/string.h>
# include <linux/delay.h>
# include <linux/errno.h>
# include <linux/init.h>
# include <linux/pm.h>
2006-06-23 01:47:18 +04:00
# include <linux/console.h>
2006-09-26 10:32:48 +04:00
# include <linux/cpu.h>
2006-09-26 10:32:58 +04:00
# include <linux/resume-trace.h>
2006-12-07 07:34:23 +03:00
# include <linux/freezer.h>
2007-02-10 12:43:03 +03:00
# include <linux/vmstat.h>
2005-04-17 02:20:36 +04:00
# include "power.h"
2005-03-19 00:27:13 +03:00
/*This is just an arbitrary number */
# define FREE_PAGE_NUMBER (100)
2006-12-07 07:34:35 +03:00
DEFINE_MUTEX ( pm_mutex ) ;
2005-04-17 02:20:36 +04:00
2005-11-30 06:34:37 +03:00
struct pm_ops * pm_ops ;
2006-11-01 15:23:14 +03:00
suspend_disk_method_t pm_disk_mode = PM_DISK_PLATFORM ;
2005-04-17 02:20:36 +04:00
/**
* pm_set_ops - Set the global power method table .
* @ ops : Pointer to ops structure .
*/
void pm_set_ops ( struct pm_ops * ops )
{
2006-12-07 07:34:35 +03:00
mutex_lock ( & pm_mutex ) ;
2005-04-17 02:20:36 +04:00
pm_ops = ops ;
2006-12-07 07:34:35 +03:00
mutex_unlock ( & pm_mutex ) ;
2005-04-17 02:20:36 +04:00
}
2007-02-10 12:43:31 +03:00
static inline void pm_finish ( suspend_state_t state )
{
if ( pm_ops - > finish )
pm_ops - > finish ( state ) ;
}
2005-04-17 02:20:36 +04:00
/**
* suspend_prepare - Do prep work before entering low - power state .
* @ state : State we ' re entering .
*
* This is common code that is called for each state that we ' re
* entering . Allocate a console , stop all processes , then make sure
* the platform can enter the requested state .
*/
static int suspend_prepare ( suspend_state_t state )
{
2006-09-26 10:32:48 +04:00
int error ;
2005-03-19 00:27:13 +03:00
unsigned int free_pages ;
2005-04-17 02:20:36 +04:00
if ( ! pm_ops | | ! pm_ops - > enter )
return - EPERM ;
pm_prepare_console ( ) ;
if ( freeze_processes ( ) ) {
error = - EAGAIN ;
goto Thaw ;
}
2007-02-10 12:43:03 +03:00
if ( ( free_pages = global_page_state ( NR_FREE_PAGES ) )
< FREE_PAGE_NUMBER ) {
2005-03-19 00:27:13 +03:00
pr_debug ( " PM: free some memory \n " ) ;
shrink_all_memory ( FREE_PAGE_NUMBER - free_pages ) ;
if ( nr_free_pages ( ) < FREE_PAGE_NUMBER ) {
error = - ENOMEM ;
printk ( KERN_ERR " PM: No enough memory \n " ) ;
goto Thaw ;
}
}
2005-04-17 02:20:36 +04:00
if ( pm_ops - > prepare ) {
if ( ( error = pm_ops - > prepare ( state ) ) )
goto Thaw ;
}
2006-06-20 05:16:01 +04:00
suspend_console ( ) ;
2007-02-10 12:43:31 +03:00
error = device_suspend ( PMSG_SUSPEND ) ;
if ( error ) {
2005-04-17 02:20:36 +04:00
printk ( KERN_ERR " Some devices failed to suspend \n " ) ;
2007-02-10 12:43:31 +03:00
goto Resume_devices ;
2005-04-17 02:20:36 +04:00
}
2007-02-10 12:43:31 +03:00
error = disable_nonboot_cpus ( ) ;
if ( ! error )
return 0 ;
enable_nonboot_cpus ( ) ;
Resume_devices :
pm_finish ( state ) ;
device_resume ( ) ;
resume_console ( ) ;
2005-04-17 02:20:36 +04:00
Thaw :
thaw_processes ( ) ;
pm_restore_console ( ) ;
return error ;
}
2006-03-23 14:00:09 +03:00
int suspend_enter ( suspend_state_t state )
2005-04-17 02:20:36 +04:00
{
int error = 0 ;
unsigned long flags ;
local_irq_save ( flags ) ;
if ( ( error = device_power_down ( PMSG_SUSPEND ) ) ) {
printk ( KERN_ERR " Some devices failed to power down \n " ) ;
goto Done ;
}
error = pm_ops - > enter ( state ) ;
device_power_up ( ) ;
Done :
local_irq_restore ( flags ) ;
return error ;
}
/**
* suspend_finish - Do final work before exiting suspend sequence .
* @ state : State we ' re coming out of .
*
* Call platform code to clean up , restart processes , and free the
* console that we ' ve allocated . This is not called for suspend - to - disk .
*/
static void suspend_finish ( suspend_state_t state )
{
2007-02-10 12:43:31 +03:00
enable_nonboot_cpus ( ) ;
pm_finish ( state ) ;
2005-04-17 02:20:36 +04:00
device_resume ( ) ;
2006-06-20 05:16:01 +04:00
resume_console ( ) ;
2005-04-17 02:20:36 +04:00
thaw_processes ( ) ;
pm_restore_console ( ) ;
}
2006-06-25 16:47:56 +04:00
static const char * const pm_states [ PM_SUSPEND_MAX ] = {
2005-04-17 02:20:36 +04:00
[ PM_SUSPEND_STANDBY ] = " standby " ,
[ PM_SUSPEND_MEM ] = " mem " ,
2005-09-04 02:57:06 +04:00
# ifdef CONFIG_SOFTWARE_SUSPEND
2005-04-17 02:20:36 +04:00
[ PM_SUSPEND_DISK ] = " disk " ,
2005-09-04 02:57:06 +04:00
# endif
2005-04-17 02:20:36 +04:00
} ;
2005-11-30 06:34:37 +03:00
static inline int valid_state ( suspend_state_t state )
{
/* Suspend-to-disk does not really need low-level support.
* It can work with reboot if needed . */
if ( state = = PM_SUSPEND_DISK )
return 1 ;
2007-02-16 12:38:29 +03:00
/* all other states need lowlevel support and need to be
* valid to the lowlevel implementation , no valid callback
* implies that all are valid . */
if ( ! pm_ops | | ( pm_ops - > valid & & ! pm_ops - > valid ( state ) ) )
2005-11-30 06:34:37 +03:00
return 0 ;
return 1 ;
}
2005-04-17 02:20:36 +04:00
/**
* enter_state - Do common work of entering low - power state .
* @ state : pm_state structure for state we ' re entering .
*
* Make sure we ' re the only ones trying to enter a sleep state . Fail
* if someone has beat us to it , since we don ' t want anything weird to
* happen when we wake up .
* Then , do the setup for suspend , enter the state , and cleaup ( after
* we ' ve woken up ) .
*/
static int enter_state ( suspend_state_t state )
{
int error ;
2005-11-30 06:34:37 +03:00
if ( ! valid_state ( state ) )
2005-10-31 02:00:01 +03:00
return - ENODEV ;
2006-12-07 07:34:35 +03:00
if ( ! mutex_trylock ( & pm_mutex ) )
2005-04-17 02:20:36 +04:00
return - EBUSY ;
if ( state = = PM_SUSPEND_DISK ) {
error = pm_suspend_disk ( ) ;
goto Unlock ;
}
2005-05-09 19:07:00 +04:00
pr_debug ( " PM: Preparing system for %s sleep \n " , pm_states [ state ] ) ;
2005-04-17 02:20:36 +04:00
if ( ( error = suspend_prepare ( state ) ) )
goto Unlock ;
2005-05-09 19:07:00 +04:00
pr_debug ( " PM: Entering %s sleep \n " , pm_states [ state ] ) ;
2005-04-17 02:20:36 +04:00
error = suspend_enter ( state ) ;
2005-05-09 19:07:00 +04:00
pr_debug ( " PM: Finishing wakeup. \n " ) ;
2005-04-17 02:20:36 +04:00
suspend_finish ( state ) ;
Unlock :
2006-12-07 07:34:35 +03:00
mutex_unlock ( & pm_mutex ) ;
2005-04-17 02:20:36 +04:00
return error ;
}
/*
* This is main interface to the outside world . It needs to be
* called from process context .
*/
int software_suspend ( void )
{
return enter_state ( PM_SUSPEND_DISK ) ;
}
/**
* pm_suspend - Externally visible function for suspending system .
* @ state : Enumarted value of state to enter .
*
* Determine whether or not value is within range , get state
* structure , and enter ( above ) .
*/
int pm_suspend ( suspend_state_t state )
{
2005-03-19 00:20:46 +03:00
if ( state > PM_SUSPEND_ON & & state < = PM_SUSPEND_MAX )
2005-04-17 02:20:36 +04:00
return enter_state ( state ) ;
return - EINVAL ;
}
2006-12-07 07:36:06 +03:00
EXPORT_SYMBOL ( pm_suspend ) ;
2005-04-17 02:20:36 +04:00
decl_subsys ( power , NULL , NULL ) ;
/**
* state - control system power state .
*
* show ( ) returns what states are supported , which is hard - coded to
* ' standby ' ( Power - On Suspend ) , ' mem ' ( Suspend - to - RAM ) , and
* ' disk ' ( Suspend - to - Disk ) .
*
* store ( ) accepts one of those strings , translates it into the
* proper enumerated value , and initiates a suspend transition .
*/
static ssize_t state_show ( struct subsystem * subsys , char * buf )
{
int i ;
char * s = buf ;
for ( i = 0 ; i < PM_SUSPEND_MAX ; i + + ) {
2005-11-30 06:34:37 +03:00
if ( pm_states [ i ] & & valid_state ( i ) )
s + = sprintf ( s , " %s " , pm_states [ i ] ) ;
2005-04-17 02:20:36 +04:00
}
s + = sprintf ( s , " \n " ) ;
return ( s - buf ) ;
}
static ssize_t state_store ( struct subsystem * subsys , const char * buf , size_t n )
{
suspend_state_t state = PM_SUSPEND_STANDBY ;
2006-06-25 16:47:56 +04:00
const char * const * s ;
2005-04-17 02:20:36 +04:00
char * p ;
int error ;
int len ;
p = memchr ( buf , ' \n ' , n ) ;
len = p ? p - buf : n ;
for ( s = & pm_states [ state ] ; state < PM_SUSPEND_MAX ; s + + , state + + ) {
if ( * s & & ! strncmp ( buf , * s , len ) )
break ;
}
2006-04-28 05:39:17 +04:00
if ( state < PM_SUSPEND_MAX & & * s )
2005-04-17 02:20:36 +04:00
error = enter_state ( state ) ;
else
error = - EINVAL ;
return error ? error : n ;
}
power_attr ( state ) ;
2006-09-26 10:32:58 +04:00
# ifdef CONFIG_PM_TRACE
int pm_trace_enabled ;
static ssize_t pm_trace_show ( struct subsystem * subsys , char * buf )
{
return sprintf ( buf , " %d \n " , pm_trace_enabled ) ;
}
static ssize_t
pm_trace_store ( struct subsystem * subsys , const char * buf , size_t n )
{
int val ;
if ( sscanf ( buf , " %d " , & val ) = = 1 ) {
pm_trace_enabled = ! ! val ;
return n ;
}
return - EINVAL ;
}
power_attr ( pm_trace ) ;
static struct attribute * g [ ] = {
& state_attr . attr ,
& pm_trace_attr . attr ,
NULL ,
} ;
# else
2005-04-17 02:20:36 +04:00
static struct attribute * g [ ] = {
& state_attr . attr ,
NULL ,
} ;
2006-09-26 10:32:58 +04:00
# endif /* CONFIG_PM_TRACE */
2005-04-17 02:20:36 +04:00
static struct attribute_group attr_group = {
. attrs = g ,
} ;
static int __init pm_init ( void )
{
int error = subsystem_register ( & power_subsys ) ;
if ( ! error )
error = sysfs_create_group ( & power_subsys . kset . kobj , & attr_group ) ;
return error ;
}
core_initcall ( pm_init ) ;