Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 35cd133c authored by Rafael J. Wysocki's avatar Rafael J. Wysocki
Browse files

PM: Run the driver callback directly if the subsystem one is not there



Make the PM core execute driver PM callbacks directly if the
corresponding subsystem callbacks are not present.

There are three reasons for doing that.  First, it reflects the
behavior of drivers/base/dd.c:really_probe() that runs the driver's
.probe() callback directly if the bus type's one is not defined, so
this change will remove one arbitrary difference between the PM core
and the remaining parts of the driver core.  Second, it will allow
some subsystems, whose PM callbacks don't do anything except for
executing driver callbacks, to be simplified quite a bit by removing
those "forward-only" callbacks.  Finally, it will allow us to remove
one level of indirection in the system suspend and resume code paths
where it is not necessary, which is going to lead to less debug noise
with initcall_debug passed in the kernel command line (messages won't
be printed for driverless devices whose subsystems don't provide
PM callbacks among other things).

Signed-off-by: default avatarRafael J. Wysocki <rjw@sisk.pl>
parent 9cf519d1
Loading
Loading
Loading
Loading
+21 −16
Original line number Original line Diff line number Diff line
@@ -126,7 +126,9 @@ The core methods to suspend and resume devices reside in struct dev_pm_ops
pointed to by the ops member of struct dev_pm_domain, or by the pm member of
pointed to by the ops member of struct dev_pm_domain, or by the pm member of
struct bus_type, struct device_type and struct class.  They are mostly of
struct bus_type, struct device_type and struct class.  They are mostly of
interest to the people writing infrastructure for platforms and buses, like PCI
interest to the people writing infrastructure for platforms and buses, like PCI
or USB, or device type and device class drivers.
or USB, or device type and device class drivers.  They also are relevant to the
writers of device drivers whose subsystems (PM domains, device types, device
classes and bus types) don't provide all power management methods.


Bus drivers implement these methods as appropriate for the hardware and the
Bus drivers implement these methods as appropriate for the hardware and the
drivers using it; PCI works differently from USB, and so on.  Not many people
drivers using it; PCI works differently from USB, and so on.  Not many people
@@ -268,32 +270,35 @@ various phases always run after tasks have been frozen and before they are
unfrozen.  Furthermore, the *_noirq phases run at a time when IRQ handlers have
unfrozen.  Furthermore, the *_noirq phases run at a time when IRQ handlers have
been disabled (except for those marked with the IRQF_NO_SUSPEND flag).
been disabled (except for those marked with the IRQF_NO_SUSPEND flag).


All phases use PM domain, bus, type, or class callbacks (that is, methods
All phases use PM domain, bus, type, class or driver callbacks (that is, methods
defined in dev->pm_domain->ops, dev->bus->pm, dev->type->pm, or dev->class->pm).
defined in dev->pm_domain->ops, dev->bus->pm, dev->type->pm, dev->class->pm or
These callbacks are regarded by the PM core as mutually exclusive.  Moreover,
dev->driver->pm).  These callbacks are regarded by the PM core as mutually
PM domain callbacks always take precedence over bus, type and class callbacks,
exclusive.  Moreover, PM domain callbacks always take precedence over all of the
while type callbacks take precedence over bus and class callbacks, and class
other callbacks and, for example, type callbacks take precedence over bus, class
callbacks take precedence over bus callbacks.  To be precise, the following
and driver callbacks.  To be precise, the following rules are used to determine
rules are used to determine which callback to execute in the given phase:
which callback to execute in the given phase:


    1.	If dev->pm_domain is present, the PM core will attempt to execute the
    1.	If dev->pm_domain is present, the PM core will choose the callback
	callback included in dev->pm_domain->ops.  If that callback is not
	included in dev->pm_domain->ops for execution
	present, no action will be carried out for the given device.


    2.	Otherwise, if both dev->type and dev->type->pm are present, the callback
    2.	Otherwise, if both dev->type and dev->type->pm are present, the callback
	included in dev->type->pm will be executed.
	included in dev->type->pm will be chosen for execution.


    3.	Otherwise, if both dev->class and dev->class->pm are present, the
    3.	Otherwise, if both dev->class and dev->class->pm are present, the
	callback included in dev->class->pm will be executed.
	callback included in dev->class->pm will be chosen for execution.


    4.	Otherwise, if both dev->bus and dev->bus->pm are present, the callback
    4.	Otherwise, if both dev->bus and dev->bus->pm are present, the callback
	included in dev->bus->pm will be executed.
	included in dev->bus->pm will be chosen for execution.


This allows PM domains and device types to override callbacks provided by bus
This allows PM domains and device types to override callbacks provided by bus
types or device classes if necessary.
types or device classes if necessary.


These callbacks may in turn invoke device- or driver-specific methods stored in
The PM domain, type, class and bus callbacks may in turn invoke device- or
dev->driver->pm, but they don't have to.
driver-specific methods stored in dev->driver->pm, but they don't have to do
that.

If the subsystem callback chosen for execution is not present, the PM core will
execute the corresponding method from dev->driver->pm instead if there is one.




Entering System Suspend
Entering System Suspend
+68 −62
Original line number Original line Diff line number Diff line
@@ -57,6 +57,10 @@ the following:


  4. Bus type of the device, if both dev->bus and dev->bus->pm are present.
  4. Bus type of the device, if both dev->bus and dev->bus->pm are present.


If the subsystem chosen by applying the above rules doesn't provide the relevant
callback, the PM core will invoke the corresponding driver callback stored in
dev->driver->pm directly (if present).

The PM core always checks which callback to use in the order given above, so the
The PM core always checks which callback to use in the order given above, so the
priority order of callbacks from high to low is: PM domain, device type, class
priority order of callbacks from high to low is: PM domain, device type, class
and bus type.  Moreover, the high-priority one will always take precedence over
and bus type.  Moreover, the high-priority one will always take precedence over
@@ -64,86 +68,88 @@ a low-priority one. The PM domain, bus type, device type and class callbacks
are referred to as subsystem-level callbacks in what follows.
are referred to as subsystem-level callbacks in what follows.


By default, the callbacks are always invoked in process context with interrupts
By default, the callbacks are always invoked in process context with interrupts
enabled.  However, subsystems can use the pm_runtime_irq_safe() helper function
enabled.  However, the pm_runtime_irq_safe() helper function can be used to tell
to tell the PM core that their ->runtime_suspend(), ->runtime_resume() and
the PM core that it is safe to run the ->runtime_suspend(), ->runtime_resume()
->runtime_idle() callbacks may be invoked in atomic context with interrupts
and ->runtime_idle() callbacks for the given device in atomic context with
disabled for a given device.  This implies that the callback routines in
interrupts disabled.  This implies that the callback routines in question must
question must not block or sleep, but it also means that the synchronous helper
not block or sleep, but it also means that the synchronous helper functions
functions listed at the end of Section 4 may be used for that device within an
listed at the end of Section 4 may be used for that device within an interrupt
interrupt handler or generally in an atomic context.
handler or generally in an atomic context.


The subsystem-level suspend callback is _entirely_ _responsible_ for handling
The subsystem-level suspend callback, if present, is _entirely_ _responsible_
the suspend of the device as appropriate, which may, but need not include
for handling the suspend of the device as appropriate, which may, but need not
executing the device driver's own ->runtime_suspend() callback (from the
include executing the device driver's own ->runtime_suspend() callback (from the
PM core's point of view it is not necessary to implement a ->runtime_suspend()
PM core's point of view it is not necessary to implement a ->runtime_suspend()
callback in a device driver as long as the subsystem-level suspend callback
callback in a device driver as long as the subsystem-level suspend callback
knows what to do to handle the device).
knows what to do to handle the device).


  * Once the subsystem-level suspend callback has completed successfully
  * Once the subsystem-level suspend callback (or the driver suspend callback,
    for given device, the PM core regards the device as suspended, which need
    if invoked directly) has completed successfully for the given device, the PM
    not mean that the device has been put into a low power state.  It is
    core regards the device as suspended, which need not mean that it has been
    supposed to mean, however, that the device will not process data and will
    put into a low power state.  It is supposed to mean, however, that the
    not communicate with the CPU(s) and RAM until the subsystem-level resume
    device will not process data and will not communicate with the CPU(s) and
    callback is executed for it.  The runtime PM status of a device after
    RAM until the appropriate resume callback is executed for it.  The runtime
    successful execution of the subsystem-level suspend callback is 'suspended'.
    PM status of a device after successful execution of the suspend callback is

    'suspended'.
  * If the subsystem-level suspend callback returns -EBUSY or -EAGAIN,

    the device's runtime PM status is 'active', which means that the device
  * If the suspend callback returns -EBUSY or -EAGAIN, the device's runtime PM
    _must_ be fully operational afterwards.
    status remains 'active', which means that the device _must_ be fully

    operational afterwards.
  * If the subsystem-level suspend callback returns an error code different

    from -EBUSY or -EAGAIN, the PM core regards this as a fatal error and will
  * If the suspend callback returns an error code different from -EBUSY and
    refuse to run the helper functions described in Section 4 for the device,
    -EAGAIN, the PM core regards this as a fatal error and will refuse to run
    until the status of it is directly set either to 'active', or to 'suspended'
    the helper functions described in Section 4 for the device until its status
    (the PM core provides special helper functions for this purpose).
    is directly set to  either'active', or 'suspended' (the PM core provides

    special helper functions for this purpose).
In particular, if the driver requires remote wake-up capability (i.e. hardware

In particular, if the driver requires remote wakeup capability (i.e. hardware
mechanism allowing the device to request a change of its power state, such as
mechanism allowing the device to request a change of its power state, such as
PCI PME) for proper functioning and device_run_wake() returns 'false' for the
PCI PME) for proper functioning and device_run_wake() returns 'false' for the
device, then ->runtime_suspend() should return -EBUSY.  On the other hand, if
device, then ->runtime_suspend() should return -EBUSY.  On the other hand, if
device_run_wake() returns 'true' for the device and the device is put into a low
device_run_wake() returns 'true' for the device and the device is put into a
power state during the execution of the subsystem-level suspend callback, it is
low-power state during the execution of the suspend callback, it is expected
expected that remote wake-up will be enabled for the device.  Generally, remote
that remote wakeup will be enabled for the device.  Generally, remote wakeup
wake-up should be enabled for all input devices put into a low power state at
should be enabled for all input devices put into low-power states at run time.
run time.


The subsystem-level resume callback, if present, is _entirely_ _responsible_ for
The subsystem-level resume callback is _entirely_ _responsible_ for handling the
handling the resume of the device as appropriate, which may, but need not
resume of the device as appropriate, which may, but need not include executing
include executing the device driver's own ->runtime_resume() callback (from the
the device driver's own ->runtime_resume() callback (from the PM core's point of
PM core's point of view it is not necessary to implement a ->runtime_resume()
view it is not necessary to implement a ->runtime_resume() callback in a device
callback in a device driver as long as the subsystem-level resume callback knows
driver as long as the subsystem-level resume callback knows what to do to handle
what to do to handle the device).
the device).


  * Once the subsystem-level resume callback (or the driver resume callback, if
  * Once the subsystem-level resume callback has completed successfully, the PM
    invoked directly) has completed successfully, the PM core regards the device
    core regards the device as fully operational, which means that the device
    as fully operational, which means that the device _must_ be able to complete
    _must_ be able to complete I/O operations as needed.  The runtime PM status
    I/O operations as needed.  The runtime PM status of the device is then
    of the device is then 'active'.
    'active'.


  * If the subsystem-level resume callback returns an error code, the PM core
  * If the resume callback returns an error code, the PM core regards this as a
    regards this as a fatal error and will refuse to run the helper functions
    fatal error and will refuse to run the helper functions described in Section
    described in Section 4 for the device, until its status is directly set
    4 for the device, until its status is directly set to either 'active', or
    either to 'active' or to 'suspended' (the PM core provides special helper
    'suspended' (by means of special helper functions provided by the PM core
    functions for this purpose).
    for this purpose).


The subsystem-level idle callback is executed by the PM core whenever the device
The idle callback (a subsystem-level one, if present, or the driver one) is
appears to be idle, which is indicated to the PM core by two counters, the
executed by the PM core whenever the device appears to be idle, which is
device's usage counter and the counter of 'active' children of the device.
indicated to the PM core by two counters, the device's usage counter and the
counter of 'active' children of the device.


  * If any of these counters is decreased using a helper function provided by
  * If any of these counters is decreased using a helper function provided by
    the PM core and it turns out to be equal to zero, the other counter is
    the PM core and it turns out to be equal to zero, the other counter is
    checked.  If that counter also is equal to zero, the PM core executes the
    checked.  If that counter also is equal to zero, the PM core executes the
    subsystem-level idle callback with the device as an argument.
    idle callback with the device as its argument.


The action performed by a subsystem-level idle callback is totally dependent on
The action performed by the idle callback is totally dependent on the subsystem
the subsystem in question, but the expected and recommended action is to check
(or driver) in question, but the expected and recommended action is to check
if the device can be suspended (i.e. if all of the conditions necessary for
if the device can be suspended (i.e. if all of the conditions necessary for
suspending the device are satisfied) and to queue up a suspend request for the
suspending the device are satisfied) and to queue up a suspend request for the
device in that case.  The value returned by this callback is ignored by the PM
device in that case.  The value returned by this callback is ignored by the PM
core.
core.


The helper functions provided by the PM core, described in Section 4, guarantee
The helper functions provided by the PM core, described in Section 4, guarantee
that the following constraints are met with respect to the bus type's runtime
that the following constraints are met with respect to runtime PM callbacks for
PM callbacks:
one device:


(1) The callbacks are mutually exclusive (e.g. it is forbidden to execute
(1) The callbacks are mutually exclusive (e.g. it is forbidden to execute
    ->runtime_suspend() in parallel with ->runtime_resume() or with another
    ->runtime_suspend() in parallel with ->runtime_resume() or with another
+72 −37
Original line number Original line Diff line number Diff line
@@ -383,10 +383,15 @@ static int device_resume_noirq(struct device *dev, pm_message_t state)
		info = "EARLY class ";
		info = "EARLY class ";
		callback = pm_noirq_op(dev->class->pm, state);
		callback = pm_noirq_op(dev->class->pm, state);
	} else if (dev->bus && dev->bus->pm) {
	} else if (dev->bus && dev->bus->pm) {
		info = "EARLY ";
		info = "EARLY bus ";
		callback = pm_noirq_op(dev->bus->pm, state);
		callback = pm_noirq_op(dev->bus->pm, state);
	}
	}


	if (!callback && dev->driver && dev->driver->pm) {
		info = "EARLY driver ";
		callback = pm_noirq_op(dev->driver->pm, state);
	}

	error = dpm_run_callback(callback, dev, state, info);
	error = dpm_run_callback(callback, dev, state, info);


	TRACE_RESUME(error);
	TRACE_RESUME(error);
@@ -464,20 +469,20 @@ static int device_resume(struct device *dev, pm_message_t state, bool async)
	if (dev->pm_domain) {
	if (dev->pm_domain) {
		info = "power domain ";
		info = "power domain ";
		callback = pm_op(&dev->pm_domain->ops, state);
		callback = pm_op(&dev->pm_domain->ops, state);
		goto End;
		goto Driver;
	}
	}


	if (dev->type && dev->type->pm) {
	if (dev->type && dev->type->pm) {
		info = "type ";
		info = "type ";
		callback = pm_op(dev->type->pm, state);
		callback = pm_op(dev->type->pm, state);
		goto End;
		goto Driver;
	}
	}


	if (dev->class) {
	if (dev->class) {
		if (dev->class->pm) {
		if (dev->class->pm) {
			info = "class ";
			info = "class ";
			callback = pm_op(dev->class->pm, state);
			callback = pm_op(dev->class->pm, state);
			goto End;
			goto Driver;
		} else if (dev->class->resume) {
		} else if (dev->class->resume) {
			info = "legacy class ";
			info = "legacy class ";
			callback = dev->class->resume;
			callback = dev->class->resume;
@@ -487,14 +492,21 @@ static int device_resume(struct device *dev, pm_message_t state, bool async)


	if (dev->bus) {
	if (dev->bus) {
		if (dev->bus->pm) {
		if (dev->bus->pm) {
			info = "";
			info = "bus ";
			callback = pm_op(dev->bus->pm, state);
			callback = pm_op(dev->bus->pm, state);
		} else if (dev->bus->resume) {
		} else if (dev->bus->resume) {
			info = "legacy ";
			info = "legacy bus ";
			callback = dev->bus->resume;
			callback = dev->bus->resume;
			goto End;
		}
		}
	}
	}


 Driver:
	if (!callback && dev->driver && dev->driver->pm) {
		info = "driver ";
		callback = pm_op(dev->driver->pm, state);
	}

 End:
 End:
	error = dpm_run_callback(callback, dev, state, info);
	error = dpm_run_callback(callback, dev, state, info);
	dev->power.is_suspended = false;
	dev->power.is_suspended = false;
@@ -588,24 +600,33 @@ void dpm_resume(pm_message_t state)
 */
 */
static void device_complete(struct device *dev, pm_message_t state)
static void device_complete(struct device *dev, pm_message_t state)
{
{
	void (*callback)(struct device *) = NULL;
	char *info = NULL;

	device_lock(dev);
	device_lock(dev);


	if (dev->pm_domain) {
	if (dev->pm_domain) {
		pm_dev_dbg(dev, state, "completing power domain ");
		info = "completing power domain ";
		if (dev->pm_domain->ops.complete)
		callback = dev->pm_domain->ops.complete;
			dev->pm_domain->ops.complete(dev);
	} else if (dev->type && dev->type->pm) {
	} else if (dev->type && dev->type->pm) {
		pm_dev_dbg(dev, state, "completing type ");
		info = "completing type ";
		if (dev->type->pm->complete)
		callback = dev->type->pm->complete;
			dev->type->pm->complete(dev);
	} else if (dev->class && dev->class->pm) {
	} else if (dev->class && dev->class->pm) {
		pm_dev_dbg(dev, state, "completing class ");
		info = "completing class ";
		if (dev->class->pm->complete)
		callback = dev->class->pm->complete;
			dev->class->pm->complete(dev);
	} else if (dev->bus && dev->bus->pm) {
	} else if (dev->bus && dev->bus->pm) {
		pm_dev_dbg(dev, state, "completing ");
		info = "completing bus ";
		if (dev->bus->pm->complete)
		callback = dev->bus->pm->complete;
			dev->bus->pm->complete(dev);
	}

	if (!callback && dev->driver && dev->driver->pm) {
		info = "completing driver ";
		callback = dev->driver->pm->complete;
	}

	if (callback) {
		pm_dev_dbg(dev, state, info);
		callback(dev);
	}
	}


	device_unlock(dev);
	device_unlock(dev);
@@ -704,10 +725,15 @@ static int device_suspend_noirq(struct device *dev, pm_message_t state)
		info = "LATE class ";
		info = "LATE class ";
		callback = pm_noirq_op(dev->class->pm, state);
		callback = pm_noirq_op(dev->class->pm, state);
	} else if (dev->bus && dev->bus->pm) {
	} else if (dev->bus && dev->bus->pm) {
		info = "LATE ";
		info = "LATE bus ";
		callback = pm_noirq_op(dev->bus->pm, state);
		callback = pm_noirq_op(dev->bus->pm, state);
	}
	}


	if (!callback && dev->driver && dev->driver->pm) {
		info = "LATE driver ";
		callback = pm_noirq_op(dev->driver->pm, state);
	}

	return dpm_run_callback(callback, dev, state, info);
	return dpm_run_callback(callback, dev, state, info);
}
}


@@ -832,16 +858,21 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async)


	if (dev->bus) {
	if (dev->bus) {
		if (dev->bus->pm) {
		if (dev->bus->pm) {
			info = "";
			info = "bus ";
			callback = pm_op(dev->bus->pm, state);
			callback = pm_op(dev->bus->pm, state);
		} else if (dev->bus->suspend) {
		} else if (dev->bus->suspend) {
			pm_dev_dbg(dev, state, "legacy ");
			pm_dev_dbg(dev, state, "legacy bus ");
			error = legacy_suspend(dev, state, dev->bus->suspend);
			error = legacy_suspend(dev, state, dev->bus->suspend);
			goto End;
			goto End;
		}
		}
	}
	}


 Run:
 Run:
	if (!callback && dev->driver && dev->driver->pm) {
		info = "driver ";
		callback = pm_op(dev->driver->pm, state);
	}

	error = dpm_run_callback(callback, dev, state, info);
	error = dpm_run_callback(callback, dev, state, info);


 End:
 End:
@@ -949,6 +980,8 @@ int dpm_suspend(pm_message_t state)
 */
 */
static int device_prepare(struct device *dev, pm_message_t state)
static int device_prepare(struct device *dev, pm_message_t state)
{
{
	int (*callback)(struct device *) = NULL;
	char *info = NULL;
	int error = 0;
	int error = 0;


	device_lock(dev);
	device_lock(dev);
@@ -956,25 +989,27 @@ static int device_prepare(struct device *dev, pm_message_t state)
	dev->power.wakeup_path = device_may_wakeup(dev);
	dev->power.wakeup_path = device_may_wakeup(dev);


	if (dev->pm_domain) {
	if (dev->pm_domain) {
		pm_dev_dbg(dev, state, "preparing power domain ");
		info = "preparing power domain ";
		if (dev->pm_domain->ops.prepare)
		callback = dev->pm_domain->ops.prepare;
			error = dev->pm_domain->ops.prepare(dev);
		suspend_report_result(dev->pm_domain->ops.prepare, error);
	} else if (dev->type && dev->type->pm) {
	} else if (dev->type && dev->type->pm) {
		pm_dev_dbg(dev, state, "preparing type ");
		info = "preparing type ";
		if (dev->type->pm->prepare)
		callback = dev->type->pm->prepare;
			error = dev->type->pm->prepare(dev);
		suspend_report_result(dev->type->pm->prepare, error);
	} else if (dev->class && dev->class->pm) {
	} else if (dev->class && dev->class->pm) {
		pm_dev_dbg(dev, state, "preparing class ");
		info = "preparing class ";
		if (dev->class->pm->prepare)
		callback = dev->class->pm->prepare;
			error = dev->class->pm->prepare(dev);
		suspend_report_result(dev->class->pm->prepare, error);
	} else if (dev->bus && dev->bus->pm) {
	} else if (dev->bus && dev->bus->pm) {
		pm_dev_dbg(dev, state, "preparing ");
		info = "preparing bus ";
		if (dev->bus->pm->prepare)
		callback = dev->bus->pm->prepare;
			error = dev->bus->pm->prepare(dev);
	}
		suspend_report_result(dev->bus->pm->prepare, error);

	if (!callback && dev->driver && dev->driver->pm) {
		info = "preparing driver ";
		callback = dev->driver->pm->prepare;
	}

	if (callback) {
		error = callback(dev);
		suspend_report_result(callback, error);
	}
	}


	device_unlock(dev);
	device_unlock(dev);
+9 −0
Original line number Original line Diff line number Diff line
@@ -250,6 +250,9 @@ static int rpm_idle(struct device *dev, int rpmflags)
	else
	else
		callback = NULL;
		callback = NULL;


	if (!callback && dev->driver && dev->driver->pm)
		callback = dev->driver->pm->runtime_idle;

	if (callback)
	if (callback)
		__rpm_callback(callback, dev);
		__rpm_callback(callback, dev);


@@ -413,6 +416,9 @@ static int rpm_suspend(struct device *dev, int rpmflags)
	else
	else
		callback = NULL;
		callback = NULL;


	if (!callback && dev->driver && dev->driver->pm)
		callback = dev->driver->pm->runtime_suspend;

	retval = rpm_callback(callback, dev);
	retval = rpm_callback(callback, dev);
	if (retval) {
	if (retval) {
		__update_runtime_status(dev, RPM_ACTIVE);
		__update_runtime_status(dev, RPM_ACTIVE);
@@ -633,6 +639,9 @@ static int rpm_resume(struct device *dev, int rpmflags)
	else
	else
		callback = NULL;
		callback = NULL;


	if (!callback && dev->driver && dev->driver->pm)
		callback = dev->driver->pm->runtime_resume;

	retval = rpm_callback(callback, dev);
	retval = rpm_callback(callback, dev);
	if (retval) {
	if (retval) {
		__update_runtime_status(dev, RPM_SUSPENDED);
		__update_runtime_status(dev, RPM_SUSPENDED);