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>
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"
2007-07-19 12:47:36 +04:00
BLOCKING_NOTIFIER_HEAD ( pm_chain_head ) ;
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 ;
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-05-01 02:09:54 +04:00
/**
* pm_valid_only_mem - generic memory - only valid callback
*
* pm_ops drivers that implement mem suspend only and only need
* to check for that in their . valid callback can use this instead
* of rolling their own . valid callback .
*/
int pm_valid_only_mem ( suspend_state_t state )
{
return state = = PM_SUSPEND_MEM ;
}
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 ;
2007-07-19 12:47:36 +04:00
error = pm_notifier_call_chain ( PM_SUSPEND_PREPARE ) ;
if ( error )
goto Finish ;
2005-04-17 02:20:36 +04:00
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 ;
}
}
2007-07-01 23:07:33 +04:00
if ( pm_ops - > set_target ) {
error = pm_ops - > set_target ( state ) ;
if ( error )
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-05-17 02:28:14 +04:00
goto Resume_console ;
2005-04-17 02:20:36 +04:00
}
2007-05-17 02:28:14 +04:00
if ( pm_ops - > prepare ) {
if ( ( error = pm_ops - > prepare ( state ) ) )
goto Resume_devices ;
}
2007-02-10 12:43:31 +03:00
error = disable_nonboot_cpus ( ) ;
if ( ! error )
return 0 ;
enable_nonboot_cpus ( ) ;
pm_finish ( state ) ;
2007-05-17 02:28:14 +04:00
Resume_devices :
2007-02-10 12:43:31 +03:00
device_resume ( ) ;
2007-05-17 02:28:14 +04:00
Resume_console :
2007-02-10 12:43:31 +03:00
resume_console ( ) ;
2005-04-17 02:20:36 +04:00
Thaw :
thaw_processes ( ) ;
pm_restore_console ( ) ;
2007-07-19 12:47:36 +04:00
Finish :
pm_notifier_call_chain ( PM_POST_SUSPEND ) ;
2005-04-17 02:20:36 +04:00
return error ;
}
2007-04-26 13:43:58 +04:00
/* default implementation */
void __attribute__ ( ( weak ) ) arch_suspend_disable_irqs ( void )
{
local_irq_disable ( ) ;
}
/* default implementation */
void __attribute__ ( ( weak ) ) arch_suspend_enable_irqs ( void )
{
local_irq_enable ( ) ;
}
2005-04-17 02:20:36 +04:00
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 ;
2007-04-26 13:43:58 +04:00
arch_suspend_disable_irqs ( ) ;
BUG_ON ( ! irqs_disabled ( ) ) ;
2005-04-17 02:20:36 +04:00
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 :
2007-04-26 13:43:58 +04:00
arch_suspend_enable_irqs ( ) ;
BUG_ON ( irqs_disabled ( ) ) ;
2005-04-17 02:20:36 +04:00
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 ( ) ;
2007-07-19 12:47:36 +04:00
pm_notifier_call_chain ( PM_POST_SUSPEND ) ;
2005-04-17 02:20:36 +04:00
}
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-11-30 06:34:37 +03:00
static inline int valid_state ( suspend_state_t state )
{
2007-05-09 13:33:18 +04:00
/* All states need lowlevel support and need to be valid
* to the lowlevel implementation , no valid callback
2007-05-01 02:09:55 +04:00
* implies that none 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 ;
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 ;
}
/**
* pm_suspend - Externally visible function for suspending system .
2007-05-09 13:33:18 +04:00
* @ state : Enumerated value of state to enter .
2005-04-17 02:20:36 +04:00
*
* 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 .
*/
2007-04-14 00:15:19 +04:00
static ssize_t state_show ( struct kset * kset , char * buf )
2005-04-17 02:20:36 +04:00
{
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
}
2007-05-09 13:33:18 +04:00
# ifdef CONFIG_SOFTWARE_SUSPEND
s + = sprintf ( s , " %s \n " , " disk " ) ;
# else
if ( s ! = buf )
/* convert the last space to a newline */
* ( s - 1 ) = ' \n ' ;
# endif
2005-04-17 02:20:36 +04:00
return ( s - buf ) ;
}
2007-04-14 00:15:19 +04:00
static ssize_t state_store ( struct kset * kset , const char * buf , size_t n )
2005-04-17 02:20:36 +04:00
{
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 ;
2007-05-09 13:33:18 +04:00
/* First, check if we are requested to hibernate */
2007-05-17 09:11:19 +04:00
if ( len = = 4 & & ! strncmp ( buf , " disk " , len ) ) {
2007-05-09 13:33:18 +04:00
error = hibernate ( ) ;
return error ? error : n ;
}
2005-04-17 02:20:36 +04:00
for ( s = & pm_states [ state ] ; state < PM_SUSPEND_MAX ; s + + , state + + ) {
2007-05-17 09:11:19 +04:00
if ( * s & & len = = strlen ( * s ) & & ! strncmp ( buf , * s , len ) )
2005-04-17 02:20:36 +04:00
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 ;
2007-04-14 00:15:19 +04:00
static ssize_t pm_trace_show ( struct kset * kset , char * buf )
2006-09-26 10:32:58 +04:00
{
return sprintf ( buf , " %d \n " , pm_trace_enabled ) ;
}
static ssize_t
2007-04-14 00:15:19 +04:00
pm_trace_store ( struct kset * kset , const char * buf , size_t n )
2006-09-26 10:32:58 +04:00
{
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 )
2007-04-14 00:15:19 +04:00
error = sysfs_create_group ( & power_subsys . kobj , & attr_group ) ;
2005-04-17 02:20:36 +04:00
return error ;
}
core_initcall ( pm_init ) ;