driver core: Add support for linking devices during device addition

The firmware corresponding to a device (dev.fwnode) might be able to
provide functional dependency information between a device and its
supplier and consumer devices.  Tracking this functional dependency
allows optimizing device probe order and informing a supplier when all
its consumers have probed (and thereby actively managing their
resources).

The existing device links feature allows tracking and using
supplier-consumer relationships. So, this patch adds the add_links()
fwnode callback to allow firmware to create device links for each
device as the device is added.

However, when consumer devices are added, they might not have a supplier
device to link to despite needing mandatory resources/functionality from
one or more suppliers. A waiting_for_suppliers list is created to track
such consumers and retry linking them when new devices get added.

Signed-off-by: Saravana Kannan <saravanak@google.com>
Link: https://lore.kernel.org/r/20190904211126.47518-3-saravanak@google.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Saravana Kannan 2019-09-04 14:11:21 -07:00 коммит произвёл Greg Kroah-Hartman
Родитель 372a67c0c5
Коммит e2ae9bcc4a
4 изменённых файлов: 109 добавлений и 1 удалений

Просмотреть файл

@ -281,7 +281,8 @@ State machine
:c:func:`driver_bound()`.) :c:func:`driver_bound()`.)
* Before a consumer device is probed, presence of supplier drivers is * Before a consumer device is probed, presence of supplier drivers is
verified by checking that links to suppliers are in ``DL_STATE_AVAILABLE`` verified by checking the consumer device is not in the wait_for_suppliers
list and by checking that links to suppliers are in ``DL_STATE_AVAILABLE``
state. The state of the links is updated to ``DL_STATE_CONSUMER_PROBE``. state. The state of the links is updated to ``DL_STATE_CONSUMER_PROBE``.
(Call to :c:func:`device_links_check_suppliers()` from (Call to :c:func:`device_links_check_suppliers()` from
:c:func:`really_probe()`.) :c:func:`really_probe()`.)

Просмотреть файл

@ -44,6 +44,8 @@ early_param("sysfs.deprecated", sysfs_deprecated_setup);
#endif #endif
/* Device links support. */ /* Device links support. */
static LIST_HEAD(wait_for_suppliers);
static DEFINE_MUTEX(wfs_lock);
#ifdef CONFIG_SRCU #ifdef CONFIG_SRCU
static DEFINE_MUTEX(device_links_lock); static DEFINE_MUTEX(device_links_lock);
@ -430,6 +432,58 @@ struct device_link *device_link_add(struct device *consumer,
} }
EXPORT_SYMBOL_GPL(device_link_add); EXPORT_SYMBOL_GPL(device_link_add);
/**
* device_link_wait_for_supplier - Add device to wait_for_suppliers list
* @consumer: Consumer device
*
* Marks the @consumer device as waiting for suppliers to become available by
* adding it to the wait_for_suppliers list. The consumer device will never be
* probed until it's removed from the wait_for_suppliers list.
*
* The caller is responsible for adding the links to the supplier devices once
* they are available and removing the @consumer device from the
* wait_for_suppliers list once links to all the suppliers have been created.
*
* This function is NOT meant to be called from the probe function of the
* consumer but rather from code that creates/adds the consumer device.
*/
static void device_link_wait_for_supplier(struct device *consumer)
{
mutex_lock(&wfs_lock);
list_add_tail(&consumer->links.needs_suppliers, &wait_for_suppliers);
mutex_unlock(&wfs_lock);
}
/**
* device_link_add_missing_supplier_links - Add links from consumer devices to
* supplier devices, leaving any
* consumer with inactive suppliers on
* the wait_for_suppliers list
*
* Loops through all consumers waiting on suppliers and tries to add all their
* supplier links. If that succeeds, the consumer device is removed from
* wait_for_suppliers list. Otherwise, they are left in the wait_for_suppliers
* list. Devices left on the wait_for_suppliers list will not be probed.
*
* The fwnode add_links callback is expected to return 0 if it has found and
* added all the supplier links for the consumer device. It should return an
* error if it isn't able to do so.
*
* The caller of device_link_wait_for_supplier() is expected to call this once
* it's aware of potential suppliers becoming available.
*/
static void device_link_add_missing_supplier_links(void)
{
struct device *dev, *tmp;
mutex_lock(&wfs_lock);
list_for_each_entry_safe(dev, tmp, &wait_for_suppliers,
links.needs_suppliers)
if (!fwnode_call_int_op(dev->fwnode, add_links, dev))
list_del_init(&dev->links.needs_suppliers);
mutex_unlock(&wfs_lock);
}
static void device_link_free(struct device_link *link) static void device_link_free(struct device_link *link)
{ {
while (refcount_dec_not_one(&link->rpm_active)) while (refcount_dec_not_one(&link->rpm_active))
@ -564,6 +618,17 @@ int device_links_check_suppliers(struct device *dev)
struct device_link *link; struct device_link *link;
int ret = 0; int ret = 0;
/*
* Device waiting for supplier to become available is not allowed to
* probe.
*/
mutex_lock(&wfs_lock);
if (!list_empty(&dev->links.needs_suppliers)) {
mutex_unlock(&wfs_lock);
return -EPROBE_DEFER;
}
mutex_unlock(&wfs_lock);
device_links_write_lock(); device_links_write_lock();
list_for_each_entry(link, &dev->links.suppliers, c_node) { list_for_each_entry(link, &dev->links.suppliers, c_node) {
@ -848,6 +913,10 @@ static void device_links_purge(struct device *dev)
{ {
struct device_link *link, *ln; struct device_link *link, *ln;
mutex_lock(&wfs_lock);
list_del(&dev->links.needs_suppliers);
mutex_unlock(&wfs_lock);
/* /*
* Delete all of the remaining links from this device to any other * Delete all of the remaining links from this device to any other
* devices (either consumers or suppliers). * devices (either consumers or suppliers).
@ -1712,6 +1781,7 @@ void device_initialize(struct device *dev)
#endif #endif
INIT_LIST_HEAD(&dev->links.consumers); INIT_LIST_HEAD(&dev->links.consumers);
INIT_LIST_HEAD(&dev->links.suppliers); INIT_LIST_HEAD(&dev->links.suppliers);
INIT_LIST_HEAD(&dev->links.needs_suppliers);
dev->links.status = DL_DEV_NO_DRIVER; dev->links.status = DL_DEV_NO_DRIVER;
} }
EXPORT_SYMBOL_GPL(device_initialize); EXPORT_SYMBOL_GPL(device_initialize);
@ -2202,6 +2272,24 @@ int device_add(struct device *dev)
if (dev->fwnode && !dev->fwnode->dev) if (dev->fwnode && !dev->fwnode->dev)
dev->fwnode->dev = dev; dev->fwnode->dev = dev;
/*
* Check if any of the other devices (consumers) have been waiting for
* this device (supplier) to be added so that they can create a device
* link to it.
*
* This needs to happen after device_pm_add() because device_link_add()
* requires the supplier be registered before it's called.
*
* But this also needs to happe before bus_probe_device() to make sure
* waiting consumers can link to it before the driver is bound to the
* device and the driver sync_state callback is called for this device.
*/
device_link_add_missing_supplier_links();
if (fwnode_has_op(dev->fwnode, add_links)
&& fwnode_call_int_op(dev->fwnode, add_links, dev))
device_link_wait_for_supplier(dev);
bus_probe_device(dev); bus_probe_device(dev);
if (parent) if (parent)
klist_add_tail(&dev->p->knode_parent, klist_add_tail(&dev->p->knode_parent,

Просмотреть файл

@ -1135,11 +1135,13 @@ enum dl_dev_state {
* struct dev_links_info - Device data related to device links. * struct dev_links_info - Device data related to device links.
* @suppliers: List of links to supplier devices. * @suppliers: List of links to supplier devices.
* @consumers: List of links to consumer devices. * @consumers: List of links to consumer devices.
* @needs_suppliers: Hook to global list of devices waiting for suppliers.
* @status: Driver status information. * @status: Driver status information.
*/ */
struct dev_links_info { struct dev_links_info {
struct list_head suppliers; struct list_head suppliers;
struct list_head consumers; struct list_head consumers;
struct list_head needs_suppliers;
enum dl_dev_state status; enum dl_dev_state status;
}; };

Просмотреть файл

@ -66,6 +66,21 @@ struct fwnode_reference_args {
* endpoint node. * endpoint node.
* @graph_get_port_parent: Return the parent node of a port node. * @graph_get_port_parent: Return the parent node of a port node.
* @graph_parse_endpoint: Parse endpoint for port and endpoint id. * @graph_parse_endpoint: Parse endpoint for port and endpoint id.
* @add_links: Called after the device corresponding to the fwnode is added
* using device_add(). The function is expected to create device
* links to all the suppliers of the device that are available at
* the time this function is called. The function must NOT stop
* at the first failed device link if other unlinked supplier
* devices are present in the system. If some suppliers are not
* yet available, this function will be called again when other
* devices are added to allow creating device links to any newly
* available suppliers.
*
* Return 0 if device links have been successfully created to all
* the suppliers of this device or if the supplier information is
* not known. Return an error if and only if the supplier
* information is known but some of the suppliers are not yet
* available to create device links to.
*/ */
struct fwnode_operations { struct fwnode_operations {
struct fwnode_handle *(*get)(struct fwnode_handle *fwnode); struct fwnode_handle *(*get)(struct fwnode_handle *fwnode);
@ -103,6 +118,8 @@ struct fwnode_operations {
(*graph_get_port_parent)(struct fwnode_handle *fwnode); (*graph_get_port_parent)(struct fwnode_handle *fwnode);
int (*graph_parse_endpoint)(const struct fwnode_handle *fwnode, int (*graph_parse_endpoint)(const struct fwnode_handle *fwnode,
struct fwnode_endpoint *endpoint); struct fwnode_endpoint *endpoint);
int (*add_links)(const struct fwnode_handle *fwnode,
struct device *dev);
}; };
#define fwnode_has_op(fwnode, op) \ #define fwnode_has_op(fwnode, op) \