diff options
| author | Linus Torvalds <torvalds@home.osdl.org> | 2003-08-14 10:52:23 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@home.osdl.org> | 2003-08-14 10:52:23 -0700 |
| commit | 9508edb015641bb885ae918db2a6b84ed8f2b615 (patch) | |
| tree | 3d8335acfc5a3552f1b1923c588b37a8ecbd8b11 | |
| parent | 7490aafb582ea5274d756bce74877814dca7cf07 (diff) | |
| parent | fcab304b5df5bf6188d9addc340d78f1a153ea2d (diff) | |
Merge bk://kernel.bkbits.net//home/mochel/linux-2.5-power
into home.osdl.org:/home/torvalds/v2.5/linux
29 files changed, 1142 insertions, 377 deletions
diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig index 7217b4ec616e..2077ffebdbc1 100644 --- a/drivers/acpi/Kconfig +++ b/drivers/acpi/Kconfig @@ -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. diff --git a/drivers/acpi/sleep/main.c b/drivers/acpi/sleep/main.c index 296dd3fd99e4..ba7294275354 100644 --- a/drivers/acpi/sleep/main.c +++ b/drivers/acpi/sleep/main.c @@ -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; diff --git a/drivers/base/Makefile b/drivers/base/Makefile index 334fcc6c6ee6..5203e01c4547 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -1,7 +1,8 @@ # 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 diff --git a/drivers/base/base.h b/drivers/base/base.h index cc92a7359569..da16cc5c9e30 100644 --- a/drivers/base/base.h +++ b/drivers/base/base.h @@ -13,3 +13,5 @@ struct class_device_attribute *to_class_dev_attr(struct attribute *_attr) { return container_of(_attr,struct class_device_attribute,attr); } + + diff --git a/drivers/base/bus.c b/drivers/base/bus.c index e06e0e7859dd..a9ad9caa1cab 100644 --- a/drivers/base/bus.c +++ b/drivers/base/bus.c @@ -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; diff --git a/drivers/base/core.c b/drivers/base/core.c index 3a36e17d4890..eaa842f8add6 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -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); diff --git a/drivers/base/interface.c b/drivers/base/interface.c index 1622db6010d2..f5da0418284d 100644 --- a/drivers/base/interface.c +++ b/drivers/base/interface.c @@ -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, }; diff --git a/drivers/base/power.c b/drivers/base/power.c deleted file mode 100644 index ba83ee21f758..000000000000 --- a/drivers/base/power.c +++ /dev/null @@ -1,140 +0,0 @@ -/* - * power.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" - -#define to_dev(node) container_of(node,struct device,kobj.entry) - -extern struct subsystem devices_subsys; - -/** - * We handle system devices differently - we suspend and shut them - * down first and resume them first. That way, we do anything stupid like - * shutting down the interrupt controller before any devices.. - * - * Note that there are not different stages for power management calls - - * they only get one called once when interrupts are disabled. - */ - -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. - */ -void device_shutdown(void) -{ - struct device * dev; - - down_write(&devices_subsys.rwsem); - list_for_each_entry_reverse(dev,&devices_subsys.kset.list,kobj.entry) { - pr_debug("shutting down %s: ",dev->name); - if (dev->driver && dev->driver->shutdown) { - pr_debug("Ok\n"); - dev->driver->shutdown(dev); - } else - pr_debug("Ignored.\n"); - } - up_write(&devices_subsys.rwsem); - - sysdev_shutdown(); -} - -EXPORT_SYMBOL(device_suspend); -EXPORT_SYMBOL(device_resume); -EXPORT_SYMBOL(device_shutdown); diff --git a/drivers/base/power/Makefile b/drivers/base/power/Makefile new file mode 100644 index 000000000000..99f811cd26cf --- /dev/null +++ b/drivers/base/power/Makefile @@ -0,0 +1,2 @@ +obj-y := shutdown.o +obj-$(CONFIG_PM) += main.o suspend.o resume.o runtime.o sysfs.o diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c new file mode 100644 index 000000000000..6f53840c6b39 --- /dev/null +++ b/drivers/base/power/main.c @@ -0,0 +1,98 @@ +/* + * 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); +} + + diff --git a/drivers/base/power/power.h b/drivers/base/power/power.h new file mode 100644 index 000000000000..8130b04ffe5f --- /dev/null +++ b/drivers/base/power/power.h @@ -0,0 +1,104 @@ + + +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 diff --git a/drivers/base/power/resume.c b/drivers/base/power/resume.c new file mode 100644 index 000000000000..544104c6bbd1 --- /dev/null +++ b/drivers/base/power/resume.c @@ -0,0 +1,153 @@ +/* + * 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(); +} + diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c new file mode 100644 index 000000000000..4a4ac9f7764d --- /dev/null +++ b/drivers/base/power/runtime.c @@ -0,0 +1,89 @@ +/* + * 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); +} diff --git a/drivers/base/power/shutdown.c b/drivers/base/power/shutdown.c new file mode 100644 index 000000000000..a48b97681329 --- /dev/null +++ b/drivers/base/power/shutdown.c @@ -0,0 +1,68 @@ +/* + * 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 + * + */ + +#undef DEBUG + +#include <linux/device.h> +#include <asm/semaphore.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 + * shutting down the interrupt controller before any devices.. + * + * Note that there are not different stages for power management calls - + * they only get one called once when interrupts are disabled. + */ + +extern int sysdev_shutdown(void); + +/** + * device_shutdown - call ->remove() on each device to shutdown. + */ +void device_shutdown(void) +{ + struct device * dev; + + down_write(&devices_subsys.rwsem); + list_for_each_entry_reverse(dev,&devices_subsys.kset.list,kobj.entry) { + pr_debug("shutting down %s: ",dev->name); + if (dev->driver && dev->driver->shutdown) { + pr_debug("Ok\n"); + dev->driver->shutdown(dev); + } else + pr_debug("Ignored.\n"); + } + up_write(&devices_subsys.rwsem); + + sysdev_shutdown(); +} + diff --git a/drivers/base/power/suspend.c b/drivers/base/power/suspend.c new file mode 100644 index 000000000000..0747e409d0ec --- /dev/null +++ b/drivers/base/power/suspend.c @@ -0,0 +1,235 @@ +/* + * 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; +} + diff --git a/drivers/base/power/sysfs.c b/drivers/base/power/sysfs.c new file mode 100644 index 000000000000..0fddee58893f --- /dev/null +++ b/drivers/base/power/sysfs.c @@ -0,0 +1,68 @@ +/* + * 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); +} diff --git a/include/linux/device.h b/include/linux/device.h index 6d711633d263..59808676d3ef 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -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); diff --git a/include/linux/pm.h b/include/linux/pm.h index ab9305b0953a..6942ad5d5cb3 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h @@ -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 */ diff --git a/include/linux/reboot.h b/include/linux/reboot.h index 68b0cb81d986..b73b84d514d6 100644 --- a/include/linux/reboot.h +++ b/include/linux/reboot.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 diff --git a/include/linux/suspend.h b/include/linux/suspend.h index af0143331c94..28788d8a65ff 100644 --- a/include/linux/suspend.h +++ b/include/linux/suspend.h @@ -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 */ diff --git a/init/do_mounts.c b/init/do_mounts.c index 41a5934d09a9..63066472d568 100644 --- a/init/do_mounts.c +++ b/init/do_mounts.c @@ -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; diff --git a/kernel/Makefile b/kernel/Makefile index 495214d708bd..72eb0287e843 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -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 diff --git a/kernel/power/Makefile b/kernel/power/Makefile index 2a1e6368f7e5..9640751c4338 100644 --- a/kernel/power/Makefile +++ b/kernel/power/Makefile @@ -1,2 +1,4 @@ -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 diff --git a/kernel/power/console.c b/kernel/power/console.c index bfaf3f14c114..c05d0e43675f 100644 --- a/kernel/power/console.c +++ b/kernel/power/console.c @@ -1,3 +1,9 @@ +/* + * 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; } diff --git a/kernel/power/main.c b/kernel/power/main.c new file mode 100644 index 000000000000..603936964815 --- /dev/null +++ b/kernel/power/main.c @@ -0,0 +1,129 @@ +/* + * 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); diff --git a/kernel/pm.c b/kernel/power/pm.c index afffd046c2f6..09d55363cefc 100644 --- a/kernel/pm.c +++ b/kernel/power/pm.c @@ -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); diff --git a/kernel/power/poweroff.c b/kernel/power/poweroff.c new file mode 100644 index 000000000000..9abe4ac23f02 --- /dev/null +++ b/kernel/power/poweroff.c @@ -0,0 +1,44 @@ +/* + * 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); diff --git a/kernel/power/process.c b/kernel/power/process.c index 671730612e0f..15c1b340c2ed 100644 --- a/kernel/power/process.c +++ b/kernel/power/process.c @@ -8,6 +8,7 @@ #undef DEBUG +#include <linux/smp_lock.h> #include <linux/interrupt.h> #include <linux/suspend.h> #include <linux/module.h> diff --git a/kernel/power/swsusp.c b/kernel/power/swsusp.c index b9cd4bd18358..3da9c0142676 100644 --- a/kernel/power/swsusp.c +++ b/kernel/power/swsusp.c @@ -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; } |
