Hi.
This patch provides support for keeping part of the device tree alive
while suspending the remainder. This is accomplished by abstracting the
dpm_active, dpm_off and dpm_irq lists into a new struct partial device
tree, and providing functions to create new device trees and move
devices and their parents between trees. The current API for suspending
and resuming devices is completely unchanged by this patch. New
functions provide the extra functionality and existing functions are
wrappers that work on the default tree. Suspend2 uses this to keep the
storage device and graphics driver alive while putting other devices to
sleep at the start of saving the image.
Regards,
Nigel
diff -ruN linux-2.6.9-rc1/drivers/base/power/main.c software-suspend-linux-2.6.9-rc1-rev3/drivers/base/power/main.c
--- linux-2.6.9-rc1/drivers/base/power/main.c 2004-09-07 21:58:30.000000000 +1000
+++ software-suspend-linux-2.6.9-rc1-rev3/drivers/base/power/main.c 2004-09-09 19:36:24.000000000 +1000
@@ -4,6 +4,9 @@
* Copyright (c) 2003 Patrick Mochel
* Copyright (c) 2003 Open Source Development Lab
*
+ * Partial tree additions
+ * Copyright (c) 2004 Nigel Cunningham
+ *
* This file is released under the GPLv2
*
*
@@ -23,10 +26,18 @@
#include <linux/device.h>
#include "power.h"
-LIST_HEAD(dpm_active);
-LIST_HEAD(dpm_off);
-LIST_HEAD(dpm_off_irq);
-
+struct partial_device_tree default_device_tree =
+{
+ .dpm_active = LIST_HEAD_INIT(default_device_tree.dpm_active),
+ .dpm_off = LIST_HEAD_INIT(default_device_tree.dpm_off),
+ .dpm_off_irq = LIST_HEAD_INIT(default_device_tree.dpm_off_irq),
+};
+EXPORT_SYMBOL(default_device_tree);
+
+/*
+ * One mutex for all trees because we can be moving items
+ * between trees.
+ */
DECLARE_MUTEX(dpm_sem);
/*
@@ -76,7 +87,9 @@
dev->bus ? dev->bus->name : "No Bus", dev->kobj.name);
atomic_set(&dev->power.pm_users, 0);
down(&dpm_sem);
- list_add_tail(&dev->power.entry, &dpm_active);
+ list_add_tail(&dev->power.entry, &default_device_tree.dpm_active);
+ dev->current_list = DEVICE_LIST_DPM_ACTIVE;
+ dev->tree = &default_device_tree;
device_pm_set_parent(dev, dev->parent);
if ((error = dpm_sysfs_add(dev)))
list_del(&dev->power.entry);
@@ -92,6 +105,7 @@
dpm_sysfs_remove(dev);
device_pm_release(dev->power.pm_parent);
list_del(&dev->power.entry);
+ dev->current_list = DEVICE_LIST_NONE;
up(&dpm_sem);
}
diff -ruN linux-2.6.9-rc1/drivers/base/power/Makefile software-suspend-linux-2.6.9-rc1-rev3/drivers/base/power/Makefile
--- linux-2.6.9-rc1/drivers/base/power/Makefile 2004-04-17 03:21:03.000000000 +1000
+++ software-suspend-linux-2.6.9-rc1-rev3/drivers/base/power/Makefile 2004-09-09 19:36:24.000000000 +1000
@@ -1,5 +1,5 @@
obj-y := shutdown.o
-obj-$(CONFIG_PM) += main.o suspend.o resume.o runtime.o sysfs.o
+obj-$(CONFIG_PM) += main.o suspend.o resume.o runtime.o sysfs.o tree.o
ifeq ($(CONFIG_DEBUG_DRIVER),y)
EXTRA_CFLAGS += -DDEBUG
diff -ruN linux-2.6.9-rc1/drivers/base/power/power.h software-suspend-linux-2.6.9-rc1-rev3/drivers/base/power/power.h
--- linux-2.6.9-rc1/drivers/base/power/power.h 2004-09-07 21:58:30.000000000 +1000
+++ software-suspend-linux-2.6.9-rc1-rev3/drivers/base/power/power.h 2004-09-09 19:36:24.000000000 +1000
@@ -30,10 +30,22 @@
/*
* The PM lists.
*/
-extern struct list_head dpm_active;
-extern struct list_head dpm_off;
-extern struct list_head dpm_off_irq;
+struct partial_device_tree
+{
+ struct list_head dpm_active;
+ struct list_head dpm_off;
+ struct list_head dpm_off_irq;
+};
+
+enum {
+ DEVICE_LIST_NONE,
+ DEVICE_LIST_DPM_ACTIVE,
+ DEVICE_LIST_DPM_OFF,
+ DEVICE_LIST_DPM_OFF_IRQ,
+};
+
+extern struct partial_device_tree default_device_tree;
static inline struct dev_pm_info * to_pm_info(struct list_head * entry)
{
@@ -59,7 +71,9 @@
* resume.c
*/
+extern void dpm_resume_tree(struct partial_device_tree * tree);
extern void dpm_resume(void);
+extern void dpm_power_up_tree(struct partial_device_tree * tree);
extern void dpm_power_up(void);
extern int resume_device(struct device *);
diff -ruN linux-2.6.9-rc1/drivers/base/power/resume.c software-suspend-linux-2.6.9-rc1-rev3/drivers/base/power/resume.c
--- linux-2.6.9-rc1/drivers/base/power/resume.c 2004-09-07 21:58:30.000000000 +1000
+++ software-suspend-linux-2.6.9-rc1-rev3/drivers/base/power/resume.c 2004-09-09 19:36:24.000000000 +1000
@@ -29,20 +29,25 @@
-void dpm_resume(void)
+void dpm_resume_tree(struct partial_device_tree * tree)
{
- while(!list_empty(&dpm_off)) {
- struct list_head * entry = dpm_off.next;
+ while(!list_empty(&tree->dpm_off)) {
+ struct list_head * entry = tree->dpm_off.next;
struct device * dev = to_device(entry);
list_del_init(entry);
if (!dev->power.prev_state)
resume_device(dev);
- list_add_tail(entry, &dpm_active);
+ list_add_tail(entry, &tree->dpm_active);
+ dev->current_list = DEVICE_LIST_DPM_ACTIVE;
}
}
+void dpm_resume(void)
+{
+ dpm_resume_tree(&default_device_tree);
+}
/**
* device_resume - Restore state of each device in system.
@@ -60,6 +65,14 @@
EXPORT_SYMBOL(device_resume);
+void device_resume_tree(struct partial_device_tree * tree)
+{
+ down(&dpm_sem);
+ dpm_resume_tree(tree);
+ up(&dpm_sem);
+}
+
+EXPORT_SYMBOL(device_resume_tree);
/**
* device_power_up_irq - Power on some devices.
@@ -72,16 +85,23 @@
* Interrupts must be disabled when calling this.
*/
-void dpm_power_up(void)
+void dpm_power_up_tree(struct partial_device_tree * tree)
{
- while(!list_empty(&dpm_off_irq)) {
- struct list_head * entry = dpm_off_irq.next;
+ while(!list_empty(&tree->dpm_off_irq)) {
+ struct list_head * entry = tree->dpm_off_irq.next;
+ struct device * dev = to_device(entry);
list_del_init(entry);
- resume_device(to_device(entry));
- list_add_tail(entry, &dpm_active);
+ resume_device(dev);
+ list_add_tail(entry, &tree->dpm_active);
+ dev->current_list = DEVICE_LIST_DPM_ACTIVE;
}
}
+EXPORT_SYMBOL(dpm_power_up_tree);
+void dpm_power_up(void)
+{
+ dpm_power_up_tree(&default_device_tree);
+}
/**
* device_pm_power_up - Turn on all devices that need special attention.
@@ -97,6 +117,58 @@
dpm_power_up();
}
+#if 0
+
+/**
+ *
+ * pci_find_class_storage
+ *
+ * Find a PCI storage device.
+ * Based upon pci_find_class, but less strict.
+ */
+
+static struct pci_dev *
+pci_find_class_storage(unsigned int class, const struct pci_dev *from)
+{
+ struct list_head *n;
+ struct pci_dev *dev;
+
+ spin_lock(&pci_bus_lock);
+ n = from ? from->global_list.next : pci_devices.next;
+
+ while (n && (n != &pci_devices)) {
+ dev = pci_dev_g(n);
+ if (((dev->class & 0xff00) >> 16) == class)
+ goto exit;
+ n = n->next;
+ }
+ dev = NULL;
+exit:
+ spin_unlock(&pci_bus_lock);
+ return dev;
+}
+
+
+/**
+ * device_resume_type - Resume some devices.
+ *
+ * Resume devices of a specific type and their parents.
+ * Interrupts must be disabled when calling this.
+ *
+ * Note that we only handle pci devices at the moment.
+ * We have no way that I can tell of getting the class
+ * of devices not on the pci bus.
+ */
+void device_resume_type(type)
+{
+ struct device * dev_dev;
+ struct pci_dev * pci_dev = NULL;
+
+ while ((dev = pci_find_class(PCI_BASE_CLASS_STORAGE, dev))) {
+ }
+}
+#endif
+
EXPORT_SYMBOL(device_power_up);
diff -ruN linux-2.6.9-rc1/drivers/base/power/shutdown.c software-suspend-linux-2.6.9-rc1-rev3/drivers/base/power/shutdown.c
--- linux-2.6.9-rc1/drivers/base/power/shutdown.c 2004-09-07 21:58:30.000000000 +1000
+++ software-suspend-linux-2.6.9-rc1-rev3/drivers/base/power/shutdown.c 2004-09-09 19:36:24.000000000 +1000
@@ -65,3 +65,4 @@
sysdev_shutdown();
}
+EXPORT_SYMBOL(device_shutdown);
diff -ruN linux-2.6.9-rc1/drivers/base/power/suspend.c software-suspend-linux-2.6.9-rc1-rev3/drivers/base/power/suspend.c
--- linux-2.6.9-rc1/drivers/base/power/suspend.c 2004-09-07 21:58:30.000000000 +1000
+++ software-suspend-linux-2.6.9-rc1-rev3/drivers/base/power/suspend.c 2004-09-09 19:36:24.000000000 +1000
@@ -51,7 +51,7 @@
/**
- * device_suspend - Save state and stop all devices in system.
+ * device_suspend_tree - 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
@@ -60,7 +60,7 @@
* 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.
*
- * If we hit a failure with any of the devices, call device_resume()
+ * If we hit a failure with any of the devices, call device_resume_tree()
* above to bring the suspended devices back to life.
*
* Note this function leaves dpm_sem held to
@@ -70,22 +70,24 @@
*
*/
-int device_suspend(u32 state)
+int device_suspend_tree(u32 state, struct partial_device_tree * tree)
{
int error = 0;
down(&dpm_sem);
- while(!list_empty(&dpm_active)) {
- struct list_head * entry = dpm_active.prev;
+ while(!list_empty(&tree->dpm_active)) {
+ struct list_head * entry = tree->dpm_active.prev;
struct device * dev = to_device(entry);
error = suspend_device(dev, state);
if (!error) {
list_del(&dev->power.entry);
- list_add(&dev->power.entry, &dpm_off);
+ list_add(&dev->power.entry, &tree->dpm_off);
+ dev->current_list = DEVICE_LIST_DPM_OFF;
} else if (error == -EAGAIN) {
list_del(&dev->power.entry);
- list_add(&dev->power.entry, &dpm_off_irq);
+ list_add(&dev->power.entry, &tree->dpm_off_irq);
+ dev->current_list = DEVICE_LIST_DPM_OFF_IRQ;
} else {
printk(KERN_ERR "Could not suspend device %s: "
"error %d\n", kobject_name(&dev->kobj), error);
@@ -96,10 +98,15 @@
up(&dpm_sem);
return error;
Error:
- dpm_resume();
+ dpm_resume_tree(tree);
goto Done;
}
+EXPORT_SYMBOL(device_suspend_tree);
+int device_suspend(u32 state)
+{
+ return device_suspend_tree(state, &default_device_tree);
+}
EXPORT_SYMBOL(device_suspend);
@@ -112,25 +119,31 @@
* done, power down system devices.
*/
-int device_power_down(u32 state)
+int device_power_down_tree(u32 state, struct partial_device_tree * tree)
{
int error = 0;
struct device * dev;
- list_for_each_entry_reverse(dev, &dpm_off_irq, power.entry) {
+ list_for_each_entry_reverse(dev, &tree->dpm_off_irq, power.entry) {
if ((error = suspend_device(dev, state)))
break;
}
if (error)
goto Error;
- if ((error = sysdev_suspend(state)))
- goto Error;
Done:
return error;
Error:
dpm_power_up();
goto Done;
}
+EXPORT_SYMBOL(device_power_down_tree);
-EXPORT_SYMBOL(device_power_down);
+int device_power_down(u32 state)
+{
+ int error;
+ if (!(error = device_power_down_tree(state, &default_device_tree)))
+ error = sysdev_suspend(state);
+ return error;
+}
+EXPORT_SYMBOL(device_power_down);
diff -ruN linux-2.6.9-rc1/drivers/base/power/tree.c software-suspend-linux-2.6.9-rc1-rev3/drivers/base/power/tree.c
--- linux-2.6.9-rc1/drivers/base/power/tree.c 1970-01-01 10:00:00.000000000 +1000
+++ software-suspend-linux-2.6.9-rc1-rev3/drivers/base/power/tree.c 2004-09-09 19:36:24.000000000 +1000
@@ -0,0 +1,105 @@
+/*
+ * suspend.c - Functions for moving devices between trees.
+ *
+ * Copyright (c) 2004 Nigel Cunningham
+ *
+ * This file is released under the GPLv2
+ *
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include "power.h"
+
+/*
+ * device_merge_tree - Move an entire tree into another tree
+ * @source: The tree to be moved
+ * @dest : The destination tree
+ */
+
+void device_merge_tree( struct partial_device_tree * source,
+ struct partial_device_tree * dest)
+{
+ down(&dpm_sem);
+ list_splice_init(&source->dpm_active, &dest->dpm_active);
+ list_splice_init(&source->dpm_off, &dest->dpm_off);
+ list_splice_init(&source->dpm_off_irq, &dest->dpm_off_irq);
+ up(&dpm_sem);
+}
+EXPORT_SYMBOL(device_merge_tree);
+
+/*
+ * device_switch_trees - Move a device and its ancestors to a new tree
+ * @dev: The lowest device to be moved.
+ * @tree: The destination tree.
+ *
+ * Note that siblings can be left in the original tree. This is because
+ * we want to be able to keep part of a tree in one state while part is
+ * in another.
+ *
+ * Since we iterate all the way back to the top, and may move entries
+ * already in the destination tree, we will never violate the depth
+ * first property of the destination tree.
+ */
+
+void device_switch_trees(struct device * dev, struct partial_device_tree * tree)
+{
+ down(&dpm_sem);
+ while (dev) {
+ list_del(&dev->power.entry);
+ switch (dev->current_list) {
+ case DEVICE_LIST_DPM_ACTIVE:
+ list_add(&dev->power.entry, &tree->dpm_active);
+ break;
+ case DEVICE_LIST_DPM_OFF:
+ list_add(&dev->power.entry, &tree->dpm_off);
+ break;
+ case DEVICE_LIST_DPM_OFF_IRQ:
+ list_add(&dev->power.entry, &tree->dpm_off_irq);
+ break;
+ }
+
+ dev = dev->parent;
+ }
+ up(&dpm_sem);
+}
+
+EXPORT_SYMBOL(device_switch_trees);
+
+/*
+ * create_device_tree - Create a new device tree
+ */
+
+struct partial_device_tree * device_create_tree(void)
+{
+ struct partial_device_tree * new_tree;
+
+ new_tree = (struct partial_device_tree *)
+ kmalloc(sizeof(struct partial_device_tree), GFP_ATOMIC);
+
+ if (!IS_ERR(new_tree)) {
+ INIT_LIST_HEAD(&new_tree->dpm_active);
+ INIT_LIST_HEAD(&new_tree->dpm_off);
+ INIT_LIST_HEAD(&new_tree->dpm_off_irq);
+ }
+
+ return new_tree;
+}
+EXPORT_SYMBOL(device_create_tree);
+
+/*
+ * device_destroy_tree - Destroy a dynamically created tree
+ */
+
+void device_destroy_tree(struct partial_device_tree * tree)
+{
+ BUG_ON(tree == &default_device_tree);
+
+ BUG_ON(!list_empty(&tree->dpm_active));
+ BUG_ON(!list_empty(&tree->dpm_off));
+ BUG_ON(!list_empty(&tree->dpm_off_irq));
+
+ kfree(tree);
+}
+
+EXPORT_SYMBOL(device_destroy_tree);
diff -ruN linux-2.6.9-rc1/drivers/base/sys.c software-suspend-linux-2.6.9-rc1-rev3/drivers/base/sys.c
--- linux-2.6.9-rc1/drivers/base/sys.c 2004-09-07 21:58:30.000000000 +1000
+++ software-suspend-linux-2.6.9-rc1-rev3/drivers/base/sys.c 2004-09-09 19:36:24.000000000 +1000
@@ -337,7 +337,7 @@
}
return 0;
}
-
+EXPORT_SYMBOL(sysdev_suspend);
/**
* sysdev_resume - Bring system devices back to life.
@@ -384,6 +384,7 @@
}
return 0;
}
+EXPORT_SYMBOL(sysdev_resume);
int __init system_bus_init(void)
diff -ruN linux-2.6.9-rc1/include/linux/device.h software-suspend-linux-2.6.9-rc1-rev3/include/linux/device.h
--- linux-2.6.9-rc1/include/linux/device.h 2004-09-07 21:58:59.000000000 +1000
+++ software-suspend-linux-2.6.9-rc1-rev3/include/linux/device.h 2004-09-09 19:36:24.000000000 +1000
@@ -286,6 +287,11 @@
struct list_head dma_pools; /* dma pools (if dma'ble) */
void (*release)(struct device * dev);
+
+ struct partial_device_tree * tree; /* Which tree of devices this
+ device is in */
+ int current_list; /* Which list within the tree the
+ device is on (speeds moving) */
};
static inline struct device *
diff -ruN linux-2.6.9-rc1/include/linux/pm.h software-suspend-linux-2.6.9-rc1-rev3/include/linux/pm.h
--- linux-2.6.9-rc1/include/linux/pm.h 2004-09-07 21:59:00.000000000 +1000
+++ software-suspend-linux-2.6.9-rc1-rev3/include/linux/pm.h 2004-09-09 19:36:24.000000000 +1000
@@ -241,12 +241,25 @@
extern void device_pm_set_parent(struct device * dev, struct device * parent);
+struct partial_device_tree;
+extern struct partial_device_tree default_device_tree;
+
extern int device_suspend(u32 state);
+extern int device_suspend_tree(u32 state, struct partial_device_tree * tree);
extern int device_power_down(u32 state);
+extern int device_power_down_tree(u32 state, struct partial_device_tree * tree);
extern void device_power_up(void);
+extern void device_power_up_tree(struct partial_device_tree * tree);
extern void device_resume(void);
-
-
+extern void device_resume_tree(struct partial_device_tree * tree);
+extern void device_merge_tree( struct partial_device_tree * source,
+ struct partial_device_tree * dest);
+extern void device_switch_trees(struct device * dev, struct partial_device_tree * tree);
+extern void dpm_power_up_tree(struct partial_device_tree * tree);
+extern int sysdev_suspend(u32 state);
+extern int sysdev_resume(void);
+extern struct partial_device_tree * device_create_tree(void);
+extern void device_destroy_tree(struct partial_device_tree * tree);
#endif /* __KERNEL__ */
#endif /* _LINUX_PM_H */
--
Nigel Cunningham
Pastoral Worker
Christian Reformed Church of Tuggeranong
PO Box 1004, Tuggeranong, ACT 2901
Many today claim to be tolerant. True tolerance, however, can cope with others
being intolerant.
> +struct partial_device_tree default_device_tree =
> +{
> + .dpm_active = LIST_HEAD_INIT(default_device_tree.dpm_active),
> + .dpm_off = LIST_HEAD_INIT(default_device_tree.dpm_off),
> + .dpm_off_irq = LIST_HEAD_INIT(default_device_tree.dpm_off_irq),
> +};
> +EXPORT_SYMBOL(default_device_tree);
_never_ exports lists, only accessory funktions. But honestly please get
some builtin code merged first, then think about making it modular.
We don't want to add random exports just to see them never used later on.