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:
Rafael J. Wysocki 2016-01-21 00:42:59 +01:00
commit 6efd3f8cde
18 changed files with 132 additions and 23 deletions

View File

@ -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>
@ -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);
@ -180,7 +181,7 @@ odbfd_exit1:
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;
}
@ -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);
}

View File

@ -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>
@ -875,13 +876,14 @@ 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);
@ -889,6 +891,7 @@ static int acpi_lpss_platform_notify(struct notifier_block *nb,
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;

View File

@ -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"
@ -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) {
/*
@ -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);

View File

@ -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,

View File

@ -597,7 +597,6 @@ static void platform_drv_shutdown(struct device *_dev)
if (drv->shutdown)
drv->shutdown(dev);
dev_pm_domain_detach(_dev, true);
}
/**

View File

@ -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;
}

View File

@ -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);

View File

@ -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);

View File

@ -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);
}

View File

@ -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)

View File

@ -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>
@ -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);
@ -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);

View File

@ -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>
@ -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);
}
}
@ -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 = {

View File

@ -27,6 +27,7 @@
#include <linux/jiffies.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
#include <linux/mei.h>
@ -388,7 +389,7 @@ static inline void mei_txe_set_pm_domain(struct mei_device *dev)
dev->pg_domain.ops.runtime_resume = mei_txe_pm_runtime_resume;
dev->pg_domain.ops.runtime_idle = mei_txe_pm_runtime_idle;
pdev->dev.pm_domain = &dev->pg_domain;
dev_pm_domain_set(&pdev->dev, &dev->pg_domain);
}
}
@ -400,7 +401,7 @@ static inline void mei_txe_set_pm_domain(struct mei_device *dev)
static inline void mei_txe_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_txe_pm_ops = {

View File

@ -168,12 +168,18 @@ static int usb_port_runtime_suspend(struct device *dev)
return retval;
}
static int usb_port_prepare(struct device *dev)
{
return 1;
}
#endif
static const struct dev_pm_ops usb_port_pm_ops = {
#ifdef CONFIG_PM
.runtime_suspend = usb_port_runtime_suspend,
.runtime_resume = usb_port_runtime_resume,
.prepare = usb_port_prepare,
#endif
};

View File

@ -316,7 +316,13 @@ static int usb_dev_uevent(struct device *dev, struct kobj_uevent_env *env)
static int usb_dev_prepare(struct device *dev)
{
return 0; /* Implement eventually? */
struct usb_device *udev = to_usb_device(dev);
/* Return 0 if the current wakeup setting is wrong, otherwise 1 */
if (udev->do_remote_wakeup != device_may_wakeup(dev))
return 0;
return 1;
}
static void usb_dev_complete(struct device *dev)

View File

@ -1044,6 +1044,8 @@ extern int __must_check driver_attach(struct device_driver *drv);
extern void device_initial_probe(struct device *dev);
extern int __must_check device_reprobe(struct device *dev);
extern bool device_is_bound(struct device *dev);
/*
* Easy functions for dynamically creating devices on the fly
*/

View File

@ -573,6 +573,7 @@ struct dev_pm_info {
struct wakeup_source *wakeup;
bool wakeup_path:1;
bool syscore:1;
bool no_pm_callbacks:1; /* Owned by the PM core */
#else
unsigned int should_wakeup:1;
#endif

View File

@ -240,12 +240,15 @@ static inline int of_genpd_add_provider_onecell(struct device_node *np,
#ifdef CONFIG_PM
extern int dev_pm_domain_attach(struct device *dev, bool power_on);
extern void dev_pm_domain_detach(struct device *dev, bool power_off);
extern void dev_pm_domain_set(struct device *dev, struct dev_pm_domain *pd);
#else
static inline int dev_pm_domain_attach(struct device *dev, bool power_on)
{
return -ENODEV;
}
static inline void dev_pm_domain_detach(struct device *dev, bool power_off) {}
static inline void dev_pm_domain_set(struct device *dev,
struct dev_pm_domain *pd) {}
#endif
#endif /* _LINUX_PM_DOMAIN_H */