Commit b0e2fa4f authored by Saravana Kannan's avatar Saravana Kannan Committed by Greg Kroah-Hartman

driver core: Handle cycles in device links created by fw_devlink

Sometimes, firmware can have cyclic dependencies between devices. But
one or more of those dependencies in the cycle are false dependencies
that don't affect the probing of the device.

fw_devlink can detect some of these false dependencies using logic. But
when it can't, we don't want to block probing of the devices in this
cyclic dependency.

So, instead of using normal device links for the devices in this cycle,
we need to switch to SYNC_STATE_ONLY device links between these devices.
This is so that sync_state() callback correctness is still maintained
while we allow these device to probe.

This is functionally similar to switching to fw_devlink=permissive just
for the devices in the cycle.
Signed-off-by: default avatarSaravana Kannan <saravanak@google.com>
Link: https://lore.kernel.org/r/20201218031703.3053753-5-saravanak@google.comSigned-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent b90fb8f6
...@@ -1505,6 +1505,53 @@ static void fw_devlink_parse_fwtree(struct fwnode_handle *fwnode) ...@@ -1505,6 +1505,53 @@ static void fw_devlink_parse_fwtree(struct fwnode_handle *fwnode)
fw_devlink_parse_fwtree(child); fw_devlink_parse_fwtree(child);
} }
/**
* fw_devlink_relax_cycle - Convert cyclic links to SYNC_STATE_ONLY links
* @con: Device to check dependencies for.
* @sup: Device to check against.
*
* Check if @sup depends on @con or any device dependent on it (its child or
* its consumer etc). When such a cyclic dependency is found, convert all
* device links created solely by fw_devlink into SYNC_STATE_ONLY device links.
* This is the equivalent of doing fw_devlink=permissive just between the
* devices in the cycle. We need to do this because, at this point, fw_devlink
* can't tell which of these dependencies is not a real dependency.
*
* Return 1 if a cycle is found. Otherwise, return 0.
*/
int fw_devlink_relax_cycle(struct device *con, void *sup)
{
struct device_link *link;
int ret;
if (con == sup)
return 1;
ret = device_for_each_child(con, sup, fw_devlink_relax_cycle);
if (ret)
return ret;
list_for_each_entry(link, &con->links.consumers, s_node) {
if ((link->flags & ~DL_FLAG_INFERRED) ==
(DL_FLAG_SYNC_STATE_ONLY | DL_FLAG_MANAGED))
continue;
if (!fw_devlink_relax_cycle(link->consumer, sup))
continue;
ret = 1;
if (!(link->flags & DL_FLAG_INFERRED))
continue;
pm_runtime_drop_link(link);
link->flags = DL_FLAG_MANAGED | FW_DEVLINK_FLAGS_PERMISSIVE;
dev_dbg(link->consumer, "Relaxing link with %s\n",
dev_name(link->supplier));
}
return ret;
}
/** /**
* fw_devlink_create_devlink - Create a device link from a consumer to fwnode * fw_devlink_create_devlink - Create a device link from a consumer to fwnode
* @con - Consumer device for the device link * @con - Consumer device for the device link
...@@ -1536,8 +1583,17 @@ static int fw_devlink_create_devlink(struct device *con, ...@@ -1536,8 +1583,17 @@ static int fw_devlink_create_devlink(struct device *con,
* If this fails, it is due to cycles in device links. Just * If this fails, it is due to cycles in device links. Just
* give up on this link and treat it as invalid. * give up on this link and treat it as invalid.
*/ */
if (!device_link_add(con, sup_dev, flags)) if (!device_link_add(con, sup_dev, flags) &&
!(flags & DL_FLAG_SYNC_STATE_ONLY)) {
dev_info(con, "Fixing up cyclic dependency with %s\n",
dev_name(sup_dev));
device_links_write_lock();
fw_devlink_relax_cycle(con, sup_dev);
device_links_write_unlock();
device_link_add(con, sup_dev,
FW_DEVLINK_FLAGS_PERMISSIVE);
ret = -EINVAL; ret = -EINVAL;
}
goto out; goto out;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment