This patch adds support for creating supplier-consumer links between fwnode. It is intentionally kept simple and with limited APIs as it is meant to be used only by driver core and firmware code (Eg: device tree, ACPI, etc). We can expand the APIs later if there is ever a need for drivers/frameworks to start using them. Signed-off-by: Saravana Kannan <saravanak@xxxxxxxxxx> --- drivers/base/core.c | 95 ++++++++++++++++++++++++++++++++++++++++++ drivers/of/dynamic.c | 1 + include/linux/fwnode.h | 14 +++++++ 3 files changed, 110 insertions(+) diff --git a/drivers/base/core.c b/drivers/base/core.c index 31a76159f118..1a1d9a55645c 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -50,6 +50,101 @@ static LIST_HEAD(wait_for_suppliers); static DEFINE_MUTEX(wfs_lock); static LIST_HEAD(deferred_sync); static unsigned int defer_sync_state_count = 1; +static DEFINE_MUTEX(fwnode_link_lock); + +/** + * fwnode_link_add - Create a link between two fwnode_handles. + * @con: Consumer end of the link. + * @sup: Supplier end of the link. + * + * Creates a fwnode link between two fwnode_handles. These fwnode links are + * used by the driver core to automatically generate device links. Attempts to + * create duplicate links are simply ignored and there is no refcounting. + * + * These links are automatically deleted once they are converted to device + * links or when the fwnode_handles (or their corresponding devices) are + * deleted. + */ +int fwnode_link_add(struct fwnode_handle *con, struct fwnode_handle *sup) +{ + struct fwnode_link *link; + int ret = 0; + + mutex_lock(&fwnode_link_lock); + + /* Duplicate requests are intentionally not refcounted. */ + list_for_each_entry(link, &sup->consumers, s_hook) + if (link->consumer == con) + goto out; + + link = kzalloc(sizeof(*link), GFP_KERNEL); + if (!link) { + ret = -ENOMEM; + goto out; + } + + link->supplier = sup; + INIT_LIST_HEAD(&link->s_hook); + link->consumer = con; + INIT_LIST_HEAD(&link->c_hook); + + list_add(&link->s_hook, &sup->consumers); + list_add(&link->c_hook, &con->suppliers); +out: + mutex_unlock(&fwnode_link_lock); + + return ret; +} + +/** + * fwnode_links_purge_suppliers - Delete all supplier links of fwnode_handle. + * @fwnode: fwnode whose supplier links needs to be deleted + * + * Deletes all supplier links connecting directly to a fwnode. + */ +static void fwnode_links_purge_suppliers(struct fwnode_handle *fwnode) +{ + struct fwnode_link *link, *tmp; + + mutex_lock(&fwnode_link_lock); + list_for_each_entry_safe(link, tmp, &fwnode->suppliers, c_hook) { + list_del(&link->s_hook); + list_del(&link->c_hook); + kfree(link); + } + mutex_unlock(&fwnode_link_lock); +} + +/** + * fwnode_links_purge_consumers - Delete all consumer links of fwnode_handle. + * @fwnode: fwnode whose consumer links needs to be deleted + * + * Deletes all consumer links connecting directly to a fwnode. + */ +static void fwnode_links_purge_consumers(struct fwnode_handle *fwnode) +{ + struct fwnode_link *link, *tmp; + + mutex_lock(&fwnode_link_lock); + list_for_each_entry_safe(link, tmp, &fwnode->consumers, s_hook) { + list_del(&link->s_hook); + list_del(&link->c_hook); + kfree(link); + } + mutex_unlock(&fwnode_link_lock); +} + +/** + * fwnode_links_purge - Delete all links connected to a fwnode_handle. + * @fwnode: fwnode whose links needs to be deleted + * + * Deletes all links connecting directly to a fwnode. + */ +void fwnode_links_purge(struct fwnode_handle *fwnode) +{ + fwnode_links_purge_suppliers(fwnode); + fwnode_links_purge_consumers(fwnode); +} #ifdef CONFIG_SRCU static DEFINE_MUTEX(device_links_lock); diff --git a/drivers/of/dynamic.c b/drivers/of/dynamic.c index fe64430b438a..9a824decf61f 100644 --- a/drivers/of/dynamic.c +++ b/drivers/of/dynamic.c @@ -356,6 +356,7 @@ void of_node_release(struct kobject *kobj) property_list_free(node->properties); property_list_free(node->deadprops); + fwnode_links_purge(of_fwnode_handle(node)); kfree(node->full_name); kfree(node->data); diff --git a/include/linux/fwnode.h b/include/linux/fwnode.h index 593fb8e58f21..afde643f37a2 100644 --- a/include/linux/fwnode.h +++ b/include/linux/fwnode.h @@ -10,6 +10,7 @@ #define _LINUX_FWNODE_H_ #include <linux/types.h> +#include <linux/list.h> struct fwnode_operations; struct device; @@ -18,6 +19,15 @@ struct fwnode_handle { struct fwnode_handle *secondary; const struct fwnode_operations *ops; struct device *dev; + struct list_head suppliers; + struct list_head consumers; +}; + +struct fwnode_link { + struct fwnode_handle *supplier; + struct list_head s_hook; + struct fwnode_handle *consumer; + struct list_head c_hook; }; /** @@ -173,8 +183,12 @@ static inline void fwnode_init(struct fwnode_handle *fwnode, const struct fwnode_operations *ops) { fwnode->ops = ops; + INIT_LIST_HEAD(&fwnode->consumers); + INIT_LIST_HEAD(&fwnode->suppliers); } extern u32 fw_devlink_get_flags(void); +int fwnode_link_add(struct fwnode_handle *con, struct fwnode_handle *sup); +void fwnode_links_purge(struct fwnode_handle *fwnode); #endif -- 2.29.1.341.ge80a0c044ae-goog