Commit 9508edb0 authored by Linus Torvalds's avatar Linus Torvalds

Merge bk://kernel.bkbits.net//home/mochel/linux-2.5-power

into home.osdl.org:/home/torvalds/v2.5/linux
parents 7490aafb fcab304b
......@@ -68,7 +68,7 @@ config ACPI_BOOT
config ACPI_SLEEP
bool "Sleep States (EXPERIMENTAL)"
depends on X86 && ACPI
depends on EXPERIMENTAL
depends on EXPERIMENTAL && PM
default y
---help---
This option adds support for ACPI suspend states.
......
......@@ -22,6 +22,7 @@ ACPI_MODULE_NAME ("sleep")
u8 sleep_states[ACPI_S_STATE_COUNT];
extern void do_suspend_lowlevel_s4bios(int);
extern void do_suspend_lowlevel(int);
/**
* acpi_system_restore_state - OS-specific restoration of state
......@@ -71,10 +72,6 @@ acpi_system_restore_state (
* First, we call to the device driver layer to save device state.
* Once we have that, we save whatevery processor and kernel state we
* need to memory.
* If we're entering S4, we then write the memory image to disk.
*
* Only then is it safe for us to power down devices, since we may need
* the disks and upstream buses to write to.
*/
acpi_status
acpi_system_save_state(
......@@ -185,12 +182,11 @@ acpi_system_suspend(
status = acpi_enter_sleep_state(state);
break;
#ifdef CONFIG_SOFTWARE_SUSPEND
case ACPI_STATE_S2:
case ACPI_STATE_S3:
do_suspend_lowlevel(0);
break;
#endif
case ACPI_STATE_S4:
do_suspend_lowlevel_s4bios(0);
break;
......
# Makefile for the Linux device tree
obj-y := core.o sys.o interface.o power.o bus.o \
obj-y := core.o sys.o interface.o bus.o \
driver.o class.o platform.o \
cpu.o firmware.o init.o map.o
obj-y += power/
obj-$(CONFIG_FW_LOADER) += firmware_class.o
obj-$(CONFIG_NUMA) += node.o memblk.o
......@@ -13,3 +13,5 @@ struct class_device_attribute *to_class_dev_attr(struct attribute *_attr)
{
return container_of(_attr,struct class_device_attribute,attr);
}
......@@ -16,6 +16,7 @@
#include <linux/init.h>
#include <linux/string.h>
#include "base.h"
#include "power/power.h"
#define to_dev(node) container_of(node,struct device,bus_list)
#define to_drv(node) container_of(node,struct device_driver,kobj.entry)
......@@ -364,6 +365,7 @@ void device_release_driver(struct device * dev)
if (drv) {
sysfs_remove_link(&drv->kobj,dev->kobj.name);
list_del_init(&dev->driver_list);
device_detach_shutdown(dev);
if (drv->remove)
drv->remove(dev);
dev->driver = NULL;
......
......@@ -20,6 +20,7 @@
#include <asm/semaphore.h>
#include "base.h"
#include "power/power.h"
int (*platform_notify)(struct device * dev) = NULL;
int (*platform_notify_remove)(struct device * dev) = NULL;
......@@ -230,6 +231,8 @@ int device_add(struct device *dev)
bus_add_device(dev);
device_pm_add(dev);
/* notify platform of device entry */
if (platform_notify)
platform_notify(dev);
......@@ -304,6 +307,8 @@ void device_del(struct device * dev)
{
struct device * parent = dev->parent;
device_pm_remove(dev);
down_write(&devices_subsys.rwsem);
if (parent)
list_del_init(&dev->node);
......
......@@ -21,77 +21,39 @@ static ssize_t device_read_name(struct device * dev, char * buf)
static DEVICE_ATTR(name,S_IRUGO,device_read_name,NULL);
static ssize_t
device_read_power(struct device * dev, char * page)
/**
* detach_state - control the default power state for the device.
*
* This is the state the device enters when it's driver module is
* unloaded. The value is an unsigned integer, in the range of 0-4.
* '0' indicates 'On', so no action will be taken when the driver is
* unloaded. This is the default behavior.
* '4' indicates 'Off', meaning the driver core will call the driver's
* shutdown method to quiesce the device.
* 1-3 indicate a low-power state for the device to enter via the
* driver's suspend method.
*/
static ssize_t detach_show(struct device * dev, char * buf)
{
return sprintf(page,"%d\n",dev->power_state);
return sprintf(buf,"%u\n",dev->detach_state);
}
static ssize_t
device_write_power(struct device * dev, const char * buf, size_t count)
static ssize_t detach_store(struct device * dev, const char * buf, size_t n)
{
char str_command[20];
char str_level[20];
int num_args;
u32 state;
u32 int_level;
int error = 0;
if (!dev->driver)
goto done;
num_args = sscanf(buf,"%10s %10s %u",str_command,str_level,&state);
error = -EINVAL;
if (!num_args)
goto done;
if (!strnicmp(str_command,"suspend",7)) {
if (num_args != 3)
goto done;
if (!strnicmp(str_level,"notify",6))
int_level = SUSPEND_NOTIFY;
else if (!strnicmp(str_level,"save",4))
int_level = SUSPEND_SAVE_STATE;
else if (!strnicmp(str_level,"disable",7))
int_level = SUSPEND_DISABLE;
else if (!strnicmp(str_level,"powerdown",8))
int_level = SUSPEND_POWER_DOWN;
else
goto done;
if (dev->driver->suspend)
error = dev->driver->suspend(dev,state,int_level);
else
error = 0;
} else if (!strnicmp(str_command,"resume",6)) {
if (num_args != 2)
goto done;
if (!strnicmp(str_level,"poweron",7))
int_level = RESUME_POWER_ON;
else if (!strnicmp(str_level,"restore",7))
int_level = RESUME_RESTORE_STATE;
else if (!strnicmp(str_level,"enable",6))
int_level = RESUME_ENABLE;
else
goto done;
if (dev->driver->resume)
error = dev->driver->resume(dev,int_level);
else
error = 0;
}
done:
return error < 0 ? error : count;
u32 state;
state = simple_strtoul(buf,NULL,10);
if (state > 4)
return -EINVAL;
dev->detach_state = state;
return n;
}
static DEVICE_ATTR(power,S_IWUSR | S_IRUGO,
device_read_power,device_write_power);
static DEVICE_ATTR(detach_state,0644,detach_show,detach_store);
struct attribute * dev_default_attrs[] = {
&dev_attr_name.attr,
&dev_attr_power.attr,
&dev_attr_detach_state.attr,
NULL,
};
obj-y := shutdown.o
obj-$(CONFIG_PM) += main.o suspend.o resume.o runtime.o sysfs.o
/*
* drivers/base/power/main.c - Where the driver meets power management.
*
* Copyright (c) 2003 Patrick Mochel
* Copyright (c) 2003 Open Source Development Lab
*
* This file is released under the GPLv2
*
*
* The driver model core calls device_pm_add() when a device is registered.
* This will intialize the embedded device_pm_info object in the device
* and add it to the list of power-controlled devices. sysfs entries for
* controlling device power management will also be added.
*
* A different set of lists than the global subsystem list are used to
* keep track of power info because we use different lists to hold
* devices based on what stage of the power management process they
* are in. The power domain dependencies may also differ from the
* ancestral dependencies that the subsystem list maintains.
*/
#define DEBUG
#include <linux/device.h>
#include "power.h"
LIST_HEAD(dpm_active);
LIST_HEAD(dpm_suspended);
LIST_HEAD(dpm_off);
LIST_HEAD(dpm_off_irq);
DECLARE_MUTEX(dpm_sem);
/*
* PM Reference Counting.
*/
static inline void device_pm_hold(struct device * dev)
{
atomic_inc(&dev->power.pm_users);
}
static inline void device_pm_release(struct device * dev)
{
atomic_inc(&dev->power.pm_users);
}
/**
* device_pm_set_parent - Specify power dependency.
* @dev: Device who needs power.
* @parent: Device that supplies power.
*
* This function is used to manually describe a power-dependency
* relationship. It may be used to specify a transversal relationship
* (where the power supplier is not the physical (or electrical)
* ancestor of a specific device.
* The effect of this is that the supplier will not be powered down
* before the power dependent.
*/
void device_pm_set_parent(struct device * dev, struct device * parent)
{
struct device * old_parent = dev->power.pm_parent;
if (old_parent)
device_pm_release(old_parent);
dev->power.pm_parent = parent;
if (parent)
device_pm_hold(parent);
}
EXPORT_SYMBOL(device_pm_set_parent);
int device_pm_add(struct device * dev)
{
int error;
pr_debug("PM: Adding info for %s:%s\n",
dev->bus ? dev->bus->name : "No Bus", dev->kobj.name);
down(&dpm_sem);
list_add_tail(&dev->power.entry,&dpm_active);
device_pm_set_parent(dev,dev->parent);
if ((error = dpm_sysfs_add(dev)))
list_del(&dev->power.entry);
up(&dpm_sem);
return error;
}
void device_pm_remove(struct device * dev)
{
pr_debug("PM: Removing info for %s:%s\n",
dev->bus ? dev->bus->name : "No Bus", dev->kobj.name);
down(&dpm_sem);
dpm_sysfs_remove(dev);
list_del(&dev->power.entry);
up(&dpm_sem);
}
enum {
DEVICE_PM_ON,
DEVICE_PM1,
DEVICE_PM2,
DEVICE_PM3,
DEVICE_PM_OFF,
};
/*
* shutdown.c
*/
extern int device_detach_shutdown(struct device *);
extern void device_shutdown(void);
#ifdef CONFIG_PM
/*
* main.c
*/
/*
* Used to synchronize global power management operations.
*/
extern struct semaphore dpm_sem;
/*
* The PM lists.
*/
extern struct list_head dpm_active;
extern struct list_head dpm_suspended;
extern struct list_head dpm_off;
extern struct list_head dpm_off_irq;
static inline struct dev_pm_info * to_pm_info(struct list_head * entry)
{
return container_of(entry,struct dev_pm_info,entry);
}
static inline struct device * to_device(struct list_head * entry)
{
return container_of(to_pm_info(entry),struct device,power);
}
extern int device_pm_add(struct device *);
extern void device_pm_remove(struct device *);
/*
* sysfs.c
*/
extern int dpm_sysfs_add(struct device *);
extern void dpm_sysfs_remove(struct device *);
/*
* resume.c
*/
extern int dpm_resume(void);
extern void dpm_power_up(void);
extern void dpm_power_up_irq(void);
extern void power_up_device(struct device *);
extern int resume_device(struct device *);
/*
* suspend.c
*/
extern int suspend_device(struct device *, u32);
extern int power_down_device(struct device *, u32);
/*
* runtime.c
*/
extern int dpm_runtime_suspend(struct device *, u32);
extern void dpm_runtime_resume(struct device *);
#else /* CONFIG_PM */
static inline int device_pm_add(struct device * dev)
{
return 0;
}
static inline void device_pm_remove(struct device * dev)
{
}
static inline int dpm_runtime_suspend(struct device * dev, u32 state)
{
return 0;
}
static inline void dpm_runtime_resume(struct device * dev)
{
}
#endif
/*
* resume.c - Functions for waking devices up.
*
* Copyright (c) 2003 Patrick Mochel
* Copyright (c) 2003 Open Source Development Labs
*
* This file is released under the GPLv2
*
*/
#include <linux/device.h>
#include "power.h"
extern int sysdev_resume(void);
extern int sysdev_restore(void);
/**
* resume_device - Restore state for one device.
* @dev: Device.
*
*/
int resume_device(struct device * dev)
{
struct device_driver * drv = dev->driver;
if (drv && drv->resume)
return drv->resume(dev,RESUME_RESTORE_STATE);
return 0;
}
/**
* dpm_resume - Restore all device state.
*
* Walk the dpm_suspended list and restore each device. As they are
* resumed, move the devices to the dpm_active list.
*/
int dpm_resume(void)
{
while(!list_empty(&dpm_suspended)) {
struct list_head * entry = dpm_suspended.next;
struct device * dev = to_device(entry);
list_del_init(entry);
resume_device(dev);
list_add_tail(entry,&dpm_active);
}
return 0;
}
/**
* device_pm_resume - Restore state of each device in system.
*
* Restore system device state, then common device state. Finally,
* release dpm_sem, as we're done with device PM.
*/
void device_pm_resume(void)
{
sysdev_restore();
dpm_resume();
up(&dpm_sem);
}
/**
* power_up_device - Power one device on.
* @dev: Device.
*/
void power_up_device(struct device * dev)
{
struct device_driver * drv = dev->driver;
if (drv && drv->resume)
drv->resume(dev,RESUME_POWER_ON);
}
/**
* device_power_up_irq - Power on some devices.
*
* Walk the dpm_off_irq list and power each device up. This
* is used for devices that required they be powered down with
* interrupts disabled. As devices are powered on, they are moved to
* the dpm_suspended list.
*
* Interrupts must be disabled when calling this.
*/
void dpm_power_up_irq(void)
{
while(!list_empty(&dpm_off_irq)) {
struct list_head * entry = dpm_off_irq.next;
list_del_init(entry);
power_up_device(to_device(entry));
list_add_tail(entry,&dpm_suspended);
}
}
/**
* dpm_power_up - Power on most devices.
*
* Walk the dpm_off list and power each device up. This is used
* to power on devices that were able to power down with interrupts
* enabled.
*/
void dpm_power_up(void)
{
while (!list_empty(&dpm_off)) {
struct list_head * entry = dpm_off.next;
list_del_init(entry);
power_up_device(to_device(entry));
list_add_tail(entry,&dpm_suspended);
}
}
/**
* device_pm_power_up - Turn on all devices.
*
* First, power on system devices, which must happen with interrupts
* disbled. Then, power on devices that also require interrupts disabled.
* Turn interrupts back on, and finally power up the rest of the normal
* devices.
*/
void device_pm_power_up(void)
{
sysdev_resume();
dpm_power_up_irq();
local_irq_enable();
dpm_power_up();
}
/**
* device_resume - resume all the devices in the system
* @level: stage of resume process we're at
*
* This function is deprecated, and should be replaced with appropriate
* calls to device_pm_power_up() and device_pm_resume() above.
*/
void device_resume(u32 level)
{
printk("%s is deprecated. Called from:\n",__FUNCTION__);
dump_stack();
}
/*
* drivers/base/power/runtime.c - Handling dynamic device power management.
*
* Copyright (c) 2003 Patrick Mochel
* Copyright (c) 2003 Open Source Development Lab
*
*/
#include <linux/device.h>
#include "power.h"
static void runtime_resume(struct device * dev)
{
if (!dev->power.power_state)
return;
power_up_device(dev);
resume_device(dev);
}
/**
* dpm_runtime_resume - Power one device back on.
* @dev: Device.
*
* Bring one device back to the on state by first powering it
* on, then restoring state. We only operate on devices that aren't
* already on.
* FIXME: We need to handle devices that are in an unknown state.
*/
void dpm_runtime_resume(struct device * dev)
{
down(&dpm_sem);
runtime_resume(dev);
up(&dpm_sem);
}
/**
* dpm_runtime_suspend - Put one device in low-power state.
* @dev: Device.
* @state: State to enter.
*/
int dpm_runtime_suspend(struct device * dev, u32 state)
{
int error = 0;
down(&dpm_sem);
if (dev->power.power_state == state)
goto Done;
if (dev->power.power_state)
dpm_runtime_resume(dev);
error = suspend_device(dev,state);
if (!error) {
error = power_down_device(dev,state);
if (error)
goto ErrResume;
dev->power.power_state = state;
}
Done:
up(&dpm_sem);
return error;
ErrResume:
resume_device(dev);
goto Done;
}
/**
* dpm_set_power_state - Update power_state field.
* @dev: Device.
* @state: Power state device is in.
*
* This is an update mechanism for drivers to notify the core
* what power state a device is in. Device probing code may not
* always be able to tell, but we need accurate information to
* work reliably.
*/
void dpm_set_power_state(struct device * dev, u32 state)
{
down(&dpm_sem);
dev->power.power_state = state;
up(&dpm_sem);
}
/*
* power.c - power management functions for the device tree.
* shutdown.c - power management functions for the device tree.
*
* Copyright (c) 2002-3 Patrick Mochel
* 2002-3 Open Source Development Lab
*
* This file is released under the GPLv2
*
* Kai Germaschewski contributed to the list walking routines.
*
*/
#undef DEBUG
#include <linux/device.h>
#include <linux/module.h>
#include <asm/semaphore.h>
#include "base.h"
#include "power.h"
#define to_dev(node) container_of(node,struct device,kobj.entry)
extern struct subsystem devices_subsys;
int device_detach_shutdown(struct device * dev)
{
if (!dev->detach_state)
return 0;
if (dev->detach_state == DEVICE_PM_OFF) {
if (dev->driver && dev->driver->shutdown)
dev->driver->shutdown(dev);
return 0;
}
return dpm_runtime_suspend(dev,dev->detach_state);
}
/**
* We handle system devices differently - we suspend and shut them
* down first and resume them first. That way, we do anything stupid like
......@@ -31,88 +44,6 @@ extern struct subsystem devices_subsys;
*/
extern int sysdev_shutdown(void);
extern int sysdev_save(u32 state);
extern int sysdev_suspend(u32 state);
extern int sysdev_resume(void);
extern int sysdev_restore(void);
/**
* device_suspend - suspend/remove all devices on the device ree
* @state: state we're entering
* @level: what stage of the suspend process we're at
* (emb: it seems that these two arguments are described backwards of what
* they actually mean .. is this correct?)
*
* The entries in the global device list are inserted such that they're in a
* depth-first ordering. So, simply interate over the list, and call the
* driver's suspend or remove callback for each device.
*/
int device_suspend(u32 state, u32 level)
{
struct device * dev;
int error = 0;
down_write(&devices_subsys.rwsem);
list_for_each_entry_reverse(dev,&devices_subsys.kset.list,kobj.entry) {
if (dev->driver && dev->driver->suspend) {
pr_debug("suspending device %s\n",dev->name);
error = dev->driver->suspend(dev,state,level);
if (error)
printk(KERN_ERR "%s: suspend returned %d\n",
dev->name,error);
}
}
up_write(&devices_subsys.rwsem);
/*
* Make sure system devices are suspended.
*/
switch(level) {
case SUSPEND_SAVE_STATE:
sysdev_save(state);
break;
case SUSPEND_POWER_DOWN:
sysdev_suspend(state);
break;
default:
break;
}
return error;
}
/**
* device_resume - resume all the devices in the system
* @level: stage of resume process we're at
*
* Similar to device_suspend above, though we want to do a breadth-first
* walk of the tree to make sure we wake up parents before children.
* So, we iterate over the list backward.
*/
void device_resume(u32 level)
{
struct device * dev;
switch (level) {
case RESUME_POWER_ON:
sysdev_resume();
break;
case RESUME_RESTORE_STATE:
sysdev_restore();
break;
default:
break;
}
down_write(&devices_subsys.rwsem);
list_for_each_entry(dev,&devices_subsys.kset.list,kobj.entry) {
if (dev->driver && dev->driver->resume) {
pr_debug("resuming device %s\n",dev->name);
dev->driver->resume(dev,level);
}
}
up_write(&devices_subsys.rwsem);
}
/**
* device_shutdown - call ->remove() on each device to shutdown.
......@@ -135,6 +66,3 @@ void device_shutdown(void)
sysdev_shutdown();
}
EXPORT_SYMBOL(device_suspend);
EXPORT_SYMBOL(device_resume);
EXPORT_SYMBOL(device_shutdown);
/*
* suspend.c - Functions for putting devices to sleep.
*
* Copyright (c) 2003 Patrick Mochel
* Copyright (c) 2003 Open Source Development Labs
*
* This file is released under the GPLv2
*
*/
#include <linux/device.h>
#include "power.h"
extern int sysdev_save(u32 state);
extern int sysdev_suspend(u32 state);
/*
* The entries in the dpm_active list are in a depth first order, simply
* because children are guaranteed to be discovered after parents, and
* are inserted at the back of the list on discovery.
*
* All list on the suspend path are done in reverse order, so we operate
* on the leaves of the device tree (or forests, depending on how you want
* to look at it ;) first. As nodes are removed from the back of the list,
* they are inserted into the front of their destintation lists.
*
* Things are the reverse on the resume path - iterations are done in
* forward order, and nodes are inserted at the back of their destination
* lists. This way, the ancestors will be accessed before their descendents.
*/
/**
* suspend_device - Save state of one device.
* @dev: Device.
* @state: Power state device is entering.
*/
int suspend_device(struct device * dev, u32 state)
{
struct device_driver * drv = dev->driver;
int error = 0;
if (drv && drv->suspend)
error = drv->suspend(dev,state,SUSPEND_SAVE_STATE);
if (!error) {
list_del(&dev->power.entry);
list_add(&dev->power.entry,&dpm_suspended);
}
return error;
}
/**
* device_pm_suspend - Save state and stop all devices in system.
* @state: Power state to put each device in.
*
* Walk the dpm_active list, call ->suspend() for each device, and move
* it to dpm_suspended. If we hit a failure with any of the devices, call
* dpm_resume() above to bring the suspended devices back to life.
*
* Have system devices save state last.
*
* Note this function leaves dpm_sem held to
* a) block other devices from registering.
* b) prevent other PM operations from happening after we've begun.
* c) make sure we're exclusive when we disable interrupts.
*
* device_pm_resume() will release dpm_sem after restoring state to
* all devices (as will this on error). You must call it once you've
* called device_pm_suspend().
*/
int device_pm_suspend(u32 state)
{
int error = 0;
down(&dpm_sem);
while(!list_empty(&dpm_active)) {
struct list_head * entry = dpm_active.prev;
struct device * dev = to_device(entry);
if ((error = suspend_device(dev,state)))
goto Error;
}
if ((error = sysdev_save(state)))
goto Error;
Done:
return error;
Error:
dpm_resume();
up(&dpm_sem);
goto Done;
}
/**
* power_down_device - Put one device in low power state.
* @dev: Device.
* @state: Power state to enter.
*/
int power_down_device(struct device * dev, u32 state)
{
struct device_driver * drv = dev->driver;
int error = 0;
if (drv && drv->suspend)
error = drv->suspend(dev,state,SUSPEND_POWER_DOWN);
if (!error) {
list_del(&dev->power.entry);
list_add(&dev->power.entry,&dpm_off);
}
return error;
}
/**
* dpm_power_down - Put all devices in low power state.
* @state: Power state to enter.
*
* Walk the dpm_suspended list (with interrupts enabled) and try
* to power down each each. If any fail with -EAGAIN, they require
* the call to be done with interrupts disabled. So, we move them to
* the dpm_off_irq list.
*
* If the call succeeds, we move each device to the dpm_off list.
*/
static int dpm_power_down(u32 state)
{
while(!list_empty(&dpm_suspended)) {
struct list_head * entry = dpm_suspended.prev;
int error;
error = power_down_device(to_device(entry),state);
if (error) {
if (error == -EAGAIN) {
list_del(entry);
list_add(entry,&dpm_off_irq);
continue;
}
return error;
}
}
return 0;
}
/**
* dpm_power_down_irq - Power down devices without interrupts.
* @state: State to enter.
*
* Walk the dpm_off_irq list (built by dpm_power_down) and power
* down each device that requires the call to be made with interrupts
* disabled.
*/
static int dpm_power_down_irq(u32 state)
{
struct device * dev;
int error = 0;
list_for_each_entry_reverse(dev,&dpm_off_irq,power.entry) {
if ((error = power_down_device(dev,state)))
break;
}
return error;
}
/**
* device_pm_power_down - Put all devices in low power state.
* @state: Power state to enter.
*
* Walk the dpm_suspended list, calling ->power_down() for each device.
* Check the return value for each. If it returns 0, then we move the
* the device to the dpm_off list. If it returns -EAGAIN, we move it to
* the dpm_off_irq list. If we get a different error, try and back out.
*
* dpm_irq_off is for devices that require interrupts to be disabled to
* either to power down the device or power it back on.
*
* When we're done, we disable interrrupts (!!) and walk the dpm_off_irq
* list to shut down the devices that need interrupts disabled.
*
* This function leaves interrupts disabled on exit, since powering down
* devices should be the very last thing before the system is put into a
* low-power state.
*
* device_pm_power_on() should be called to re-enable interrupts and power
* the devices back on.
*/
int device_pm_power_down(u32 state)
{
int error = 0;
if ((error = dpm_power_down(state)))
goto ErrorIRQOn;
local_irq_disable();
if ((error = dpm_power_down_irq(state)))
goto ErrorIRQOff;
sysdev_suspend(state);
Done:
return error;
ErrorIRQOff:
dpm_power_up_irq();
local_irq_enable();
ErrorIRQOn:
dpm_power_up();
goto Done;
}
/**
* device_suspend - suspend all devices on the device ree
* @state: state we're entering
* @level: Stage of suspend sequence we're in.
*
*
* This function is deprecated. Calls should be replaced with
* appropriate calls to device_pm_suspend() and device_pm_power_down().
*/
int device_suspend(u32 state, u32 level)
{
printk("%s Called from:\n",__FUNCTION__);
dump_stack();
return -EFAULT;
}
/*
* drivers/base/power/sysfs.c - sysfs entries for device PM
*/
#include <linux/device.h>
#include "power.h"
/**
* state - Control current power state of device
*
* show() returns the current power state of the device. '0' indicates
* the device is on. Other values (1-3) indicate the device is in a low
* power state.
*
* store() sets the current power state, which is an integer value
* between 0-3. If the device is on ('0'), and the value written is
* greater than 0, then the device is placed directly into the low-power
* state (via its driver's ->suspend() method).
* If the device is currently in a low-power state, and the value is 0,
* the device is powered back on (via the ->resume() method).
* If the device is in a low-power state, and a different low-power state
* is requested, the device is first resumed, then suspended into the new
* low-power state.
*/
static ssize_t state_show(struct device * dev, char * buf)
{
return sprintf(buf,"%u\n",dev->power.power_state);
}
static ssize_t state_store(struct device * dev, const char * buf, size_t n)
{
u32 state;
char * rest;
int error = 0;
state = simple_strtoul(buf,&rest,10);
if (rest)
return -EINVAL;
if (state)
error = dpm_runtime_suspend(dev,state);
else
dpm_runtime_resume(dev);
return error ? error : n;
}
static DEVICE_ATTR(state,0644,state_show,state_store);
static struct attribute * power_attrs[] = {
&dev_attr_state.attr,
NULL,
};
static struct attribute_group pm_attr_group = {
.name = "power",
.attrs = power_attrs,
};
int dpm_sysfs_add(struct device * dev)
{
return sysfs_create_group(&dev->kobj,&pm_attr_group);
}
void dpm_sysfs_remove(struct device * dev)
{
sysfs_remove_group(&dev->kobj,&pm_attr_group);
}
......@@ -19,6 +19,7 @@
#include <linux/types.h>
#include <linux/ioport.h>
#include <linux/module.h>
#include <linux/pm.h>
#include <asm/semaphore.h>
#include <asm/atomic.h>
......@@ -41,13 +42,6 @@ enum {
RESUME_ENABLE,
};
enum device_state {
DEVICE_UNINITIALIZED = 0,
DEVICE_INITIALIZED = 1,
DEVICE_REGISTERED = 2,
DEVICE_GONE = 3,
};
struct device;
struct device_driver;
struct class;
......@@ -64,8 +58,8 @@ struct bus_type {
struct device * (*add) (struct device * parent, char * bus_id);
int (*hotplug) (struct device *dev, char **envp,
int num_envp, char *buffer, int buffer_size);
};
};
extern int bus_register(struct bus_type * bus);
extern void bus_unregister(struct bus_type * bus);
......@@ -269,13 +263,16 @@ struct device {
void *driver_data; /* data private to the driver */
void *platform_data; /* Platform specific data (e.g. ACPI,
BIOS data relevant to device) */
struct dev_pm_info power;
u32 power_state; /* Current operating state. In
ACPI-speak, this is D0-D3, D0
being fully functional, and D3
being off. */
unsigned char *saved_state; /* saved device state */
u32 detach_state; /* State to enter when device is
detached from its driver. */
u64 *dma_mask; /* dma mask (if dma'able device) */
void (*release)(struct device * dev);
......
......@@ -25,6 +25,7 @@
#include <linux/config.h>
#include <linux/list.h>
#include <asm/atomic.h>
/*
* Power management requests
......@@ -118,29 +119,29 @@ extern int pm_active;
/*
* Register a device with power management
*/
struct pm_dev *pm_register(pm_dev_t type,
unsigned long id,
pm_callback callback);
struct pm_dev __deprecated *pm_register(pm_dev_t type,
unsigned long id,
pm_callback callback);
/*
* Unregister a device with power management
*/
void pm_unregister(struct pm_dev *dev);
void __deprecated pm_unregister(struct pm_dev *dev);
/*
* Unregister all devices with matching callback
*/
void pm_unregister_all(pm_callback callback);
void __deprecated pm_unregister_all(pm_callback callback);
/*
* Send a request to a single device
*/
int pm_send(struct pm_dev *dev, pm_request_t rqst, void *data);
int __deprecated pm_send(struct pm_dev *dev, pm_request_t rqst, void *data);
/*
* Send a request to all devices
*/
int pm_send_all(pm_request_t rqst, void *data);
int __deprecated pm_send_all(pm_request_t rqst, void *data);
/*
* Find a device
......@@ -188,6 +189,26 @@ static inline void pm_dev_idle(struct pm_dev *dev) {}
extern void (*pm_idle)(void);
extern void (*pm_power_off)(void);
struct device;
struct dev_pm_info {
#ifdef CONFIG_PM
u32 power_state;
u8 * saved_state;
atomic_t pm_users;
struct device * pm_parent;
struct list_head entry;
#endif
};
extern void device_pm_set_parent(struct device * dev, struct device * parent);
extern int device_pm_suspend(u32 state);
extern int device_pm_power_down(u32 state);
extern void device_pm_power_up(void);
extern void device_pm_resume(void);
#endif /* __KERNEL__ */
#endif /* _LINUX_PM_H */
......@@ -21,7 +21,7 @@
* CAD_OFF Ctrl-Alt-Del sequence sends SIGINT to init task.
* POWER_OFF Stop OS and remove all power from system, if possible.
* RESTART2 Restart system using given command string.
* SW_SUSPEND Suspend system using Software Suspend if compiled in
* SW_SUSPEND Suspend system using software suspend if compiled in.
*/
#define LINUX_REBOOT_CMD_RESTART 0x01234567
......
......@@ -11,9 +11,6 @@
extern unsigned char software_suspend_enabled;
#define NORESUME 1
#define RESUME_SPECIFIED 2
#ifdef CONFIG_SOFTWARE_SUSPEND
/* page backup entry */
typedef struct pbe {
......@@ -50,8 +47,7 @@ extern int shrink_mem(void);
extern void drain_local_pages(void);
/* kernel/suspend.c */
extern void software_suspend(void);
extern void software_resume(void);
extern int software_suspend(void);
extern int register_suspend_notifier(struct notifier_block *);
extern int unregister_suspend_notifier(struct notifier_block *);
......@@ -72,10 +68,10 @@ extern void do_suspend_lowlevel(int resume);
extern void do_suspend_lowlevel_s4bios(int resume);
#else /* CONFIG_SOFTWARE_SUSPEND */
static inline void software_suspend(void)
static inline int software_suspend(void)
{
return -EPERM;
}
#define software_resume() do { } while(0)
#define register_suspend_notifier(a) do { } while(0)
#define unregister_suspend_notifier(a) do { } while(0)
#endif /* CONFIG_SOFTWARE_SUSPEND */
......
......@@ -385,10 +385,6 @@ void __init prepare_namespace(void)
is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR;
/* This has to be before mounting root, because even readonly mount of reiserfs would replay
log corrupting stuff */
software_resume();
if (initrd_load())
goto out;
......
......@@ -14,7 +14,7 @@ obj-$(CONFIG_SMP) += cpu.o
obj-$(CONFIG_UID16) += uid16.o
obj-$(CONFIG_MODULES) += ksyms.o module.o
obj-$(CONFIG_KALLSYMS) += kallsyms.o
obj-$(CONFIG_PM) += pm.o power/
obj-$(CONFIG_PM) += power/
obj-$(CONFIG_CPU_FREQ) += cpufreq.o
obj-$(CONFIG_BSD_PROCESS_ACCT) += acct.o
obj-$(CONFIG_COMPAT) += compat.o
......
obj-y := process.o console.o
obj-y := main.o process.o console.o pm.o
obj-$(CONFIG_SOFTWARE_SUSPEND) += swsusp.o
obj-$(CONFIG_MAGIC_SYSRQ) += poweroff.o
/*
* drivers/power/process.c - Functions for saving/restoring console.
*
* Originally from swsusp.
*/
#include <linux/vt_kern.h>
#include <linux/kbd_kern.h>
#include "power.h"
......@@ -14,13 +20,13 @@ int pm_prepare_console(void)
#ifdef SUSPEND_CONSOLE
orig_fgconsole = fg_console;
if(vc_allocate(SUSPEND_CONSOLE))
if (vc_allocate(SUSPEND_CONSOLE))
/* we can't have a free VC for now. Too bad,
* we don't want to mess the screen for now. */
return 1;
set_console (SUSPEND_CONSOLE);
if(vt_waitactive(SUSPEND_CONSOLE)) {
set_console(SUSPEND_CONSOLE);
if (vt_waitactive(SUSPEND_CONSOLE)) {
pr_debug("Suspend: Can't switch VCs.");
return 1;
}
......@@ -34,7 +40,7 @@ void pm_restore_console(void)
{
console_loglevel = orig_loglevel;
#ifdef SUSPEND_CONSOLE
set_console (orig_fgconsole);
set_console(orig_fgconsole);
#endif
return;
}
/*
* kernel/power/main.c - PM subsystem core functionality.
*
* Copyright (c) 2003 Patrick Mochel
* Copyright (c) 2003 Open Source Development Lab
*
* This file is release under the GPLv2
*
*/
#include <linux/kobject.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/pm.h>
static int standby(void)
{
return 0;
}
static int suspend(void)
{
return 0;
}
static int hibernate(void)
{
return 0;
}
#define decl_state(_name) \
{ .name = __stringify(_name), .fn = _name }
struct pm_state {
char * name;
int (*fn)(void);
} pm_states[] = {
decl_state(standby),
decl_state(suspend),
decl_state(hibernate),
{ NULL },
};
static int enter_state(struct pm_state * state)
{
return state->fn();
}
decl_subsys(power,NULL,NULL);
#define power_attr(_name) \
static struct subsys_attribute _name##_attr = { \
.attr = { \
.name = __stringify(_name), \
.mode = 0644, \
}, \
.show = _name##_show, \
.store = _name##_store, \
}
/**
* state - control system power state.
*
* show() returns what states are supported, which is hard-coded to
* 'standby' (Power-On Suspend), 'suspend' (Suspend-to-RAM), and
* 'hibernate' (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)
{
struct pm_state * state;
char * s = buf;
for (state = &pm_states[0]; state->name; state++)
s += sprintf(s,"%s ",state->name);
s += sprintf(s,"\n");
return (s - buf);
}
static ssize_t state_store(struct subsystem * s, const char * buf, size_t n)
{
struct pm_state * state;
int error;
char * end = strchr(buf,'\n');
if (end)
*end = '\0';
for (state = &pm_states[0]; state; state++) {
if (!strcmp(buf,state->name))
break;
}
if (!state)
return -EINVAL;
error = enter_state(state);
return error ? error : n;
}
power_attr(state);
static struct attribute * g[] = {
&state_attr.attr,
NULL,
};
static struct attribute_group attr_group = {
.attrs = g,
};
static int 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);
......@@ -24,7 +24,6 @@
#include <linux/slab.h>
#include <linux/pm.h>
#include <linux/interrupt.h>
#include <linux/sysrq.h>
int pm_active;
......@@ -295,39 +294,3 @@ EXPORT_SYMBOL(pm_find);
EXPORT_SYMBOL(pm_active);
#ifdef CONFIG_MAGIC_SYSRQ
/**
* handle_poweroff - sysrq callback for power down
* @key: key pressed (unused)
* @pt_regs: register state (unused)
* @kbd: keyboard state (unused)
* @tty: tty involved (unused)
*
* When the user hits Sys-Rq o to power down the machine this is the
* callback we use.
*/
static void handle_poweroff (int key, struct pt_regs *pt_regs,
struct tty_struct *tty)
{
if (pm_power_off)
pm_power_off();
}
static struct sysrq_key_op sysrq_poweroff_op = {
.handler = handle_poweroff,
.help_msg = "powerOff",
.action_msg = "Power Off\n"
};
#endif /* CONFIG_MAGIC_SYSRQ */
static int pm_init(void)
{
register_sysrq_key('o', &sysrq_poweroff_op);
return 0;
}
subsys_initcall(pm_init);
/*
* poweroff.c - sysrq handler to gracefully power down machine.
*
* This file is released under the GPL v2
*/
#include <linux/kernel.h>
#include <linux/sysrq.h>
#include <linux/init.h>
#include <linux/pm.h>
/**
* handle_poweroff - sysrq callback for power down
* @key: key pressed (unused)
* @pt_regs: register state (unused)
* @kbd: keyboard state (unused)
* @tty: tty involved (unused)
*
* When the user hits Sys-Rq o to power down the machine this is the
* callback we use.
*/
static void handle_poweroff (int key, struct pt_regs *pt_regs,
struct tty_struct *tty)
{
if (pm_power_off)
pm_power_off();
}
static struct sysrq_key_op sysrq_poweroff_op = {
.handler = handle_poweroff,
.help_msg = "powerOff",
.action_msg = "Power Off\n"
};
static int pm_sysrq_init(void)
{
register_sysrq_key('o', &sysrq_poweroff_op);
return 0;
}
subsys_initcall(pm_sysrq_init);
......@@ -8,6 +8,7 @@
#undef DEBUG
#include <linux/smp_lock.h>
#include <linux/interrupt.h>
#include <linux/suspend.h>
#include <linux/module.h>
......
......@@ -67,7 +67,7 @@
extern long sys_sync(void);
unsigned char software_suspend_enabled = 0;
unsigned char software_suspend_enabled = 1;
#define __ADDRESS(x) ((unsigned long) phys_to_virt(x))
#define ADDRESS(x) __ADDRESS((x) << PAGE_SHIFT)
......@@ -85,8 +85,7 @@ spinlock_t suspend_pagedir_lock __nosavedata = SPIN_LOCK_UNLOCKED;
static int pagedir_order_check;
static int nr_copy_pages_check;
static int resume_status;
static char resume_file[256] = ""; /* For resume= kernel option */
static char resume_file[256]; /* For resume= kernel option */
static dev_t resume_device;
/* Local variables that should not be affected by save */
unsigned int nr_copy_pages __nosavedata = 0;
......@@ -352,15 +351,10 @@ static int count_and_copy_data_pages(struct pbe *pagedir_p)
int pfn;
struct page *page;
#ifdef CONFIG_DISCONTIGMEM
panic("Discontingmem not supported");
#else
BUG_ON (max_pfn != num_physpages);
#endif
for (pfn = 0; pfn < max_pfn; pfn++) {
page = pfn_to_page(pfn);
if (PageHighMem(page))
panic("Swsusp not supported on highmem boxes. Send 1GB of RAM to <pavel@ucw.cz> and try again ;-).");
if (!PageReserved(page)) {
if (PageNosave(page))
......@@ -479,19 +473,23 @@ static void drivers_unsuspend(void)
/* Called from process context */
static int drivers_suspend(void)
{
device_suspend(4, SUSPEND_NOTIFY);
device_suspend(4, SUSPEND_SAVE_STATE);
device_suspend(4, SUSPEND_DISABLE);
if(!pm_suspend_state) {
if (device_suspend(4, SUSPEND_NOTIFY))
return -EIO;
if (device_suspend(4, SUSPEND_SAVE_STATE)) {
device_resume(RESUME_RESTORE_STATE);
return -EIO;
}
if (!pm_suspend_state) {
if(pm_send_all(PM_SUSPEND,(void *)3)) {
printk(KERN_WARNING "Problem while sending suspend event\n");
return(1);
return -EIO;
}
pm_suspend_state=1;
} else
printk(KERN_WARNING "PM suspend state already raised\n");
device_suspend(4, SUSPEND_DISABLE);
return(0);
return 0;
}
#define RESUME_PHASE1 1 /* Called from interrupts disabled */
......@@ -504,7 +502,7 @@ static void drivers_resume(int flags)
device_resume(RESUME_ENABLE);
}
if (flags & RESUME_PHASE2) {
if(pm_suspend_state) {
if (pm_suspend_state) {
if(pm_send_all(PM_RESUME,(void *)0))
printk(KERN_WARNING "Problem while sending resume event\n");
pm_suspend_state=0;
......@@ -696,7 +694,7 @@ void do_magic_suspend_2(void)
mark_swapfiles(((swp_entry_t) {0}), MARK_SWAP_RESUME);
}
static void do_software_suspend(void)
static int do_software_suspend(void)
{
arch_prepare_suspend();
if (pm_prepare_console())
......@@ -715,7 +713,7 @@ static void do_software_suspend(void)
blk_run_queues();
/* Save state of all device drivers, and stop them. */
if(drivers_suspend()==0)
if (drivers_suspend()==0)
/* If stopping device drivers worked, we proceed basically into
* suspend_save_image.
*
......@@ -731,20 +729,35 @@ static void do_software_suspend(void)
software_suspend_enabled = 1;
MDELAY(1000);
pm_restore_console();
return 0;
}
/*
* This is main interface to the outside world. It needs to be
* called from process context.
/**
* software_suspend - initiate suspend-to-swap transition.
*
* This is main interface to the outside world. It needs to be
* called from process context.
*/
void software_suspend(void)
int software_suspend(void)
{
if(!software_suspend_enabled)
return;
return -EINVAL;
if (num_online_cpus() > 1) {
printk(KERN_WARNING "swsusp does not support SMP.\n");
return -EPERM;
}
#if defined (CONFIG_HIGHMEM) || defined (COFNIG_DISCONTIGMEM)
printk("swsusp is not supported with high- or discontig-mem.\n");
return -EPERM;
#endif
software_suspend_enabled = 0;
might_sleep();
do_software_suspend();
return do_software_suspend();
}
/* More restore stuff */
......@@ -890,31 +903,9 @@ static int bdev_read_page(struct block_device *bdev, long pos, void *buf)
return 0;
}
static int bdev_write_page(struct block_device *bdev, long pos, void *buf)
{
#if 0
struct buffer_head *bh;
BUG_ON (pos%PAGE_SIZE);
bh = __bread(bdev, pos/PAGE_SIZE, PAGE_SIZE);
if (!bh || (!bh->b_data)) {
return -1;
}
memcpy(bh->b_data, buf, PAGE_SIZE); /* FIXME: may need kmap() */
BUG_ON(!buffer_uptodate(bh));
generic_make_request(WRITE, bh);
if (!buffer_uptodate(bh))
printk(KERN_CRIT "%sWarning %s: Fixing swap signatures unsuccessful...\n", name_resume, resume_file);
wait_on_buffer(bh);
brelse(bh);
return 0;
#endif
printk(KERN_CRIT "%sWarning %s: Fixing swap signatures unimplemented...\n", name_resume, resume_file);
return 0;
}
extern dev_t __init name_to_dev_t(const char *line);
static int __read_suspend_image(struct block_device *bdev, union diskpage *cur, int noresume)
static int __read_suspend_image(struct block_device *bdev, union diskpage *cur)
{
swp_entry_t next;
int i, nr_pgdir_pages;
......@@ -939,18 +930,9 @@ static int __read_suspend_image(struct block_device *bdev, union diskpage *cur,
else if (!memcmp("S2",cur->swh.magic.magic,2))
memcpy(cur->swh.magic.magic,"SWAPSPACE2",10);
else {
if (noresume)
return -EINVAL;
panic("%sUnable to find suspended-data signature (%.10s - misspelled?\n",
printk("swsusp: %s: Unable to find suspended-data signature (%.10s - misspelled?\n",
name_resume, cur->swh.magic.magic);
}
if (noresume) {
/* We don't do a sanity check here: we want to restore the swap
whatever version of kernel made the suspend image;
We need to write swap, but swap is *not* enabled so
we must write the device directly */
printk("%s: Fixing swap signatures %s...\n", name_resume, resume_file);
bdev_write_page(bdev, 0, cur);
return -EFAULT;
}
printk( "%sSignature found, resuming\n", name_resume );
......@@ -1000,7 +982,7 @@ static int __read_suspend_image(struct block_device *bdev, union diskpage *cur,
return 0;
}
static int read_suspend_image(const char * specialfile, int noresume)
static int read_suspend_image(const char * specialfile)
{
union diskpage *cur;
unsigned long scratch_page = 0;
......@@ -1019,7 +1001,7 @@ static int read_suspend_image(const char * specialfile, int noresume)
error = PTR_ERR(bdev);
} else {
set_blocksize(bdev, PAGE_SIZE);
error = __read_suspend_image(bdev, cur, noresume);
error = __read_suspend_image(bdev, cur);
blkdev_put(bdev, BDEV_RAW);
}
} else error = -ENOMEM;
......@@ -1048,64 +1030,47 @@ static int read_suspend_image(const char * specialfile, int noresume)
return error;
}
/*
* Called from init kernel_thread.
* We check if we have an image and if so we try to resume
/**
* software_resume - Check and load saved image from swap.
*
* Defined as a late_initcall, so it gets called after all devices
* have been probed and initialized, but before we've mounted anything.
*/
void software_resume(void)
static int software_resume(void)
{
if (num_online_cpus() > 1) {
printk(KERN_WARNING "Software Suspend has malfunctioning SMP support. Disabled :(\n");
return;
}
/* We enable the possibility of machine suspend */
software_suspend_enabled = 1;
if (!resume_status)
return;
printk( "%s", name_resume );
if (resume_status == NORESUME) {
if(resume_file[0])
read_suspend_image(resume_file, 1);
printk( "disabled\n" );
return;
}
MDELAY(1000);
if (!strlen(resume_file))
return 0;
if (pm_prepare_console())
printk("swsusp: Can't allocate a console... proceeding\n");
if (!resume_file[0] && resume_status == RESUME_SPECIFIED) {
printk( "suspension device unspecified\n" );
return;
}
printk("swsusp: %s\n", name_resume );
MDELAY(1000);
printk( "resuming from %s\n", resume_file);
if (read_suspend_image(resume_file, 0))
printk("swsusp: resuming from %s\n", resume_file);
if (read_suspend_image(resume_file))
goto read_failure;
do_magic(1);
panic("This never returns");
printk("swsusp: Resume failed. Continuing.\n");
read_failure:
pm_restore_console();
return;
return -EFAULT;
}
late_initcall(software_resume);
static int __init resume_setup(char *str)
{
if (resume_status == NORESUME)
return 1;
strncpy( resume_file, str, 255 );
resume_status = RESUME_SPECIFIED;
return 1;
}
static int __init noresume_setup(char *str)
{
resume_status = NORESUME;
resume_file[0] = '\0';
return 1;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment