aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/base
diff options
context:
space:
mode:
authorRafael J. Wysocki <rafael.j.wysocki@intel.com>2016-01-21 00:42:59 +0100
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>2016-01-21 00:42:59 +0100
commit6efd3f8cde1d6acc20a715ac6ea17e01421742df (patch)
tree5fc4caece6907a2ccdeb8b13b887a01d683d9803 /drivers/base
parentMerge branches 'acpica', 'acpi-video' and 'acpi-fan' (diff)
parentdriver core: Avoid NULL pointer dereferences in device_is_bound() (diff)
downloadlinux-dev-6efd3f8cde1d6acc20a715ac6ea17e01421742df.tar.xz
linux-dev-6efd3f8cde1d6acc20a715ac6ea17e01421742df.zip
Merge branch 'pm-core'
* pm-core: driver core: Avoid NULL pointer dereferences in device_is_bound() platform: Do not detach from PM domains on shutdown USB / PM: Allow USB devices to remain runtime-suspended when sleeping PM / sleep: Go direct_complete if driver has no callbacks PM / Domains: add setter for dev.pm_domain device core: add device_is_bound()
Diffstat (limited to 'drivers/base')
-rw-r--r--drivers/base/dd.c21
-rw-r--r--drivers/base/platform.c1
-rw-r--r--drivers/base/power/clock_ops.c5
-rw-r--r--drivers/base/power/common.c24
-rw-r--r--drivers/base/power/domain.c8
-rw-r--r--drivers/base/power/main.c35
-rw-r--r--drivers/base/power/power.h3
7 files changed, 90 insertions, 7 deletions
diff --git a/drivers/base/dd.c b/drivers/base/dd.c
index 7399be790b5d..c4da2df62e02 100644
--- a/drivers/base/dd.c
+++ b/drivers/base/dd.c
@@ -223,9 +223,23 @@ static int deferred_probe_initcall(void)
}
late_initcall(deferred_probe_initcall);
+/**
+ * device_is_bound() - Check if device is bound to a driver
+ * @dev: device to check
+ *
+ * Returns true if passed device has already finished probing successfully
+ * against a driver.
+ *
+ * This function must be called with the device lock held.
+ */
+bool device_is_bound(struct device *dev)
+{
+ return dev->p && klist_node_attached(&dev->p->knode_driver);
+}
+
static void driver_bound(struct device *dev)
{
- if (klist_node_attached(&dev->p->knode_driver)) {
+ if (device_is_bound(dev)) {
printk(KERN_WARNING "%s: device %s already bound\n",
__func__, kobject_name(&dev->kobj));
return;
@@ -236,6 +250,8 @@ static void driver_bound(struct device *dev)
klist_add_tail(&dev->p->knode_driver, &dev->driver->p->klist_devices);
+ device_pm_check_callbacks(dev);
+
/*
* Make sure the device is no longer in one of the deferred lists and
* kick off retrying all pending devices
@@ -601,7 +617,7 @@ static int __device_attach(struct device *dev, bool allow_async)
device_lock(dev);
if (dev->driver) {
- if (klist_node_attached(&dev->p->knode_driver)) {
+ if (device_is_bound(dev)) {
ret = 1;
goto out_unlock;
}
@@ -752,6 +768,7 @@ static void __device_release_driver(struct device *dev)
pm_runtime_reinit(dev);
klist_remove(&dev->p->knode_driver);
+ device_pm_check_callbacks(dev);
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_UNBOUND_DRIVER,
diff --git a/drivers/base/platform.c b/drivers/base/platform.c
index 8dcbb266643b..73d6e5d39e33 100644
--- a/drivers/base/platform.c
+++ b/drivers/base/platform.c
@@ -597,7 +597,6 @@ static void platform_drv_shutdown(struct device *_dev)
if (drv->shutdown)
drv->shutdown(dev);
- dev_pm_domain_detach(_dev, true);
}
/**
diff --git a/drivers/base/power/clock_ops.c b/drivers/base/power/clock_ops.c
index c39b8617280f..272a52ebafc0 100644
--- a/drivers/base/power/clock_ops.c
+++ b/drivers/base/power/clock_ops.c
@@ -15,6 +15,7 @@
#include <linux/clkdev.h>
#include <linux/slab.h>
#include <linux/err.h>
+#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
#ifdef CONFIG_PM_CLK
@@ -348,7 +349,7 @@ static int pm_clk_notify(struct notifier_block *nb,
if (error)
break;
- dev->pm_domain = clknb->pm_domain;
+ dev_pm_domain_set(dev, clknb->pm_domain);
if (clknb->con_ids[0]) {
for (con_id = clknb->con_ids; *con_id; con_id++)
pm_clk_add(dev, *con_id);
@@ -361,7 +362,7 @@ static int pm_clk_notify(struct notifier_block *nb,
if (dev->pm_domain != clknb->pm_domain)
break;
- dev->pm_domain = NULL;
+ dev_pm_domain_set(dev, NULL);
pm_clk_destroy(dev);
break;
}
diff --git a/drivers/base/power/common.c b/drivers/base/power/common.c
index f48e33385b3e..93ed14cc2252 100644
--- a/drivers/base/power/common.c
+++ b/drivers/base/power/common.c
@@ -14,6 +14,8 @@
#include <linux/acpi.h>
#include <linux/pm_domain.h>
+#include "power.h"
+
/**
* dev_pm_get_subsys_data - Create or refcount power.subsys_data for device.
* @dev: Device to handle.
@@ -128,3 +130,25 @@ void dev_pm_domain_detach(struct device *dev, bool power_off)
dev->pm_domain->detach(dev, power_off);
}
EXPORT_SYMBOL_GPL(dev_pm_domain_detach);
+
+/**
+ * dev_pm_domain_set - Set PM domain of a device.
+ * @dev: Device whose PM domain is to be set.
+ * @pd: PM domain to be set, or NULL.
+ *
+ * Sets the PM domain the device belongs to. The PM domain of a device needs
+ * to be set before its probe finishes (it's bound to a driver).
+ *
+ * This function must be called with the device lock held.
+ */
+void dev_pm_domain_set(struct device *dev, struct dev_pm_domain *pd)
+{
+ if (dev->pm_domain == pd)
+ return;
+
+ WARN(device_is_bound(dev),
+ "PM domains can only be changed for unbound devices\n");
+ dev->pm_domain = pd;
+ device_pm_check_callbacks(dev);
+}
+EXPORT_SYMBOL_GPL(dev_pm_domain_set);
diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c
index b80379012840..6ac9a7f33b64 100644
--- a/drivers/base/power/domain.c
+++ b/drivers/base/power/domain.c
@@ -20,6 +20,8 @@
#include <linux/suspend.h>
#include <linux/export.h>
+#include "power.h"
+
#define GENPD_RETRY_MAX_MS 250 /* Approximate */
#define GENPD_DEV_CALLBACK(genpd, type, callback, dev) \
@@ -1188,10 +1190,11 @@ static struct generic_pm_domain_data *genpd_alloc_dev_data(struct device *dev,
}
dev->power.subsys_data->domain_data = &gpd_data->base;
- dev->pm_domain = &genpd->domain;
spin_unlock_irq(&dev->power.lock);
+ dev_pm_domain_set(dev, &genpd->domain);
+
return gpd_data;
err_free:
@@ -1205,9 +1208,10 @@ static struct generic_pm_domain_data *genpd_alloc_dev_data(struct device *dev,
static void genpd_free_dev_data(struct device *dev,
struct generic_pm_domain_data *gpd_data)
{
+ dev_pm_domain_set(dev, NULL);
+
spin_lock_irq(&dev->power.lock);
- dev->pm_domain = NULL;
dev->power.subsys_data->domain_data = NULL;
spin_unlock_irq(&dev->power.lock);
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
index 9d626ac08d9c..6e7c3ccea24b 100644
--- a/drivers/base/power/main.c
+++ b/drivers/base/power/main.c
@@ -125,6 +125,7 @@ void device_pm_add(struct device *dev)
{
pr_debug("PM: Adding info for %s:%s\n",
dev->bus ? dev->bus->name : "No Bus", dev_name(dev));
+ device_pm_check_callbacks(dev);
mutex_lock(&dpm_list_mtx);
if (dev->parent && dev->parent->power.is_prepared)
dev_warn(dev, "parent %s should not be sleeping\n",
@@ -147,6 +148,7 @@ void device_pm_remove(struct device *dev)
mutex_unlock(&dpm_list_mtx);
device_wakeup_disable(dev);
pm_runtime_remove(dev);
+ device_pm_check_callbacks(dev);
}
/**
@@ -1572,6 +1574,11 @@ static int device_prepare(struct device *dev, pm_message_t state)
dev->power.wakeup_path = device_may_wakeup(dev);
+ if (dev->power.no_pm_callbacks) {
+ ret = 1; /* Let device go direct_complete */
+ goto unlock;
+ }
+
if (dev->pm_domain) {
info = "preparing power domain ";
callback = dev->pm_domain->ops.prepare;
@@ -1594,6 +1601,7 @@ static int device_prepare(struct device *dev, pm_message_t state)
if (callback)
ret = callback(dev);
+unlock:
device_unlock(dev);
if (ret < 0) {
@@ -1736,3 +1744,30 @@ void dpm_for_each_dev(void *data, void (*fn)(struct device *, void *))
device_pm_unlock();
}
EXPORT_SYMBOL_GPL(dpm_for_each_dev);
+
+static bool pm_ops_is_empty(const struct dev_pm_ops *ops)
+{
+ if (!ops)
+ return true;
+
+ return !ops->prepare &&
+ !ops->suspend &&
+ !ops->suspend_late &&
+ !ops->suspend_noirq &&
+ !ops->resume_noirq &&
+ !ops->resume_early &&
+ !ops->resume &&
+ !ops->complete;
+}
+
+void device_pm_check_callbacks(struct device *dev)
+{
+ spin_lock_irq(&dev->power.lock);
+ dev->power.no_pm_callbacks =
+ (!dev->bus || pm_ops_is_empty(dev->bus->pm)) &&
+ (!dev->class || pm_ops_is_empty(dev->class->pm)) &&
+ (!dev->type || pm_ops_is_empty(dev->type->pm)) &&
+ (!dev->pm_domain || pm_ops_is_empty(&dev->pm_domain->ops)) &&
+ (!dev->driver || pm_ops_is_empty(dev->driver->pm));
+ spin_unlock_irq(&dev->power.lock);
+}
diff --git a/drivers/base/power/power.h b/drivers/base/power/power.h
index 8b06193d4a5e..50e30e7b059d 100644
--- a/drivers/base/power/power.h
+++ b/drivers/base/power/power.h
@@ -125,6 +125,7 @@ extern void device_pm_remove(struct device *);
extern void device_pm_move_before(struct device *, struct device *);
extern void device_pm_move_after(struct device *, struct device *);
extern void device_pm_move_last(struct device *);
+extern void device_pm_check_callbacks(struct device *dev);
#else /* !CONFIG_PM_SLEEP */
@@ -143,6 +144,8 @@ static inline void device_pm_move_after(struct device *deva,
struct device *devb) {}
static inline void device_pm_move_last(struct device *dev) {}
+static inline void device_pm_check_callbacks(struct device *dev) {}
+
#endif /* !CONFIG_PM_SLEEP */
static inline void device_pm_init(struct device *dev)