suspend_delay implementation

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

 



On Thu, 2 Sep 2010, Rafael J. Wysocki wrote:

> > Ah, but the way this is designed means that the value to be used _must_
> > be stored in dev_pm_info.  This will become obvious when you see the
> > code.  I can send a preliminary patch if you are interested.
> 
> Well, I guess it won't hurt. :-)

Here it is.  No documentation written yet, but there is some kerneldoc 
for the new routines.

Alan Stern



Index: usb-2.6/include/linux/pm.h
===================================================================
--- usb-2.6.orig/include/linux/pm.h
+++ usb-2.6/include/linux/pm.h
@@ -474,9 +474,12 @@ struct dev_pm_info {
 	unsigned int		deferred_resume:1;
 	unsigned int		run_wake:1;
 	unsigned int		runtime_auto:1;
+	unsigned int		use_suspend_delay:1;
 	enum rpm_request	request;
 	enum rpm_status		runtime_status;
 	int			runtime_error;
+	int			suspend_delay;
+	unsigned long		last_busy;
 	unsigned long		active_jiffies;
 	unsigned long		suspended_jiffies;
 	unsigned long		accounting_timestamp;
Index: usb-2.6/include/linux/pm_runtime.h
===================================================================
--- usb-2.6.orig/include/linux/pm_runtime.h
+++ usb-2.6/include/linux/pm_runtime.h
@@ -14,6 +14,8 @@
 
 #ifdef CONFIG_PM_RUNTIME
 
+#include <linux/jiffies.h>
+
 extern struct workqueue_struct *pm_wq;
 
 extern int pm_runtime_idle(struct device *dev);
@@ -24,6 +26,7 @@ extern int pm_schedule_suspend(struct de
 extern int pm_request_resume(struct device *dev);
 extern int __pm_runtime_get(struct device *dev, bool sync);
 extern int __pm_runtime_put(struct device *dev, bool sync);
+extern int pm_runtime_put_delay(struct device *dev);
 extern int __pm_runtime_set_status(struct device *dev, unsigned int status);
 extern int pm_runtime_barrier(struct device *dev);
 extern void pm_runtime_enable(struct device *dev);
@@ -33,6 +36,9 @@ extern void pm_runtime_forbid(struct dev
 extern int pm_generic_runtime_idle(struct device *dev);
 extern int pm_generic_runtime_suspend(struct device *dev);
 extern int pm_generic_runtime_resume(struct device *dev);
+extern void __pm_runtime_use_suspend_delay(struct device *dev, bool use);
+extern void pm_runtime_set_suspend_delay(struct device *dev, int delay);
+extern unsigned long pm_runtime_suspend_delay_expiration(struct device *dev);
 
 static inline bool pm_children_suspended(struct device *dev)
 {
@@ -70,6 +76,11 @@ static inline bool pm_runtime_suspended(
 	return dev->power.runtime_status == RPM_SUSPENDED;
 }
 
+static inline void pm_runtime_mark_last_busy(struct device *dev)
+{
+	dev->power.last_busy = jiffies;
+}
+
 #else /* !CONFIG_PM_RUNTIME */
 
 static inline int pm_runtime_idle(struct device *dev) { return -ENOSYS; }
@@ -83,6 +94,7 @@ static inline int pm_schedule_suspend(st
 static inline int pm_request_resume(struct device *dev) { return 0; }
 static inline int __pm_runtime_get(struct device *dev, bool sync) { return 1; }
 static inline int __pm_runtime_put(struct device *dev, bool sync) { return 0; }
+static inline int pm_runtime_put_delay(struct device *dev) { return 0; }
 static inline int __pm_runtime_set_status(struct device *dev,
 					    unsigned int status) { return 0; }
 static inline int pm_runtime_barrier(struct device *dev) { return 0; }
@@ -103,6 +115,14 @@ static inline int pm_generic_runtime_idl
 static inline int pm_generic_runtime_suspend(struct device *dev) { return 0; }
 static inline int pm_generic_runtime_resume(struct device *dev) { return 0; }
 
+static inline void pm_runtime_mark_last_busy(struct device *dev) {}
+static inline void __pm_runtime_use_suspend_delay(struct device *dev,
+						bool use) {}
+static inline void pm_runtime_set_suspend_delay(struct device *dev,
+						int delay) {}
+static inline unsigned long pm_runtime_suspend_delay_expiration(
+				struct device *dev) { return 0; }
+
 #endif /* !CONFIG_PM_RUNTIME */
 
 static inline int pm_runtime_get(struct device *dev)
@@ -140,4 +160,14 @@ static inline void pm_runtime_disable(st
 	__pm_runtime_disable(dev, true);
 }
 
+static inline void pm_runtime_use_suspend_delay(struct device *dev)
+{
+	__pm_runtime_use_suspend_delay(dev, true);
+}
+
+static inline void pm_runtime_dont_use_suspend_delay(struct device *dev)
+{
+	__pm_runtime_use_suspend_delay(dev, false);
+}
+
 #endif
Index: usb-2.6/drivers/base/power/runtime.c
===================================================================
--- usb-2.6.orig/drivers/base/power/runtime.c
+++ usb-2.6/drivers/base/power/runtime.c
@@ -8,11 +8,11 @@
 
 #include <linux/sched.h>
 #include <linux/pm_runtime.h>
-#include <linux/jiffies.h>
 
 static int __pm_runtime_resume(struct device *dev, bool from_wq);
 static int __pm_request_idle(struct device *dev);
 static int __pm_request_resume(struct device *dev);
+static int __pm_schedule_suspend(struct device *dev, unsigned int delay);
 
 /**
  * pm_runtime_deactivate_timer - Deactivate given device's suspend timer.
@@ -197,6 +197,13 @@ int __pm_runtime_suspend(struct device *
 		goto out;
 	}
 
+	/* If the suspend_delay time hasn't expired yet, reschedule. */
+	if (pm_runtime_suspend_delay_expiration(dev) != 0) {
+		__pm_schedule_suspend(dev, 0);
+		retval = -EAGAIN;
+		goto out;
+	}
+
 	/* Other scheduled or pending requests need to be canceled. */
 	pm_runtime_cancel_pending(dev);
 
@@ -653,38 +660,64 @@ static void pm_suspend_timer_fn(unsigned
 	spin_lock_irqsave(&dev->power.lock, flags);
 
 	expires = dev->power.timer_expires;
+
 	/* If 'expire' is after 'jiffies' we've been called too early. */
-	if (expires > 0 && !time_after(expires, jiffies)) {
-		dev->power.timer_expires = 0;
+	if (expires == 0 || time_after(expires, jiffies))
+		goto out;
+
+	/* If the suspend_delay time hasn't expired yet, restart the timer. */
+	dev->power.timer_expires = pm_runtime_suspend_delay_expiration(dev);
+	if (dev->power.timer_expires)
+		mod_timer(&dev->power.suspend_timer, dev->power.timer_expires);
+	else
 		__pm_request_suspend(dev);
-	}
 
+ out:
 	spin_unlock_irqrestore(&dev->power.lock, flags);
 }
 
 /**
- * pm_schedule_suspend - Set up a timer to submit a suspend request in future.
+ * __pm_schedule_suspend - Set up a timer to submit a suspend request in the future.
  * @dev: Device to suspend.
  * @delay: Time to wait before submitting a suspend request, in milliseconds.
+ *
+ * This function must be called under dev->power.lock with interrupts disabled.
  */
-int pm_schedule_suspend(struct device *dev, unsigned int delay)
+static int __pm_schedule_suspend(struct device *dev, unsigned int delay)
 {
-	unsigned long flags;
 	int retval = 0;
-
-	spin_lock_irqsave(&dev->power.lock, flags);
+	unsigned long suspend_delay_time;
+	unsigned long delay_time;
 
 	if (dev->power.runtime_error) {
 		retval = -EINVAL;
 		goto out;
 	}
 
-	if (!delay) {
+	/* If there's no delay and no suspend_delay, suspend immediately. */
+	suspend_delay_time = pm_runtime_suspend_delay_expiration(dev);
+	if (!delay && !suspend_delay_time) {
 		retval = __pm_request_suspend(dev);
 		goto out;
 	}
 
-	pm_runtime_deactivate_timer(dev);
+	/* Use the larger of delay and the suspend_delay. */
+	delay_time = jiffies + msecs_to_jiffies(delay);
+	delay_time += !delay_time;
+	if (suspend_delay_time &&
+	    time_after_eq(suspend_delay_time, delay_time)) {
+		delay_time = suspend_delay_time;
+
+		/*
+		 * Optimization: If the timer is already running and is
+		 * set to expire at or before suspend_delay_time, avoid
+		 * the overhead of resetting it.  Just let it expire;
+		 * pm_suspend_timer_fn() will take care of the rest.
+		 */
+		if (dev->power.timer_expires && time_before_eq(
+		    dev->power.timer_expires, suspend_delay_time))
+			goto out;
+	}
 
 	if (dev->power.request_pending) {
 		/*
@@ -708,12 +741,25 @@ int pm_schedule_suspend(struct device *d
 	if (retval)
 		goto out;
 
-	dev->power.timer_expires = jiffies + msecs_to_jiffies(delay);
-	if (!dev->power.timer_expires)
-		dev->power.timer_expires = 1;
+	dev->power.timer_expires = delay_time;
 	mod_timer(&dev->power.suspend_timer, dev->power.timer_expires);
 
  out:
+	return retval;
+}
+
+/**
+ * pm_schedule_suspend - Set up a timer to submit a suspend request in the future.
+ * @dev: Device to suspend.
+ * @delay: Time to wait before submitting a suspend request, in milliseconds.
+ */
+int pm_schedule_suspend(struct device *dev, unsigned int delay)
+{
+	unsigned long flags;
+	int retval;
+
+	spin_lock_irqsave(&dev->power.lock, flags);
+	retval = __pm_schedule_suspend(dev, delay);
 	spin_unlock_irqrestore(&dev->power.lock, flags);
 
 	return retval;
@@ -820,6 +866,23 @@ int __pm_runtime_put(struct device *dev,
 EXPORT_SYMBOL_GPL(__pm_runtime_put);
 
 /**
+ * pm_runtime_put_delay - Decrement the device's usage counter and schedule a delayed suspend.
+ * @dev: Device to handle.
+ *
+ * Decrement the usage count.  If it reaches zero, schedule a suspend for when
+ * the suspend_delay time expires.
+ */
+int pm_runtime_put_delay(struct device *dev)
+{
+	int retval = 0;
+
+	if (atomic_dec_and_test(&dev->power.usage_count))
+		retval = pm_schedule_suspend(dev, 0);
+	return retval;
+}
+EXPORT_SYMBOL_GPL(pm_runtime_put_delay);
+
+/**
  * __pm_runtime_set_status - Set run-time PM status of a device.
  * @dev: Device to handle.
  * @status: New run-time PM status of the device.
@@ -1094,6 +1157,112 @@ void pm_runtime_allow(struct device *dev
 EXPORT_SYMBOL_GPL(pm_runtime_allow);
 
 /**
+ * pm_runtime_suspend_delay_expiration - Get a device's suspend-delay expiration time.
+ * @dev: Device to handle.
+ *
+ * Compute the suspend-delay expiration time based on the device's
+ * power.last_busy time.  If the delay has already expired or is disabled
+ * (negative) or the power.use_suspend_delay flag isn't set, return 0.
+ * Otherwise return the expiration time in jiffies (adjusted to be nonzero).
+ *
+ * This function may be called either with or without dev->power.lock held.
+ * Either way it can be racy, since power.last_busy may be updated at any time.
+ */
+unsigned long pm_runtime_suspend_delay_expiration(struct device *dev)
+{
+	int suspend_delay;
+	long elapsed;
+	unsigned long last_busy;
+	unsigned long expires = 0;
+
+	if (!dev->power.use_suspend_delay)
+		goto out;
+
+	suspend_delay = ACCESS_ONCE(dev->power.suspend_delay);
+	if (suspend_delay < 0)
+		goto out;
+
+	last_busy = ACCESS_ONCE(dev->power.last_busy);
+	elapsed = jiffies - last_busy;
+	if (elapsed < 0)
+		goto out;	/* jiffies has wrapped around */
+
+	/*
+	 * If the suspend_delay is >= 1 second, align the timer by rounding
+	 * up to the nearest second.
+	 */
+	expires = last_busy + msecs_to_jiffies(suspend_delay);
+	if (suspend_delay >= 1000)
+		expires = round_jiffies(expires);
+	expires += !expires;
+	if (elapsed >= expires - last_busy)
+		expires = 0;	/* Already expired */
+
+ out:
+	return expires;
+}
+EXPORT_SYMBOL_GPL(pm_runtime_suspend_delay_expiration);
+
+/**
+ * pm_runtime_set_suspend_delay - Set a device's suspend_delay value.
+ * @dev: Device to handle.
+ * @delay: Value of the new delay in milliseconds.
+ *
+ * Set the device's power.suspend_delay value.  If it changes to negative
+ * and the power.use_suspend_delay flag is set, prevent run-time suspends.
+ * If it changes the other way, allow run-time suspends.
+ *
+ * The caller is responsible for initiating a delayed suspend or a resume
+ * if the new value is >= 0 or < 0, respectively.
+ */
+void pm_runtime_set_suspend_delay(struct device *dev, int delay)
+{
+	unsigned long flags;
+	int old_delay;
+
+	spin_lock_irqsave(&dev->power.lock, flags);
+	old_delay = dev->power.suspend_delay;
+	dev->power.suspend_delay = delay;
+
+	if (dev->power.use_suspend_delay) {
+		if (old_delay >= 0 && delay < 0)
+			pm_runtime_get_noresume(dev);
+		else if (old_delay < 0 && delay >= 0)
+			pm_runtime_put_noidle(dev);
+	}
+	spin_unlock_irqrestore(&dev->power.lock, flags);
+}
+EXPORT_SYMBOL_GPL(pm_runtime_set_suspend_delay);
+
+/**
+ * __pm_runtime_use_suspend_delay - Set a device's use_suspend_delay flag.
+ * @dev: Device to handle.
+ * @use: New value for use_suspend_delay
+ *
+ * Set the device's power.use_suspend_delay flag and allow or disable
+ * run-time suspends as needed.
+ */
+void __pm_runtime_use_suspend_delay(struct device *dev, bool use)
+{
+	int old_use;
+	int delay;
+
+	spin_lock_irq(&dev->power.lock);
+	old_use = dev->power.use_suspend_delay;
+	dev->power.use_suspend_delay = use;
+	delay = dev->power.suspend_delay;
+	spin_unlock_irq(&dev->power.lock);
+
+	if (delay < 0) {
+		if (!old_use && use)
+			pm_runtime_get_sync(dev);
+		else if (old_use && !use)
+			pm_runtime_put_sync(dev);
+	}
+}
+EXPORT_SYMBOL_GPL(__pm_runtime_use_suspend_delay);
+
+/**
  * pm_runtime_init - Initialize run-time PM fields in given device object.
  * @dev: Device object to initialize.
  */
Index: usb-2.6/drivers/base/power/sysfs.c
===================================================================
--- usb-2.6.orig/drivers/base/power/sysfs.c
+++ usb-2.6/drivers/base/power/sysfs.c
@@ -75,6 +75,18 @@
  *	attribute is set to "enabled" by bus type code or device drivers and in
  *	that cases it should be safe to leave the default value.
  *
+ *	suspend_delay_ms - Report/change a device's suspend_delay value
+ *
+ *	Some drivers don't want to carry out a runtime suspend as soon as a
+ *	device becomes idle; they want it to remain idle for some period of
+ *	time before suspending it.  This period is the suspend_delay value
+ *	(expressed in milliseconds) and it can be controlled by the user.
+ *	If the value is negative then the device will never be runtime
+ *	suspended.
+ *
+ *	NOTE: The suspend_delay_ms attribute and the suspend_delay value
+ *	are used only if the driver calls pm_runtime_use_suspend_delay().
+ *
  *	wakeup_count - Report the number of wakeup events related to the device
  */
 
@@ -170,6 +182,35 @@ static ssize_t rtpm_status_show(struct d
 }
 
 static DEVICE_ATTR(runtime_status, 0444, rtpm_status_show, NULL);
+
+static ssize_t suspend_delay_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	if (!dev->power.use_suspend_delay)
+		return -EIO;
+	return sprintf("%d\n", buf, dev->power.suspend_delay);
+}
+
+static ssize_t suspend_delay_store(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t n)
+{
+	int delay;
+
+	if (!dev->power.use_suspend_delay)
+		return -EIO;
+
+	delay = simple_strtol(buf, NULL, 10);
+	pm_runtime_set_suspend_delay(dev, delay);
+	if (delay < 0)
+		pm_runtime_resume(dev);
+	else
+		pm_runtime_suspend(dev);
+	return n;
+}
+
+static DEVICE_ATTR(suspend_delay_ms, 0644, suspend_delay_show,
+		suspend_delay_store);
+
 #endif
 
 static ssize_t
@@ -284,6 +325,7 @@ static struct attribute * power_attrs[] 
 	&dev_attr_runtime_status.attr,
 	&dev_attr_runtime_suspended_time.attr,
 	&dev_attr_runtime_active_time.attr,
+	&dev_attr_suspend_delay_ms.attr,
 #endif
 	&dev_attr_wakeup.attr,
 #ifdef CONFIG_PM_SLEEP

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


[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux