[RFC/PATCH 2/3] Allow objects other than "struct device" in pm list

From: Dmitry Torokhov
Date: Wed Oct 11 2006 - 02:12:00 EST


PM: allow objects other than "struct device" in pm list

Any object with embedded dev_pm_info structure can be added to
ower management list and have its suspend/resume methods called
automatically by driver core.

Signed-off-by: Dmitry Torokhov <dtor@xxxxxxx>
---

drivers/base/power/main.c | 69 ++++++++++++++++++++++++++--------
drivers/base/power/power.h | 23 ++++++++++-
drivers/base/power/resume.c | 61 +++++++++++++++++-------------
drivers/base/power/runtime.c | 30 ++++++++-------
drivers/base/power/suspend.c | 85 ++++++++++++++++++++++++-------------------
drivers/base/power/trace.c | 17 +++++---
drivers/pcmcia/ds.c | 8 ++--
include/linux/pm.h | 21 +++++++---
include/linux/resume-trace.h | 10 ++---
9 files changed, 207 insertions(+), 117 deletions(-)

Index: work/drivers/base/power/main.c
===================================================================
--- work.orig/drivers/base/power/main.c
+++ work/drivers/base/power/main.c
@@ -31,8 +31,8 @@ DECLARE_MUTEX(dpm_list_sem);

/**
* device_pm_set_parent - Specify power dependency.
- * @dev: Device who needs power.
- * @parent: Device that supplies power.
+ * @dpm: Object who needs power.
+ * @parent: Object that supplies power.
*
* This function is used to manually describe a power-dependency
* relationship. It may be used to specify a transversal relationship
@@ -42,26 +42,63 @@ DECLARE_MUTEX(dpm_list_sem);
* before the power dependent.
*/

-void device_pm_set_parent(struct device * dev, struct device * parent)
+void dpm_set_parent(struct dev_pm_info *dpm, struct dev_pm_info *parent)
{
- put_device(dev->power.pm_parent);
- dev->power.pm_parent = get_device(parent);
+ if (dpm->pm_parent)
+ kobject_put(dpm->pm_parent->pm_object);
+ if (parent)
+ kobject_get(parent->pm_object);
+ dpm->pm_parent = parent;
}
-EXPORT_SYMBOL_GPL(device_pm_set_parent);
+EXPORT_SYMBOL_GPL(dpm_set_parent);

-int device_pm_add(struct device * dev)
+int dpm_add_object(struct dev_pm_info *dpm)
{
+ down(&dpm_list_sem);
+ list_add_tail(&dpm->entry, &dpm_active);
+ up(&dpm_list_sem);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(dpm_add_object);
+
+void dpm_delete_object(struct dev_pm_info *dpm)
+{
+ down(&dpm_list_sem);
+ dpm_set_parent(dpm, NULL);
+ list_del_init(&dpm->entry);
+ up(&dpm_list_sem);
+}
+EXPORT_SYMBOL_GPL(dpm_delete_object);
+
+static const struct dev_pm_ops device_pm_ops = {
+ .suspend = suspend_device,
+ .suspend_late = suspend_device_late,
+ .resume = resume_device,
+ .resume_early = resume_device_early,
+};
+
+int device_pm_add(struct device *dev)
+{
+ struct dev_pm_info *dpm = &dev->power;
int error;

pr_debug("PM: Adding info for %s:%s\n",
dev->bus ? dev->bus->name : "No Bus",
kobject_name(&dev->kobj));
- down(&dpm_list_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_list_sem);
+
+ dpm->pm_object = &dev->kobj;
+ dpm->pm_ops = &device_pm_ops;
+ if (dev->parent)
+ dpm_set_parent(dpm, &dev->parent->power);
+ error = dpm_add_object(dpm);
+ if (error)
+ return error;
+
+ error = dpm_sysfs_add(dev);
+ if (error)
+ dpm_delete_object(dpm);
+
return error;
}

@@ -70,11 +107,9 @@ void device_pm_remove(struct device * de
pr_debug("PM: Removing info for %s:%s\n",
dev->bus ? dev->bus->name : "No Bus",
kobject_name(&dev->kobj));
- down(&dpm_list_sem);
+
dpm_sysfs_remove(dev);
- put_device(dev->power.pm_parent);
- list_del_init(&dev->power.entry);
- up(&dpm_list_sem);
+ dpm_delete_object(&dev->power);
}


Index: work/drivers/base/power/resume.c
===================================================================
--- work.orig/drivers/base/power/resume.c
+++ work/drivers/base/power/resume.c
@@ -20,26 +20,28 @@
*
*/

-int resume_device(struct device * dev)
+int resume_device(struct dev_pm_info *dpm)
{
int error = 0;
+ struct device *dev = container_of(dpm, struct device, power);
+ struct dev_pm_info *pm_parent = dpm->pm_parent;

- TRACE_DEVICE(dev);
+ TRACE_DEVICE(dpm);
TRACE_RESUME(0);
down(&dev->sem);
- if (dev->power.pm_parent
- && dev->power.pm_parent->power.power_state.event) {
- dev_err(dev, "PM: resume from %d, parent %s still %d\n",
- dev->power.power_state.event,
- dev->power.pm_parent->bus_id,
- dev->power.pm_parent->power.power_state.event);
+
+ if (pm_parent && pm_parent->power_state.event) {
+ pm_err(dpm, "resume from %d, parent %s still %d\n",
+ dpm->power_state.event,
+ kobject_name(pm_parent->pm_object),
+ pm_parent->power_state.event);
}
if (dev->bus && dev->bus->resume) {
- dev_dbg(dev,"resuming\n");
+ pm_dbg(dpm, "resuming\n");
error = dev->bus->resume(dev);
}
if (dev->class && dev->class->resume) {
- dev_dbg(dev,"class resume\n");
+ pm_dbg(dpm, "class resume\n");
error = dev->class->resume(dev);
}
up(&dev->sem);
@@ -47,12 +49,12 @@ int resume_device(struct device * dev)
return error;
}

-
-static int resume_device_early(struct device * dev)
+int resume_device_early(struct dev_pm_info *dpm)
{
int error = 0;
+ struct device *dev = container_of(dpm, struct device, power);

- TRACE_DEVICE(dev);
+ TRACE_DEVICE(dpm);
TRACE_RESUME(0);
if (dev->bus && dev->bus->resume_early) {
dev_dbg(dev,"EARLY resume\n");
@@ -62,6 +64,7 @@ static int resume_device_early(struct de
return error;
}

+
/*
* Resume the devices that have either not gone through
* the late suspend, or that did go through it but also
@@ -69,19 +72,21 @@ static int resume_device_early(struct de
*/
void dpm_resume(void)
{
- down(&dpm_list_sem);
- while(!list_empty(&dpm_off)) {
- struct list_head * entry = dpm_off.next;
- struct device * dev = to_device(entry);
+ struct dev_pm_info *dpm;

- get_device(dev);
- list_move_tail(entry, &dpm_active);
+ down(&dpm_list_sem);
+ while (!list_empty(&dpm_off)) {
+ dpm = list_entry(dpm_off.next, struct dev_pm_info, entry);

+ kobject_get(dpm->pm_object);
+ list_move_tail(&dpm->entry, &dpm_active);
up(&dpm_list_sem);
- if (!dev->power.prev_state.event)
- resume_device(dev);
+
+ if (!dpm->prev_state.event)
+ dpm->pm_ops->resume(dpm);
+
down(&dpm_list_sem);
- put_device(dev);
+ kobject_put(dpm->pm_object);
}
up(&dpm_list_sem);
}
@@ -118,12 +123,14 @@ EXPORT_SYMBOL_GPL(device_resume);

void dpm_power_up(void)
{
- while(!list_empty(&dpm_off_irq)) {
- struct list_head * entry = dpm_off_irq.next;
- struct device * dev = to_device(entry);
+ struct dev_pm_info *dpm;
+
+ while (!list_empty(&dpm_off_irq)) {
+ dpm = list_entry(dpm_off_irq.next, struct dev_pm_info, entry);

- list_move_tail(entry, &dpm_off);
- resume_device_early(dev);
+ list_move_tail(&dpm->entry, &dpm_off);
+ if (dpm->pm_ops->resume_early)
+ dpm->pm_ops->resume_early(dpm);
}
}

Index: work/drivers/base/power/suspend.c
===================================================================
--- work.orig/drivers/base/power/suspend.c
+++ work/drivers/base/power/suspend.c
@@ -46,28 +46,29 @@ static inline char *suspend_verb(u32 eve
* @state: Power state device is entering.
*/

-int suspend_device(struct device * dev, pm_message_t state)
+int suspend_device(struct dev_pm_info *dpm, pm_message_t state)
{
int error = 0;
+ struct device *dev = container_of(dpm, struct device, power);
+ struct dev_pm_info *dpm_parent = dpm->pm_parent;

down(&dev->sem);
- if (dev->power.power_state.event) {
- dev_dbg(dev, "PM: suspend %d-->%d\n",
- dev->power.power_state.event, state.event);
- }
- if (dev->power.pm_parent
- && dev->power.pm_parent->power.power_state.event) {
- dev_err(dev,
- "PM: suspend %d->%d, parent %s already %d\n",
- dev->power.power_state.event, state.event,
- dev->power.pm_parent->bus_id,
- dev->power.pm_parent->power.power_state.event);
- }

- dev->power.prev_state = dev->power.power_state;
+ if (dpm->power_state.event)
+ pm_dbg(dpm, "suspend %d-->%d\n",
+ dpm->power_state.event, state.event);
+
+ if (dpm_parent && dpm_parent->power_state.event)
+ pm_err(dpm,
+ "suspend %d->%d, parent %s already %d\n",
+ dpm->power_state.event, state.event,
+ kobject_name(dpm_parent->pm_object),
+ dpm_parent->power_state.event);
+
+ dpm->prev_state = dpm->power_state;

- if (dev->class && dev->class->suspend && !dev->power.power_state.event) {
- dev_dbg(dev, "class %s%s\n",
+ if (dev->class && dev->class->suspend && !dpm->power_state.event) {
+ pm_dbg(dpm, "class %s%s\n",
suspend_verb(state.event),
((state.event == PM_EVENT_SUSPEND)
&& device_may_wakeup(dev))
@@ -78,8 +79,8 @@ int suspend_device(struct device * dev,
suspend_report_result(dev->class->suspend, error);
}

- if (!error && dev->bus && dev->bus->suspend && !dev->power.power_state.event) {
- dev_dbg(dev, "%s%s\n",
+ if (!error && dev->bus && dev->bus->suspend && !dpm->power_state.event) {
+ pm_dbg(dpm, "%s%s\n",
suspend_verb(state.event),
((state.event == PM_EVENT_SUSPEND)
&& device_may_wakeup(dev))
@@ -89,22 +90,23 @@ int suspend_device(struct device * dev,
error = dev->bus->suspend(dev, state);
suspend_report_result(dev->bus->suspend, error);
}
+
up(&dev->sem);
return error;
}

-
/*
* This is called with interrupts off, only a single CPU
* running. We can't do down() on a semaphore (and we don't
* need the protection)
*/
-static int suspend_device_late(struct device *dev, pm_message_t state)
+int suspend_device_late(struct dev_pm_info *dpm, pm_message_t state)
{
int error = 0;
+ struct device *dev = container_of(dpm, struct device, power);

if (dev->bus && dev->bus->suspend_late && !dev->power.power_state.event) {
- dev_dbg(dev, "LATE %s%s\n",
+ pm_dbg(dpm, "LATE %s%s\n",
suspend_verb(state.event),
((state.event == PM_EVENT_SUSPEND)
&& device_may_wakeup(dev))
@@ -139,34 +141,40 @@ static int suspend_device_late(struct de
int device_suspend(pm_message_t state)
{
int error = 0;
+ struct dev_pm_info *dpm;

might_sleep();
+
down(&dpm_sem);
down(&dpm_list_sem);
+
while (!list_empty(&dpm_active) && error == 0) {
- struct list_head * entry = dpm_active.prev;
- struct device * dev = to_device(entry);

- get_device(dev);
+ dpm = list_entry(dpm_active.prev, struct dev_pm_info, entry);
+ kobject_get(dpm->pm_object);
+
up(&dpm_list_sem);

- error = suspend_device(dev, state);
+ error = dpm->pm_ops->suspend(dpm, state);

down(&dpm_list_sem);

/* Check if the device got removed */
- if (!list_empty(&dev->power.entry)) {
+ if (!list_empty(&dpm->entry)) {
/* Move it to the dpm_off list */
if (!error)
- list_move(&dev->power.entry, &dpm_off);
+ list_move(&dpm->entry, &dpm_off);
}
+
if (error)
printk(KERN_ERR "Could not suspend device %s: "
"error %d%s\n",
- kobject_name(&dev->kobj), error,
+ kobject_name(dpm->pm_object), error,
error == -EAGAIN ? " (please convert to suspend_late)" : "");
- put_device(dev);
+
+ kobject_put(dpm->pm_object);
}
+
up(&dpm_list_sem);
if (error)
dpm_resume();
@@ -189,16 +197,19 @@ EXPORT_SYMBOL_GPL(device_suspend);
int device_power_down(pm_message_t state)
{
int error = 0;
- struct device * dev;
+ struct dev_pm_info *dpm;

while (!list_empty(&dpm_off)) {
- struct list_head * entry = dpm_off.prev;

- dev = to_device(entry);
- error = suspend_device_late(dev, state);
- if (error)
- goto Error;
- list_move(&dev->power.entry, &dpm_off_irq);
+ dpm = list_entry(dpm_off.prev, struct dev_pm_info, entry);
+
+ if (dpm->pm_ops->suspend_late) {
+ error = dpm->pm_ops->suspend_late(dpm, state);
+ if (error)
+ goto Error;
+ }
+
+ list_move(&dpm->entry, &dpm_off_irq);
}

error = sysdev_suspend(state);
@@ -206,7 +217,7 @@ int device_power_down(pm_message_t state
return error;
Error:
printk(KERN_ERR "Could not power down device %s: "
- "error %d\n", kobject_name(&dev->kobj), error);
+ "error %d\n", kobject_name(dpm->pm_object), error);
dpm_power_up();
goto Done;
}
Index: work/include/linux/pm.h
===================================================================
--- work.orig/include/linux/pm.h
+++ work/include/linux/pm.h
@@ -206,12 +206,21 @@ struct dev_pm_info {
#ifdef CONFIG_PM
unsigned should_wakeup:1;
pm_message_t prev_state;
- void * saved_state;
- struct device * pm_parent;
+ void *saved_state;
+ struct kobject *pm_object;
+ struct dev_pm_info *pm_parent;
struct list_head entry;
+ const struct dev_pm_ops *pm_ops;
#endif
};

+struct dev_pm_ops {
+ int (*suspend)(struct dev_pm_info *dpm, pm_message_t state);
+ int (*suspend_late)(struct dev_pm_info *dpm, pm_message_t state);
+ int (*resume_early)(struct dev_pm_info *dpm);
+ int (*resume)(struct dev_pm_info *dpm);
+};
+
extern void device_pm_set_parent(struct device * dev, struct device * parent);

extern int device_power_down(pm_message_t state);
@@ -229,8 +238,8 @@ extern int device_prepare_suspend(pm_mes
#define device_may_wakeup(dev) \
(device_can_wakeup(dev) && (dev)->power.should_wakeup)

-extern int dpm_runtime_suspend(struct device *, pm_message_t);
-extern void dpm_runtime_resume(struct device *);
+extern int dpm_runtime_suspend(struct dev_pm_info *, pm_message_t);
+extern void dpm_runtime_resume(struct dev_pm_info *);
extern void __suspend_report_result(const char *function, void *fn, int ret);

#define suspend_report_result(fn, ret) \
@@ -248,12 +257,12 @@ static inline int device_suspend(pm_mess
#define device_set_wakeup_enable(dev,val) do{}while(0)
#define device_may_wakeup(dev) (0)

-static inline int dpm_runtime_suspend(struct device * dev, pm_message_t state)
+static inline int dpm_runtime_suspend(struct dev_pm_indo *dpm, pm_message_t state)
{
return 0;
}

-static inline void dpm_runtime_resume(struct device * dev)
+static inline void dpm_runtime_resume(struct dev_pm_info *dpm)
{
}

Index: work/drivers/base/power/power.h
===================================================================
--- work.orig/drivers/base/power/power.h
+++ work/drivers/base/power/power.h
@@ -7,6 +7,23 @@ extern void device_shutdown(void);

#ifdef CONFIG_PM

+#ifdef DEBUG
+#define pm_dbg(dpm, format, arg...) \
+ printk(KERN_DEBUG "%s PM: " format, \
+ kobject_name(dpm->pm_object), ## arg)
+#else
+#define pm_dbg(dpm, format, arg...) do { (void)(dpm); } while (0)
+#endif
+
+#define pm_err(dpm, format, arg...) \
+ printk(KERN_ERR "%s PM: " format, \
+ kobject_name(dpm->pm_object), ## arg)
+
+#define pm_info(dpm, format, arg...) \
+ printk(KERN_INFO, "%s PM: " format, \
+ kobject_name(dpm->pm_object), ## arg)
+
+
/*
* main.c
*/
@@ -55,12 +72,14 @@ extern void dpm_sysfs_remove(struct devi

extern void dpm_resume(void);
extern void dpm_power_up(void);
-extern int resume_device(struct device *);
+extern int resume_device(struct dev_pm_info *);
+extern int resume_device_early(struct dev_pm_info *);

/*
* suspend.c
*/
-extern int suspend_device(struct device *, pm_message_t);
+extern int suspend_device(struct dev_pm_info *, pm_message_t);
+extern int suspend_device_late(struct dev_pm_info *, pm_message_t);


/*
Index: work/drivers/base/power/runtime.c
===================================================================
--- work.orig/drivers/base/power/runtime.c
+++ work/drivers/base/power/runtime.c
@@ -10,13 +10,15 @@
#include "power.h"


-static void runtime_resume(struct device * dev)
+static void runtime_resume(struct dev_pm_info *dpm)
{
- dev_dbg(dev, "resuming\n");
- if (!dev->power.power_state.event)
+ pm_dbg(dpm, "resuming\n");
+
+ if (!dpm->power_state.event)
return;
- if (!resume_device(dev))
- dev->power.power_state = PMSG_ON;
+
+ if (dpm->pm_ops->resume(dpm) == 0)
+ dpm->power_state = PMSG_ON;
}


@@ -30,10 +32,10 @@ static void runtime_resume(struct device
* FIXME: We need to handle devices that are in an unknown state.
*/

-void dpm_runtime_resume(struct device * dev)
+void dpm_runtime_resume(struct dev_pm_info *dpm)
{
down(&dpm_sem);
- runtime_resume(dev);
+ runtime_resume(dpm);
up(&dpm_sem);
}
EXPORT_SYMBOL(dpm_runtime_resume);
@@ -45,19 +47,21 @@ EXPORT_SYMBOL(dpm_runtime_resume);
* @state: State to enter.
*/

-int dpm_runtime_suspend(struct device * dev, pm_message_t state)
+int dpm_runtime_suspend(struct dev_pm_info *dpm, pm_message_t state)
{
int error = 0;

down(&dpm_sem);
- if (dev->power.power_state.event == state.event)
+
+ if (dpm->power_state.event == state.event)
goto Done;

- if (dev->power.power_state.event)
- runtime_resume(dev);
+ if (dpm->power_state.event)
+ runtime_resume(dpm);

- if (!(error = suspend_device(dev, state)))
- dev->power.power_state = state;
+ error = dpm->pm_ops->suspend(dpm, state);
+ if (!error)
+ dpm->power_state = state;
Done:
up(&dpm_sem);
return error;
Index: work/drivers/pcmcia/ds.c
===================================================================
--- work.orig/drivers/pcmcia/ds.c
+++ work/drivers/pcmcia/ds.c
@@ -968,9 +968,9 @@ static ssize_t pcmcia_store_pm_state(str
return -EINVAL;

if ((!p_dev->suspended) && !strncmp(buf, "off", 3))
- ret = dpm_runtime_suspend(dev, PMSG_SUSPEND);
+ ret = dpm_runtime_suspend(&dev->power, PMSG_SUSPEND);
else if (p_dev->suspended && !strncmp(buf, "on", 2))
- dpm_runtime_resume(dev);
+ dpm_runtime_resume(&dev->power);

return ret ? ret : count;
}
@@ -1095,7 +1095,7 @@ static int pcmcia_bus_suspend_callback(s
if (p_dev->socket != skt)
return 0;

- return dpm_runtime_suspend(dev, PMSG_SUSPEND);
+ return dpm_runtime_suspend(&dev->power, PMSG_SUSPEND);
}

static int pcmcia_bus_resume_callback(struct device *dev, void * _data)
@@ -1106,7 +1106,7 @@ static int pcmcia_bus_resume_callback(st
if (p_dev->socket != skt)
return 0;

- dpm_runtime_resume(dev);
+ dpm_runtime_resume(&dev->power);

return 0;
}
Index: work/drivers/base/power/trace.c
===================================================================
--- work.orig/drivers/base/power/trace.c
+++ work/drivers/base/power/trace.c
@@ -138,9 +138,10 @@ static unsigned int hash_string(unsigned
return seed % mod;
}

-void set_trace_device(struct device *dev)
+void set_trace_device(struct dev_pm_info *dpm)
{
- dev_hash_value = hash_string(DEVSEED, dev->bus_id, DEVHASH);
+ dev_hash_value = hash_string(DEVSEED, kobject_name(dpm->pm_object),
+ DEVHASH);
}

/*
@@ -185,13 +186,17 @@ static int show_file_hash(unsigned int v
static int show_dev_hash(unsigned int value)
{
int match = 0;
- struct list_head * entry = dpm_active.prev;
+ struct list_head *entry = dpm_active.prev;
+ struct dev_pm_info *dpm;
+ unsigned int hash;

while (entry != &dpm_active) {
- struct device * dev = to_device(entry);
- unsigned int hash = hash_string(DEVSEED, dev->bus_id, DEVHASH);
+ dpm = container_of(entry, struct dev_pm_info, entry);
+ hash = hash_string(DEVSEED, kobject_name(dpm->pm_object),
+ DEVHASH);
if (hash == value) {
- printk(" hash matches device %s\n", dev->bus_id);
+ printk(" hash matches device %s\n",
+ kobject_name(dpm->pm_object));
match++;
}
entry = entry->prev;
Index: work/include/linux/resume-trace.h
===================================================================
--- work.orig/include/linux/resume-trace.h
+++ work/include/linux/resume-trace.h
@@ -5,11 +5,11 @@

extern int pm_trace_enabled;

-struct device;
-extern void set_trace_device(struct device *);
+struct dev_pm_info;
+extern void set_trace_device(struct dev_pm_info *);
extern void generate_resume_trace(void *tracedata, unsigned int user);

-#define TRACE_DEVICE(dev) set_trace_device(dev)
+#define TRACE_DEVICE(dpm) set_trace_device(dpm)
#define TRACE_RESUME(user) do { \
if (pm_trace_enabled) { \
void *tracedata; \
@@ -26,8 +26,8 @@ extern void generate_resume_trace(void *

#else

-#define TRACE_DEVICE(dev) do { } while (0)
-#define TRACE_RESUME(dev) do { } while (0)
+#define TRACE_DEVICE(dpm) do { } while (0)
+#define TRACE_RESUME(user) do { } while (0)

#endif

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/