[PATCH v1 08/18] driver core: Add fwnode link support
From: Saravana Kannan
Date: Wed Nov 04 2020 - 18:25:16 EST
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