Merge branch 'pm-core'
authorRafael J. Wysocki <rafael.j.wysocki@intel.com>
Wed, 20 Jan 2016 23:42:59 +0000 (00:42 +0100)
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>
Wed, 20 Jan 2016 23:42:59 +0000 (00:42 +0100)
* 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()

18 files changed:
arch/arm/mach-omap2/omap_device.c
drivers/acpi/acpi_lpss.c
drivers/acpi/device_pm.c
drivers/base/dd.c
drivers/base/platform.c
drivers/base/power/clock_ops.c
drivers/base/power/common.c
drivers/base/power/domain.c
drivers/base/power/main.c
drivers/base/power/power.h
drivers/gpu/vga/vga_switcheroo.c
drivers/misc/mei/pci-me.c
drivers/misc/mei/pci-txe.c
drivers/usb/core/port.c
drivers/usb/core/usb.c
include/linux/device.h
include/linux/pm.h
include/linux/pm_domain.h

index 72ebc4c16bae7e55a69775b02bfc534b9c56fda4..220822bcfe3fd92d7fd94751860e71bcd3464132 100644 (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);
 }
 
index 047281a6ae11f175c67d7d2035fceed702bb78e5..c570b1d9f09480ecec5513d3cfe61d33c26c81b5 100644 (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;
index 08a02cdc737c193d608970b6eec0b45781137d76..cd2c3d6d40e03fbc70ef802dd82c4e1ae36c7ef3 100644 (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);
index 7399be790b5dbdf8715694522ba410cdede2808f..c4da2df62e02525617a23ebe68fff4af0bb69350 100644 (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,
index 8dcbb266643b8773e212bc29f3b2137c3060e059..73d6e5d39e33e1edef9033d5e97b99ecc50a80cf 100644 (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);
 }
 
 /**
index c39b8617280feff712096e6699ffe6d4f3db13f8..272a52ebafc09d0c4a3d974b6ca0bf16aef5e612 100644 (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;
        }
index f48e33385b3e136029c6770505a3dcee3d313e86..93ed14cc22524ccdd04301dbe460e0958bb69acf 100644 (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);
index b8037901284042790a80b12a0ef507f149019240..6ac9a7f33b640ae1105a72f76974e8939a30ae45 100644 (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);
index 9d626ac08d9c05d6cd5ee97559950bedf89976f8..6e7c3ccea24bbd3d2c10e199fe4b2018477a8a0c 100644 (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);
+}
index 8b06193d4a5e9f1aa42cfae570a5499be86ee4a5..50e30e7b059d18a034c80b2a71fd846a098189a1 100644 (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)
index 41edd5a3f10097e8c030b076397fec389448319d..7b95ed2fb49bb44fa281669c1e471b0211a452ae 100644 (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);
index 27678d8154e074ea698952832aac64bb843032f9..75fc9c688df8fc6fac004c78d8067061eff70749 100644 (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 = {
index 0882c02019072bbdd43db49811ae86fc86d4ff52..71f8a747571756b0941a82f0ee6af99247464744 100644 (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 = {
index 5487fe308f01b7be30bcecebc12726a295f9daa9..514b563e3e5fbc964107a31bbd18a7735d96e565 100644 (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
 };
 
index f8bbd0b6d9fe44cb0077a200588c1ea054914580..cafc11902794bb1c13bf0bc7f5a68f7283ae7d81 100644 (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)
index f627ba20a46cd892c8e8f993baf08137d617aba5..6d6f1fec092fe5df78671f5b352931a2edae4aca 100644 (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
  */
index 528be6787796b52a405fbc0af8bf707c31d55192..6a5d654f444726abc824d9d07377ad66e86c6a44 100644 (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
index ba4ced38efae0c9249a97ea57c0cccd1ac37e7f7..db21d3995f7e339f7f7f7f1adf21536c7b9a4e8b 100644 (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 */
This page took 0.037449 seconds and 5 git commands to generate.