summaryrefslogtreecommitdiffstats
path: root/drivers/base
diff options
context:
space:
mode:
authorSaravana Kannan <saravanak@google.com>2019-07-31 15:17:15 -0700
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2019-08-01 16:04:13 +0200
commit134b23eec9e3a3c795a6ceb0efe2fa63e87983b2 (patch)
treeb6e05eec658629729fcb33cbae0f0e84a426ac2d /drivers/base
parent5302dd7dd0b6d04c63cdce51d1e9fda9ef0be886 (diff)
downloadlinux-stable-134b23eec9e3a3c795a6ceb0efe2fa63e87983b2.tar.gz
linux-stable-134b23eec9e3a3c795a6ceb0efe2fa63e87983b2.tar.bz2
linux-stable-134b23eec9e3a3c795a6ceb0efe2fa63e87983b2.zip
driver core: Add edit_links() callback for drivers
The driver core/bus adding supplier-consumer dependencies by default enables functional dependencies to be tracked correctly even when the consumer devices haven't had their drivers registered or loaded (if they are modules). However, when the bus incorrectly adds dependencies that it shouldn't have added, the devices might never probe. For example, if device-C is a consumer of device-S and they have phandles to each other in DT, the following could happen: 1. Device-S get added first. 2. The bus add_links() callback will (incorrectly) try to link it as a consumer of device-C. 3. Since device-C isn't present, device-S will be put in "waiting-for-supplier" list. 4. Device-C gets added next. 5. All devices in "waiting-for-supplier" list are retried for linking. 6. Device-S gets linked as consumer to Device-C. 7. The bus add_links() callback will (correctly) try to link it as a consumer of device-S. 8. This isn't allowed because it would create a cyclic device links. Neither devices will get probed since the supplier is marked as dependent on the consumer. And the consumer will never probe because the consumer can't get resources from the supplier. Without this patch, things stay in this broken state. However, with this patch, the execution will continue like this: 9. Device-C's driver is loaded. 10. Device-C's driver removes Device-S as a consumer of Device-C. 11. Device-C's driver adds Device-C as a consumer of Device-S. 12. Device-S probes. 14. Device-C probes. kbuild test robot reported missing documentation for device.has_edit_links Reported-by: kbuild test robot <lkp@intel.com> Signed-off-by: Saravana Kannan <saravanak@google.com> Link: https://lore.kernel.org/r/20190731221721.187713-3-saravanak@google.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/base')
-rw-r--r--drivers/base/core.c24
-rw-r--r--drivers/base/dd.c29
2 files changed, 51 insertions, 2 deletions
diff --git a/drivers/base/core.c b/drivers/base/core.c
index 18e04ca1de13..feec8dee1e91 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -440,6 +440,19 @@ static void device_link_wait_for_supplier(struct device *consumer)
}
/**
+ * device_link_remove_from_wfs - Unmark device as waiting for supplier
+ * @consumer: Consumer device
+ *
+ * Unmark the consumer device as waiting for suppliers to become available.
+ */
+void device_link_remove_from_wfs(struct device *consumer)
+{
+ mutex_lock(&wfs_lock);
+ list_del_init(&consumer->links.needs_suppliers);
+ mutex_unlock(&wfs_lock);
+}
+
+/**
* device_link_check_waiting_consumers - Try to unmark waiting consumers
*
* Loops through all consumers waiting on suppliers and tries to add all their
@@ -456,12 +469,19 @@ static void device_link_wait_for_supplier(struct device *consumer)
static void device_link_check_waiting_consumers(void)
{
struct device *dev, *tmp;
+ int ret;
mutex_lock(&wfs_lock);
list_for_each_entry_safe(dev, tmp, &wait_for_suppliers,
- links.needs_suppliers)
- if (!dev->bus->add_links(dev))
+ links.needs_suppliers) {
+ ret = 0;
+ if (dev->has_edit_links)
+ ret = driver_edit_links(dev);
+ else if (dev->bus->add_links)
+ ret = dev->bus->add_links(dev);
+ if (!ret)
list_del_init(&dev->links.needs_suppliers);
+ }
mutex_unlock(&wfs_lock);
}
diff --git a/drivers/base/dd.c b/drivers/base/dd.c
index 994a90747420..5e7041ede0d7 100644
--- a/drivers/base/dd.c
+++ b/drivers/base/dd.c
@@ -698,6 +698,12 @@ int driver_probe_device(struct device_driver *drv, struct device *dev)
pr_debug("bus: '%s': %s: matched device %s with driver %s\n",
drv->bus->name, __func__, dev_name(dev), drv->name);
+ if (drv->edit_links) {
+ if (drv->edit_links(dev))
+ dev->has_edit_links = true;
+ else
+ device_link_remove_from_wfs(dev);
+ }
pm_runtime_get_suppliers(dev);
if (dev->parent)
pm_runtime_get_sync(dev->parent);
@@ -786,6 +792,29 @@ struct device_attach_data {
bool have_async;
};
+static int __driver_edit_links(struct device_driver *drv, void *data)
+{
+ struct device *dev = data;
+
+ if (!drv->edit_links)
+ return 0;
+
+ if (driver_match_device(drv, dev) <= 0)
+ return 0;
+
+ return drv->edit_links(dev);
+}
+
+int driver_edit_links(struct device *dev)
+{
+ int ret;
+
+ device_lock(dev);
+ ret = bus_for_each_drv(dev->bus, NULL, dev, __driver_edit_links);
+ device_unlock(dev);
+ return ret;
+}
+
static int __device_attach_driver(struct device_driver *drv, void *_data)
{
struct device_attach_data *data = _data;