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()
This commit is contained in:
@@ -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,
|
||||
|
@@ -597,7 +597,6 @@ static void platform_drv_shutdown(struct device *_dev)
|
||||
|
||||
if (drv->shutdown)
|
||||
drv->shutdown(dev);
|
||||
dev_pm_domain_detach(_dev, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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)
|
||||
|
Reference in New Issue
Block a user