Re: [PATCH 5/7] PM / QoS: Make it possible to expose PM QoS device flags to user space

mark gross
2012-10-10T03:33:30Z

On Mon, Oct 08, 2012 at 10:07:58AM +0200, Rafael J. Wysocki wrote:
> From: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
>
> Define two device PM QoS flags, PM_QOS_FLAG_NO_POWER_OFF
> and PM_QOS_FLAG_REMOTE_WAKEUP, and introduce routines
> dev_pm_qos_expose_flags() and dev_pm_qos_hide_flags() allowing the
> caller to expose those two flags to user space or to hide them
> from it, respectively.
>
> After the flags have been exposed, user space will see two
> additional sysfs attributes, pm_qos_no_power_off and
> pm_qos_remote_wakeup, under the device's /sys/devices/.../power/
> directory. Then, writing 1 to one of them will update the
> PM QoS flags request owned by user space so that the corresponding
> flag is requested to be set. In turn, writing 0 to one of them
> will cause the corresponding flag in the user space's request to
> be cleared (however, the owners of the other PM QoS flags requests
> for the same device may still request the flag to be set and it
> may be effectively set even if user space doesn't request that).
>
> Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
> Reviewed-by: Jean Pihet <j-pihet@ti.com>
> ---
> Documentation/ABI/testing/sysfs-devices-power | 31 ++++
> drivers/base/power/power.h | 6
> drivers/base/power/qos.c | 167 ++++++++++++++++++++------
> drivers/base/power/sysfs.c | 95 +++++++++++++-
> include/linux/pm.h | 1
> include/linux/pm_qos.h | 26 ++++
> 6 files changed, 278 insertions(+), 48 deletions(-)
>
> Index: linux/include/linux/pm_qos.h
> ===================================================================
> --- linux.orig/include/linux/pm_qos.h
> +++ linux/include/linux/pm_qos.h
> @@ -34,6 +34,9 @@ enum pm_qos_flags_status {
> #define PM_QOS_NETWORK_THROUGHPUT_DEFAULT_VALUE 0
> #define PM_QOS_DEV_LAT_DEFAULT_VALUE 0
>
> +#define PM_QOS_FLAG_NO_POWER_OFF (1 << 0)
> +#define PM_QOS_FLAG_REMOTE_WAKEUP (1 << 1)
> +
> struct pm_qos_request {
> struct plist_node node;
> int pm_qos_class;
> @@ -86,6 +89,8 @@ struct pm_qos_flags {
> struct dev_pm_qos {
> struct pm_qos_constraints latency;
> struct pm_qos_flags flags;
> + struct dev_pm_qos_request *latency_req;
> + struct dev_pm_qos_request *flags_req;

I think I'm getting it now. if someday we have per device throughput
you would have us add a pm_qos_constraints throughput; and a
dev_pm_qos_request *throughput_req;

> };
>
> /* Action requested to pm_qos_update_target */
> @@ -187,10 +192,31 @@ static inline int dev_pm_qos_add_ancesto
> #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);
> +int dev_pm_qos_expose_flags(struct device *dev, s32 value);
> +void dev_pm_qos_hide_flags(struct device *dev);
> +int dev_pm_qos_update_flags(struct device *dev, s32 mask, bool set);
> +
> +static inline s32 dev_pm_qos_requested_latency(struct device *dev)
> +{
> + return dev->power.qos->latency_req->data.pnode.prio;
> +}
> +
> +static inline s32 dev_pm_qos_requested_flags(struct device *dev)
> +{
> + return dev->power.qos->flags_req->data.flr.flags;
> +}
> #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) {}
> +static inline int dev_pm_qos_expose_flags(struct device *dev, s32 value)
> + { return 0; }
> +static inline void dev_pm_qos_hide_flags(struct device *dev) {}
> +static inline int dev_pm_qos_update_flags(struct device *dev, s32 m, bool set)
> + { return 0; }
> +
> +static inline s32 dev_pm_qos_requested_latency(struct device *dev) { return 0; }
> +static inline s32 dev_pm_qos_requested_flags(struct device *dev) { return 0; }
> #endif
>
> #endif
> Index: linux/include/linux/pm.h
> ===================================================================
> --- linux.orig/include/linux/pm.h
> +++ linux/include/linux/pm.h
> @@ -548,7 +548,6 @@ struct dev_pm_info {
> unsigned long active_jiffies;
> unsigned long suspended_jiffies;
> unsigned long accounting_timestamp;
> - struct dev_pm_qos_request *pq_req;
> #endif
> struct pm_subsys_data *subsys_data; /* Owned by the subsystem. */
> struct dev_pm_qos *qos;
> Index: linux/drivers/base/power/qos.c
> ===================================================================
> --- linux.orig/drivers/base/power/qos.c
> +++ linux/drivers/base/power/qos.c
> @@ -40,6 +40,7 @@
> #include <linux/device.h>
> #include <linux/mutex.h>
> #include <linux/export.h>
> +#include <linux/pm_runtime.h>
>
> #include "power.h"
>
> @@ -322,6 +323,36 @@ int dev_pm_qos_add_request(struct device
> EXPORT_SYMBOL_GPL(dev_pm_qos_add_request);
>
> /**
> + * __dev_pm_qos_update_request - Modify an existing device PM QoS request.
> + * @req : PM QoS request to modify.
> + * @new_value: New value to request.
> + */
> +int __dev_pm_qos_update_request(struct dev_pm_qos_request *req, s32 new_value)
> +{
> + s32 curr_value;
> + int ret = 0;
> +
> + if (!req->dev->power.qos)
> + return -ENODEV;
> +
> + switch(req->type) {
> + case DEV_PM_QOS_LATENCY:
> + curr_value = req->data.pnode.prio;
> + break;
> + case DEV_PM_QOS_FLAGS:
> + curr_value = req->data.flr.flags;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + if (curr_value != new_value)
> + ret = apply_constraint(req, PM_QOS_UPDATE_REQ, new_value);
> +
> + return ret;
> +}
> +
> +/**
> * dev_pm_qos_update_request - modifies an existing qos request
> * @req : handle to list element holding a dev_pm_qos request to use
> * @new_value: defines the qos request
> @@ -336,11 +367,9 @@ EXPORT_SYMBOL_GPL(dev_pm_qos_add_request
> * -EINVAL in case of wrong parameters, -ENODEV if the device has been
> * removed from the system
> */
> -int dev_pm_qos_update_request(struct dev_pm_qos_request *req,
> - s32 new_value)
> +int dev_pm_qos_update_request(struct dev_pm_qos_request *req, s32 new_value)
> {
> - s32 curr_value;
> - int ret = 0;
> + int ret;
>
> if (!req) /*guard against callers passing in null */
> return -EINVAL;
> @@ -350,29 +379,9 @@ int dev_pm_qos_update_request(struct dev
> return -EINVAL;
>
> mutex_lock(&dev_pm_qos_mtx);
> -
> - if (!req->dev->power.qos) {
> - ret = -ENODEV;
> - goto out;
> - }
> -
> - switch(req->type) {
> - case DEV_PM_QOS_LATENCY:
> - curr_value = req->data.pnode.prio;
> - break;
> - case DEV_PM_QOS_FLAGS:
> - curr_value = req->data.flr.flags;
> - break;
> - default:
> - ret = -EINVAL;
> - goto out;
> - }
> -
> - if (curr_value != new_value)
> - ret = apply_constraint(req, PM_QOS_UPDATE_REQ, new_value);
> -
> - out:
> + __dev_pm_qos_update_request(req, new_value);
> mutex_unlock(&dev_pm_qos_mtx);
> +
> return ret;
> }
> EXPORT_SYMBOL_GPL(dev_pm_qos_update_request);
> @@ -533,10 +542,19 @@ int dev_pm_qos_add_ancestor_request(stru
> EXPORT_SYMBOL_GPL(dev_pm_qos_add_ancestor_request);
>
> #ifdef CONFIG_PM_RUNTIME
> -static void __dev_pm_qos_drop_user_request(struct device *dev)
> +static void __dev_pm_qos_drop_user_request(struct device *dev,
> + enum dev_pm_qos_req_type type)
> {
> - dev_pm_qos_remove_request(dev->power.pq_req);
> - dev->power.pq_req = NULL;
> + switch(type) {
> + case DEV_PM_QOS_LATENCY:
> + dev_pm_qos_remove_request(dev->power.qos->latency_req);
> + dev->power.qos->latency_req = NULL;
> + break;
> + case DEV_PM_QOS_FLAGS:
> + dev_pm_qos_remove_request(dev->power.qos->flags_req);
> + dev->power.qos->flags_req = NULL;
> + break;
> + }
> }
>
> /**
> @@ -552,7 +570,7 @@ int dev_pm_qos_expose_latency_limit(stru
> if (!device_is_registered(dev) || value < 0)
> return -EINVAL;
>
> - if (dev->power.pq_req)
> + if (dev->power.qos && dev->power.qos->latency_req)
> return -EEXIST;
>
> req = kzalloc(sizeof(*req), GFP_KERNEL);
> @@ -563,10 +581,10 @@ int dev_pm_qos_expose_latency_limit(stru
> if (ret < 0)
> return ret;
>
> - dev->power.pq_req = req;
> - ret = pm_qos_sysfs_add(dev);
> + dev->power.qos->latency_req = req;
> + ret = pm_qos_sysfs_add_latency(dev);
> if (ret)
> - __dev_pm_qos_drop_user_request(dev);
> + __dev_pm_qos_drop_user_request(dev, DEV_PM_QOS_LATENCY);
>
> return ret;
> }
> @@ -578,10 +596,87 @@ EXPORT_SYMBOL_GPL(dev_pm_qos_expose_late
> */
> 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);
> + if (dev->power.qos && dev->power.qos->latency_req) {
> + pm_qos_sysfs_remove_latency(dev);
> + __dev_pm_qos_drop_user_request(dev, DEV_PM_QOS_LATENCY);
> }
> }
> EXPORT_SYMBOL_GPL(dev_pm_qos_hide_latency_limit);
> +
> +/**
> + * dev_pm_qos_expose_flags - Expose PM QoS flags of a device to user space.
> + * @dev: Device whose PM QoS flags are to be exposed to user space.
> + * @val: Initial values of the flags.
> + */
> +int dev_pm_qos_expose_flags(struct device *dev, s32 val)
> +{
> + struct dev_pm_qos_request *req;
> + int ret;
> +
> + if (!device_is_registered(dev))
> + return -EINVAL;
> +
> + if (dev->power.qos && dev->power.qos->flags_req)
> + return -EEXIST;
> +
> + req = kzalloc(sizeof(*req), GFP_KERNEL);
> + if (!req)
> + return -ENOMEM;
> +
> + ret = dev_pm_qos_add_request(dev, req, DEV_PM_QOS_FLAGS, val);
> + if (ret < 0)
> + return ret;
> +
> + dev->power.qos->flags_req = req;
> + ret = pm_qos_sysfs_add_flags(dev);
> + if (ret)
> + __dev_pm_qos_drop_user_request(dev, DEV_PM_QOS_FLAGS);
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(dev_pm_qos_expose_flags);
> +
> +/**
> + * dev_pm_qos_hide_flags - Hide PM QoS flags of a device from user space.
> + * @dev: Device whose PM QoS flags are to be hidden from user space.
> + */
> +void dev_pm_qos_hide_flags(struct device *dev)
> +{
> + if (dev->power.qos && dev->power.qos->flags_req) {
> + pm_qos_sysfs_remove_flags(dev);
> + __dev_pm_qos_drop_user_request(dev, DEV_PM_QOS_FLAGS);
> + }
> +}
> +EXPORT_SYMBOL_GPL(dev_pm_qos_hide_flags);
> +
> +/**
> + * dev_pm_qos_update_flags - Update PM QoS flags request owned by user space.
> + * @dev: Device to update the PM QoS flags request for.
> + * @mask: Flags to set/clear.
> + * @set: Whether to set or clear the flags (true means set).
> + */
> +int dev_pm_qos_update_flags(struct device *dev, s32 mask, bool set)
> +{
> + s32 value;
> + int ret;
> +
> + if (!dev->power.qos || !dev->power.qos->flags_req)
> + return -EINVAL;
> +
> + pm_runtime_get_sync(dev);
> + mutex_lock(&dev_pm_qos_mtx);
> +
> + value = dev_pm_qos_requested_flags(dev);
> + if (set)
> + value |= mask;
> + else
> + value &= ~mask;
> +
> + ret = __dev_pm_qos_update_request(dev->power.qos->flags_req, value);
> +
> + mutex_unlock(&dev_pm_qos_mtx);
> + pm_runtime_put(dev);
> +
> + return ret;
> +}
> #endif /* CONFIG_PM_RUNTIME */
> Index: linux/drivers/base/power/power.h
> ===================================================================
> --- linux.orig/drivers/base/power/power.h
> +++ linux/drivers/base/power/power.h
> @@ -93,8 +93,10 @@ extern void dpm_sysfs_remove(struct devi
> 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);
> +extern int pm_qos_sysfs_add_latency(struct device *dev);
> +extern void pm_qos_sysfs_remove_latency(struct device *dev);
> +extern int pm_qos_sysfs_add_flags(struct device *dev);
> +extern void pm_qos_sysfs_remove_flags(struct device *dev);
>
> #else /* CONFIG_PM */
>
> Index: linux/drivers/base/power/sysfs.c
> ===================================================================
> --- linux.orig/drivers/base/power/sysfs.c
> +++ linux/drivers/base/power/sysfs.c
> @@ -221,7 +221,7 @@ static DEVICE_ATTR(autosuspend_delay_ms,
> 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->data.pnode.prio);
> + return sprintf(buf, "%d\n", dev_pm_qos_requested_latency(dev));
> }
>
> static ssize_t pm_qos_latency_store(struct device *dev,
> @@ -237,12 +237,67 @@ static ssize_t pm_qos_latency_store(stru
> if (value < 0)
> return -EINVAL;
>
> - ret = dev_pm_qos_update_request(dev->power.pq_req, value);
> + ret = dev_pm_qos_update_request(dev->power.qos->latency_req, value);
> return ret < 0 ? ret : n;
> }
>
> static DEVICE_ATTR(pm_qos_resume_latency_us, 0644,
> pm_qos_latency_show, pm_qos_latency_store);
> +
> +static ssize_t pm_qos_no_power_off_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + return sprintf(buf, "%d\n", !!(dev_pm_qos_requested_flags(dev)
> + & PM_QOS_FLAG_NO_POWER_OFF));
> +}
> +
> +static ssize_t pm_qos_no_power_off_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t n)
> +{
> + int ret;
> +
> + if (kstrtoint(buf, 0, &ret))
> + return -EINVAL;
> +
> + if (ret != 0 && ret != 1)
> + return -EINVAL;
> +
> + ret = dev_pm_qos_update_flags(dev, PM_QOS_FLAG_NO_POWER_OFF, ret);
> + return ret < 0 ? ret : n;
> +}
> +
> +static DEVICE_ATTR(pm_qos_no_power_off, 0644,
> + pm_qos_no_power_off_show, pm_qos_no_power_off_store);
> +
> +static ssize_t pm_qos_remote_wakeup_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + return sprintf(buf, "%d\n", !!(dev_pm_qos_requested_flags(dev)
> + & PM_QOS_FLAG_REMOTE_WAKEUP));
> +}
> +
> +static ssize_t pm_qos_remote_wakeup_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t n)
> +{
> + s32 value;
> + int ret;
> +
> + if (kstrtoint(buf, 0, &ret))
> + return -EINVAL;
> +
> + if (ret != 0 && ret != 1)
> + return -EINVAL;
> +
> + ret = dev_pm_qos_update_flags(dev, PM_QOS_FLAG_REMOTE_WAKEUP, ret);
> + return ret < 0 ? ret : n;
> +}
> +
> +static DEVICE_ATTR(pm_qos_remote_wakeup, 0644,
> + pm_qos_remote_wakeup_show, pm_qos_remote_wakeup_store);
> #endif /* CONFIG_PM_RUNTIME */
>
> #ifdef CONFIG_PM_SLEEP
> @@ -564,15 +619,27 @@ static struct attribute_group pm_runtime
> .attrs = runtime_attrs,
> };
>
> -static struct attribute *pm_qos_attrs[] = {
> +static struct attribute *pm_qos_latency_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 = {
> +static struct attribute_group pm_qos_latency_attr_group = {
> + .name = power_group_name,
> + .attrs = pm_qos_latency_attrs,
> +};
> +
> +static struct attribute *pm_qos_flags_attrs[] = {
> +#ifdef CONFIG_PM_RUNTIME
> + &dev_attr_pm_qos_no_power_off.attr,
> + &dev_attr_pm_qos_remote_wakeup.attr,
> +#endif /* CONFIG_PM_RUNTIME */
> + NULL,
> +};
> +static struct attribute_group pm_qos_flags_attr_group = {
> .name = power_group_name,
> - .attrs = pm_qos_attrs,
> + .attrs = pm_qos_flags_attrs,
> };
>
> int dpm_sysfs_add(struct device *dev)
> @@ -615,14 +682,24 @@ void wakeup_sysfs_remove(struct device *
> sysfs_unmerge_group(&dev->kobj, &pm_wakeup_attr_group);
> }
>
> -int pm_qos_sysfs_add(struct device *dev)
> +int pm_qos_sysfs_add_latency(struct device *dev)
> +{
> + return sysfs_merge_group(&dev->kobj, &pm_qos_latency_attr_group);
> +}
> +
> +void pm_qos_sysfs_remove_latency(struct device *dev)
> +{
> + sysfs_unmerge_group(&dev->kobj, &pm_qos_latency_attr_group);
> +}
> +
> +int pm_qos_sysfs_add_flags(struct device *dev)
> {
> - return sysfs_merge_group(&dev->kobj, &pm_qos_attr_group);
> + return sysfs_merge_group(&dev->kobj, &pm_qos_flags_attr_group);
> }
>
> -void pm_qos_sysfs_remove(struct device *dev)
> +void pm_qos_sysfs_remove_flags(struct device *dev)
> {
> - sysfs_unmerge_group(&dev->kobj, &pm_qos_attr_group);
> + sysfs_unmerge_group(&dev->kobj, &pm_qos_flags_attr_group);
> }
>
> void rpm_sysfs_remove(struct device *dev)
> Index: linux/Documentation/ABI/testing/sysfs-devices-power
> ===================================================================
> --- linux.orig/Documentation/ABI/testing/sysfs-devices-power
> +++ linux/Documentation/ABI/testing/sysfs-devices-power
> @@ -204,3 +204,34 @@ Description:
>
> This attribute has no effect on system-wide suspend/resume and
> hibernation.
> +
> +What: /sys/devices/.../power/pm_qos_no_power_off
> +Date: September 2012
> +Contact: Rafael J. Wysocki <rjw@sisk.pl>
> +Description:
> + The /sys/devices/.../power/pm_qos_no_power_off attribute
> + is used for manipulating the PM QoS "no power off" flag. If
> + set, this flag indicates to the kernel that power should not
> + be removed entirely from the device.
> +
> + 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.
> +
> +What: /sys/devices/.../power/pm_qos_remote_wakeup
> +Date: September 2012
> +Contact: Rafael J. Wysocki <rjw@sisk.pl>
> +Description:
> + The /sys/devices/.../power/pm_qos_remote_wakeup attribute
> + is used for manipulating the PM QoS "remote wakeup required"
> + flag. If set, this flag indicates to the kernel that the
> + device is a source of user events that have to be signaled from
> + its low-power states.
> +
> + 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.
>
acked-by: mark gross <markgross@thegnar.org>