| From 93d69cf1fff51419dd636b72da27e92c62638682 Mon Sep 17 00:00:00 2001 |
| From: "Rafael J. Wysocki" <rjw@sisk.pl> |
| Date: Tue, 13 Mar 2012 01:01:39 +0100 |
| Subject: PM / QoS: Make it possible to expose PM QoS latency constraints |
| |
| A runtime suspend of a device (e.g. an MMC controller) belonging to |
| a power domain or, in a more complicated scenario, a runtime suspend |
| of another device in the same power domain, may cause power to be |
| removed from the entire domain. In that case, the amount of time |
| necessary to runtime-resume the given device (e.g. the MMC |
| controller) is often substantially greater than the time needed to |
| run its driver's runtime resume callback. That may hurt performance |
| in some situations, because user data may need to wait for the |
| device to become operational, so we should make it possible to |
| prevent that from happening. |
| |
| For this reason, introduce a new sysfs attribute for devices, |
| power/pm_qos_resume_latency_us, allowing user space to specify the |
| upper bound of the time necessary to bring the (runtime-suspended) |
| device up after the resume of it has been requested. However, make |
| that attribute appear only for the devices whose drivers declare |
| support for it by calling the (new) dev_pm_qos_expose_latency_limit() |
| helper function with the appropriate initial value of the attribute. |
| |
| Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl> |
| Reviewed-by: Kevin Hilman <khilman@ti.com> |
| Reviewed-by: Mark Brown <broonie@opensource.wolfsonmicro.com> |
| Acked-by: Linus Walleij <linus.walleij@linaro.org> |
| (cherry picked from commit 85dc0b8a4019e38ad4fd0c008f89a5c241805ac2) |
| |
| Signed-off-by: Simon Horman <horms@verge.net.au> |
| --- |
| Documentation/ABI/testing/sysfs-devices-power | 18 ++++++++ |
| drivers/base/power/power.h | 4 ++ |
| drivers/base/power/qos.c | 61 +++++++++++++++++++++++++ |
| drivers/base/power/sysfs.c | 47 +++++++++++++++++++ |
| include/linux/pm.h | 1 + |
| include/linux/pm_qos.h | 9 ++++ |
| 6 files changed, 140 insertions(+) |
| |
| diff --git a/Documentation/ABI/testing/sysfs-devices-power b/Documentation/ABI/testing/sysfs-devices-power |
| index 8ffbc25..840f7d6 100644 |
| --- a/Documentation/ABI/testing/sysfs-devices-power |
| +++ b/Documentation/ABI/testing/sysfs-devices-power |
| @@ -165,3 +165,21 @@ Description: |
| |
| Not all drivers support this attribute. If it isn't supported, |
| attempts to read or write it will yield I/O errors. |
| + |
| +What: /sys/devices/.../power/pm_qos_latency_us |
| +Date: March 2012 |
| +Contact: Rafael J. Wysocki <rjw@sisk.pl> |
| +Description: |
| + The /sys/devices/.../power/pm_qos_resume_latency_us attribute |
| + contains the PM QoS resume latency limit for the given device, |
| + which is the maximum allowed time it can take to resume the |
| + device, after it has been suspended at run time, from a resume |
| + request to the moment the device will be ready to process I/O, |
| + in microseconds. If it is equal to 0, however, this means that |
| + the PM QoS resume latency may be arbitrary. |
| + |
| + Not all drivers support this attribute. If it isn't supported, |
| + it is not present. |
| + |
| + This attribute has no effect on system-wide suspend/resume and |
| + hibernation. |
| diff --git a/drivers/base/power/power.h b/drivers/base/power/power.h |
| index 9bf6232..eeb4bff 100644 |
| --- a/drivers/base/power/power.h |
| +++ b/drivers/base/power/power.h |
| @@ -71,6 +71,8 @@ extern void dpm_sysfs_remove(struct device *dev); |
| extern void rpm_sysfs_remove(struct device *dev); |
| extern int wakeup_sysfs_add(struct device *dev); |
| extern void wakeup_sysfs_remove(struct device *dev); |
| +extern int pm_qos_sysfs_add(struct device *dev); |
| +extern void pm_qos_sysfs_remove(struct device *dev); |
| |
| #else /* CONFIG_PM */ |
| |
| @@ -79,5 +81,7 @@ static inline void dpm_sysfs_remove(struct device *dev) {} |
| static inline void rpm_sysfs_remove(struct device *dev) {} |
| static inline int wakeup_sysfs_add(struct device *dev) { return 0; } |
| static inline void wakeup_sysfs_remove(struct device *dev) {} |
| +static inline int pm_qos_sysfs_add(struct device *dev) { return 0; } |
| +static inline void pm_qos_sysfs_remove(struct device *dev) {} |
| |
| #endif |
| diff --git a/drivers/base/power/qos.c b/drivers/base/power/qos.c |
| index c5d3588..7185557 100644 |
| --- a/drivers/base/power/qos.c |
| +++ b/drivers/base/power/qos.c |
| @@ -41,6 +41,7 @@ |
| #include <linux/mutex.h> |
| #include <linux/export.h> |
| |
| +#include "power.h" |
| |
| static DEFINE_MUTEX(dev_pm_qos_mtx); |
| |
| @@ -166,6 +167,12 @@ void dev_pm_qos_constraints_destroy(struct device *dev) |
| struct dev_pm_qos_request *req, *tmp; |
| struct pm_qos_constraints *c; |
| |
| + /* |
| + * If the device's PM QoS resume latency limit has been exposed to user |
| + * space, it has to be hidden at this point. |
| + */ |
| + dev_pm_qos_hide_latency_limit(dev); |
| + |
| mutex_lock(&dev_pm_qos_mtx); |
| |
| dev->power.power_state = PMSG_INVALID; |
| @@ -445,3 +452,57 @@ int dev_pm_qos_add_ancestor_request(struct device *dev, |
| return error; |
| } |
| EXPORT_SYMBOL_GPL(dev_pm_qos_add_ancestor_request); |
| + |
| +#ifdef CONFIG_PM_RUNTIME |
| +static void __dev_pm_qos_drop_user_request(struct device *dev) |
| +{ |
| + dev_pm_qos_remove_request(dev->power.pq_req); |
| + dev->power.pq_req = 0; |
| +} |
| + |
| +/** |
| + * dev_pm_qos_expose_latency_limit - Expose PM QoS latency limit to user space. |
| + * @dev: Device whose PM QoS latency limit is to be exposed to user space. |
| + * @value: Initial value of the latency limit. |
| + */ |
| +int dev_pm_qos_expose_latency_limit(struct device *dev, s32 value) |
| +{ |
| + struct dev_pm_qos_request *req; |
| + int ret; |
| + |
| + if (!device_is_registered(dev) || value < 0) |
| + return -EINVAL; |
| + |
| + if (dev->power.pq_req) |
| + return -EEXIST; |
| + |
| + req = kzalloc(sizeof(*req), GFP_KERNEL); |
| + if (!req) |
| + return -ENOMEM; |
| + |
| + ret = dev_pm_qos_add_request(dev, req, value); |
| + if (ret < 0) |
| + return ret; |
| + |
| + dev->power.pq_req = req; |
| + ret = pm_qos_sysfs_add(dev); |
| + if (ret) |
| + __dev_pm_qos_drop_user_request(dev); |
| + |
| + return ret; |
| +} |
| +EXPORT_SYMBOL_GPL(dev_pm_qos_expose_latency_limit); |
| + |
| +/** |
| + * dev_pm_qos_hide_latency_limit - Hide PM QoS latency limit from user space. |
| + * @dev: Device whose PM QoS latency limit is to be hidden from user space. |
| + */ |
| +void dev_pm_qos_hide_latency_limit(struct device *dev) |
| +{ |
| + if (dev->power.pq_req) { |
| + pm_qos_sysfs_remove(dev); |
| + __dev_pm_qos_drop_user_request(dev); |
| + } |
| +} |
| +EXPORT_SYMBOL_GPL(dev_pm_qos_hide_latency_limit); |
| +#endif /* CONFIG_PM_RUNTIME */ |
| diff --git a/drivers/base/power/sysfs.c b/drivers/base/power/sysfs.c |
| index ac63d48..7f3d5d8 100644 |
| --- a/drivers/base/power/sysfs.c |
| +++ b/drivers/base/power/sysfs.c |
| @@ -5,6 +5,7 @@ |
| #include <linux/device.h> |
| #include <linux/string.h> |
| #include <linux/export.h> |
| +#include <linux/pm_qos.h> |
| #include <linux/pm_runtime.h> |
| #include <asm/atomic.h> |
| #include <linux/jiffies.h> |
| @@ -217,6 +218,31 @@ static ssize_t autosuspend_delay_ms_store(struct device *dev, |
| static DEVICE_ATTR(autosuspend_delay_ms, 0644, autosuspend_delay_ms_show, |
| autosuspend_delay_ms_store); |
| |
| +static ssize_t pm_qos_latency_show(struct device *dev, |
| + struct device_attribute *attr, char *buf) |
| +{ |
| + return sprintf(buf, "%d\n", dev->power.pq_req->node.prio); |
| +} |
| + |
| +static ssize_t pm_qos_latency_store(struct device *dev, |
| + struct device_attribute *attr, |
| + const char *buf, size_t n) |
| +{ |
| + s32 value; |
| + int ret; |
| + |
| + if (kstrtos32(buf, 0, &value)) |
| + return -EINVAL; |
| + |
| + if (value < 0) |
| + return -EINVAL; |
| + |
| + ret = dev_pm_qos_update_request(dev->power.pq_req, value); |
| + return ret < 0 ? ret : n; |
| +} |
| + |
| +static DEVICE_ATTR(pm_qos_resume_latency_us, 0644, |
| + pm_qos_latency_show, pm_qos_latency_store); |
| #endif /* CONFIG_PM_RUNTIME */ |
| |
| #ifdef CONFIG_PM_SLEEP |
| @@ -490,6 +516,17 @@ static struct attribute_group pm_runtime_attr_group = { |
| .attrs = runtime_attrs, |
| }; |
| |
| +static struct attribute *pm_qos_attrs[] = { |
| +#ifdef CONFIG_PM_RUNTIME |
| + &dev_attr_pm_qos_resume_latency_us.attr, |
| +#endif /* CONFIG_PM_RUNTIME */ |
| + NULL, |
| +}; |
| +static struct attribute_group pm_qos_attr_group = { |
| + .name = power_group_name, |
| + .attrs = pm_qos_attrs, |
| +}; |
| + |
| int dpm_sysfs_add(struct device *dev) |
| { |
| int rc; |
| @@ -530,6 +567,16 @@ void wakeup_sysfs_remove(struct device *dev) |
| sysfs_unmerge_group(&dev->kobj, &pm_wakeup_attr_group); |
| } |
| |
| +int pm_qos_sysfs_add(struct device *dev) |
| +{ |
| + return sysfs_merge_group(&dev->kobj, &pm_qos_attr_group); |
| +} |
| + |
| +void pm_qos_sysfs_remove(struct device *dev) |
| +{ |
| + sysfs_unmerge_group(&dev->kobj, &pm_qos_attr_group); |
| +} |
| + |
| void rpm_sysfs_remove(struct device *dev) |
| { |
| sysfs_unmerge_group(&dev->kobj, &pm_runtime_attr_group); |
| diff --git a/include/linux/pm.h b/include/linux/pm.h |
| index d6dd6f6..715305e 100644 |
| --- a/include/linux/pm.h |
| +++ b/include/linux/pm.h |
| @@ -546,6 +546,7 @@ struct dev_pm_info { |
| unsigned long accounting_timestamp; |
| ktime_t suspend_time; |
| s64 max_time_suspended_ns; |
| + struct dev_pm_qos_request *pq_req; |
| #endif |
| struct pm_subsys_data *subsys_data; /* Owned by the subsystem. */ |
| struct pm_qos_constraints *constraints; |
| diff --git a/include/linux/pm_qos.h b/include/linux/pm_qos.h |
| index c8a541e..2e9191a 100644 |
| --- a/include/linux/pm_qos.h |
| +++ b/include/linux/pm_qos.h |
| @@ -137,4 +137,13 @@ static inline int dev_pm_qos_add_ancestor_request(struct device *dev, |
| { return 0; } |
| #endif |
| |
| +#ifdef CONFIG_PM_RUNTIME |
| +int dev_pm_qos_expose_latency_limit(struct device *dev, s32 value); |
| +void dev_pm_qos_hide_latency_limit(struct device *dev); |
| +#else |
| +static inline int dev_pm_qos_expose_latency_limit(struct device *dev, s32 value) |
| + { return 0; } |
| +static inline void dev_pm_qos_hide_latency_limit(struct device *dev) {} |
| +#endif |
| + |
| #endif |
| -- |
| 1.7.10.1.362.g242cab3 |
| |