Documentation/core-api/device_link: Add initial documentation
Document device links as introduced in v4.10 with commits:4bdb35506b
("driver core: Add a wrapper around __device_release_driver()")9ed9895370
("driver core: Functional dependencies tracking support")8c73b42884
("PM / sleep: Make async suspend/resume of devices use device links")21d5c57b37
("PM / runtime: Use device links")baa8809f60
("PM / runtime: Optimize the use of device links") Signed-off-by: Lukas Wunner <lukas@wunner.de> [ jc: Moved from core-api to driver-api ] Signed-off-by: Jonathan Corbet <corbet@lwn.net>
This commit is contained in:
parent
2ba90ccca7
commit
aad800403a
279
Documentation/driver-api/device_link.rst
Normal file
279
Documentation/driver-api/device_link.rst
Normal file
|
@ -0,0 +1,279 @@
|
|||
============
|
||||
Device links
|
||||
============
|
||||
|
||||
By default, the driver core only enforces dependencies between devices
|
||||
that are borne out of a parent/child relationship within the device
|
||||
hierarchy: When suspending, resuming or shutting down the system, devices
|
||||
are ordered based on this relationship, i.e. children are always suspended
|
||||
before their parent, and the parent is always resumed before its children.
|
||||
|
||||
Sometimes there is a need to represent device dependencies beyond the
|
||||
mere parent/child relationship, e.g. between siblings, and have the
|
||||
driver core automatically take care of them.
|
||||
|
||||
Secondly, the driver core by default does not enforce any driver presence
|
||||
dependencies, i.e. that one device must be bound to a driver before
|
||||
another one can probe or function correctly.
|
||||
|
||||
Often these two dependency types come together, so a device depends on
|
||||
another one both with regards to driver presence *and* with regards to
|
||||
suspend/resume and shutdown ordering.
|
||||
|
||||
Device links allow representation of such dependencies in the driver core.
|
||||
|
||||
In its standard form, a device link combines *both* dependency types:
|
||||
It guarantees correct suspend/resume and shutdown ordering between a
|
||||
"supplier" device and its "consumer" devices, and it guarantees driver
|
||||
presence on the supplier. The consumer devices are not probed before the
|
||||
supplier is bound to a driver, and they're unbound before the supplier
|
||||
is unbound.
|
||||
|
||||
When driver presence on the supplier is irrelevant and only correct
|
||||
suspend/resume and shutdown ordering is needed, the device link may
|
||||
simply be set up with the ``DL_FLAG_STATELESS`` flag. In other words,
|
||||
enforcing driver presence on the supplier is optional.
|
||||
|
||||
Another optional feature is runtime PM integration: By setting the
|
||||
``DL_FLAG_PM_RUNTIME`` flag on addition of the device link, the PM core
|
||||
is instructed to runtime resume the supplier and keep it active
|
||||
whenever and for as long as the consumer is runtime resumed.
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
The earliest point in time when device links can be added is after
|
||||
:c:func:`device_add()` has been called for the supplier and
|
||||
:c:func:`device_initialize()` has been called for the consumer.
|
||||
|
||||
It is legal to add them later, but care must be taken that the system
|
||||
remains in a consistent state: E.g. a device link cannot be added in
|
||||
the midst of a suspend/resume transition, so either commencement of
|
||||
such a transition needs to be prevented with :c:func:`lock_system_sleep()`,
|
||||
or the device link needs to be added from a function which is guaranteed
|
||||
not to run in parallel to a suspend/resume transition, such as from a
|
||||
device ``->probe`` callback or a boot-time PCI quirk.
|
||||
|
||||
Another example for an inconsistent state would be a device link that
|
||||
represents a driver presence dependency, yet is added from the consumer's
|
||||
``->probe`` callback while the supplier hasn't probed yet: Had the driver
|
||||
core known about the device link earlier, it wouldn't have probed the
|
||||
consumer in the first place. The onus is thus on the consumer to check
|
||||
presence of the supplier after adding the link, and defer probing on
|
||||
non-presence.
|
||||
|
||||
If a device link is added in the ``->probe`` callback of the supplier or
|
||||
consumer driver, it is typically deleted in its ``->remove`` callback for
|
||||
symmetry. That way, if the driver is compiled as a module, the device
|
||||
link is added on module load and orderly deleted on unload. The same
|
||||
restrictions that apply to device link addition (e.g. exclusion of a
|
||||
parallel suspend/resume transition) apply equally to deletion.
|
||||
|
||||
Several flags may be specified on device link addition, two of which
|
||||
have already been mentioned above: ``DL_FLAG_STATELESS`` to express that no
|
||||
driver presence dependency is needed (but only correct suspend/resume and
|
||||
shutdown ordering) and ``DL_FLAG_PM_RUNTIME`` to express that runtime PM
|
||||
integration is desired.
|
||||
|
||||
Two other flags are specifically targeted at use cases where the device
|
||||
link is added from the consumer's ``->probe`` callback: ``DL_FLAG_RPM_ACTIVE``
|
||||
can be specified to runtime resume the supplier upon addition of the
|
||||
device link. ``DL_FLAG_AUTOREMOVE`` causes the device link to be automatically
|
||||
purged when the consumer fails to probe or later unbinds. This obviates
|
||||
the need to explicitly delete the link in the ``->remove`` callback or in
|
||||
the error path of the ``->probe`` callback.
|
||||
|
||||
Limitations
|
||||
===========
|
||||
|
||||
Driver authors should be aware that a driver presence dependency (i.e. when
|
||||
``DL_FLAG_STATELESS`` is not specified on link addition) may cause probing of
|
||||
the consumer to be deferred indefinitely. This can become a problem if the
|
||||
consumer is required to probe before a certain initcall level is reached.
|
||||
Worse, if the supplier driver is blacklisted or missing, the consumer will
|
||||
never be probed.
|
||||
|
||||
Sometimes drivers depend on optional resources. They are able to operate
|
||||
in a degraded mode (reduced feature set or performance) when those resources
|
||||
are not present. An example is an SPI controller that can use a DMA engine
|
||||
or work in PIO mode. The controller can determine presence of the optional
|
||||
resources at probe time but on non-presence there is no way to know whether
|
||||
they will become available in the near future (due to a supplier driver
|
||||
probing) or never. Consequently it cannot be determined whether to defer
|
||||
probing or not. It would be possible to notify drivers when optional
|
||||
resources become available after probing, but it would come at a high cost
|
||||
for drivers as switching between modes of operation at runtime based on the
|
||||
availability of such resources would be much more complex than a mechanism
|
||||
based on probe deferral. In any case optional resources are beyond the
|
||||
scope of device links.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
* An MMU device exists alongside a busmaster device, both are in the same
|
||||
power domain. The MMU implements DMA address translation for the busmaster
|
||||
device and shall be runtime resumed and kept active whenever and as long
|
||||
as the busmaster device is active. The busmaster device's driver shall
|
||||
not bind before the MMU is bound. To achieve this, a device link with
|
||||
runtime PM integration is added from the busmaster device (consumer)
|
||||
to the MMU device (supplier). The effect with regards to runtime PM
|
||||
is the same as if the MMU was the parent of the master device.
|
||||
|
||||
The fact that both devices share the same power domain would normally
|
||||
suggest usage of a :c:type:`struct dev_pm_domain` or :c:type:`struct
|
||||
generic_pm_domain`, however these are not independent devices that
|
||||
happen to share a power switch, but rather the MMU device serves the
|
||||
busmaster device and is useless without it. A device link creates a
|
||||
synthetic hierarchical relationship between the devices and is thus
|
||||
more apt.
|
||||
|
||||
* A Thunderbolt host controller comprises a number of PCIe hotplug ports
|
||||
and an NHI device to manage the PCIe switch. On resume from system sleep,
|
||||
the NHI device needs to re-establish PCI tunnels to attached devices
|
||||
before the hotplug ports can resume. If the hotplug ports were children
|
||||
of the NHI, this resume order would automatically be enforced by the
|
||||
PM core, but unfortunately they're aunts. The solution is to add
|
||||
device links from the hotplug ports (consumers) to the NHI device
|
||||
(supplier). A driver presence dependency is not necessary for this
|
||||
use case.
|
||||
|
||||
* Discrete GPUs in hybrid graphics laptops often feature an HDA controller
|
||||
for HDMI/DP audio. In the device hierarchy the HDA controller is a sibling
|
||||
of the VGA device, yet both share the same power domain and the HDA
|
||||
controller is only ever needed when an HDMI/DP display is attached to the
|
||||
VGA device. A device link from the HDA controller (consumer) to the
|
||||
VGA device (supplier) aptly represents this relationship.
|
||||
|
||||
* ACPI allows definition of a device start order by way of _DEP objects.
|
||||
A classical example is when ACPI power management methods on one device
|
||||
are implemented in terms of I\ :sup:`2`\ C accesses and require a specific
|
||||
I\ :sup:`2`\ C controller to be present and functional for the power
|
||||
management of the device in question to work.
|
||||
|
||||
* In some SoCs a functional dependency exists from display, video codec and
|
||||
video processing IP cores on transparent memory access IP cores that handle
|
||||
burst access and compression/decompression.
|
||||
|
||||
Alternatives
|
||||
============
|
||||
|
||||
* A :c:type:`struct dev_pm_domain` can be used to override the bus,
|
||||
class or device type callbacks. It is intended for devices sharing
|
||||
a single on/off switch, however it does not guarantee a specific
|
||||
suspend/resume ordering, this needs to be implemented separately.
|
||||
It also does not by itself track the runtime PM status of the involved
|
||||
devices and turn off the power switch only when all of them are runtime
|
||||
suspended. Furthermore it cannot be used to enforce a specific shutdown
|
||||
ordering or a driver presence dependency.
|
||||
|
||||
* A :c:type:`struct generic_pm_domain` is a lot more heavyweight than a
|
||||
device link and does not allow for shutdown ordering or driver presence
|
||||
dependencies. It also cannot be used on ACPI systems.
|
||||
|
||||
Implementation
|
||||
==============
|
||||
|
||||
The device hierarchy, which -- as the name implies -- is a tree,
|
||||
becomes a directed acyclic graph once device links are added.
|
||||
|
||||
Ordering of these devices during suspend/resume is determined by the
|
||||
dpm_list. During shutdown it is determined by the devices_kset. With
|
||||
no device links present, the two lists are a flattened, one-dimensional
|
||||
representations of the device tree such that a device is placed behind
|
||||
all its ancestors. That is achieved by traversing the ACPI namespace
|
||||
or OpenFirmware device tree top-down and appending devices to the lists
|
||||
as they are discovered.
|
||||
|
||||
Once device links are added, the lists need to satisfy the additional
|
||||
constraint that a device is placed behind all its suppliers, recursively.
|
||||
To ensure this, upon addition of the device link the consumer and the
|
||||
entire sub-graph below it (all children and consumers of the consumer)
|
||||
are moved to the end of the list. (Call to :c:func:`device_reorder_to_tail()`
|
||||
from :c:func:`device_link_add()`.)
|
||||
|
||||
To prevent introduction of dependency loops into the graph, it is
|
||||
verified upon device link addition that the supplier is not dependent
|
||||
on the consumer or any children or consumers of the consumer.
|
||||
(Call to :c:func:`device_is_dependent()` from :c:func:`device_link_add()`.)
|
||||
If that constraint is violated, :c:func:`device_link_add()` will return
|
||||
``NULL`` and a ``WARNING`` will be logged.
|
||||
|
||||
Notably this also prevents the addition of a device link from a parent
|
||||
device to a child. However the converse is allowed, i.e. a device link
|
||||
from a child to a parent. Since the driver core already guarantees
|
||||
correct suspend/resume and shutdown ordering between parent and child,
|
||||
such a device link only makes sense if a driver presence dependency is
|
||||
needed on top of that. In this case driver authors should weigh
|
||||
carefully if a device link is at all the right tool for the purpose.
|
||||
A more suitable approach might be to simply use deferred probing or
|
||||
add a device flag causing the parent driver to be probed before the
|
||||
child one.
|
||||
|
||||
State machine
|
||||
=============
|
||||
|
||||
.. kernel-doc:: include/linux/device.h
|
||||
:functions: device_link_state
|
||||
|
||||
::
|
||||
|
||||
.=============================.
|
||||
| |
|
||||
v |
|
||||
DORMANT <=> AVAILABLE <=> CONSUMER_PROBE => ACTIVE
|
||||
^ |
|
||||
| |
|
||||
'============ SUPPLIER_UNBIND <============'
|
||||
|
||||
* The initial state of a device link is automatically determined by
|
||||
:c:func:`device_link_add()` based on the driver presence on the supplier
|
||||
and consumer. If the link is created before any devices are probed, it
|
||||
is set to ``DL_STATE_DORMANT``.
|
||||
|
||||
* When a supplier device is bound to a driver, links to its consumers
|
||||
progress to ``DL_STATE_AVAILABLE``.
|
||||
(Call to :c:func:`device_links_driver_bound()` from
|
||||
:c:func:`driver_bound()`.)
|
||||
|
||||
* Before a consumer device is probed, presence of supplier drivers is
|
||||
verified by checking that links to suppliers are in ``DL_STATE_AVAILABLE``
|
||||
state. The state of the links is updated to ``DL_STATE_CONSUMER_PROBE``.
|
||||
(Call to :c:func:`device_links_check_suppliers()` from
|
||||
:c:func:`really_probe()`.)
|
||||
This prevents the supplier from unbinding.
|
||||
(Call to :c:func:`wait_for_device_probe()` from
|
||||
:c:func:`device_links_unbind_consumers()`.)
|
||||
|
||||
* If the probe fails, links to suppliers revert back to ``DL_STATE_AVAILABLE``.
|
||||
(Call to :c:func:`device_links_no_driver()` from :c:func:`really_probe()`.)
|
||||
|
||||
* If the probe succeeds, links to suppliers progress to ``DL_STATE_ACTIVE``.
|
||||
(Call to :c:func:`device_links_driver_bound()` from :c:func:`driver_bound()`.)
|
||||
|
||||
* When the consumer's driver is later on removed, links to suppliers revert
|
||||
back to ``DL_STATE_AVAILABLE``.
|
||||
(Call to :c:func:`__device_links_no_driver()` from
|
||||
:c:func:`device_links_driver_cleanup()`, which in turn is called from
|
||||
:c:func:`__device_release_driver()`.)
|
||||
|
||||
* Before a supplier's driver is removed, links to consumers that are not
|
||||
bound to a driver are updated to ``DL_STATE_SUPPLIER_UNBIND``.
|
||||
(Call to :c:func:`device_links_busy()` from
|
||||
:c:func:`__device_release_driver()`.)
|
||||
This prevents the consumers from binding.
|
||||
(Call to :c:func:`device_links_check_suppliers()` from
|
||||
:c:func:`really_probe()`.)
|
||||
Consumers that are bound are freed from their driver; consumers that are
|
||||
probing are waited for until they are done.
|
||||
(Call to :c:func:`device_links_unbind_consumers()` from
|
||||
:c:func:`__device_release_driver()`.)
|
||||
Once all links to consumers are in ``DL_STATE_SUPPLIER_UNBIND`` state,
|
||||
the supplier driver is released and the links revert to ``DL_STATE_DORMANT``.
|
||||
(Call to :c:func:`device_links_driver_cleanup()` from
|
||||
:c:func:`__device_release_driver()`.)
|
||||
|
||||
API
|
||||
===
|
||||
|
||||
.. kernel-doc:: drivers/base/core.c
|
||||
:functions: device_link_add device_link_del
|
|
@ -16,6 +16,7 @@ available subsections can be seen below.
|
|||
|
||||
basics
|
||||
infrastructure
|
||||
device_link
|
||||
message-based
|
||||
sound
|
||||
frame-buffer
|
||||
|
|
Loading…
Reference in a new issue