Commit 37530f2b authored by Rafael J. Wysocki's avatar Rafael J. Wysocki

PM / QoS: Remove device PM QoS sysfs attributes at the right place

Device PM QoS sysfs attributes, if present during device removal,
are removed from within device_pm_remove(), which is too late,
since dpm_sysfs_remove() has already removed the whole attribute
group they belonged to.  However, moving the removal of those
attributes to dpm_sysfs_remove() alone is not sufficient, because
in theory they still can be re-added right after being removed by it
(the device's driver is still bound to it at that point).

For this reason, move the entire desctruction of device PM QoS
constraints to dpm_sysfs_remove() and make it prevent any new
constraints from being added after it has run.  Also, move the
initialization of the power.qos field in struct device to
device_pm_init_common() and drop the no longer needed
dev_pm_qos_constraints_init().
Reported-by: default avatarSasha Levin <sasha.levin@oracle.com>
Signed-off-by: default avatarRafael J. Wysocki <rafael.j.wysocki@intel.com>
parent b81ea1b5
...@@ -99,7 +99,6 @@ void device_pm_add(struct device *dev) ...@@ -99,7 +99,6 @@ void device_pm_add(struct device *dev)
dev_warn(dev, "parent %s should not be sleeping\n", dev_warn(dev, "parent %s should not be sleeping\n",
dev_name(dev->parent)); dev_name(dev->parent));
list_add_tail(&dev->power.entry, &dpm_list); list_add_tail(&dev->power.entry, &dpm_list);
dev_pm_qos_constraints_init(dev);
mutex_unlock(&dpm_list_mtx); mutex_unlock(&dpm_list_mtx);
} }
...@@ -113,7 +112,6 @@ void device_pm_remove(struct device *dev) ...@@ -113,7 +112,6 @@ void device_pm_remove(struct device *dev)
dev->bus ? dev->bus->name : "No Bus", dev_name(dev)); dev->bus ? dev->bus->name : "No Bus", dev_name(dev));
complete_all(&dev->power.completion); complete_all(&dev->power.completion);
mutex_lock(&dpm_list_mtx); mutex_lock(&dpm_list_mtx);
dev_pm_qos_constraints_destroy(dev);
list_del_init(&dev->power.entry); list_del_init(&dev->power.entry);
mutex_unlock(&dpm_list_mtx); mutex_unlock(&dpm_list_mtx);
device_wakeup_disable(dev); device_wakeup_disable(dev);
......
...@@ -4,7 +4,7 @@ static inline void device_pm_init_common(struct device *dev) ...@@ -4,7 +4,7 @@ static inline void device_pm_init_common(struct device *dev)
{ {
if (!dev->power.early_init) { if (!dev->power.early_init) {
spin_lock_init(&dev->power.lock); spin_lock_init(&dev->power.lock);
dev->power.power_state = PMSG_INVALID; dev->power.qos = NULL;
dev->power.early_init = true; dev->power.early_init = true;
} }
} }
...@@ -56,14 +56,10 @@ extern void device_pm_move_last(struct device *); ...@@ -56,14 +56,10 @@ extern void device_pm_move_last(struct device *);
static inline void device_pm_sleep_init(struct device *dev) {} static inline void device_pm_sleep_init(struct device *dev) {}
static inline void device_pm_add(struct device *dev) static inline void device_pm_add(struct device *dev) {}
{
dev_pm_qos_constraints_init(dev);
}
static inline void device_pm_remove(struct device *dev) static inline void device_pm_remove(struct device *dev)
{ {
dev_pm_qos_constraints_destroy(dev);
pm_runtime_remove(dev); pm_runtime_remove(dev);
} }
......
...@@ -41,6 +41,7 @@ ...@@ -41,6 +41,7 @@
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/export.h> #include <linux/export.h>
#include <linux/pm_runtime.h> #include <linux/pm_runtime.h>
#include <linux/err.h>
#include "power.h" #include "power.h"
...@@ -61,7 +62,7 @@ enum pm_qos_flags_status __dev_pm_qos_flags(struct device *dev, s32 mask) ...@@ -61,7 +62,7 @@ enum pm_qos_flags_status __dev_pm_qos_flags(struct device *dev, s32 mask)
struct pm_qos_flags *pqf; struct pm_qos_flags *pqf;
s32 val; s32 val;
if (!qos) if (IS_ERR_OR_NULL(qos))
return PM_QOS_FLAGS_UNDEFINED; return PM_QOS_FLAGS_UNDEFINED;
pqf = &qos->flags; pqf = &qos->flags;
...@@ -101,7 +102,8 @@ EXPORT_SYMBOL_GPL(dev_pm_qos_flags); ...@@ -101,7 +102,8 @@ EXPORT_SYMBOL_GPL(dev_pm_qos_flags);
*/ */
s32 __dev_pm_qos_read_value(struct device *dev) s32 __dev_pm_qos_read_value(struct device *dev)
{ {
return dev->power.qos ? pm_qos_read_value(&dev->power.qos->latency) : 0; return IS_ERR_OR_NULL(dev->power.qos) ?
0 : pm_qos_read_value(&dev->power.qos->latency);
} }
/** /**
...@@ -198,20 +200,8 @@ static int dev_pm_qos_constraints_allocate(struct device *dev) ...@@ -198,20 +200,8 @@ static int dev_pm_qos_constraints_allocate(struct device *dev)
return 0; return 0;
} }
/** static void __dev_pm_qos_hide_latency_limit(struct device *dev);
* dev_pm_qos_constraints_init - Initalize device's PM QoS constraints pointer. static void __dev_pm_qos_hide_flags(struct device *dev);
* @dev: target device
*
* Called from the device PM subsystem during device insertion under
* device_pm_lock().
*/
void dev_pm_qos_constraints_init(struct device *dev)
{
mutex_lock(&dev_pm_qos_mtx);
dev->power.qos = NULL;
dev->power.power_state = PMSG_ON;
mutex_unlock(&dev_pm_qos_mtx);
}
/** /**
* dev_pm_qos_constraints_destroy * dev_pm_qos_constraints_destroy
...@@ -226,16 +216,15 @@ void dev_pm_qos_constraints_destroy(struct device *dev) ...@@ -226,16 +216,15 @@ void dev_pm_qos_constraints_destroy(struct device *dev)
struct pm_qos_constraints *c; struct pm_qos_constraints *c;
struct pm_qos_flags *f; struct pm_qos_flags *f;
mutex_lock(&dev_pm_qos_mtx);
/* /*
* If the device's PM QoS resume latency limit or PM QoS flags have been * If the device's PM QoS resume latency limit or PM QoS flags have been
* exposed to user space, they have to be hidden at this point. * exposed to user space, they have to be hidden at this point.
*/ */
dev_pm_qos_hide_latency_limit(dev); __dev_pm_qos_hide_latency_limit(dev);
dev_pm_qos_hide_flags(dev); __dev_pm_qos_hide_flags(dev);
mutex_lock(&dev_pm_qos_mtx);
dev->power.power_state = PMSG_INVALID;
qos = dev->power.qos; qos = dev->power.qos;
if (!qos) if (!qos)
goto out; goto out;
...@@ -257,7 +246,7 @@ void dev_pm_qos_constraints_destroy(struct device *dev) ...@@ -257,7 +246,7 @@ void dev_pm_qos_constraints_destroy(struct device *dev)
} }
spin_lock_irq(&dev->power.lock); spin_lock_irq(&dev->power.lock);
dev->power.qos = NULL; dev->power.qos = ERR_PTR(-ENODEV);
spin_unlock_irq(&dev->power.lock); spin_unlock_irq(&dev->power.lock);
kfree(c->notifiers); kfree(c->notifiers);
...@@ -301,32 +290,19 @@ int dev_pm_qos_add_request(struct device *dev, struct dev_pm_qos_request *req, ...@@ -301,32 +290,19 @@ int dev_pm_qos_add_request(struct device *dev, struct dev_pm_qos_request *req,
"%s() called for already added request\n", __func__)) "%s() called for already added request\n", __func__))
return -EINVAL; return -EINVAL;
req->dev = dev;
mutex_lock(&dev_pm_qos_mtx); mutex_lock(&dev_pm_qos_mtx);
if (!dev->power.qos) { if (IS_ERR(dev->power.qos))
if (dev->power.power_state.event == PM_EVENT_INVALID) {
/* The device has been removed from the system. */
req->dev = NULL;
ret = -ENODEV; ret = -ENODEV;
goto out; else if (!dev->power.qos)
} else {
/*
* Allocate the constraints data on the first call to
* add_request, i.e. only if the data is not already
* allocated and if the device has not been removed.
*/
ret = dev_pm_qos_constraints_allocate(dev); ret = dev_pm_qos_constraints_allocate(dev);
}
}
if (!ret) { if (!ret) {
req->dev = dev;
req->type = type; req->type = type;
ret = apply_constraint(req, PM_QOS_ADD_REQ, value); ret = apply_constraint(req, PM_QOS_ADD_REQ, value);
} }
out:
mutex_unlock(&dev_pm_qos_mtx); mutex_unlock(&dev_pm_qos_mtx);
return ret; return ret;
...@@ -351,7 +327,7 @@ static int __dev_pm_qos_update_request(struct dev_pm_qos_request *req, ...@@ -351,7 +327,7 @@ static int __dev_pm_qos_update_request(struct dev_pm_qos_request *req,
"%s() called for unknown object\n", __func__)) "%s() called for unknown object\n", __func__))
return -EINVAL; return -EINVAL;
if (!req->dev->power.qos) if (IS_ERR_OR_NULL(req->dev->power.qos))
return -ENODEV; return -ENODEV;
switch(req->type) { switch(req->type) {
...@@ -402,7 +378,7 @@ EXPORT_SYMBOL_GPL(dev_pm_qos_update_request); ...@@ -402,7 +378,7 @@ EXPORT_SYMBOL_GPL(dev_pm_qos_update_request);
static int __dev_pm_qos_remove_request(struct dev_pm_qos_request *req) static int __dev_pm_qos_remove_request(struct dev_pm_qos_request *req)
{ {
int ret = 0; int ret;
if (!req) /*guard against callers passing in null */ if (!req) /*guard against callers passing in null */
return -EINVAL; return -EINVAL;
...@@ -411,13 +387,11 @@ static int __dev_pm_qos_remove_request(struct dev_pm_qos_request *req) ...@@ -411,13 +387,11 @@ static int __dev_pm_qos_remove_request(struct dev_pm_qos_request *req)
"%s() called for unknown object\n", __func__)) "%s() called for unknown object\n", __func__))
return -EINVAL; return -EINVAL;
if (req->dev->power.qos) { if (IS_ERR_OR_NULL(req->dev->power.qos))
ret = apply_constraint(req, PM_QOS_REMOVE_REQ, return -ENODEV;
PM_QOS_DEFAULT_VALUE);
ret = apply_constraint(req, PM_QOS_REMOVE_REQ, PM_QOS_DEFAULT_VALUE);
memset(req, 0, sizeof(*req)); memset(req, 0, sizeof(*req));
} else {
ret = -ENODEV;
}
return ret; return ret;
} }
...@@ -466,9 +440,10 @@ int dev_pm_qos_add_notifier(struct device *dev, struct notifier_block *notifier) ...@@ -466,9 +440,10 @@ int dev_pm_qos_add_notifier(struct device *dev, struct notifier_block *notifier)
mutex_lock(&dev_pm_qos_mtx); mutex_lock(&dev_pm_qos_mtx);
if (!dev->power.qos) if (IS_ERR(dev->power.qos))
ret = dev->power.power_state.event != PM_EVENT_INVALID ? ret = -ENODEV;
dev_pm_qos_constraints_allocate(dev) : -ENODEV; else if (!dev->power.qos)
ret = dev_pm_qos_constraints_allocate(dev);
if (!ret) if (!ret)
ret = blocking_notifier_chain_register( ret = blocking_notifier_chain_register(
...@@ -497,7 +472,7 @@ int dev_pm_qos_remove_notifier(struct device *dev, ...@@ -497,7 +472,7 @@ int dev_pm_qos_remove_notifier(struct device *dev,
mutex_lock(&dev_pm_qos_mtx); mutex_lock(&dev_pm_qos_mtx);
/* Silently return if the constraints object is not present. */ /* Silently return if the constraints object is not present. */
if (dev->power.qos) if (!IS_ERR_OR_NULL(dev->power.qos))
retval = blocking_notifier_chain_unregister( retval = blocking_notifier_chain_unregister(
dev->power.qos->latency.notifiers, dev->power.qos->latency.notifiers,
notifier); notifier);
...@@ -608,7 +583,7 @@ int dev_pm_qos_expose_latency_limit(struct device *dev, s32 value) ...@@ -608,7 +583,7 @@ int dev_pm_qos_expose_latency_limit(struct device *dev, s32 value)
mutex_lock(&dev_pm_qos_mtx); mutex_lock(&dev_pm_qos_mtx);
if (!dev->power.qos) if (IS_ERR_OR_NULL(dev->power.qos))
ret = -ENODEV; ret = -ENODEV;
else if (dev->power.qos->latency_req) else if (dev->power.qos->latency_req)
ret = -EEXIST; ret = -EEXIST;
...@@ -630,6 +605,14 @@ int dev_pm_qos_expose_latency_limit(struct device *dev, s32 value) ...@@ -630,6 +605,14 @@ int dev_pm_qos_expose_latency_limit(struct device *dev, s32 value)
} }
EXPORT_SYMBOL_GPL(dev_pm_qos_expose_latency_limit); EXPORT_SYMBOL_GPL(dev_pm_qos_expose_latency_limit);
static void __dev_pm_qos_hide_latency_limit(struct device *dev)
{
if (!IS_ERR_OR_NULL(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);
}
}
/** /**
* dev_pm_qos_hide_latency_limit - Hide PM QoS latency limit from user space. * 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. * @dev: Device whose PM QoS latency limit is to be hidden from user space.
...@@ -637,12 +620,7 @@ EXPORT_SYMBOL_GPL(dev_pm_qos_expose_latency_limit); ...@@ -637,12 +620,7 @@ 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); mutex_lock(&dev_pm_qos_mtx);
__dev_pm_qos_hide_latency_limit(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);
}
mutex_unlock(&dev_pm_qos_mtx); mutex_unlock(&dev_pm_qos_mtx);
} }
EXPORT_SYMBOL_GPL(dev_pm_qos_hide_latency_limit); EXPORT_SYMBOL_GPL(dev_pm_qos_hide_latency_limit);
...@@ -673,7 +651,7 @@ int dev_pm_qos_expose_flags(struct device *dev, s32 val) ...@@ -673,7 +651,7 @@ int dev_pm_qos_expose_flags(struct device *dev, s32 val)
pm_runtime_get_sync(dev); pm_runtime_get_sync(dev);
mutex_lock(&dev_pm_qos_mtx); mutex_lock(&dev_pm_qos_mtx);
if (!dev->power.qos) if (IS_ERR_OR_NULL(dev->power.qos))
ret = -ENODEV; ret = -ENODEV;
else if (dev->power.qos->flags_req) else if (dev->power.qos->flags_req)
ret = -EEXIST; ret = -EEXIST;
...@@ -696,6 +674,14 @@ int dev_pm_qos_expose_flags(struct device *dev, s32 val) ...@@ -696,6 +674,14 @@ int dev_pm_qos_expose_flags(struct device *dev, s32 val)
} }
EXPORT_SYMBOL_GPL(dev_pm_qos_expose_flags); EXPORT_SYMBOL_GPL(dev_pm_qos_expose_flags);
static void __dev_pm_qos_hide_flags(struct device *dev)
{
if (!IS_ERR_OR_NULL(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);
}
}
/** /**
* dev_pm_qos_hide_flags - Hide PM QoS flags of a device from user space. * 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. * @dev: Device whose PM QoS flags are to be hidden from user space.
...@@ -704,12 +690,7 @@ void dev_pm_qos_hide_flags(struct device *dev) ...@@ -704,12 +690,7 @@ void dev_pm_qos_hide_flags(struct device *dev)
{ {
pm_runtime_get_sync(dev); pm_runtime_get_sync(dev);
mutex_lock(&dev_pm_qos_mtx); mutex_lock(&dev_pm_qos_mtx);
__dev_pm_qos_hide_flags(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);
}
mutex_unlock(&dev_pm_qos_mtx); mutex_unlock(&dev_pm_qos_mtx);
pm_runtime_put(dev); pm_runtime_put(dev);
} }
...@@ -729,7 +710,7 @@ int dev_pm_qos_update_flags(struct device *dev, s32 mask, bool set) ...@@ -729,7 +710,7 @@ int dev_pm_qos_update_flags(struct device *dev, s32 mask, bool set)
pm_runtime_get_sync(dev); pm_runtime_get_sync(dev);
mutex_lock(&dev_pm_qos_mtx); mutex_lock(&dev_pm_qos_mtx);
if (!dev->power.qos || !dev->power.qos->flags_req) { if (IS_ERR_OR_NULL(dev->power.qos) || !dev->power.qos->flags_req) {
ret = -EINVAL; ret = -EINVAL;
goto out; goto out;
} }
...@@ -747,4 +728,7 @@ int dev_pm_qos_update_flags(struct device *dev, s32 mask, bool set) ...@@ -747,4 +728,7 @@ int dev_pm_qos_update_flags(struct device *dev, s32 mask, bool set)
pm_runtime_put(dev); pm_runtime_put(dev);
return ret; return ret;
} }
#else /* !CONFIG_PM_RUNTIME */
static void __dev_pm_qos_hide_latency_limit(struct device *dev) {}
static void __dev_pm_qos_hide_flags(struct device *dev) {}
#endif /* CONFIG_PM_RUNTIME */ #endif /* CONFIG_PM_RUNTIME */
...@@ -708,6 +708,7 @@ void rpm_sysfs_remove(struct device *dev) ...@@ -708,6 +708,7 @@ void rpm_sysfs_remove(struct device *dev)
void dpm_sysfs_remove(struct device *dev) void dpm_sysfs_remove(struct device *dev)
{ {
dev_pm_qos_constraints_destroy(dev);
rpm_sysfs_remove(dev); rpm_sysfs_remove(dev);
sysfs_unmerge_group(&dev->kobj, &pm_wakeup_attr_group); sysfs_unmerge_group(&dev->kobj, &pm_wakeup_attr_group);
sysfs_remove_group(&dev->kobj, &pm_attr_group); sysfs_remove_group(&dev->kobj, &pm_attr_group);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment