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 ;
rework pm_ops pm_disk_mode, kill misuse
This patch series cleans up some misconceptions about pm_ops. Some users of
the pm_ops structure attempt to use it to stop the user from entering suspend
to disk, this, however, is not possible since the user can always use
"shutdown" in /sys/power/disk and then the pm_ops are never invoked. Also,
platforms that don't support suspend to disk simply should not allow
configuring SOFTWARE_SUSPEND (read the help text on it, it only selects
suspend to disk and nothing else, all the other stuff depends on PM).
The pm_ops structure is actually intended to provide a way to enter
platform-defined sleep states (currently supported states are "standby" and
"mem" (suspend to ram)) and additionally (if SOFTWARE_SUSPEND is configured)
allows a platform to support a platform specific way to enter low-power mode
once everything has been saved to disk. This is currently only used by ACPI
(S4).
This patch:
The pm_ops.pm_disk_mode is used in totally bogus ways since nobody really
seems to understand what it actually does.
This patch clarifies the pm_disk_mode description.
It also removes all the arm and sh users that think they can veto suspend to
disk via pm_ops; not so since the user can always do echo shutdown >
/sys/power/disk, they need to find a better way involving Kconfig or such.
ACPI is the only user left with a non-zero pm_disk_mode.
The patch also sets the default mode to shutdown again, but when a new pm_ops
is registered its pm_disk_mode is selected as default, that way the default
stays for ACPI where it is apparently required.
Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Cc: David Brownell <david-b@pacbell.net>
Acked-by: Pavel Machek <pavel@ucw.cz>
Cc: <linux-pm@lists.linux-foundation.org>
Cc: Len Brown <lenb@kernel.org>
Acked-by: Russell King <rmk@arm.linux.org.uk>
Cc: Greg KH <greg@kroah.com>
Cc: "Rafael J. Wysocki" <rjw@sisk.pl>
Acked-by: Paul Mundt <lethal@linux-sh.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2007-05-01 02:09:51 +04:00
suspend_disk_method_t pm_disk_mode = PM_DISK_SHUTDOWN ;
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 ;
rework pm_ops pm_disk_mode, kill misuse
This patch series cleans up some misconceptions about pm_ops. Some users of
the pm_ops structure attempt to use it to stop the user from entering suspend
to disk, this, however, is not possible since the user can always use
"shutdown" in /sys/power/disk and then the pm_ops are never invoked. Also,
platforms that don't support suspend to disk simply should not allow
configuring SOFTWARE_SUSPEND (read the help text on it, it only selects
suspend to disk and nothing else, all the other stuff depends on PM).
The pm_ops structure is actually intended to provide a way to enter
platform-defined sleep states (currently supported states are "standby" and
"mem" (suspend to ram)) and additionally (if SOFTWARE_SUSPEND is configured)
allows a platform to support a platform specific way to enter low-power mode
once everything has been saved to disk. This is currently only used by ACPI
(S4).
This patch:
The pm_ops.pm_disk_mode is used in totally bogus ways since nobody really
seems to understand what it actually does.
This patch clarifies the pm_disk_mode description.
It also removes all the arm and sh users that think they can veto suspend to
disk via pm_ops; not so since the user can always do echo shutdown >
/sys/power/disk, they need to find a better way involving Kconfig or such.
ACPI is the only user left with a non-zero pm_disk_mode.
The patch also sets the default mode to shutdown again, but when a new pm_ops
is registered its pm_disk_mode is selected as default, that way the default
stays for ACPI where it is apparently required.
Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Cc: David Brownell <david-b@pacbell.net>
Acked-by: Pavel Machek <pavel@ucw.cz>
Cc: <linux-pm@lists.linux-foundation.org>
Cc: Len Brown <lenb@kernel.org>
Acked-by: Russell King <rmk@arm.linux.org.uk>
Cc: Greg KH <greg@kroah.com>
Cc: "Rafael J. Wysocki" <rjw@sisk.pl>
Acked-by: Paul Mundt <lethal@linux-sh.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2007-05-01 02:09:51 +04:00
if ( ops & & ops - > pm_disk_mode ! = PM_DISK_INVALID ) {
pm_disk_mode = ops - > pm_disk_mode ;
} else
pm_disk_mode = PM_DISK_SHUTDOWN ;
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 ;
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 ;
}
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 ( ) ;
}
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
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 ;
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 ) ;