Skip to content

Commit

Permalink
driver core: Handle cycles in device links created by fw_devlink
Browse files Browse the repository at this point in the history
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: Saravana Kannan <saravanak@google.com>
Link: https://lore.kernel.org/r/20201218031703.3053753-5-saravanak@google.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
  • Loading branch information
Saravana Kannan authored and gregkh committed Jan 7, 2021
1 parent b90fb8f commit b0e2fa4
Showing 1 changed file with 57 additions and 1 deletion.
58 changes: 57 additions & 1 deletion drivers/base/core.c
Expand Up @@ -1505,6 +1505,53 @@ static void fw_devlink_parse_fwtree(struct fwnode_handle *fwnode)
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
* @con - Consumer device for the device link
Expand Down Expand Up @@ -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
* 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;
}

goto out;
}
Expand Down

0 comments on commit b0e2fa4

Please sign in to comment.