PM / Runtime: Use device PM QoS constraints (v2)
Make the runtime PM core use device PM QoS constraints to check if it is allowed to suspend a given device, so that an error code is returned if the device's own PM QoS constraint is negative or one of its children has already been suspended for too long. If this is not the case, the maximum estimated time the device is allowed to be suspended, computed as the minimum of the device's PM QoS constraint and the PM QoS constraints of its children (reduced by the difference between the current time and their suspend times) is stored in a new device's PM field power.max_time_suspended_ns that can be used by the device's subsystem or PM domain to decide whether or not to put the device into lower-power (and presumably higher-latency) states later (if the constraint is 0, which means "no constraint", the power.max_time_suspended_ns is set to -1). Additionally, the time of execution of the subsystem-level .runtime_suspend() callback for the device is recorded in the new power.suspend_time field for later use by the device's subsystem or PM domain along with power.max_time_suspended_ns (it also is used by the core code when the device's parent is suspended). Introduce a new helper function, pm_runtime_update_max_time_suspended(), allowing subsystems and PM domains (or device drivers) to update the power.max_time_suspended_ns field, for example after changing the power state of a suspended device. Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
This commit is contained in:
parent
b930c26416
commit
00dc9ad18d
|
@ -47,21 +47,29 @@ static DEFINE_MUTEX(dev_pm_qos_mtx);
|
||||||
static BLOCKING_NOTIFIER_HEAD(dev_pm_notifiers);
|
static BLOCKING_NOTIFIER_HEAD(dev_pm_notifiers);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* dev_pm_qos_read_value - Get PM QoS constraint for a given device.
|
* __dev_pm_qos_read_value - Get PM QoS constraint for a given device.
|
||||||
|
* @dev: Device to get the PM QoS constraint value for.
|
||||||
|
*
|
||||||
|
* This routine must be called with dev->power.lock held.
|
||||||
|
*/
|
||||||
|
s32 __dev_pm_qos_read_value(struct device *dev)
|
||||||
|
{
|
||||||
|
struct pm_qos_constraints *c = dev->power.constraints;
|
||||||
|
|
||||||
|
return c ? pm_qos_read_value(c) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* dev_pm_qos_read_value - Get PM QoS constraint for a given device (locked).
|
||||||
* @dev: Device to get the PM QoS constraint value for.
|
* @dev: Device to get the PM QoS constraint value for.
|
||||||
*/
|
*/
|
||||||
s32 dev_pm_qos_read_value(struct device *dev)
|
s32 dev_pm_qos_read_value(struct device *dev)
|
||||||
{
|
{
|
||||||
struct pm_qos_constraints *c;
|
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
s32 ret = 0;
|
s32 ret;
|
||||||
|
|
||||||
spin_lock_irqsave(&dev->power.lock, flags);
|
spin_lock_irqsave(&dev->power.lock, flags);
|
||||||
|
ret = __dev_pm_qos_read_value(dev);
|
||||||
c = dev->power.constraints;
|
|
||||||
if (c)
|
|
||||||
ret = pm_qos_read_value(c);
|
|
||||||
|
|
||||||
spin_unlock_irqrestore(&dev->power.lock, flags);
|
spin_unlock_irqrestore(&dev->power.lock, flags);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
|
@ -279,6 +279,47 @@ static int rpm_callback(int (*cb)(struct device *), struct device *dev)
|
||||||
return retval != -EACCES ? retval : -EIO;
|
return retval != -EACCES ? retval : -EIO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct rpm_qos_data {
|
||||||
|
ktime_t time_now;
|
||||||
|
s64 constraint_ns;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* rpm_update_qos_constraint - Update a given PM QoS constraint data.
|
||||||
|
* @dev: Device whose timing data to use.
|
||||||
|
* @data: PM QoS constraint data to update.
|
||||||
|
*
|
||||||
|
* Use the suspend timing data of @dev to update PM QoS constraint data pointed
|
||||||
|
* to by @data.
|
||||||
|
*/
|
||||||
|
static int rpm_update_qos_constraint(struct device *dev, void *data)
|
||||||
|
{
|
||||||
|
struct rpm_qos_data *qos = data;
|
||||||
|
unsigned long flags;
|
||||||
|
s64 delta_ns;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
spin_lock_irqsave(&dev->power.lock, flags);
|
||||||
|
|
||||||
|
if (dev->power.max_time_suspended_ns < 0)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
delta_ns = dev->power.max_time_suspended_ns -
|
||||||
|
ktime_to_ns(ktime_sub(qos->time_now, dev->power.suspend_time));
|
||||||
|
if (delta_ns <= 0) {
|
||||||
|
ret = -EBUSY;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qos->constraint_ns > delta_ns || qos->constraint_ns == 0)
|
||||||
|
qos->constraint_ns = delta_ns;
|
||||||
|
|
||||||
|
out:
|
||||||
|
spin_unlock_irqrestore(&dev->power.lock, flags);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* rpm_suspend - Carry out runtime suspend of given device.
|
* rpm_suspend - Carry out runtime suspend of given device.
|
||||||
* @dev: Device to suspend.
|
* @dev: Device to suspend.
|
||||||
|
@ -305,6 +346,7 @@ static int rpm_suspend(struct device *dev, int rpmflags)
|
||||||
{
|
{
|
||||||
int (*callback)(struct device *);
|
int (*callback)(struct device *);
|
||||||
struct device *parent = NULL;
|
struct device *parent = NULL;
|
||||||
|
struct rpm_qos_data qos;
|
||||||
int retval;
|
int retval;
|
||||||
|
|
||||||
trace_rpm_suspend(dev, rpmflags);
|
trace_rpm_suspend(dev, rpmflags);
|
||||||
|
@ -400,8 +442,38 @@ static int rpm_suspend(struct device *dev, int rpmflags)
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qos.constraint_ns = __dev_pm_qos_read_value(dev);
|
||||||
|
if (qos.constraint_ns < 0) {
|
||||||
|
/* Negative constraint means "never suspend". */
|
||||||
|
retval = -EPERM;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
qos.constraint_ns *= NSEC_PER_USEC;
|
||||||
|
qos.time_now = ktime_get();
|
||||||
|
|
||||||
__update_runtime_status(dev, RPM_SUSPENDING);
|
__update_runtime_status(dev, RPM_SUSPENDING);
|
||||||
|
|
||||||
|
if (!dev->power.ignore_children) {
|
||||||
|
if (dev->power.irq_safe)
|
||||||
|
spin_unlock(&dev->power.lock);
|
||||||
|
else
|
||||||
|
spin_unlock_irq(&dev->power.lock);
|
||||||
|
|
||||||
|
retval = device_for_each_child(dev, &qos,
|
||||||
|
rpm_update_qos_constraint);
|
||||||
|
|
||||||
|
if (dev->power.irq_safe)
|
||||||
|
spin_lock(&dev->power.lock);
|
||||||
|
else
|
||||||
|
spin_lock_irq(&dev->power.lock);
|
||||||
|
|
||||||
|
if (retval)
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev->power.suspend_time = qos.time_now;
|
||||||
|
dev->power.max_time_suspended_ns = qos.constraint_ns ? : -1;
|
||||||
|
|
||||||
if (dev->pm_domain)
|
if (dev->pm_domain)
|
||||||
callback = dev->pm_domain->ops.runtime_suspend;
|
callback = dev->pm_domain->ops.runtime_suspend;
|
||||||
else if (dev->type && dev->type->pm)
|
else if (dev->type && dev->type->pm)
|
||||||
|
@ -414,27 +486,9 @@ static int rpm_suspend(struct device *dev, int rpmflags)
|
||||||
callback = NULL;
|
callback = NULL;
|
||||||
|
|
||||||
retval = rpm_callback(callback, dev);
|
retval = rpm_callback(callback, dev);
|
||||||
if (retval) {
|
if (retval)
|
||||||
__update_runtime_status(dev, RPM_ACTIVE);
|
goto fail;
|
||||||
dev->power.deferred_resume = false;
|
|
||||||
if (retval == -EAGAIN || retval == -EBUSY) {
|
|
||||||
dev->power.runtime_error = 0;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If the callback routine failed an autosuspend, and
|
|
||||||
* if the last_busy time has been updated so that there
|
|
||||||
* is a new autosuspend expiration time, automatically
|
|
||||||
* reschedule another autosuspend.
|
|
||||||
*/
|
|
||||||
if ((rpmflags & RPM_AUTO) &&
|
|
||||||
pm_runtime_autosuspend_expiration(dev) != 0)
|
|
||||||
goto repeat;
|
|
||||||
} else {
|
|
||||||
pm_runtime_cancel_pending(dev);
|
|
||||||
}
|
|
||||||
wake_up_all(&dev->power.wait_queue);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
no_callback:
|
no_callback:
|
||||||
__update_runtime_status(dev, RPM_SUSPENDED);
|
__update_runtime_status(dev, RPM_SUSPENDED);
|
||||||
pm_runtime_deactivate_timer(dev);
|
pm_runtime_deactivate_timer(dev);
|
||||||
|
@ -466,6 +520,29 @@ static int rpm_suspend(struct device *dev, int rpmflags)
|
||||||
trace_rpm_return_int(dev, _THIS_IP_, retval);
|
trace_rpm_return_int(dev, _THIS_IP_, retval);
|
||||||
|
|
||||||
return retval;
|
return retval;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
__update_runtime_status(dev, RPM_ACTIVE);
|
||||||
|
dev->power.suspend_time = ktime_set(0, 0);
|
||||||
|
dev->power.max_time_suspended_ns = -1;
|
||||||
|
dev->power.deferred_resume = false;
|
||||||
|
if (retval == -EAGAIN || retval == -EBUSY) {
|
||||||
|
dev->power.runtime_error = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the callback routine failed an autosuspend, and
|
||||||
|
* if the last_busy time has been updated so that there
|
||||||
|
* is a new autosuspend expiration time, automatically
|
||||||
|
* reschedule another autosuspend.
|
||||||
|
*/
|
||||||
|
if ((rpmflags & RPM_AUTO) &&
|
||||||
|
pm_runtime_autosuspend_expiration(dev) != 0)
|
||||||
|
goto repeat;
|
||||||
|
} else {
|
||||||
|
pm_runtime_cancel_pending(dev);
|
||||||
|
}
|
||||||
|
wake_up_all(&dev->power.wait_queue);
|
||||||
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -620,6 +697,9 @@ static int rpm_resume(struct device *dev, int rpmflags)
|
||||||
if (dev->power.no_callbacks)
|
if (dev->power.no_callbacks)
|
||||||
goto no_callback; /* Assume success. */
|
goto no_callback; /* Assume success. */
|
||||||
|
|
||||||
|
dev->power.suspend_time = ktime_set(0, 0);
|
||||||
|
dev->power.max_time_suspended_ns = -1;
|
||||||
|
|
||||||
__update_runtime_status(dev, RPM_RESUMING);
|
__update_runtime_status(dev, RPM_RESUMING);
|
||||||
|
|
||||||
if (dev->pm_domain)
|
if (dev->pm_domain)
|
||||||
|
@ -1279,6 +1359,9 @@ void pm_runtime_init(struct device *dev)
|
||||||
setup_timer(&dev->power.suspend_timer, pm_suspend_timer_fn,
|
setup_timer(&dev->power.suspend_timer, pm_suspend_timer_fn,
|
||||||
(unsigned long)dev);
|
(unsigned long)dev);
|
||||||
|
|
||||||
|
dev->power.suspend_time = ktime_set(0, 0);
|
||||||
|
dev->power.max_time_suspended_ns = -1;
|
||||||
|
|
||||||
init_waitqueue_head(&dev->power.wait_queue);
|
init_waitqueue_head(&dev->power.wait_queue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1296,3 +1379,28 @@ void pm_runtime_remove(struct device *dev)
|
||||||
if (dev->power.irq_safe && dev->parent)
|
if (dev->power.irq_safe && dev->parent)
|
||||||
pm_runtime_put_sync(dev->parent);
|
pm_runtime_put_sync(dev->parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pm_runtime_update_max_time_suspended - Update device's suspend time data.
|
||||||
|
* @dev: Device to handle.
|
||||||
|
* @delta_ns: Value to subtract from the device's max_time_suspended_ns field.
|
||||||
|
*
|
||||||
|
* Update the device's power.max_time_suspended_ns field by subtracting
|
||||||
|
* @delta_ns from it. The resulting value of power.max_time_suspended_ns is
|
||||||
|
* never negative.
|
||||||
|
*/
|
||||||
|
void pm_runtime_update_max_time_suspended(struct device *dev, s64 delta_ns)
|
||||||
|
{
|
||||||
|
unsigned long flags;
|
||||||
|
|
||||||
|
spin_lock_irqsave(&dev->power.lock, flags);
|
||||||
|
|
||||||
|
if (delta_ns > 0 && dev->power.max_time_suspended_ns > 0) {
|
||||||
|
if (dev->power.max_time_suspended_ns > delta_ns)
|
||||||
|
dev->power.max_time_suspended_ns -= delta_ns;
|
||||||
|
else
|
||||||
|
dev->power.max_time_suspended_ns = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
spin_unlock_irqrestore(&dev->power.lock, flags);
|
||||||
|
}
|
||||||
|
|
|
@ -521,6 +521,8 @@ struct dev_pm_info {
|
||||||
unsigned long active_jiffies;
|
unsigned long active_jiffies;
|
||||||
unsigned long suspended_jiffies;
|
unsigned long suspended_jiffies;
|
||||||
unsigned long accounting_timestamp;
|
unsigned long accounting_timestamp;
|
||||||
|
ktime_t suspend_time;
|
||||||
|
s64 max_time_suspended_ns;
|
||||||
#endif
|
#endif
|
||||||
struct pm_subsys_data *subsys_data; /* Owned by the subsystem. */
|
struct pm_subsys_data *subsys_data; /* Owned by the subsystem. */
|
||||||
struct pm_qos_constraints *constraints;
|
struct pm_qos_constraints *constraints;
|
||||||
|
|
|
@ -78,6 +78,7 @@ int pm_qos_remove_notifier(int pm_qos_class, struct notifier_block *notifier);
|
||||||
int pm_qos_request_active(struct pm_qos_request *req);
|
int pm_qos_request_active(struct pm_qos_request *req);
|
||||||
s32 pm_qos_read_value(struct pm_qos_constraints *c);
|
s32 pm_qos_read_value(struct pm_qos_constraints *c);
|
||||||
|
|
||||||
|
s32 __dev_pm_qos_read_value(struct device *dev);
|
||||||
s32 dev_pm_qos_read_value(struct device *dev);
|
s32 dev_pm_qos_read_value(struct device *dev);
|
||||||
int dev_pm_qos_add_request(struct device *dev, struct dev_pm_qos_request *req,
|
int dev_pm_qos_add_request(struct device *dev, struct dev_pm_qos_request *req,
|
||||||
s32 value);
|
s32 value);
|
||||||
|
@ -119,6 +120,8 @@ static inline int pm_qos_request_active(struct pm_qos_request *req)
|
||||||
static inline s32 pm_qos_read_value(struct pm_qos_constraints *c)
|
static inline s32 pm_qos_read_value(struct pm_qos_constraints *c)
|
||||||
{ return 0; }
|
{ return 0; }
|
||||||
|
|
||||||
|
static inline s32 __dev_pm_qos_read_value(struct device *dev)
|
||||||
|
{ return 0; }
|
||||||
static inline s32 dev_pm_qos_read_value(struct device *dev)
|
static inline s32 dev_pm_qos_read_value(struct device *dev)
|
||||||
{ return 0; }
|
{ return 0; }
|
||||||
static inline int dev_pm_qos_add_request(struct device *dev,
|
static inline int dev_pm_qos_add_request(struct device *dev,
|
||||||
|
|
|
@ -45,6 +45,8 @@ extern void pm_runtime_irq_safe(struct device *dev);
|
||||||
extern void __pm_runtime_use_autosuspend(struct device *dev, bool use);
|
extern void __pm_runtime_use_autosuspend(struct device *dev, bool use);
|
||||||
extern void pm_runtime_set_autosuspend_delay(struct device *dev, int delay);
|
extern void pm_runtime_set_autosuspend_delay(struct device *dev, int delay);
|
||||||
extern unsigned long pm_runtime_autosuspend_expiration(struct device *dev);
|
extern unsigned long pm_runtime_autosuspend_expiration(struct device *dev);
|
||||||
|
extern void pm_runtime_update_max_time_suspended(struct device *dev,
|
||||||
|
s64 delta_ns);
|
||||||
|
|
||||||
static inline bool pm_children_suspended(struct device *dev)
|
static inline bool pm_children_suspended(struct device *dev)
|
||||||
{
|
{
|
||||||
|
@ -148,6 +150,9 @@ static inline void pm_runtime_set_autosuspend_delay(struct device *dev,
|
||||||
static inline unsigned long pm_runtime_autosuspend_expiration(
|
static inline unsigned long pm_runtime_autosuspend_expiration(
|
||||||
struct device *dev) { return 0; }
|
struct device *dev) { return 0; }
|
||||||
|
|
||||||
|
static inline void pm_runtime_update_max_time_suspended(struct device *dev,
|
||||||
|
s64 delta_ns) {}
|
||||||
|
|
||||||
#endif /* !CONFIG_PM_RUNTIME */
|
#endif /* !CONFIG_PM_RUNTIME */
|
||||||
|
|
||||||
static inline int pm_runtime_idle(struct device *dev)
|
static inline int pm_runtime_idle(struct device *dev)
|
||||||
|
|
Loading…
Reference in a new issue