From b8b2c7d845d57f7a4b9f1f941f24728165e27626 Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Fri, 7 Aug 2015 07:19:22 +0200 Subject: base/platform: assert that dev_pm_domain callbacks are called unconditionally MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a platform driver doesn't provide a .remove callback the function platform_drv_remove isn't called and so the call to dev_pm_domain_attach called at probe time isn't paired by dev_pm_domain_detach at remove time. To fix this (and similar issues if different callbacks are missing) hook up the driver callbacks unconditionally and make them aware that the platform callbacks might be missing. Signed-off-by: Uwe Kleine-König Signed-off-by: Greg Kroah-Hartman --- drivers/base/platform.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) (limited to 'drivers/base/platform.c') diff --git a/drivers/base/platform.c b/drivers/base/platform.c index f80aaaf9f610..07cec9ba5e70 100644 --- a/drivers/base/platform.c +++ b/drivers/base/platform.c @@ -513,7 +513,7 @@ static int platform_drv_probe(struct device *_dev) return ret; ret = dev_pm_domain_attach(_dev, true); - if (ret != -EPROBE_DEFER) { + if (ret != -EPROBE_DEFER && drv->probe) { ret = drv->probe(dev); if (ret) dev_pm_domain_detach(_dev, true); @@ -536,9 +536,10 @@ static int platform_drv_remove(struct device *_dev) { struct platform_driver *drv = to_platform_driver(_dev->driver); struct platform_device *dev = to_platform_device(_dev); - int ret; + int ret = 0; - ret = drv->remove(dev); + if (drv->remove) + ret = drv->remove(dev); dev_pm_domain_detach(_dev, true); return ret; @@ -549,7 +550,8 @@ static void platform_drv_shutdown(struct device *_dev) struct platform_driver *drv = to_platform_driver(_dev->driver); struct platform_device *dev = to_platform_device(_dev); - drv->shutdown(dev); + if (drv->shutdown) + drv->shutdown(dev); dev_pm_domain_detach(_dev, true); } @@ -563,12 +565,9 @@ int __platform_driver_register(struct platform_driver *drv, { drv->driver.owner = owner; drv->driver.bus = &platform_bus_type; - if (drv->probe) - drv->driver.probe = platform_drv_probe; - if (drv->remove) - drv->driver.remove = platform_drv_remove; - if (drv->shutdown) - drv->driver.shutdown = platform_drv_shutdown; + drv->driver.probe = platform_drv_probe; + drv->driver.remove = platform_drv_remove; + drv->driver.shutdown = platform_drv_shutdown; return driver_register(&drv->driver); } -- cgit v1.2.3 From dbe2256ddd8e8420c254c79f4045c41cb5f4bd6b Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 25 Sep 2015 17:29:04 +0200 Subject: driver-core: platform: Provide helpers for multi-driver modules Some modules register several sub-drivers. Provide a helper that makes it easy to register and unregister a list of sub-drivers, as well as unwind properly on error. Signed-off-by: Thierry Reding Signed-off-by: Greg Kroah-Hartman --- Documentation/driver-model/platform.txt | 14 ++++++++ drivers/base/platform.c | 61 +++++++++++++++++++++++++++++++++ include/linux/platform_device.h | 8 +++++ 3 files changed, 83 insertions(+) (limited to 'drivers/base/platform.c') diff --git a/Documentation/driver-model/platform.txt b/Documentation/driver-model/platform.txt index 07795ec51cde..e456696cfef2 100644 --- a/Documentation/driver-model/platform.txt +++ b/Documentation/driver-model/platform.txt @@ -63,6 +63,20 @@ runtime memory footprint: int platform_driver_probe(struct platform_driver *drv, int (*probe)(struct platform_device *)) +Kernel modules can be composed of several platform drivers. The platform core +provides helpers to register and unregister an array of drivers: + + int __platform_register_drivers(struct platform_driver * const *drivers, + unsigned int count, struct module *owner); + void platform_unregister_drivers(struct platform_driver * const *drivers, + unsigned int count); + +If one of the drivers fails to register, all drivers registered up to that +point will be unregistered in reverse order. Note that there is a convenience +macro that passes THIS_MODULE as owner parameter: + + #define platform_register_driver(drivers, count) + Device Enumeration ~~~~~~~~~~~~~~~~~~ diff --git a/drivers/base/platform.c b/drivers/base/platform.c index 07cec9ba5e70..1dd6d3bf1098 100644 --- a/drivers/base/platform.c +++ b/drivers/base/platform.c @@ -710,6 +710,67 @@ err_out: } EXPORT_SYMBOL_GPL(__platform_create_bundle); +/** + * __platform_register_drivers - register an array of platform drivers + * @drivers: an array of drivers to register + * @count: the number of drivers to register + * @owner: module owning the drivers + * + * Registers platform drivers specified by an array. On failure to register a + * driver, all previously registered drivers will be unregistered. Callers of + * this API should use platform_unregister_drivers() to unregister drivers in + * the reverse order. + * + * Returns: 0 on success or a negative error code on failure. + */ +int __platform_register_drivers(struct platform_driver * const *drivers, + unsigned int count, struct module *owner) +{ + unsigned int i; + int err; + + for (i = 0; i < count; i++) { + pr_debug("registering platform driver %ps\n", drivers[i]); + + err = __platform_driver_register(drivers[i], owner); + if (err < 0) { + pr_err("failed to register platform driver %ps: %d\n", + drivers[i], err); + goto error; + } + } + + return 0; + +error: + while (i--) { + pr_debug("unregistering platform driver %ps\n", drivers[i]); + platform_driver_unregister(drivers[i]); + } + + return err; +} +EXPORT_SYMBOL_GPL(__platform_register_drivers); + +/** + * platform_unregister_drivers - unregister an array of platform drivers + * @drivers: an array of drivers to unregister + * @count: the number of drivers to unregister + * + * Unegisters platform drivers specified by an array. This is typically used + * to complement an earlier call to platform_register_drivers(). Drivers are + * unregistered in the reverse order in which they were registered. + */ +void platform_unregister_drivers(struct platform_driver * const *drivers, + unsigned int count) +{ + while (count--) { + pr_debug("unregistering platform driver %ps\n", drivers[count]); + platform_driver_unregister(drivers[count]); + } +} +EXPORT_SYMBOL_GPL(platform_unregister_drivers); + /* modalias support enables more hands-off userspace setup: * (a) environment variable lets new-style hotplug events work once system is * fully running: "modprobe $MODALIAS" diff --git a/include/linux/platform_device.h b/include/linux/platform_device.h index bba08f44cc97..dc777be5f2e1 100644 --- a/include/linux/platform_device.h +++ b/include/linux/platform_device.h @@ -270,6 +270,14 @@ extern struct platform_device *__platform_create_bundle( struct resource *res, unsigned int n_res, const void *data, size_t size, struct module *module); +int __platform_register_drivers(struct platform_driver * const *drivers, + unsigned int count, struct module *owner); +void platform_unregister_drivers(struct platform_driver * const *drivers, + unsigned int count); + +#define platform_register_drivers(drivers, count) \ + __platform_register_drivers(drivers, count, THIS_MODULE) + /* early platform driver interface */ struct early_platform_driver { const char *class_str; -- cgit v1.2.3