The current device PM QoS code assumes that certain functions will
never be called in parallel with each other (for example, it is
assumed that dev_pm_qos_expose_flags() won't be called in parallel
with dev_pm_qos_hide_flags() for the same device and analogously
for the latency limit), which may be overly optimistic. Moreover,
dev_pm_qos_expose_flags() and dev_pm_qos_expose_latency_limit()
leak memory in error code paths (req needs to be freed on errors)
and __dev_pm_qos_drop_user_request() forgets to free the request.
To fix the above issues put more things under the device PM QoS
mutex to make them mutually exclusive and add the missing freeing
of memory.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
s32 curr_value;
int ret = 0;
s32 curr_value;
int ret = 0;
+ if (!req) /*guard against callers passing in null */
+ return -EINVAL;
+
+ if (WARN(!dev_pm_qos_request_active(req),
+ "%s() called for unknown object\n", __func__))
+ return -EINVAL;
+
if (!req->dev->power.qos)
return -ENODEV;
if (!req->dev->power.qos)
return -ENODEV;
+ mutex_lock(&dev_pm_qos_mtx);
+ ret = __dev_pm_qos_update_request(req, new_value);
+ mutex_unlock(&dev_pm_qos_mtx);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(dev_pm_qos_update_request);
+
+static int __dev_pm_qos_remove_request(struct dev_pm_qos_request *req)
+{
+ int ret = 0;
+
if (!req) /*guard against callers passing in null */
return -EINVAL;
if (!req) /*guard against callers passing in null */
return -EINVAL;
"%s() called for unknown object\n", __func__))
return -EINVAL;
"%s() called for unknown object\n", __func__))
return -EINVAL;
- mutex_lock(&dev_pm_qos_mtx);
- ret = __dev_pm_qos_update_request(req, new_value);
- mutex_unlock(&dev_pm_qos_mtx);
-
+ if (req->dev->power.qos) {
+ ret = apply_constraint(req, PM_QOS_REMOVE_REQ,
+ PM_QOS_DEFAULT_VALUE);
+ memset(req, 0, sizeof(*req));
+ } else {
+ ret = -ENODEV;
+ }
-EXPORT_SYMBOL_GPL(dev_pm_qos_update_request);
/**
* dev_pm_qos_remove_request - modifies an existing qos request
/**
* dev_pm_qos_remove_request - modifies an existing qos request
*/
int dev_pm_qos_remove_request(struct dev_pm_qos_request *req)
{
*/
int dev_pm_qos_remove_request(struct dev_pm_qos_request *req)
{
- int ret = 0;
-
- if (!req) /*guard against callers passing in null */
- return -EINVAL;
-
- if (WARN(!dev_pm_qos_request_active(req),
- "%s() called for unknown object\n", __func__))
- return -EINVAL;
mutex_lock(&dev_pm_qos_mtx);
mutex_lock(&dev_pm_qos_mtx);
-
- if (req->dev->power.qos) {
- ret = apply_constraint(req, PM_QOS_REMOVE_REQ,
- PM_QOS_DEFAULT_VALUE);
- memset(req, 0, sizeof(*req));
- } else {
- /* Return if the device has been removed */
- ret = -ENODEV;
- }
-
+ ret = __dev_pm_qos_remove_request(req);
mutex_unlock(&dev_pm_qos_mtx);
return ret;
}
mutex_unlock(&dev_pm_qos_mtx);
return ret;
}
static void __dev_pm_qos_drop_user_request(struct device *dev,
enum dev_pm_qos_req_type type)
{
static void __dev_pm_qos_drop_user_request(struct device *dev,
enum dev_pm_qos_req_type type)
{
+ struct dev_pm_qos_request *req = NULL;
+
switch(type) {
case DEV_PM_QOS_LATENCY:
switch(type) {
case DEV_PM_QOS_LATENCY:
- dev_pm_qos_remove_request(dev->power.qos->latency_req);
+ req = dev->power.qos->latency_req;
dev->power.qos->latency_req = NULL;
break;
case DEV_PM_QOS_FLAGS:
dev->power.qos->latency_req = NULL;
break;
case DEV_PM_QOS_FLAGS:
- dev_pm_qos_remove_request(dev->power.qos->flags_req);
+ req = dev->power.qos->flags_req;
dev->power.qos->flags_req = NULL;
break;
}
dev->power.qos->flags_req = NULL;
break;
}
+ __dev_pm_qos_remove_request(req);
+ kfree(req);
if (!device_is_registered(dev) || value < 0)
return -EINVAL;
if (!device_is_registered(dev) || value < 0)
return -EINVAL;
- if (dev->power.qos && dev->power.qos->latency_req)
- return -EEXIST;
-
req = kzalloc(sizeof(*req), GFP_KERNEL);
if (!req)
return -ENOMEM;
ret = dev_pm_qos_add_request(dev, req, DEV_PM_QOS_LATENCY, value);
req = kzalloc(sizeof(*req), GFP_KERNEL);
if (!req)
return -ENOMEM;
ret = dev_pm_qos_add_request(dev, req, DEV_PM_QOS_LATENCY, value);
+ if (ret < 0) {
+ kfree(req);
+ }
+
+ mutex_lock(&dev_pm_qos_mtx);
+
+ if (!dev->power.qos)
+ ret = -ENODEV;
+ else if (dev->power.qos->latency_req)
+ ret = -EEXIST;
+
+ if (ret < 0) {
+ __dev_pm_qos_remove_request(req);
+ kfree(req);
+ goto out;
+ }
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_LATENCY);
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_LATENCY);
+ out:
+ mutex_unlock(&dev_pm_qos_mtx);
return ret;
}
EXPORT_SYMBOL_GPL(dev_pm_qos_expose_latency_limit);
return ret;
}
EXPORT_SYMBOL_GPL(dev_pm_qos_expose_latency_limit);
*/
void dev_pm_qos_hide_latency_limit(struct device *dev)
{
*/
void dev_pm_qos_hide_latency_limit(struct device *dev)
{
+ mutex_lock(&dev_pm_qos_mtx);
+
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);
}
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);
}
+
+ mutex_unlock(&dev_pm_qos_mtx);
}
EXPORT_SYMBOL_GPL(dev_pm_qos_hide_latency_limit);
}
EXPORT_SYMBOL_GPL(dev_pm_qos_hide_latency_limit);
if (!device_is_registered(dev))
return -EINVAL;
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;
req = kzalloc(sizeof(*req), GFP_KERNEL);
if (!req)
return -ENOMEM;
- pm_runtime_get_sync(dev);
ret = dev_pm_qos_add_request(dev, req, DEV_PM_QOS_FLAGS, val);
ret = dev_pm_qos_add_request(dev, req, DEV_PM_QOS_FLAGS, val);
- if (ret < 0)
- goto fail;
+ if (ret < 0) {
+ kfree(req);
+ return ret;
+ }
+
+ pm_runtime_get_sync(dev);
+ mutex_lock(&dev_pm_qos_mtx);
+
+ if (!dev->power.qos)
+ ret = -ENODEV;
+ else if (dev->power.qos->flags_req)
+ ret = -EEXIST;
+
+ if (ret < 0) {
+ __dev_pm_qos_remove_request(req);
+ kfree(req);
+ goto out;
+ }
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);
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);
+ out:
+ mutex_unlock(&dev_pm_qos_mtx);
pm_runtime_put(dev);
return ret;
}
pm_runtime_put(dev);
return ret;
}
*/
void dev_pm_qos_hide_flags(struct device *dev)
{
*/
void dev_pm_qos_hide_flags(struct device *dev)
{
+ pm_runtime_get_sync(dev);
+ mutex_lock(&dev_pm_qos_mtx);
+
if (dev->power.qos && dev->power.qos->flags_req) {
pm_qos_sysfs_remove_flags(dev);
if (dev->power.qos && dev->power.qos->flags_req) {
pm_qos_sysfs_remove_flags(dev);
- pm_runtime_get_sync(dev);
__dev_pm_qos_drop_user_request(dev, DEV_PM_QOS_FLAGS);
__dev_pm_qos_drop_user_request(dev, DEV_PM_QOS_FLAGS);
+
+ mutex_unlock(&dev_pm_qos_mtx);
+ pm_runtime_put(dev);
}
EXPORT_SYMBOL_GPL(dev_pm_qos_hide_flags);
}
EXPORT_SYMBOL_GPL(dev_pm_qos_hide_flags);
- if (!dev->power.qos || !dev->power.qos->flags_req)
- return -EINVAL;
-
pm_runtime_get_sync(dev);
mutex_lock(&dev_pm_qos_mtx);
pm_runtime_get_sync(dev);
mutex_lock(&dev_pm_qos_mtx);
+ if (!dev->power.qos || !dev->power.qos->flags_req) {
+ ret = -EINVAL;
+ goto out;
+ }
+
value = dev_pm_qos_requested_flags(dev);
if (set)
value |= mask;
value = dev_pm_qos_requested_flags(dev);
if (set)
value |= mask;
ret = __dev_pm_qos_update_request(dev->power.qos->flags_req, value);
ret = __dev_pm_qos_update_request(dev->power.qos->flags_req, value);
mutex_unlock(&dev_pm_qos_mtx);
pm_runtime_put(dev);
mutex_unlock(&dev_pm_qos_mtx);
pm_runtime_put(dev);
return ret;
}
#endif /* CONFIG_PM_RUNTIME */
return ret;
}
#endif /* CONFIG_PM_RUNTIME */