[PATCH] driver core / PM: Add callbacks for PM domain initialization/cleanup

Rafael J. Wysocki rjw at rjwysocki.net
Wed Mar 18 08:02:11 PDT 2015

From: Rafael J. Wysocki <rafael.j.wysocki at intel.com>

If PM domains are in use, it may be necessary to prepare the code
handling a PM domain for driver probing.  For example, in some
cases device drivers rely on the ability to power on the devices
with the help of the IO runtime PM framework and the PM domain
code needs to be ready for that.  Also, if that code has not been
fully initialized yet, the driver probing should be deferred.

Moreover, after the probing is complete, it may be necessary to
put the PM domain in question into the state reflecting the current
needs of the devices in it, for example, to prevent power from being
drawn in vain.

For these reasons, introduce new PM domain callbacks, ->activate
and ->sync, called, respectively, before probing for a device
driver and after the probing has been completed.

That is not sufficient, however, because the device's PM domain
pointer has to be populated for the ->activate callback to be
executed, so setting it in bus type ->probe callback routines
would be too late.  Also, there are bus types where PM domains
are not used at all and the core should not attempt to set the
pm_domain pointer for the devices on those buses.

To overcome that difficulty, introduce two new bus type
callbacks, ->init and ->release, called by bus_add_device() and
bus_remove_device(), respectively.  That will allow ->init to
be used to populate the pm_domain pointer for the bus types
that want to do that and ->release will be useful for any
cleanup that may be necessary after removing a device that
was part of a PM domain.

Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki at intel.com>

It occured to me that we might want to ->sync regardless of whether or
not the probing had been succenssful, so I changed the code in
really_probe() along these lines.  Please let me know if that's
not OK.

 drivers/base/bus.c     |   12 +++++++++++-
 drivers/base/dd.c      |   20 ++++++++++++++------
 include/linux/device.h |    5 +++++
 include/linux/pm.h     |    6 ++++++
 4 files changed, 36 insertions(+), 7 deletions(-)

Index: linux-pm/drivers/base/bus.c
--- linux-pm.orig/drivers/base/bus.c
+++ linux-pm/drivers/base/bus.c
@@ -509,10 +509,15 @@ int bus_add_device(struct device *dev)
 	int error = 0;
 	if (bus) {
+		if (bus->init) {
+			error = bus->init(dev);
+			if (error)
+				goto out_put;
+		}
 		pr_debug("bus: '%s': add device %s\n", bus->name, dev_name(dev));
 		error = device_add_attrs(bus, dev);
 		if (error)
-			goto out_put;
+			goto out_release;
 		error = device_add_groups(dev, bus->dev_groups);
 		if (error)
 			goto out_groups;
@@ -534,6 +539,9 @@ out_groups:
 	device_remove_groups(dev, bus->dev_groups);
 	device_remove_attrs(bus, dev);
+	if (bus->release)
+		bus->release(dev);
 	return error;
@@ -597,6 +605,8 @@ void bus_remove_device(struct device *de
 	device_remove_groups(dev, dev->bus->dev_groups);
 	if (klist_node_attached(&dev->p->knode_bus))
+	if (bus->release)
+		bus->release(dev);
 	pr_debug("bus: '%s': remove device %s\n",
 		 dev->bus->name, dev_name(dev));
Index: linux-pm/include/linux/device.h
--- linux-pm.orig/include/linux/device.h
+++ linux-pm/include/linux/device.h
@@ -69,6 +69,8 @@ extern void bus_remove_file(struct bus_t
  * @bus_groups:	Default attributes of the bus.
  * @dev_groups:	Default attributes of the devices on the bus.
  * @drv_groups: Default attributes of the device drivers on the bus.
+ * @init:	Called when registering a device on this bus.
+ * @release:	Called when unregistering a device on this bus.
  * @match:	Called, perhaps multiple times, whenever a new device or driver
  *		is added for this bus. It should return a nonzero value if the
  *		given device can be handled by the given driver.
@@ -111,6 +113,9 @@ struct bus_type {
 	const struct attribute_group **dev_groups;
 	const struct attribute_group **drv_groups;
+	int (*init)(struct device *dev);
+	void (*release)(struct device *dev);
 	int (*match)(struct device *dev, struct device_driver *drv);
 	int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
 	int (*probe)(struct device *dev);
Index: linux-pm/include/linux/pm.h
--- linux-pm.orig/include/linux/pm.h
+++ linux-pm/include/linux/pm.h
@@ -603,10 +603,16 @@ extern void dev_pm_put_subsys_data(struc
  * Power domains provide callbacks that are executed during system suspend,
  * hibernation, system resume and during runtime PM transitions along with
  * subsystem-level and driver-level callbacks.
+ *
+ * @detach: Called when removing a device from the domain.
+ * @activate: Called before executing probe routines for bus types and drivers.
+ * @sync: Called after successful execiton of the probe routines.
 struct dev_pm_domain {
 	struct dev_pm_ops	ops;
 	void (*detach)(struct device *dev, bool power_off);
+	int (*activate)(struct device *dev);
+	void (*sync)(struct device *dev);
Index: linux-pm/drivers/base/dd.c
--- linux-pm.orig/drivers/base/dd.c
+++ linux-pm/drivers/base/dd.c
@@ -279,6 +279,7 @@ static int really_probe(struct device *d
 	int ret = 0;
 	int local_trigger_count = atomic_read(&deferred_trigger_count);
+	struct dev_pm_domain *pm_domain = dev->pm_domain;
 	pr_debug("bus: '%s': %s: probing driver %s with device %s\n",
@@ -298,16 +299,23 @@ static int really_probe(struct device *d
 		goto probe_failed;
-	if (dev->bus->probe) {
-		ret = dev->bus->probe(dev);
-		if (ret)
-			goto probe_failed;
-	} else if (drv->probe) {
-		ret = drv->probe(dev);
+	if (pm_domain && pm_domain->activate) {
+		ret = pm_domain->activate(dev);
 		if (ret)
 			goto probe_failed;
+	if (dev->bus->probe)
+		ret = dev->bus->probe(dev);
+	else if (drv->probe)
+		ret = drv->probe(dev);
+	if (pm_domain && pm_domain->sync)
+		pm_domain->sync(dev);
+	if (ret)
+		goto probe_failed;
 	ret = 1;
 	pr_debug("bus: '%s': %s: bound device %s to driver %s\n",

More information about the linux-arm-kernel mailing list