driver core: fw_devlink: Allow marking a fwnode link as being part of a cycle

To improve detection and handling of dependency cycles, we need to be
able to mark fwnode links as being part of cycles. fwnode links marked
as being part of a cycle should not block their consumers from probing.

Fixes: 2de9d8e0d2 ("driver core: fw_devlink: Improve handling of cyclic dependencies")
Signed-off-by: Saravana Kannan <saravanak@google.com>
Tested-by: Colin Foster <colin.foster@in-advantage.com>
Tested-by: Sudeep Holla <sudeep.holla@arm.com>
Tested-by: Douglas Anderson <dianders@chromium.org>
Tested-by: Geert Uytterhoeven <geert+renesas@glider.be>
Tested-by: Luca Weiss <luca.weiss@fairphone.com> # qcom/sm7225-fairphone-fp4
Link: https://lore.kernel.org/r/20230207014207.1678715-7-saravanak@google.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Saravana Kannan 2023-02-06 17:41:58 -08:00 committed by Greg Kroah-Hartman
parent 67cad5c670
commit 6a6dfdf8b3
2 changed files with 50 additions and 11 deletions

View File

@ -76,13 +76,15 @@ static bool fw_devlink_best_effort;
* are ignored and there is no reference counting. * are ignored and there is no reference counting.
*/ */
static int __fwnode_link_add(struct fwnode_handle *con, static int __fwnode_link_add(struct fwnode_handle *con,
struct fwnode_handle *sup) struct fwnode_handle *sup, u8 flags)
{ {
struct fwnode_link *link; struct fwnode_link *link;
list_for_each_entry(link, &sup->consumers, s_hook) list_for_each_entry(link, &sup->consumers, s_hook)
if (link->consumer == con) if (link->consumer == con) {
link->flags |= flags;
return 0; return 0;
}
link = kzalloc(sizeof(*link), GFP_KERNEL); link = kzalloc(sizeof(*link), GFP_KERNEL);
if (!link) if (!link)
@ -92,6 +94,7 @@ static int __fwnode_link_add(struct fwnode_handle *con,
INIT_LIST_HEAD(&link->s_hook); INIT_LIST_HEAD(&link->s_hook);
link->consumer = con; link->consumer = con;
INIT_LIST_HEAD(&link->c_hook); INIT_LIST_HEAD(&link->c_hook);
link->flags = flags;
list_add(&link->s_hook, &sup->consumers); list_add(&link->s_hook, &sup->consumers);
list_add(&link->c_hook, &con->suppliers); list_add(&link->c_hook, &con->suppliers);
@ -106,7 +109,7 @@ int fwnode_link_add(struct fwnode_handle *con, struct fwnode_handle *sup)
int ret; int ret;
mutex_lock(&fwnode_link_lock); mutex_lock(&fwnode_link_lock);
ret = __fwnode_link_add(con, sup); ret = __fwnode_link_add(con, sup, 0);
mutex_unlock(&fwnode_link_lock); mutex_unlock(&fwnode_link_lock);
return ret; return ret;
} }
@ -126,6 +129,19 @@ static void __fwnode_link_del(struct fwnode_link *link)
kfree(link); kfree(link);
} }
/**
* __fwnode_link_cycle - Mark a fwnode link as being part of a cycle.
* @link: the fwnode_link to be marked
*
* The fwnode_link_lock needs to be held when this function is called.
*/
static void __fwnode_link_cycle(struct fwnode_link *link)
{
pr_debug("%pfwf: Relaxing link with %pfwf\n",
link->consumer, link->supplier);
link->flags |= FWLINK_FLAG_CYCLE;
}
/** /**
* fwnode_links_purge_suppliers - Delete all supplier links of fwnode_handle. * fwnode_links_purge_suppliers - Delete all supplier links of fwnode_handle.
* @fwnode: fwnode whose supplier links need to be deleted * @fwnode: fwnode whose supplier links need to be deleted
@ -199,7 +215,7 @@ static void __fwnode_links_move_consumers(struct fwnode_handle *from,
struct fwnode_link *link, *tmp; struct fwnode_link *link, *tmp;
list_for_each_entry_safe(link, tmp, &from->consumers, s_hook) { list_for_each_entry_safe(link, tmp, &from->consumers, s_hook) {
__fwnode_link_add(link->consumer, to); __fwnode_link_add(link->consumer, to, link->flags);
__fwnode_link_del(link); __fwnode_link_del(link);
} }
} }
@ -1041,6 +1057,21 @@ static bool dev_is_best_effort(struct device *dev)
(dev->fwnode && (dev->fwnode->flags & FWNODE_FLAG_BEST_EFFORT)); (dev->fwnode && (dev->fwnode->flags & FWNODE_FLAG_BEST_EFFORT));
} }
static struct fwnode_handle *fwnode_links_check_suppliers(
struct fwnode_handle *fwnode)
{
struct fwnode_link *link;
if (!fwnode || fw_devlink_is_permissive())
return NULL;
list_for_each_entry(link, &fwnode->suppliers, c_hook)
if (!(link->flags & FWLINK_FLAG_CYCLE))
return link->supplier;
return NULL;
}
/** /**
* device_links_check_suppliers - Check presence of supplier drivers. * device_links_check_suppliers - Check presence of supplier drivers.
* @dev: Consumer device. * @dev: Consumer device.
@ -1068,11 +1099,8 @@ int device_links_check_suppliers(struct device *dev)
* probe. * probe.
*/ */
mutex_lock(&fwnode_link_lock); mutex_lock(&fwnode_link_lock);
if (dev->fwnode && !list_empty(&dev->fwnode->suppliers) && sup_fw = fwnode_links_check_suppliers(dev->fwnode);
!fw_devlink_is_permissive()) { if (sup_fw) {
sup_fw = list_first_entry(&dev->fwnode->suppliers,
struct fwnode_link,
c_hook)->supplier;
if (!dev_is_best_effort(dev)) { if (!dev_is_best_effort(dev)) {
fwnode_ret = -EPROBE_DEFER; fwnode_ret = -EPROBE_DEFER;
dev_err_probe(dev, -EPROBE_DEFER, dev_err_probe(dev, -EPROBE_DEFER,
@ -1261,7 +1289,9 @@ static ssize_t waiting_for_supplier_show(struct device *dev,
bool val; bool val;
device_lock(dev); device_lock(dev);
val = !list_empty(&dev->fwnode->suppliers); mutex_lock(&fwnode_link_lock);
val = !!fwnode_links_check_suppliers(dev->fwnode);
mutex_unlock(&fwnode_link_lock);
device_unlock(dev); device_unlock(dev);
return sysfs_emit(buf, "%u\n", val); return sysfs_emit(buf, "%u\n", val);
} }

View File

@ -18,7 +18,7 @@ struct fwnode_operations;
struct device; struct device;
/* /*
* fwnode link flags * fwnode flags
* *
* LINKS_ADDED: The fwnode has already be parsed to add fwnode links. * LINKS_ADDED: The fwnode has already be parsed to add fwnode links.
* NOT_DEVICE: The fwnode will never be populated as a struct device. * NOT_DEVICE: The fwnode will never be populated as a struct device.
@ -36,6 +36,7 @@ struct device;
#define FWNODE_FLAG_INITIALIZED BIT(2) #define FWNODE_FLAG_INITIALIZED BIT(2)
#define FWNODE_FLAG_NEEDS_CHILD_BOUND_ON_ADD BIT(3) #define FWNODE_FLAG_NEEDS_CHILD_BOUND_ON_ADD BIT(3)
#define FWNODE_FLAG_BEST_EFFORT BIT(4) #define FWNODE_FLAG_BEST_EFFORT BIT(4)
#define FWNODE_FLAG_VISITED BIT(5)
struct fwnode_handle { struct fwnode_handle {
struct fwnode_handle *secondary; struct fwnode_handle *secondary;
@ -46,11 +47,19 @@ struct fwnode_handle {
u8 flags; u8 flags;
}; };
/*
* fwnode link flags
*
* CYCLE: The fwnode link is part of a cycle. Don't defer probe.
*/
#define FWLINK_FLAG_CYCLE BIT(0)
struct fwnode_link { struct fwnode_link {
struct fwnode_handle *supplier; struct fwnode_handle *supplier;
struct list_head s_hook; struct list_head s_hook;
struct fwnode_handle *consumer; struct fwnode_handle *consumer;
struct list_head c_hook; struct list_head c_hook;
u8 flags;
}; };
/** /**