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:
commit
6efd3f8cde
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 */
|
||||
|
|
Loading…
Reference in New Issue