mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	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:
		
							parent
							
								
									67cad5c670
								
							
						
					
					
						commit
						6a6dfdf8b3
					
				
					 2 changed files with 50 additions and 11 deletions
				
			
		| 
						 | 
				
			
			@ -76,13 +76,15 @@ static bool fw_devlink_best_effort;
 | 
			
		|||
 * are ignored and there is no reference counting.
 | 
			
		||||
 */
 | 
			
		||||
static int __fwnode_link_add(struct fwnode_handle *con,
 | 
			
		||||
			     struct fwnode_handle *sup)
 | 
			
		||||
			     struct fwnode_handle *sup, u8 flags)
 | 
			
		||||
{
 | 
			
		||||
	struct fwnode_link *link;
 | 
			
		||||
 | 
			
		||||
	list_for_each_entry(link, &sup->consumers, s_hook)
 | 
			
		||||
		if (link->consumer == con)
 | 
			
		||||
		if (link->consumer == con) {
 | 
			
		||||
			link->flags |= flags;
 | 
			
		||||
			return 0;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	link = kzalloc(sizeof(*link), GFP_KERNEL);
 | 
			
		||||
	if (!link)
 | 
			
		||||
| 
						 | 
				
			
			@ -92,6 +94,7 @@ static int __fwnode_link_add(struct fwnode_handle *con,
 | 
			
		|||
	INIT_LIST_HEAD(&link->s_hook);
 | 
			
		||||
	link->consumer = con;
 | 
			
		||||
	INIT_LIST_HEAD(&link->c_hook);
 | 
			
		||||
	link->flags = flags;
 | 
			
		||||
 | 
			
		||||
	list_add(&link->s_hook, &sup->consumers);
 | 
			
		||||
	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;
 | 
			
		||||
 | 
			
		||||
	mutex_lock(&fwnode_link_lock);
 | 
			
		||||
	ret = __fwnode_link_add(con, sup);
 | 
			
		||||
	ret = __fwnode_link_add(con, sup, 0);
 | 
			
		||||
	mutex_unlock(&fwnode_link_lock);
 | 
			
		||||
	return ret;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -126,6 +129,19 @@ static void __fwnode_link_del(struct fwnode_link *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: 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;
 | 
			
		||||
 | 
			
		||||
	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);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1041,6 +1057,21 @@ static bool dev_is_best_effort(struct device *dev)
 | 
			
		|||
		(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.
 | 
			
		||||
 * @dev: Consumer device.
 | 
			
		||||
| 
						 | 
				
			
			@ -1068,11 +1099,8 @@ int device_links_check_suppliers(struct device *dev)
 | 
			
		|||
	 * probe.
 | 
			
		||||
	 */
 | 
			
		||||
	mutex_lock(&fwnode_link_lock);
 | 
			
		||||
	if (dev->fwnode && !list_empty(&dev->fwnode->suppliers) &&
 | 
			
		||||
	    !fw_devlink_is_permissive()) {
 | 
			
		||||
		sup_fw = list_first_entry(&dev->fwnode->suppliers,
 | 
			
		||||
					  struct fwnode_link,
 | 
			
		||||
					  c_hook)->supplier;
 | 
			
		||||
	sup_fw = fwnode_links_check_suppliers(dev->fwnode);
 | 
			
		||||
	if (sup_fw) {
 | 
			
		||||
		if (!dev_is_best_effort(dev)) {
 | 
			
		||||
			fwnode_ret = -EPROBE_DEFER;
 | 
			
		||||
			dev_err_probe(dev, -EPROBE_DEFER,
 | 
			
		||||
| 
						 | 
				
			
			@ -1261,7 +1289,9 @@ static ssize_t waiting_for_supplier_show(struct device *dev,
 | 
			
		|||
	bool val;
 | 
			
		||||
 | 
			
		||||
	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);
 | 
			
		||||
	return sysfs_emit(buf, "%u\n", val);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,7 +18,7 @@ struct fwnode_operations;
 | 
			
		|||
struct device;
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * fwnode link flags
 | 
			
		||||
 * fwnode flags
 | 
			
		||||
 *
 | 
			
		||||
 * LINKS_ADDED:	The fwnode has already be parsed to add fwnode links.
 | 
			
		||||
 * 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_NEEDS_CHILD_BOUND_ON_ADD	BIT(3)
 | 
			
		||||
#define FWNODE_FLAG_BEST_EFFORT			BIT(4)
 | 
			
		||||
#define FWNODE_FLAG_VISITED			BIT(5)
 | 
			
		||||
 | 
			
		||||
struct fwnode_handle {
 | 
			
		||||
	struct fwnode_handle *secondary;
 | 
			
		||||
| 
						 | 
				
			
			@ -46,11 +47,19 @@ struct fwnode_handle {
 | 
			
		|||
	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_handle *supplier;
 | 
			
		||||
	struct list_head s_hook;
 | 
			
		||||
	struct fwnode_handle *consumer;
 | 
			
		||||
	struct list_head c_hook;
 | 
			
		||||
	u8 flags;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue