Skip to content

Commit

Permalink
Merge branch 'pm-core'
Browse files Browse the repository at this point in the history
* 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()
  • Loading branch information
Rafael J. Wysocki committed Jan 20, 2016
2 parents a72aea7 + 3ded910 commit 6efd3f8
Show file tree
Hide file tree
Showing 18 changed files with 132 additions and 23 deletions.
7 changes: 4 additions & 3 deletions arch/arm/mach-omap2/omap_device.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/clkdev.h>
#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
#include <linux/of.h>
#include <linux/notifier.h>
Expand Down Expand Up @@ -168,7 +169,7 @@ static int omap_device_build_from_dt(struct platform_device *pdev)
r->name = dev_name(&pdev->dev);
}

pdev->dev.pm_domain = &omap_device_pm_domain;
dev_pm_domain_set(&pdev->dev, &omap_device_pm_domain);

if (device_active) {
omap_device_enable(pdev);
Expand All @@ -180,7 +181,7 @@ static int omap_device_build_from_dt(struct platform_device *pdev)
odbfd_exit:
/* if data/we are at fault.. load up a fail handler */
if (ret)
pdev->dev.pm_domain = &omap_device_fail_pm_domain;
dev_pm_domain_set(&pdev->dev, &omap_device_fail_pm_domain);

return ret;
}
Expand Down Expand Up @@ -701,7 +702,7 @@ int omap_device_register(struct platform_device *pdev)
{
pr_debug("omap_device: %s: registering\n", pdev->name);

pdev->dev.pm_domain = &omap_device_pm_domain;
dev_pm_domain_set(&pdev->dev, &omap_device_pm_domain);
return platform_device_add(pdev);
}

Expand Down
5 changes: 4 additions & 1 deletion drivers/acpi/acpi_lpss.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/platform_data/clk-lpss.h>
#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
#include <linux/delay.h>

Expand Down Expand Up @@ -875,20 +876,22 @@ static int acpi_lpss_platform_notify(struct notifier_block *nb,

switch (action) {
case BUS_NOTIFY_BIND_DRIVER:
pdev->dev.pm_domain = &acpi_lpss_pm_domain;
dev_pm_domain_set(&pdev->dev, &acpi_lpss_pm_domain);
break;
case BUS_NOTIFY_DRIVER_NOT_BOUND:
case BUS_NOTIFY_UNBOUND_DRIVER:
pdev->dev.pm_domain = NULL;
break;
case BUS_NOTIFY_ADD_DEVICE:
dev_pm_domain_set(&pdev->dev, &acpi_lpss_pm_domain);
if (pdata->dev_desc->flags & LPSS_LTR)
return sysfs_create_group(&pdev->dev.kobj,
&lpss_attr_group);
break;
case BUS_NOTIFY_DEL_DEVICE:
if (pdata->dev_desc->flags & LPSS_LTR)
sysfs_remove_group(&pdev->dev.kobj, &lpss_attr_group);
dev_pm_domain_set(&pdev->dev, NULL);
break;
default:
break;
Expand Down
5 changes: 3 additions & 2 deletions drivers/acpi/device_pm.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <linux/export.h>
#include <linux/mutex.h>
#include <linux/pm_qos.h>
#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>

#include "internal.h"
Expand Down Expand Up @@ -1059,7 +1060,7 @@ static void acpi_dev_pm_detach(struct device *dev, bool power_off)
struct acpi_device *adev = ACPI_COMPANION(dev);

if (adev && dev->pm_domain == &acpi_general_pm_domain) {
dev->pm_domain = NULL;
dev_pm_domain_set(dev, NULL);
acpi_remove_pm_notifier(adev);
if (power_off) {
/*
Expand Down Expand Up @@ -1111,7 +1112,7 @@ int acpi_dev_pm_attach(struct device *dev, bool power_on)
return -EBUSY;

acpi_add_pm_notifier(adev, dev, acpi_pm_notify_work_func);
dev->pm_domain = &acpi_general_pm_domain;
dev_pm_domain_set(dev, &acpi_general_pm_domain);
if (power_on) {
acpi_dev_pm_full_power(adev);
acpi_device_wakeup(adev, ACPI_STATE_S0, false);
Expand Down
21 changes: 19 additions & 2 deletions drivers/base/dd.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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,
Expand Down
1 change: 0 additions & 1 deletion drivers/base/platform.c
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,6 @@ static void platform_drv_shutdown(struct device *_dev)

if (drv->shutdown)
drv->shutdown(dev);
dev_pm_domain_detach(_dev, true);
}

/**
Expand Down
5 changes: 3 additions & 2 deletions drivers/base/power/clock_ops.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -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;
}
Expand Down
24 changes: 24 additions & 0 deletions drivers/base/power/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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);
8 changes: 6 additions & 2 deletions drivers/base/power/domain.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) \
Expand Down Expand Up @@ -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:
Expand All @@ -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);
Expand Down
35 changes: 35 additions & 0 deletions drivers/base/power/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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);
}

/**
Expand Down Expand Up @@ -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;
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
}
3 changes: 3 additions & 0 deletions drivers/base/power/power.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */

Expand All @@ -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)
Expand Down
11 changes: 6 additions & 5 deletions drivers/gpu/vga/vga_switcheroo.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>
Expand Down Expand Up @@ -918,17 +919,17 @@ int vga_switcheroo_init_domain_pm_ops(struct device *dev,
domain->ops.runtime_suspend = vga_switcheroo_runtime_suspend;
domain->ops.runtime_resume = vga_switcheroo_runtime_resume;

dev->pm_domain = domain;
dev_pm_domain_set(dev, domain);
return 0;
}
dev->pm_domain = NULL;
dev_pm_domain_set(dev, NULL);
return -EINVAL;
}
EXPORT_SYMBOL(vga_switcheroo_init_domain_pm_ops);

void vga_switcheroo_fini_domain_pm_ops(struct device *dev)
{
dev->pm_domain = NULL;
dev_pm_domain_set(dev, NULL);
}
EXPORT_SYMBOL(vga_switcheroo_fini_domain_pm_ops);

Expand Down Expand Up @@ -989,10 +990,10 @@ vga_switcheroo_init_domain_pm_optimus_hdmi_audio(struct device *dev,
domain->ops.runtime_resume =
vga_switcheroo_runtime_resume_hdmi_audio;

dev->pm_domain = domain;
dev_pm_domain_set(dev, domain);
return 0;
}
dev->pm_domain = NULL;
dev_pm_domain_set(dev, NULL);
return -EINVAL;
}
EXPORT_SYMBOL(vga_switcheroo_init_domain_pm_optimus_hdmi_audio);
5 changes: 3 additions & 2 deletions drivers/misc/mei/pci-me.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include <linux/jiffies.h>
#include <linux/interrupt.h>

#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>

#include <linux/mei.h>
Expand Down Expand Up @@ -436,7 +437,7 @@ static inline void mei_me_set_pm_domain(struct mei_device *dev)
dev->pg_domain.ops.runtime_resume = mei_me_pm_runtime_resume;
dev->pg_domain.ops.runtime_idle = mei_me_pm_runtime_idle;

pdev->dev.pm_domain = &dev->pg_domain;
dev_pm_domain_set(&pdev->dev, &dev->pg_domain);
}
}

Expand All @@ -448,7 +449,7 @@ static inline void mei_me_set_pm_domain(struct mei_device *dev)
static inline void mei_me_unset_pm_domain(struct mei_device *dev)
{
/* stop using pm callbacks if any */
dev->dev->pm_domain = NULL;
dev_pm_domain_set(dev->dev, NULL);
}

static const struct dev_pm_ops mei_me_pm_ops = {
Expand Down
Loading

0 comments on commit 6efd3f8

Please sign in to comment.