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