[patch update] PM: Introduce core framework for run-time PM of I/O devices (rev. 6)

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Friday 26 June 2009, Alan Stern wrote:
> On Thu, 25 Jun 2009, Rafael J. Wysocki wrote:
> 
> > > The whole business about the runtime_notify and RPM_NOTIFY flags is 
> > > impenetrable.  My suggestion: Rename runtime_notify to notify_pending 
> > > and eliminate RPM_NOTIFY.  Then make sure that notify_pending is set 
> > > whenever a notify work item is queued.
> > 
> > I was going to do exactly that, but I realized it wouldn't work in general,
> > because ->runtime_idle() could run __pm_runtime_suspend() in theory.
> 
> I'll cut this short by noting the dilemma.  If the runtime_idle 
> callback does a synchronous suspend, and __pm_runtime_suspend sees the 
> status is already RPM_SUSPENDING, then it will wait for the suspend to 
> finish.  Hence it's not safe to do cancel_work_sync from within 
> __pm_runtime_suspend; it might deadlock.

Exactly. 

> It occurs to me that the problem would be solved if were a cancel_work
> routine.  In the same vein, it ought to be possible for
> cancel_delayed_work to run in interrupt context.  I'll see what can be
> done.

Having looked at the workqueue code I'm not sure if there's a way to implement
that in a non-racy way.  Which may be the reason why there are no such
functions already. :-)

> What do you think about adding a version of pm_runtime_put that would 
> call pm_runtime_idle directly when the counter reaches 0, instead of 
> queuing an idle request?  I feel that drivers should have a choice 
> about which sort of notification to use.

There can be pm_runtime_put_atomic() that will queue the idle request
and pm_runtime_put() that will call pm_runtime_idle() directly, why not.

> > > And don't forget to decrement the parent's child_count again if the resume
> > > fails.
> > 
> > I didn't _forget_it, because the device can't be RPM_SUSPENDED after
> > __pm_runtime_resume().
> 
> You're right; that fact escaped me.
> 
> > > In __pm_runtime_suspend, you should decrement the parent's child_count
> > > before releasing the child's lock.
> > 
> > Why exactly is that necessary?
> 
> I guess it isn't.  But it won't hurt to keep the parent's counter
> synchronized with the child's state as closely as possible.

OK

In the meantime I reworked the patch (below) to use more RPM_* flags and I
removed the runtime_break and runtime_notify bits from it.  Also added some
comments to explain some non-obvious steps (hope that helps).

I also added the pm_runtime_put_atomic() and pm_runtime_put() as per the
comment above.

It seems to be a bit cleaner this way, but that's my personal view. :-)

Best,
Rafael

---
From: Rafael J. Wysocki <rjw@xxxxxxx>
Subject: PM: Introduce core framework for run-time PM of I/O devices (rev. 6)

Introduce a core framework for run-time power management of I/O
devices.  Add device run-time PM fields to 'struct dev_pm_info'
and device run-time PM callbacks to 'struct dev_pm_ops'.  Introduce
a run-time PM workqueue and define some device run-time PM helper
functions at the core level.  Document all these things.

Signed-off-by: Rafael J. Wysocki <rjw@xxxxxxx>
---
 drivers/base/dd.c            |    9 
 drivers/base/power/Makefile  |    1 
 drivers/base/power/main.c    |   16 
 drivers/base/power/power.h   |   11 
 drivers/base/power/runtime.c |  734 +++++++++++++++++++++++++++++++++++++++++++
 include/linux/pm.h           |  117 ++++++
 include/linux/pm_runtime.h   |  133 +++++++
 kernel/power/Kconfig         |   14 
 kernel/power/main.c          |   17 
 9 files changed, 1042 insertions(+), 10 deletions(-)

Index: linux-2.6/kernel/power/Kconfig
===================================================================
--- linux-2.6.orig/kernel/power/Kconfig
+++ linux-2.6/kernel/power/Kconfig
@@ -208,3 +208,17 @@ config APM_EMULATION
 	  random kernel OOPSes or reboots that don't seem to be related to
 	  anything, try disabling/enabling this option (or disabling/enabling
 	  APM in your BIOS).
+
+config PM_RUNTIME
+	bool "Run-time PM core functionality"
+	depends on PM
+	---help---
+	  Enable functionality allowing I/O devices to be put into energy-saving
+	  (low power) states at run time (or autosuspended) after a specified
+	  period of inactivity and woken up in response to a hardware-generated
+	  wake-up event or a driver's request.
+
+	  Hardware support is generally required for this functionality to work
+	  and the bus type drivers of the buses the devices are on are
+	  responsibile for the actual handling of the autosuspend requests and
+	  wake-up events.
Index: linux-2.6/kernel/power/main.c
===================================================================
--- linux-2.6.orig/kernel/power/main.c
+++ linux-2.6/kernel/power/main.c
@@ -11,6 +11,7 @@
 #include <linux/kobject.h>
 #include <linux/string.h>
 #include <linux/resume-trace.h>
+#include <linux/workqueue.h>
 
 #include "power.h"
 
@@ -217,8 +218,24 @@ static struct attribute_group attr_group
 	.attrs = g,
 };
 
+#ifdef CONFIG_PM_RUNTIME
+struct workqueue_struct *pm_wq;
+
+static int __init pm_start_workqueue(void)
+{
+	pm_wq = create_freezeable_workqueue("pm");
+
+	return pm_wq ? 0 : -ENOMEM;
+}
+#else
+static inline int pm_start_workqueue(void) { return 0; }
+#endif
+
 static int __init pm_init(void)
 {
+	int error = pm_start_workqueue();
+	if (error)
+		return error;
 	power_kobj = kobject_create_and_add("power", NULL);
 	if (!power_kobj)
 		return -ENOMEM;
Index: linux-2.6/include/linux/pm.h
===================================================================
--- linux-2.6.orig/include/linux/pm.h
+++ linux-2.6/include/linux/pm.h
@@ -22,6 +22,9 @@
 #define _LINUX_PM_H
 
 #include <linux/list.h>
+#include <linux/workqueue.h>
+#include <linux/spinlock.h>
+#include <linux/completion.h>
 
 /*
  * Callbacks for platform drivers to implement.
@@ -165,6 +168,28 @@ typedef struct pm_message {
  * It is allowed to unregister devices while the above callbacks are being
  * executed.  However, it is not allowed to unregister a device from within any
  * of its own callbacks.
+ *
+ * There also are the following callbacks related to run-time power management
+ * of devices:
+ *
+ * @runtime_suspend: Prepare the device for a condition in which it won't be
+ *	able to communicate with the CPU(s) and RAM due to power management.
+ *	This need not mean that the device should be put into a low power state.
+ *	For example, if the device is behind a link which is about to be turned
+ *	off, the device may remain at full power.  Still, if the device does go
+ *	to low power and if device_may_wakeup(dev) is true, remote wake-up
+ *	(i.e. hardware mechanism allowing the device to request a change of its
+ *	power state, such as PCI PME) should be enabled for it.
+ *
+ * @runtime_resume: Put the device into the fully active state in response to a
+ *	wake-up event generated by hardware or at a request of software.  If
+ *	necessary, put the device into the full power state and restore its
+ *	registers, so that it is fully operational.
+ *
+ * @runtime_idle: Device appears to be inactive and it might be put into a low
+ *	power state if all of the necessary conditions are satisfied.  Check
+ *	these conditions and handle the device as appropriate, possibly queueing
+ *	a suspend request for it.
  */
 
 struct dev_pm_ops {
@@ -182,6 +207,9 @@ struct dev_pm_ops {
 	int (*thaw_noirq)(struct device *dev);
 	int (*poweroff_noirq)(struct device *dev);
 	int (*restore_noirq)(struct device *dev);
+	int (*runtime_suspend)(struct device *dev);
+	int (*runtime_resume)(struct device *dev);
+	void (*runtime_idle)(struct device *dev);
 };
 
 /**
@@ -315,14 +343,97 @@ enum dpm_state {
 	DPM_OFF_IRQ,
 };
 
+/**
+ * Device run-time power management state.
+ *
+ * These state labels are used internally by the PM core to indicate the current
+ * status of a device with respect to the PM core operations.  They do not
+ * reflect the actual power state of the device or its status as seen by the
+ * driver.
+ *
+ * RPM_ACTIVE		Device is fully operational, no run-time PM requests are
+ *			pending for it.
+ *
+ * RPM_NOTIFY		Idle notification has been scheduled for the device.
+ *
+ * RPM_NOTIFYING	Device bus type's ->runtime_idle() callback is being
+ *			executed (as a result of a scheduled idle notification
+ *			request).
+ *
+ * RPM_IDLE		It has been requested that the device be suspended.
+ *			Suspend request has been put into the run-time PM
+ *			workqueue and it's pending execution.
+ *
+ * RPM_SUSPEND		Attempt to suspend the device has started (as a result
+ *			of a scheduled request or synchronously), but the device
+ *			bus type's ->runtime_suspend() callback has not been
+ *			executed yet.
+ *
+ * RPM_SUSPENDING	Device bus type's ->runtime_suspend() callback is being
+ *			executed.
+ *
+ * RPM_SUSPENDED	Device bus type's ->runtime_suspend() callback has
+ *			completed successfully.  The device is regarded as
+ *			suspended.
+ *
+ * RPM_WAKE		It has been requested that the device be woken up.
+ *			Resume request has been put into the run-time PM
+ *			workqueue and it's pending execution.
+ *
+ * RPM_RESUME		Attempt to wake up the device has started (as a result
+ *			of a scheduled request or synchronously), but the device
+ *			bus type's ->runtime_resume() callback has not been
+ *			executed yet.
+ *
+ * RPM_RESUMING		Device bus type's ->runtime_resume() callback is being
+ *			executed.
+ *
+ * RPM_ERROR		Represents a condition from which the PM core cannot
+ *			recover by itself.  If the device's run-time PM status
+ *			field has this value, all of the run-time PM operations
+ *			carried out for the device by the core will fail, until
+ *			the status field is changed to either RPM_ACTIVE or
+ *			RPM_SUSPENDED (it is not valid to use the other values
+ *			in such a situation) by the device's driver or bus type.
+ *			This happens when the device bus type's
+ *			->runtime_suspend() or ->runtime_resume() callback
+ *			returns error code different from -EAGAIN or -EBUSY.
+ */
+
+#define RPM_ACTIVE	0
+
+#define RPM_NOTIFY	0x001
+#define RPM_NOTIFYING	0x002
+#define RPM_IDLE	0x004
+#define RPM_SUSPEND	0x008
+#define RPM_SUSPENDING	0x010
+#define RPM_SUSPENDED	0x020
+#define RPM_WAKE	0x040
+#define RPM_RESUME	0x080
+#define RPM_RESUMING	0x100
+
+#define RPM_ERROR	0x1FF
+
 struct dev_pm_info {
 	pm_message_t		power_state;
-	unsigned		can_wakeup:1;
-	unsigned		should_wakeup:1;
+	unsigned int		can_wakeup:1;
+	unsigned int		should_wakeup:1;
 	enum dpm_state		status;		/* Owned by the PM core */
-#ifdef	CONFIG_PM_SLEEP
+#ifdef CONFIG_PM_SLEEP
 	struct list_head	entry;
 #endif
+#ifdef CONFIG_PM_RUNTIME
+	struct delayed_work	suspend_work;
+	struct work_struct	work;
+	struct completion	work_done;
+	unsigned int		ignore_children:1;
+	unsigned int		runtime_disabled:1;
+	unsigned int		runtime_status;
+	int			runtime_error;
+	atomic_t		resume_count;
+	atomic_t		child_count;
+	spinlock_t		lock;
+#endif
 };
 
 /*
Index: linux-2.6/drivers/base/power/Makefile
===================================================================
--- linux-2.6.orig/drivers/base/power/Makefile
+++ linux-2.6/drivers/base/power/Makefile
@@ -1,5 +1,6 @@
 obj-$(CONFIG_PM)	+= sysfs.o
 obj-$(CONFIG_PM_SLEEP)	+= main.o
+obj-$(CONFIG_PM_RUNTIME)	+= runtime.o
 obj-$(CONFIG_PM_TRACE_RTC)	+= trace.o
 
 ccflags-$(CONFIG_DEBUG_DRIVER) := -DDEBUG
Index: linux-2.6/drivers/base/power/runtime.c
===================================================================
--- /dev/null
+++ linux-2.6/drivers/base/power/runtime.c
@@ -0,0 +1,734 @@
+/*
+ * drivers/base/power/runtime.c - Helper functions for device run-time PM
+ *
+ * Copyright (c) 2009 Rafael J. Wysocki <rjw@xxxxxxx>, Novell Inc.
+ *
+ * This file is released under the GPLv2.
+ */
+
+#include <linux/pm_runtime.h>
+#include <linux/jiffies.h>
+
+/**
+ * pm_runtime_idle - Notify device bus type if the device can be suspended.
+ * @dev: Device to notify the bus type about.
+ *
+ * It is possible that suspend request was scheduled and resume was requested
+ * before this function has a chance to run.  If there's a suspend request
+ * pending only, return doing nothing, but if resume was requested in addition
+ * to it, cancel the suspend request.
+ */
+void pm_runtime_idle(struct device *dev)
+{
+	unsigned long flags;
+
+	might_sleep();
+
+	spin_lock_irqsave(&dev->power.lock, flags);
+
+	if (dev->power.runtime_status == RPM_ERROR)
+		goto out;
+
+	if (dev->power.runtime_status & ~(RPM_NOTIFY|RPM_WAKE))
+		/*
+		 * Device suspended or run-time PM operation in progress. The
+		 * RPM_NOTIFY bit should have been cleared in that case.
+		 */
+		goto out;
+
+	dev->power.runtime_status &= ~RPM_NOTIFY;
+
+	if (dev->power.runtime_status == RPM_WAKE) {
+		/*
+		 * Resume has been requested, and because all of the suspend
+		 * status bits are clear, there must be a suspend request
+		 * pending.  We have to cancel that request.
+		 */
+
+		spin_unlock_irqrestore(&dev->power.lock, flags);
+
+		cancel_delayed_work_sync(&dev->power.suspend_work);
+
+		spin_lock_irqsave(&dev->power.lock, flags);
+
+		/*
+		 * Return if someone else has changed the status.  Otherwise,
+		 * the idle notification may still be worth running.
+		 */
+		if (dev->power.runtime_status != RPM_WAKE)
+			goto out;
+	}
+
+	if (!pm_suspend_possible(dev))
+		goto out;
+
+	dev->power.runtime_status = RPM_NOTIFYING;
+
+	spin_unlock_irqrestore(&dev->power.lock, flags);
+
+	if (dev->bus && dev->bus->pm && dev->bus->pm->runtime_idle)
+		dev->bus->pm->runtime_idle(dev);
+
+	spin_lock_irqsave(&dev->power.lock, flags);
+
+	/* The status might have been changed while executing runtime_idle(). */
+	dev->power.runtime_status &= ~RPM_NOTIFYING;
+
+ out:
+	spin_unlock_irqrestore(&dev->power.lock, flags);
+}
+EXPORT_SYMBOL_GPL(pm_runtime_idle);
+
+/**
+ * pm_runtime_idle_work - Run pm_runtime_idle() via pm_wq.
+ * @work: Work structure used for scheduling the execution of this function.
+ *
+ * Use @work to get the device object the idle notification has been scheduled
+ * for and run pm_runtime_idle() for it.
+ */
+static void pm_runtime_idle_work(struct work_struct *work)
+{
+	pm_runtime_idle(pm_work_to_device(work));
+}
+
+/**
+ * pm_runtime_put_atomic - Decrement resume counter and queue idle notification.
+ * @dev: Device to handle.
+ *
+ * Decrement the device's resume counter, check if the device's run-time PM
+ * status is right for suspending and queue up a request to run
+ * pm_runtime_idle() for it.
+ */
+void pm_runtime_put_atomic(struct device *dev)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev->power.lock, flags);
+
+	if (!__pm_runtime_put(dev)) {
+		dev_WARN(dev, "Unbalanced %s", __func__);
+		goto out;
+	}
+
+	if (dev->power.runtime_status != RPM_ACTIVE)
+		goto out;
+
+	/*
+	 * The notification is asynchronous so that this function can be called
+	 * from interrupt context.
+	 */
+	dev->power.runtime_status = RPM_NOTIFY;
+	INIT_WORK(&dev->power.work, pm_runtime_idle_work);
+	queue_work(pm_wq, &dev->power.work);
+
+ out:
+	spin_unlock_irqrestore(&dev->power.lock, flags);
+}
+EXPORT_SYMBOL_GPL(pm_runtime_put_atomic);
+
+/**
+ * pm_runtime_put - Decrement resume counter and run idle notification.
+ * @dev: Device to handle.
+ *
+ * Decrement the device's resume counter and run pm_runtime_idle() for it.
+ */
+void pm_runtime_put(struct device *dev)
+{
+	if (!__pm_runtime_put(dev)) {
+		dev_WARN(dev, "Unbalanced %s", __func__);
+		return;
+	}
+
+	pm_runtime_idle(dev);
+}
+EXPORT_SYMBOL_GPL(pm_runtime_put);
+
+/**
+ * __pm_runtime_suspend - Carry out run-time suspend of given device.
+ * @dev: Device to suspend.
+ * @sync: If unset, the funtion has been called via pm_wq.
+ *
+ * Check if the device can be suspended and run the ->runtime_suspend() callback
+ * provided by its bus type.  If another suspend has been started earlier, wait
+ * for it to finish.  If there's an idle notification pending, cancel it.  If
+ * there's a suspend request scheduled while this function is running and @sync
+ * is 'true', cancel that request.
+ */
+int __pm_runtime_suspend(struct device *dev, bool sync)
+{
+	struct device *parent = NULL;
+	unsigned long flags;
+	bool cancel_pending = false;
+	int error = -EINVAL;
+
+	might_sleep();
+
+ repeat:
+	spin_lock_irqsave(&dev->power.lock, flags);
+
+	if (dev->power.runtime_status == RPM_ERROR)
+		goto out;
+
+	if (dev->power.runtime_status & RPM_SUSPENDED) {
+		/* Device suspended, nothing to do. */
+		error = 0;
+		goto out;
+	}
+
+	if (dev->power.runtime_status & RPM_SUSPENDING) {
+		/* Another suspend is running in parallel with us. */
+
+		spin_unlock_irqrestore(&dev->power.lock, flags);
+
+		wait_for_completion(&dev->power.work_done);
+		return dev->power.runtime_error;
+	}
+
+	if (dev->power.runtime_status & (RPM_WAKE|RPM_RESUME|RPM_RESUMING)) {
+		/* Resume is scheduled or in progress. */
+		error = -EAGAIN;
+		goto out;
+	}
+
+	/*
+	 * If there's a suspend request pending and we're not running as a
+	 * result of it, the request has to be cancelled, because it may be
+	 * scheduled in the future and we can't leave it behind us.
+	 */
+	if (sync && (dev->power.runtime_status & RPM_IDLE))
+		cancel_pending = true;
+
+	/* Clear the suspend status bits in case we have to return. */
+	dev->power.runtime_status &= ~(RPM_IDLE|RPM_SUSPEND);
+
+	if (atomic_read(&dev->power.resume_count) > 0
+	    || dev->power.runtime_disabled) {
+		/* We are forbidden to suspend. */
+		error = -EAGAIN;
+		goto out;
+	}
+
+	if (!pm_children_suspended(dev)) {
+		/*
+		 * We can only suspend the device if all of its children have
+		 * been suspended.
+		 */
+		error = -EBUSY;
+		goto out;
+	}
+
+	/*
+	 * Set RPM_SUSPEND in case we have to start over, to prevent idle
+	 * notifications from happening and new suspend requests from being
+	 * scheduled.
+	 */
+	dev->power.runtime_status |= RPM_SUSPEND;
+
+	if (cancel_pending) {
+		/* Cancel the concurrent pending suspend request. */
+
+		spin_unlock_irqrestore(&dev->power.lock, flags);
+
+		cancel_delayed_work_sync(&dev->power.suspend_work);
+		goto repeat;
+	}
+
+	if (dev->power.runtime_status & RPM_NOTIFY) {
+		/* Idle notification is pending, cancel it. */
+		dev->power.runtime_status &= ~RPM_NOTIFY;
+
+		spin_unlock_irqrestore(&dev->power.lock, flags);
+
+		cancel_work_sync(&dev->power.work);
+		goto repeat;
+	}
+
+	dev->power.runtime_status &= ~RPM_SUSPEND;
+	dev->power.runtime_status |= RPM_SUSPENDING;
+	init_completion(&dev->power.work_done);
+
+	spin_unlock_irqrestore(&dev->power.lock, flags);
+
+	if (dev->bus && dev->bus->pm && dev->bus->pm->runtime_suspend)
+		error = dev->bus->pm->runtime_suspend(dev);
+
+	spin_lock_irqsave(&dev->power.lock, flags);
+
+	switch (error) {
+	case 0:
+		dev->power.runtime_status &= ~RPM_SUSPENDING;
+		dev->power.runtime_status |= RPM_SUSPENDED;
+		break;
+	case -EAGAIN:
+	case -EBUSY:
+		dev->power.runtime_status &= RPM_NOTIFYING;
+		break;
+	default:
+		dev->power.runtime_status = RPM_ERROR;
+	}
+	dev->power.runtime_error = error;
+	complete_all(&dev->power.work_done);
+
+	if (!error && !(dev->power.runtime_status & RPM_WAKE) && dev->parent) {
+		parent = dev->parent;
+		atomic_dec(&parent->power.child_count);
+	}
+
+ out:
+	spin_unlock_irqrestore(&dev->power.lock, flags);
+
+	if (parent && !parent->power.ignore_children)
+		pm_runtime_idle(parent);
+
+	if (error == -EBUSY || error == -EAGAIN)
+		pm_runtime_idle(dev);
+
+	return error;
+}
+EXPORT_SYMBOL_GPL(__pm_runtime_suspend);
+
+/**
+ * pm_runtime_suspend_work - Run __pm_runtime_suspend() for a device.
+ * @work: Work structure used for scheduling the execution of this function.
+ *
+ * Use @work to get the device object the work has been scheduled for and run
+ * __pm_runtime_suspend() for it.
+ */
+static void pm_runtime_suspend_work(struct work_struct *work)
+{
+	__pm_runtime_suspend(suspend_work_to_device(work), false);
+}
+
+/**
+ * pm_request_suspend - Schedule run-time suspend of given device.
+ * @dev: Device to suspend.
+ * @msec: Time to wait before attempting to suspend the device, in milliseconds.
+ */
+int pm_request_suspend(struct device *dev, unsigned int msec)
+{
+	unsigned long flags;
+	unsigned long delay = msecs_to_jiffies(msec);
+	int error = 0;
+
+	spin_lock_irqsave(&dev->power.lock, flags);
+
+	if (dev->power.runtime_status == RPM_ERROR)
+		error = -EINVAL;
+	else if (dev->power.runtime_status & RPM_SUSPENDED)
+		/* Device is suspended, nothing to do. */
+		error = -ECANCELED;
+	else if (atomic_read(&dev->power.resume_count) > 0
+	    || dev->power.runtime_disabled
+	    || (dev->power.runtime_status & (RPM_WAKE|RPM_RESUME|RPM_RESUMING)))
+		/* Can't suspend now. */
+		error = -EAGAIN;
+	else if (dev->power.runtime_status &
+				(RPM_IDLE|RPM_SUSPEND|RPM_SUSPENDING))
+		/* Already suspending or suspend request pending. */
+		error = -EINPROGRESS;
+	else if (!pm_children_suspended(dev))
+		error = -EBUSY;
+	if (error)
+		goto out;
+
+	dev->power.runtime_status |= RPM_IDLE;
+	queue_delayed_work(pm_wq, &dev->power.suspend_work, delay);
+
+ out:
+	spin_unlock_irqrestore(&dev->power.lock, flags);
+
+	return error;
+}
+EXPORT_SYMBOL_GPL(pm_request_suspend);
+
+/**
+ * __pm_runtime_resume - Carry out run-time resume of given device.
+ * @dev: Device to resume.
+ * @sync: If unset, the funtion has been called via pm_wq.
+ *
+ * Check if the device can be woken up and run the ->runtime_resume() callback
+ * provided by its bus type.  If another resume has been started earlier, wait
+ * for it to finish.  If there's a suspend running in parallel with this
+ * function, wait for it to finish and resume the device.  If there's a suspend
+ * request or idle notification pending, cancel it.  If there's a resume request
+ * scheduled while this function is running and @sync is 'true', cancel that
+ * request.
+ */
+int __pm_runtime_resume(struct device *dev, bool sync)
+{
+	struct device *parent = dev->parent;
+	unsigned long flags;
+	bool put_parent = false;
+	int error = -EINVAL;
+
+	might_sleep();
+
+ repeat:
+	spin_lock_irqsave(&dev->power.lock, flags);
+
+	if (dev->power.runtime_status == RPM_ERROR)
+		goto out;
+
+	if (!(dev->power.runtime_status & ~RPM_NOTIFYING)) {
+		/* Device is operational, nothing to do. */
+		error = 0;
+		goto out;
+	}
+
+	if (dev->power.runtime_status & RPM_RESUMING) {
+		/*
+		 * There's another resume running in parallel with us. Wait for
+		 * it to complete and return.
+		 */
+
+		spin_unlock_irqrestore(&dev->power.lock, flags);
+
+		wait_for_completion(&dev->power.work_done);
+		error = dev->power.runtime_error;
+		goto out_parent;
+	}
+
+	if (dev->power.runtime_disabled) {
+		/* Clear the resume flags before returning. */
+		dev->power.runtime_status &= ~(RPM_WAKE|RPM_RESUME);
+		error = -EAGAIN;
+		goto out;
+	}
+
+	/*
+	 * Set RPM_RESUME in case we have to start over, to prevent suspends and
+	 * idle notifications from happening and new resume requests from being
+	 * queued up.
+	 */
+	dev->power.runtime_status |= RPM_RESUME;
+
+	if (dev->power.runtime_status & RPM_SUSPENDING) {
+		/*
+		 * Suspend is running in parallel with us.  Wait for it to
+		 * complete and repeat.
+		 */
+		spin_unlock_irqrestore(&dev->power.lock, flags);
+
+		wait_for_completion(&dev->power.work_done);
+		goto repeat;
+	}
+
+	if ((dev->power.runtime_status & (RPM_IDLE|RPM_WAKE))
+	    && !(dev->power.runtime_status &
+				(RPM_SUSPEND|RPM_SUSPENDING|RPM_SUSPENDED))) {
+		/* Suspend request is pending that we're supposed to cancel. */
+		dev->power.runtime_status &= ~RPM_IDLE;
+
+		spin_unlock_irqrestore(&dev->power.lock, flags);
+
+		cancel_delayed_work_sync(&dev->power.suspend_work);
+		goto repeat;
+	}
+
+	/*
+	 * Clear RPM_SUSPEND in case we've been running in parallel with
+	 * __pm_runtime_suspend().
+	 */
+	dev->power.runtime_status &= ~RPM_SUSPEND;
+
+	if ((sync && (dev->power.runtime_status & RPM_WAKE))
+	    || (dev->power.runtime_status & RPM_NOTIFY)) {
+		/*
+		 * Idle notification is pending and since we're running the
+		 * device is not idle, or there's a resume request pending and
+		 * we're not running as a result of it.  In both cases it's
+		 * better to cancel the request.
+		 */
+		dev->power.runtime_status &= ~(RPM_NOTIFY|RPM_WAKE);
+
+		spin_unlock_irqrestore(&dev->power.lock, flags);
+
+		cancel_work_sync(&dev->power.work);
+		goto repeat;
+	}
+
+	/* Clear the resume status flags in case we have to return. */
+	dev->power.runtime_status &= ~(RPM_WAKE|RPM_RESUME);
+
+	if (!(dev->power.runtime_status & RPM_SUSPENDED)) {
+		/*
+		 * If the device is not suspended at this point, we have
+		 * nothing to do.
+		 */
+		error = 0;
+		goto out;
+	}
+
+	if (!put_parent && parent) {
+		/*
+		 * Increase the parent's resume counter and request that it be
+		 * woken up if necessary.
+		 */
+		spin_unlock_irqrestore(&dev->power.lock, flags);
+
+		put_parent = true;
+		pm_runtime_get(parent);
+		error = pm_runtime_resume(parent);
+		if (error)
+			goto out_parent;
+
+		error = -EINVAL;
+		dev->power.runtime_status |= RPM_RESUME;
+		goto repeat;
+	}
+
+	dev->power.runtime_status &= ~RPM_SUSPENDED;
+
+	if (parent)
+		atomic_inc(&parent->power.child_count);
+
+	dev->power.runtime_status |= RPM_RESUMING;
+	init_completion(&dev->power.work_done);
+
+	spin_unlock_irqrestore(&dev->power.lock, flags);
+
+	if (dev->bus && dev->bus->pm && dev->bus->pm->runtime_resume)
+		error = dev->bus->pm->runtime_resume(dev);
+
+	spin_lock_irqsave(&dev->power.lock, flags);
+
+	dev->power.runtime_status &= ~RPM_RESUMING;
+	if (error)
+		dev->power.runtime_status = RPM_ERROR;
+	dev->power.runtime_error = error;
+	complete_all(&dev->power.work_done);
+
+ out:
+	spin_unlock_irqrestore(&dev->power.lock, flags);
+
+ out_parent:
+	if (put_parent)
+		pm_runtime_put(parent);
+
+	if (!error)
+		pm_runtime_idle(dev);
+
+	return error;
+}
+EXPORT_SYMBOL_GPL(pm_runtime_resume);
+
+/**
+ * pm_runtime_work - Run __pm_runtime_resume() for a device.
+ * @work: Work structure used for scheduling the execution of this function.
+ *
+ * Use @work to get the device object the work has been scheduled for and run
+ * __pm_runtime_resume() for it.
+ */
+static void pm_runtime_work(struct work_struct *work)
+{
+	__pm_runtime_resume(pm_work_to_device(work), false);
+}
+
+/**
+ * pm_request_resume - Schedule run-time resume of given device.
+ * @dev: Device to resume.
+ */
+int pm_request_resume(struct device *dev)
+{
+	struct device *parent = dev->parent;
+	unsigned long flags;
+	int error = 0;
+
+	spin_lock_irqsave(&dev->power.lock, flags);
+
+	if (dev->power.runtime_status == RPM_ERROR) {
+		error = -EINVAL;
+	} else if (dev->power.runtime_disabled) {
+		error = -EAGAIN;
+	} else if (!(dev->power.runtime_status & ~RPM_NOTIFYING)) {
+		/* Device is operational, nothing to do. */
+		error = -ECANCELED;
+	} else if (dev->power.runtime_status & RPM_NOTIFY) {
+		/*
+		 * Device has an idle notification pending, which is not a
+		 * problem unless there's a suspend request pending in addition
+		 * to it.  In that case, ask the idle notification work function
+		 * to cancel the suspend request.
+		 */
+		if (dev->power.runtime_status & RPM_IDLE) {
+			dev->power.runtime_status &= ~RPM_IDLE;
+			dev->power.runtime_status |= RPM_WAKE;
+			error = -EALREADY;
+		} else {
+			error = -ECANCELED;
+		}
+	} else if (dev->power.runtime_status &
+				(RPM_WAKE|RPM_RESUME|RPM_RESUMING)) {
+		error = -EINPROGRESS;
+	}
+	if (error)
+		goto out;
+
+	if (dev->power.runtime_status & RPM_IDLE) {
+		/* Suspend request is pending.  Make sure it won't run. */
+		dev->power.runtime_status &= ~RPM_IDLE;
+		INIT_WORK(&dev->power.work, pm_runtime_idle_work);
+		error = -EALREADY;
+		goto queue;
+	}
+
+	if ((dev->power.runtime_status & RPM_SUSPENDED) && parent)
+		atomic_inc(&parent->power.child_count);
+
+	INIT_WORK(&dev->power.work, pm_runtime_work);
+
+ queue:
+	dev->power.runtime_status |= RPM_WAKE;
+	queue_work(pm_wq, &dev->power.work);
+
+ out:
+	spin_unlock_irqrestore(&dev->power.lock, flags);
+
+	return error;
+}
+EXPORT_SYMBOL_GPL(pm_request_resume);
+
+/**
+ * __pm_runtime_clear_status - Change the run-time PM status of a device.
+ * @dev: Device to handle.
+ * @status: New value of the device's run-time PM status.
+ *
+ * Change the run-time PM status of the device to @status, which must be
+ * either RPM_ACTIVE or RPM_SUSPENDED, if its current value is equal to
+ * RPM_ERROR.
+ */
+void __pm_runtime_clear_status(struct device *dev, unsigned int status)
+{
+	struct device *parent = dev->parent;
+	unsigned long flags;
+
+	if (status & ~RPM_SUSPENDED)
+		return;
+
+	spin_lock_irqsave(&dev->power.lock, flags);
+
+	if (dev->power.runtime_status != RPM_ERROR)
+		goto out;
+
+	dev->power.runtime_status = status;
+	if (status == RPM_SUSPENDED && parent)
+		atomic_dec(&parent->power.child_count);
+
+ out:
+	spin_unlock_irqrestore(&dev->power.lock, flags);
+}
+EXPORT_SYMBOL_GPL(__pm_runtime_clear_status);
+
+/**
+ * pm_runtime_enable - Enable run-time PM of a device.
+ * @dev: Device to handle.
+ */
+void pm_runtime_enable(struct device *dev)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev->power.lock, flags);
+
+	if (!dev->power.runtime_disabled)
+		goto out;
+
+	if (!__pm_runtime_put(dev))
+		dev_WARN(dev, "Unbalanced counter decrementation");
+
+	if (!atomic_read(&dev->power.resume_count))
+		dev->power.runtime_disabled = false;
+
+ out:
+	spin_unlock_irqrestore(&dev->power.lock, flags);
+}
+EXPORT_SYMBOL_GPL(pm_runtime_enable);
+
+/**
+ * pm_runtime_disable - Disable run-time PM of a device.
+ * @dev: Device to handle.
+ *
+ * Set the power.runtime_disabled flag for the device, cancel all pending
+ * run-time PM requests for it and wait for operations in progress to complete.
+ * The device can be either active or suspended after its run-time PM has been
+ * disabled.
+ */
+void pm_runtime_disable(struct device *dev)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev->power.lock, flags);
+
+	pm_runtime_get(dev);
+
+	if (dev->power.runtime_disabled)
+		goto out;
+
+	dev->power.runtime_disabled = true;
+
+	if (dev->power.runtime_status & (RPM_IDLE|RPM_WAKE)
+	    && !(dev->power.runtime_status &
+				(RPM_SUSPEND|RPM_SUSPENDING|RPM_SUSPENDED))) {
+		/* Suspend request pending. */
+		dev->power.runtime_status &= ~RPM_IDLE;
+
+		spin_unlock_irqrestore(&dev->power.lock, flags);
+
+		cancel_delayed_work_sync(&dev->power.suspend_work);
+
+		spin_lock_irqsave(&dev->power.lock, flags);
+	}
+
+	if (dev->power.runtime_status & (RPM_WAKE|RPM_NOTIFY)) {
+		/* Resume request pending or idle notification. */
+		dev->power.runtime_status &= ~(RPM_WAKE|RPM_NOTIFY);
+
+		spin_unlock_irqrestore(&dev->power.lock, flags);
+
+		cancel_work_sync(&dev->power.work);
+
+		spin_lock_irqsave(&dev->power.lock, flags);
+	}
+
+	dev->power.runtime_status &= ~RPM_NOTIFYING;
+
+	if (dev->power.runtime_status & (RPM_SUSPENDING|RPM_RESUMING)) {
+		/* Suspend or wake-up in progress. */
+
+		spin_unlock_irqrestore(&dev->power.lock, flags);
+
+		wait_for_completion(&dev->power.work_done);
+		return;
+	}
+
+ out:
+	spin_unlock_irqrestore(&dev->power.lock, flags);
+}
+EXPORT_SYMBOL_GPL(pm_runtime_disable);
+
+/**
+ * pm_runtime_init - Initialize run-time PM fields in given device object.
+ * @dev: Device object to initialize.
+ */
+void pm_runtime_init(struct device *dev)
+{
+	spin_lock_init(&dev->power.lock);
+
+	dev->power.runtime_status = RPM_ACTIVE;
+	dev->power.runtime_disabled = true;
+	atomic_set(&dev->power.resume_count, 1);
+
+	atomic_set(&dev->power.child_count, 0);
+	pm_suspend_ignore_children(dev, false);
+
+	INIT_DELAYED_WORK(&dev->power.suspend_work, pm_runtime_suspend_work);
+}
+
+/**
+ * pm_runtime_add - Update run-time PM fields of a device while adding it.
+ * @dev: Device object being added to device hierarchy.
+ */
+void pm_runtime_add(struct device *dev)
+{
+	if (dev->parent)
+		atomic_inc(&dev->parent->power.child_count);
+}
Index: linux-2.6/include/linux/pm_runtime.h
===================================================================
--- /dev/null
+++ linux-2.6/include/linux/pm_runtime.h
@@ -0,0 +1,133 @@
+/*
+ * pm_runtime.h - Device run-time power management helper functions.
+ *
+ * Copyright (C) 2009 Rafael J. Wysocki <rjw@xxxxxxx>
+ *
+ * This file is released under the GPLv2.
+ */
+
+#ifndef _LINUX_PM_RUNTIME_H
+#define _LINUX_PM_RUNTIME_H
+
+#include <linux/device.h>
+#include <linux/pm.h>
+
+#ifdef CONFIG_PM_RUNTIME
+
+extern struct workqueue_struct *pm_wq;
+
+extern void pm_runtime_init(struct device *dev);
+extern void pm_runtime_add(struct device *dev);
+extern void pm_runtime_idle(struct device *dev);
+extern void pm_runtime_put_atomic(struct device *dev);
+extern void pm_runtime_put(struct device *dev);
+extern int __pm_runtime_suspend(struct device *dev, bool sync);
+extern int pm_request_suspend(struct device *dev, unsigned int msec);
+extern int __pm_runtime_resume(struct device *dev, bool sync);
+extern int pm_request_resume(struct device *dev);
+extern void __pm_runtime_clear_status(struct device *dev, unsigned int status);
+extern void pm_runtime_enable(struct device *dev);
+extern void pm_runtime_disable(struct device *dev);
+
+static inline struct device *suspend_work_to_device(struct work_struct *work)
+{
+	struct delayed_work *dw = to_delayed_work(work);
+
+	return container_of(dw, struct device, power.suspend_work);
+}
+
+static inline struct device *pm_work_to_device(struct work_struct *work)
+{
+	return container_of(work, struct device, power.work);
+}
+
+static inline void pm_runtime_get(struct device *dev)
+{
+	atomic_inc(&dev->power.resume_count);
+}
+
+static inline bool __pm_runtime_put(struct device *dev)
+{
+	return !!atomic_add_unless(&dev->power.resume_count, -1, 0);
+}
+
+static inline bool pm_children_suspended(struct device *dev)
+{
+	return dev->power.ignore_children
+		|| !atomic_read(&dev->power.child_count);
+}
+
+static inline bool pm_suspend_possible(struct device *dev)
+{
+	return pm_children_suspended(dev)
+		&& !atomic_read(&dev->power.resume_count)
+		&& !dev->power.runtime_disabled;
+}
+
+static inline void pm_suspend_ignore_children(struct device *dev, bool enable)
+{
+	dev->power.ignore_children = enable;
+}
+
+#else /* !CONFIG_PM_RUNTIME */
+
+static inline void pm_runtime_init(struct device *dev) {}
+static inline void pm_runtime_add(struct device *dev) {}
+static inline void pm_runtime_idle(struct device *dev) {}
+static inline void pm_runtime_put_atomic(struct device *dev) {}
+static inline void pm_runtime_put(struct device *dev) {}
+static inline int __pm_runtime_suspend(struct device *dev, bool sync)
+{
+	return -ENOSYS;
+}
+static inline int pm_request_suspend(struct device *dev, unsigned int msec)
+{
+	return -ENOSYS;
+}
+static inline int __pm_runtime_resume(struct device *dev, bool sync)
+{
+	return -ENOSYS;
+}
+static inline int pm_request_resume(struct device *dev)
+{
+	return -ENOSYS;
+}
+static inline void __pm_runtime_clear_status(struct device *dev,
+					      unsigned int status) {}
+static inline void pm_runtime_enable(struct device *dev) {}
+static inline void pm_runtime_disable(struct device *dev) {}
+
+static inline void pm_runtime_get(struct device *dev) {}
+static inline bool __pm_runtime_put(struct device *dev) { return true; }
+static inline bool pm_children_suspended(struct device *dev) { return false; }
+static inline bool pm_suspend_possible(struct device *dev) { return false; }
+static inline void pm_suspend_ignore_children(struct device *dev, bool en) {}
+
+#endif /* !CONFIG_PM_RUNTIME */
+
+static inline int pm_runtime_suspend(struct device *dev)
+{
+	return __pm_runtime_suspend(dev, true);
+}
+
+static inline int pm_runtime_resume(struct device *dev)
+{
+	return __pm_runtime_resume(dev, true);
+}
+
+static inline void pm_runtime_clear_active(struct device *dev)
+{
+	__pm_runtime_clear_status(dev, RPM_ACTIVE);
+}
+
+static inline void pm_runtime_clear_suspended(struct device *dev)
+{
+	__pm_runtime_clear_status(dev, RPM_SUSPENDED);
+}
+
+static inline void pm_runtime_remove(struct device *dev)
+{
+	pm_runtime_disable(dev);
+}
+
+#endif
Index: linux-2.6/drivers/base/power/main.c
===================================================================
--- linux-2.6.orig/drivers/base/power/main.c
+++ linux-2.6/drivers/base/power/main.c
@@ -21,6 +21,7 @@
 #include <linux/kallsyms.h>
 #include <linux/mutex.h>
 #include <linux/pm.h>
+#include <linux/pm_runtime.h>
 #include <linux/resume-trace.h>
 #include <linux/rwsem.h>
 #include <linux/interrupt.h>
@@ -49,6 +50,16 @@ static DEFINE_MUTEX(dpm_list_mtx);
 static bool transition_started;
 
 /**
+ * device_pm_init - Initialize the PM-related part of a device object
+ * @dev: Device object to initialize.
+ */
+void device_pm_init(struct device *dev)
+{
+	dev->power.status = DPM_ON;
+	pm_runtime_init(dev);
+}
+
+/**
  *	device_pm_lock - lock the list of active devices used by the PM core
  */
 void device_pm_lock(void)
@@ -88,6 +99,7 @@ void device_pm_add(struct device *dev)
 	}
 
 	list_add_tail(&dev->power.entry, &dpm_list);
+	pm_runtime_add(dev);
 	mutex_unlock(&dpm_list_mtx);
 }
 
@@ -104,6 +116,7 @@ void device_pm_remove(struct device *dev
 		 kobject_name(&dev->kobj));
 	mutex_lock(&dpm_list_mtx);
 	list_del_init(&dev->power.entry);
+	pm_runtime_remove(dev);
 	mutex_unlock(&dpm_list_mtx);
 }
 
@@ -507,6 +520,7 @@ static void dpm_complete(pm_message_t st
 		get_device(dev);
 		if (dev->power.status > DPM_ON) {
 			dev->power.status = DPM_ON;
+			pm_runtime_enable(dev);
 			mutex_unlock(&dpm_list_mtx);
 
 			device_complete(dev, state);
@@ -753,6 +767,7 @@ static int dpm_prepare(pm_message_t stat
 
 		get_device(dev);
 		dev->power.status = DPM_PREPARING;
+		pm_runtime_disable(dev);
 		mutex_unlock(&dpm_list_mtx);
 
 		error = device_prepare(dev, state);
@@ -760,6 +775,7 @@ static int dpm_prepare(pm_message_t stat
 		mutex_lock(&dpm_list_mtx);
 		if (error) {
 			dev->power.status = DPM_ON;
+			pm_runtime_enable(dev);
 			if (error == -EAGAIN) {
 				put_device(dev);
 				continue;
Index: linux-2.6/drivers/base/dd.c
===================================================================
--- linux-2.6.orig/drivers/base/dd.c
+++ linux-2.6/drivers/base/dd.c
@@ -23,6 +23,7 @@
 #include <linux/kthread.h>
 #include <linux/wait.h>
 #include <linux/async.h>
+#include <linux/pm_runtime.h>
 
 #include "base.h"
 #include "power/power.h"
@@ -202,8 +203,12 @@ int driver_probe_device(struct device_dr
 	pr_debug("bus: '%s': %s: matched device %s with driver %s\n",
 		 drv->bus->name, __func__, dev_name(dev), drv->name);
 
+	pm_runtime_disable(dev);
+
 	ret = really_probe(dev, drv);
 
+	pm_runtime_enable(dev);
+
 	return ret;
 }
 
@@ -306,6 +311,8 @@ static void __device_release_driver(stru
 
 	drv = dev->driver;
 	if (drv) {
+		pm_runtime_disable(dev);
+
 		driver_sysfs_remove(dev);
 
 		if (dev->bus)
@@ -324,6 +331,8 @@ static void __device_release_driver(stru
 			blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
 						     BUS_NOTIFY_UNBOUND_DRIVER,
 						     dev);
+
+		pm_runtime_enable(dev);
 	}
 }
 
Index: linux-2.6/drivers/base/power/power.h
===================================================================
--- linux-2.6.orig/drivers/base/power/power.h
+++ linux-2.6/drivers/base/power/power.h
@@ -1,8 +1,3 @@
-static inline void device_pm_init(struct device *dev)
-{
-	dev->power.status = DPM_ON;
-}
-
 #ifdef CONFIG_PM_SLEEP
 
 /*
@@ -16,14 +11,16 @@ static inline struct device *to_device(s
 	return container_of(entry, struct device, power.entry);
 }
 
+extern void device_pm_init(struct device *dev);
 extern void device_pm_add(struct device *);
 extern void device_pm_remove(struct device *);
 extern void device_pm_move_before(struct device *, struct device *);
 extern void device_pm_move_after(struct device *, struct device *);
 extern void device_pm_move_last(struct device *);
 
-#else /* CONFIG_PM_SLEEP */
+#else /* !CONFIG_PM_SLEEP */
 
+static inline void device_pm_init(struct device *dev) {}
 static inline void device_pm_add(struct device *dev) {}
 static inline void device_pm_remove(struct device *dev) {}
 static inline void device_pm_move_before(struct device *deva,
@@ -32,7 +29,7 @@ static inline void device_pm_move_after(
 					struct device *devb) {}
 static inline void device_pm_move_last(struct device *dev) {}
 
-#endif
+#endif /* !CONFIG_PM_SLEEP */
 
 #ifdef CONFIG_PM
 
_______________________________________________
linux-pm mailing list
linux-pm@xxxxxxxxxxxxxxxxxxxxxxxxxx
https://lists.linux-foundation.org/mailman/listinfo/linux-pm

[Index of Archives]     [Linux ACPI]     [Netdev]     [Ethernet Bridging]     [Linux Wireless]     [CPU Freq]     [Kernel Newbies]     [Fedora Kernel]     [Security]     [Linux for Hams]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux Admin]     [Samba]

  Powered by Linux