From 6b9cb42752dafba3761dde0002ca58ca518b6311 Mon Sep 17 00:00:00 2001 From: Tomeu Vizoso Date: Thu, 7 Jan 2016 16:46:12 +0100 Subject: device core: add device_is_bound() Adds a function that tells whether a device is already bound to a driver. This is needed to warn when there is an attempt to change the PM domain of a device that has finished probing already. The reason why we want to enforce that is because in the general case that can cause problems and also that we can simplify code quite a bit if we can always assume that. Signed-off-by: Tomeu Vizoso Reviewed-by: Ulf Hansson Acked-by: Greg Kroah-Hartman Signed-off-by: Rafael J. Wysocki --- drivers/base/dd.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/dd.c b/drivers/base/dd.c index 7399be790b5d..13a0d66e5782 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -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 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; @@ -601,7 +615,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; } -- cgit v1.2.3 From 989561de9b5112999475b406557d9c7e9e59c041 Mon Sep 17 00:00:00 2001 From: Tomeu Vizoso Date: Thu, 7 Jan 2016 16:46:13 +0100 Subject: PM / Domains: add setter for dev.pm_domain Adds a function that sets the pointer to dev_pm_domain in struct device and that warns if the device has already finished probing. The reason why we want to enforce that is because in the general case that can cause problems and also that we can simplify code quite a bit if we can always assume that. This patch also changes all current code that directly sets the dev.pm_domain pointer. Signed-off-by: Tomeu Vizoso Reviewed-by: Ulf Hansson Signed-off-by: Rafael J. Wysocki --- arch/arm/mach-omap2/omap_device.c | 7 ++++--- drivers/acpi/acpi_lpss.c | 5 ++++- drivers/acpi/device_pm.c | 5 +++-- drivers/base/power/clock_ops.c | 5 +++-- drivers/base/power/common.c | 21 +++++++++++++++++++++ drivers/base/power/domain.c | 6 ++++-- drivers/gpu/vga/vga_switcheroo.c | 11 ++++++----- drivers/misc/mei/pci-me.c | 5 +++-- drivers/misc/mei/pci-txe.c | 5 +++-- include/linux/pm_domain.h | 3 +++ 10 files changed, 54 insertions(+), 19 deletions(-) (limited to 'drivers/base') diff --git a/arch/arm/mach-omap2/omap_device.c b/arch/arm/mach-omap2/omap_device.c index 72ebc4c16bae..220822bcfe3f 100644 --- a/arch/arm/mach-omap2/omap_device.c +++ b/arch/arm/mach-omap2/omap_device.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -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); } diff --git a/drivers/acpi/acpi_lpss.c b/drivers/acpi/acpi_lpss.c index 047281a6ae11..c570b1d9f094 100644 --- a/drivers/acpi/acpi_lpss.c +++ b/drivers/acpi/acpi_lpss.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -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; diff --git a/drivers/acpi/device_pm.c b/drivers/acpi/device_pm.c index 08a02cdc737c..cd2c3d6d40e0 100644 --- a/drivers/acpi/device_pm.c +++ b/drivers/acpi/device_pm.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #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); diff --git a/drivers/base/power/clock_ops.c b/drivers/base/power/clock_ops.c index c39b8617280f..272a52ebafc0 100644 --- a/drivers/base/power/clock_ops.c +++ b/drivers/base/power/clock_ops.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #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; } diff --git a/drivers/base/power/common.c b/drivers/base/power/common.c index f48e33385b3e..02812bcabcac 100644 --- a/drivers/base/power/common.c +++ b/drivers/base/power/common.c @@ -128,3 +128,24 @@ 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; +} +EXPORT_SYMBOL_GPL(dev_pm_domain_set); diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 167418e73445..abbac6fe8fd5 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -1177,10 +1177,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: @@ -1194,9 +1195,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); diff --git a/drivers/gpu/vga/vga_switcheroo.c b/drivers/gpu/vga/vga_switcheroo.c index 41edd5a3f100..7b95ed2fb49b 100644 --- a/drivers/gpu/vga/vga_switcheroo.c +++ b/drivers/gpu/vga/vga_switcheroo.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -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); diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c index 27678d8154e0..75fc9c688df8 100644 --- a/drivers/misc/mei/pci-me.c +++ b/drivers/misc/mei/pci-me.c @@ -31,6 +31,7 @@ #include #include +#include #include #include @@ -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 = { diff --git a/drivers/misc/mei/pci-txe.c b/drivers/misc/mei/pci-txe.c index 0882c0201907..71f8a7475717 100644 --- a/drivers/misc/mei/pci-txe.c +++ b/drivers/misc/mei/pci-txe.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -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 = { diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index ba4ced38efae..db21d3995f7e 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -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 */ -- cgit v1.2.3 From aa8e54b559479d0cb7eb632ba443b8cacd20cd4b Mon Sep 17 00:00:00 2001 From: Tomeu Vizoso Date: Thu, 7 Jan 2016 16:46:14 +0100 Subject: PM / sleep: Go direct_complete if driver has no callbacks If a suitable prepare callback cannot be found for a given device and its driver has no PM callbacks at all, assume that it can go direct to complete when the system goes to sleep. The reason for this is that there's lots of devices in a system that do no PM at all and there's no reason for them to prevent their ancestors to do direct_complete if they can support it. Signed-off-by: Tomeu Vizoso Reviewed-by: Ulf Hansson Signed-off-by: Rafael J. Wysocki --- drivers/base/dd.c | 3 +++ drivers/base/power/common.c | 3 +++ drivers/base/power/domain.c | 2 ++ drivers/base/power/main.c | 35 +++++++++++++++++++++++++++++++++++ drivers/base/power/power.h | 3 +++ include/linux/pm.h | 1 + 6 files changed, 47 insertions(+) (limited to 'drivers/base') diff --git a/drivers/base/dd.c b/drivers/base/dd.c index 13a0d66e5782..049942176b00 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -250,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 @@ -766,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, diff --git a/drivers/base/power/common.c b/drivers/base/power/common.c index 02812bcabcac..93ed14cc2252 100644 --- a/drivers/base/power/common.c +++ b/drivers/base/power/common.c @@ -14,6 +14,8 @@ #include #include +#include "power.h" + /** * dev_pm_get_subsys_data - Create or refcount power.subsys_data for device. * @dev: Device to handle. @@ -147,5 +149,6 @@ void dev_pm_domain_set(struct device *dev, struct dev_pm_domain *pd) 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); diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index abbac6fe8fd5..33a5f4b752ed 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -20,6 +20,8 @@ #include #include +#include "power.h" + #define GENPD_RETRY_MAX_MS 250 /* Approximate */ #define GENPD_DEV_CALLBACK(genpd, type, callback, dev) \ diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 9d626ac08d9c..6e7c3ccea24b 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -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); +} diff --git a/drivers/base/power/power.h b/drivers/base/power/power.h index 8b06193d4a5e..50e30e7b059d 100644 --- a/drivers/base/power/power.h +++ b/drivers/base/power/power.h @@ -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) diff --git a/include/linux/pm.h b/include/linux/pm.h index 528be6787796..6a5d654f4447 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h @@ -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 -- cgit v1.2.3 From 2d30bb0b3889adf09b342722b2ce596c0763bc93 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 12 Jan 2016 00:12:19 +0100 Subject: platform: Do not detach from PM domains on shutdown Shutdown is carried out when the driver is still bound to the device, so it is incorrect to detach it from a PM domain (if any) at this point. Signed-off-by: Rafael J. Wysocki Reported-and-tested-by: Fabio Estevam Acked-by: Ulf Hansson --- drivers/base/platform.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers/base') diff --git a/drivers/base/platform.c b/drivers/base/platform.c index 1dd6d3bf1098..484255c7bc47 100644 --- a/drivers/base/platform.c +++ b/drivers/base/platform.c @@ -552,7 +552,6 @@ static void platform_drv_shutdown(struct device *_dev) if (drv->shutdown) drv->shutdown(dev); - dev_pm_domain_detach(_dev, true); } /** -- cgit v1.2.3 From 3ded91041a762423bd49bb932ae2e36b65c6ae4b Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 12 Jan 2016 01:51:44 +0100 Subject: driver core: Avoid NULL pointer dereferences in device_is_bound() If device_is_bound() is called on a device that's not been registered yet, it will attepmt to dereference dev->p which is NULL, so avoid that by checking dev->p in there against NULL. Fixes: 6b9cb42752da "device core: add device_is_bound()" Reported-and-tested-by: Guenter Roeck Tested-by: Tony Lindgren Signed-off-by: Rafael J. Wysocki --- drivers/base/dd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/base') diff --git a/drivers/base/dd.c b/drivers/base/dd.c index 049942176b00..c4da2df62e02 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -234,7 +234,7 @@ late_initcall(deferred_probe_initcall); */ bool device_is_bound(struct device *dev) { - return klist_node_attached(&dev->p->knode_driver); + return dev->p && klist_node_attached(&dev->p->knode_driver); } static void driver_bound(struct device *dev) -- cgit v1.2.3