[PATCH] of/overlay: of overlay callbacks

From: Alan Tull
Date: Tue Feb 16 2016 - 13:40:09 EST


Add overlay callback functionality.

When DT overlays are being added, some drivers/subsystems
will want to know about the changes before they go into the
live tree. Similarly there is a need for post-remove
callbacks.

Each handler is registered with a of_device_id. When
an overlay target matches a handler's id, the handler
gets called.

The following 4 cases are handled: pre-apply, post-apply,
pre-remove, and post-remove.

Signed-off-by: Alan Tull <atull@xxxxxxxxxxxxxxxxxxxxx>
---
drivers/of/overlay.c | 90 +++++++++++++++++++++++++++++++++++++++++++++++++-
include/linux/of.h | 31 +++++++++++++++++
2 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/drivers/of/overlay.c b/drivers/of/overlay.c
index 8225081..ee88e4e 100644
--- a/drivers/of/overlay.c
+++ b/drivers/of/overlay.c
@@ -20,9 +20,13 @@
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/idr.h>
+#include <linux/list.h>

#include "of_private.h"

+static DEFINE_SPINLOCK(of_overlay_handler_lock);
+static LIST_HEAD(of_overlay_handler_list);
+
/**
* struct of_overlay_info - Holds a single overlay info
* @target: target of the overlay operation
@@ -56,6 +60,80 @@ struct of_overlay {
static int of_overlay_apply_one(struct of_overlay *ov,
struct device_node *target, const struct device_node *overlay);

+/*
+ * Send overlay callbacks to handlers that match. This call is blocking. In
+ * the case OF_OVERLAY_PRE_APPLY, return error for the first handler that fails.
+ * Otherwise, notify all the matching handlers and return success.
+ */
+static int send_overlay_callbacks(struct of_overlay *ov, int type)
+{
+ int i, ret;
+
+ spin_lock(&of_overlay_handler_lock);
+
+ for (i = 0; i < ov->count; i++) {
+ struct of_overlay_info *ovinfo = &ov->ovinfo_tab[i];
+ struct of_overlay_handler *handler;
+ int (*callback)(struct of_overlay_msg *msg);
+ struct of_overlay_msg msg_data;
+
+ msg_data.target = ovinfo->target;
+ msg_data.overlay = ovinfo->overlay;
+
+ list_for_each_entry(handler, &of_overlay_handler_list, node) {
+ msg_data.priv = handler->priv;
+ switch (type) {
+ case OF_OVERLAY_PRE_APPLY:
+ callback = handler->pre_apply_callback;
+ break;
+ case OF_OVERLAY_POST_APPLY:
+ callback = handler->post_apply_callback;
+ break;
+ case OF_OVERLAY_PRE_REMOVE:
+ callback = handler->pre_remove_callback;
+ break;
+ case OF_OVERLAY_POST_REMOVE:
+ callback = handler->post_remove_callback;
+ break;
+ default:
+ continue;
+ };
+ if (!callback)
+ continue;
+ if (of_match_node(handler->of_match, ovinfo->target)) {
+ ret = callback(&msg_data);
+ if ((ret < 0) && (type == OF_OVERLAY_PRE_APPLY)) {
+ spin_unlock(&of_overlay_handler_lock);
+ return ret;
+ }
+ continue;
+ }
+ }
+ }
+
+ spin_unlock(&of_overlay_handler_lock);
+
+ return 0;
+}
+
+int of_overlay_handler_register(struct of_overlay_handler *handler)
+{
+ spin_lock(&of_overlay_handler_lock);
+ list_add_tail(&handler->node, &of_overlay_handler_list);
+ spin_unlock(&of_overlay_handler_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(of_overlay_handler_register);
+
+void of_overlay_handler_unregister(struct of_overlay_handler *handler)
+{
+ spin_lock(&of_overlay_handler_lock);
+ list_del(&handler->node);
+ spin_unlock(&of_overlay_handler_lock);
+}
+EXPORT_SYMBOL_GPL(of_overlay_handler_unregister);
+
static int of_overlay_apply_single_property(struct of_overlay *ov,
struct device_node *target, struct property *prop)
{
@@ -370,6 +448,13 @@ int of_overlay_create(struct device_node *tree)
goto err_free_idr;
}

+ err = send_overlay_callbacks(ov, OF_OVERLAY_PRE_APPLY);
+ if (err < 0) {
+ pr_err("%s: Pre apply handler failed (err=%d)\n",
+ __func__, err);
+ goto err_free_idr;
+ }
+
/* apply the overlay */
err = of_overlay_apply(ov);
if (err) {
@@ -389,6 +474,8 @@ int of_overlay_create(struct device_node *tree)
/* add to the tail of the overlay list */
list_add_tail(&ov->node, &ov_list);

+ send_overlay_callbacks(ov, OF_OVERLAY_POST_APPLY);
+
mutex_unlock(&of_mutex);

return id;
@@ -509,9 +596,10 @@ int of_overlay_destroy(int id)
goto out;
}

-
+ send_overlay_callbacks(ov, OF_OVERLAY_PRE_REMOVE);
list_del(&ov->node);
__of_changeset_revert(&ov->cset);
+ send_overlay_callbacks(ov, OF_OVERLAY_POST_REMOVE);
of_free_overlay_info(ov);
idr_remove(&ov_idr, id);
of_changeset_destroy(&ov->cset);
diff --git a/include/linux/of.h b/include/linux/of.h
index dc6e396..def9481 100644
--- a/include/linux/of.h
+++ b/include/linux/of.h
@@ -101,9 +101,33 @@ static inline int of_node_is_attached(struct device_node *node)
return node && node->kobj.state_in_sysfs;
}

+/* Callback types */
+#define OF_OVERLAY_PRE_APPLY (0)
+#define OF_OVERLAY_POST_APPLY (1)
+#define OF_OVERLAY_PRE_REMOVE (2)
+#define OF_OVERLAY_POST_REMOVE (3)
+
+struct of_overlay_msg {
+ struct device_node *overlay;
+ struct device_node *target;
+ void *priv;
+};
+
+struct of_overlay_handler {
+ int (*pre_apply_callback)(struct of_overlay_msg *msg);
+ int (*post_apply_callback)(struct of_overlay_msg *msg);
+ int (*pre_remove_callback)(struct of_overlay_msg *msg);
+ int (*post_remove_callback)(struct of_overlay_msg *msg);
+ const struct of_device_id *of_match;
+ struct list_head node;
+ void *priv;
+};
+
#ifdef CONFIG_OF_DYNAMIC
extern struct device_node *of_node_get(struct device_node *node);
extern void of_node_put(struct device_node *node);
+extern int of_overlay_handler_register(struct of_overlay_handler *handler);
+extern void of_overlay_handler_unregister(struct of_overlay_handler *handler);
#else /* CONFIG_OF_DYNAMIC */
/* Dummy ref counting routines - to be implemented later */
static inline struct device_node *of_node_get(struct device_node *node)
@@ -111,6 +135,13 @@ static inline struct device_node *of_node_get(struct device_node *node)
return node;
}
static inline void of_node_put(struct device_node *node) { }
+static int of_overlay_handler_register(struct of_overlay_handler *handler)
+{
+ return -EINVAL;
+}
+static void of_overlay_handler_unregister(struct of_overlay_handler *handler)
+{
+}
#endif /* !CONFIG_OF_DYNAMIC */

/* Pointer for first entry in chain of all nodes. */
--
1.7.9.5