| From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 |
| From: Saravana Kannan <saravanak@google.com> |
| Date: Wed, 4 Sep 2019 14:11:21 -0700 |
| Subject: FROMGIT: 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> |
| |
| (cherry-picked from commit e2ae9bcc4aaacda04edb75c4eea93384719efaa5 |
| https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/driver-core.git driver-core-next) |
| |
| Bug: 141703324 |
| Change-Id: I97ffa57fa71588bf198e78d8b5a313b860b17bf5 |
| --- |
| Documentation/driver-api/device_link.rst | 3 +- |
| drivers/base/core.c | 88 ++++++++++++++++++++++++ |
| include/linux/device.h | 2 + |
| include/linux/fwnode.h | 17 +++++ |
| 4 files changed, 109 insertions(+), 1 deletion(-) |
| |
| diff --git a/Documentation/driver-api/device_link.rst b/Documentation/driver-api/device_link.rst |
| index 1b5020ec6517..bc2d89af88ce 100644 |
| --- a/Documentation/driver-api/device_link.rst |
| +++ b/Documentation/driver-api/device_link.rst |
| @@ -281,7 +281,8 @@ State machine |
| :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`` |
| + 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``. |
| (Call to :c:func:`device_links_check_suppliers()` from |
| :c:func:`really_probe()`.) |
| diff --git a/drivers/base/core.c b/drivers/base/core.c |
| index 2cb393bd8b6b..e54fef73f469 100644 |
| --- a/drivers/base/core.c |
| +++ b/drivers/base/core.c |
| @@ -45,6 +45,8 @@ early_param("sysfs.deprecated", sysfs_deprecated_setup); |
| #endif |
| |
| /* Device links support. */ |
| +static LIST_HEAD(wait_for_suppliers); |
| +static DEFINE_MUTEX(wfs_lock); |
| |
| #ifdef CONFIG_SRCU |
| static DEFINE_MUTEX(device_links_lock); |
| @@ -431,6 +433,58 @@ struct device_link *device_link_add(struct device *consumer, |
| } |
| 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) |
| { |
| while (refcount_dec_not_one(&link->rpm_active)) |
| @@ -565,6 +619,17 @@ int device_links_check_suppliers(struct device *dev) |
| struct device_link *link; |
| 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(); |
| |
| list_for_each_entry(link, &dev->links.suppliers, c_node) { |
| @@ -849,6 +914,10 @@ static void device_links_purge(struct device *dev) |
| { |
| 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 |
| * devices (either consumers or suppliers). |
| @@ -1713,6 +1782,7 @@ void device_initialize(struct device *dev) |
| #endif |
| INIT_LIST_HEAD(&dev->links.consumers); |
| INIT_LIST_HEAD(&dev->links.suppliers); |
| + INIT_LIST_HEAD(&dev->links.needs_suppliers); |
| dev->links.status = DL_DEV_NO_DRIVER; |
| } |
| EXPORT_SYMBOL_GPL(device_initialize); |
| @@ -2203,6 +2273,24 @@ int device_add(struct device *dev) |
| if (dev->fwnode && !dev->fwnode->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); |
| if (parent) |
| klist_add_tail(&dev->p->knode_parent, |
| diff --git a/include/linux/device.h b/include/linux/device.h |
| index 297239a08bb7..c6fb5b3431b7 100644 |
| --- a/include/linux/device.h |
| +++ b/include/linux/device.h |
| @@ -1135,11 +1135,13 @@ enum dl_dev_state { |
| * struct dev_links_info - Device data related to device links. |
| * @suppliers: List of links to supplier devices. |
| * @consumers: List of links to consumer devices. |
| + * @needs_suppliers: Hook to global list of devices waiting for suppliers. |
| * @status: Driver status information. |
| */ |
| struct dev_links_info { |
| struct list_head suppliers; |
| struct list_head consumers; |
| + struct list_head needs_suppliers; |
| enum dl_dev_state status; |
| }; |
| |
| diff --git a/include/linux/fwnode.h b/include/linux/fwnode.h |
| index d8c6d231d577..6ae05b9ce359 100644 |
| --- a/include/linux/fwnode.h |
| +++ b/include/linux/fwnode.h |
| @@ -66,6 +66,21 @@ struct fwnode_reference_args { |
| * endpoint node. |
| * @graph_get_port_parent: Return the parent node of a port node. |
| * @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_handle *(*get)(struct fwnode_handle *fwnode); |
| @@ -103,6 +118,8 @@ struct fwnode_operations { |
| (*graph_get_port_parent)(struct fwnode_handle *fwnode); |
| int (*graph_parse_endpoint)(const struct fwnode_handle *fwnode, |
| struct fwnode_endpoint *endpoint); |
| + int (*add_links)(const struct fwnode_handle *fwnode, |
| + struct device *dev); |
| }; |
| |
| #define fwnode_has_op(fwnode, op) \ |