@@ -4,7 +4,7 @@ obj-y := component.o core.o bus.o dd.o syscore.o \
driver.o class.o platform.o \
cpu.o firmware.o init.o map.o devres.o \
attribute_container.o transport_class.o \
- topology.o container.o
+ topology.o container.o interface_tracker.o
obj-$(CONFIG_DEVTMPFS) += devtmpfs.o
obj-$(CONFIG_DMA_CMA) += dma-contiguous.o
obj-y += power/
new file mode 100644
@@ -0,0 +1,307 @@
+/*
+ * Generic framework for tracking kernel interfaces
+ *
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd
+ * Andrzej Hajda <a.hajda@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * interface_tracker is a generic framework which allows to track appearance
+ * and disappearance of different interfaces provided by kernel/driver code
+ * inside the kernel. Examples of such interfaces: clocks, phys, regulators,
+ * drm_panel...
+ * Interface is specified by a pair of object pointer and interface type. Object
+ * type depends on interface type, for example interface type drm_panel
+ * determines that object is a device_node. Object pointer is used
+ * to distinguish different interfaces of the same type and should identify
+ * object the interface is bound to. For example it could be DT node,
+ * struct device...
+ *
+ * The framework provides two functions for interface providers which should be
+ * called just after interface becomes available or just before interface
+ * removal. Interface consumers can register callback which will be called when
+ * the requested interface changes its state, if during callback registration
+ * the interface is already up, notification will be sent also.
+ *
+ * The framework allows nesting calls, for example it is possible to signal one
+ * interface in tracker callback of another interface. It is done by putting
+ * every request into the queue. If the queue is empty before adding
+ * the request, the queue will be processed after, if there is already another
+ * request in the queue it means the queue is already processed by different
+ * thread so no additional action is required. With this pattern only spinlock
+ * is necessary to protect the queue. However in case of removal of either
+ * callback or interface caller should be sure that his request has been
+ * processed so framework waits until the queue becomes empty, it is done using
+ * completion mechanism.
+ * The framework functions should not be called in atomic context. Callbacks
+ * should be aware that they can be called in any time between registration and
+ * unregistration, so they should use synchronization mechanisms carefully.
+ * Callbacks should also avoid to perform time consuming tasks, the framework
+ * serializes them, so it could stall other callbacks.
+ */
+
+#include <linux/completion.h>
+#include <linux/interface_tracker.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+enum interface_tracker_task_id {
+ interface_tracker_task_register,
+ interface_tracker_task_unregister,
+ interface_tracker_task_ifup,
+ interface_tracker_task_ifdown,
+};
+
+struct interface_tracker_task {
+ struct list_head list;
+ enum interface_tracker_task_id task_id;
+
+ const void *object;
+ unsigned long type;
+ union {
+ struct interface_tracker_block *itb;
+ void *data;
+ };
+};
+
+struct interface_tracker_node {
+ struct list_head list;
+ struct list_head itb_head;
+ const void *object;
+ unsigned long type;
+ void *data;
+ bool ifup;
+};
+
+static LIST_HEAD(interface_tracker_queue);
+static DEFINE_SPINLOCK(interface_tracker_queue_lock);
+static DECLARE_COMPLETION(interface_tracker_queue_completion);
+
+static LIST_HEAD(interface_tracker_nodes);
+
+static struct interface_tracker_node *
+interface_tracker_get_node(const void *object, unsigned long type, bool create)
+{
+ struct interface_tracker_node *node;
+
+ list_for_each_entry(node, &interface_tracker_nodes, list) {
+ if (node->type == type && node->object == object)
+ return node;
+ }
+
+ if (!create)
+ return NULL;
+
+ node = kmalloc(sizeof(*node), GFP_KERNEL);
+ node->object = object;
+ node->type = type;
+ node->ifup = false;
+ INIT_LIST_HEAD(&node->itb_head);
+ list_add(&node->list, &interface_tracker_nodes);
+
+ return node;
+}
+
+static void interface_tracker_process_task(struct interface_tracker_task *task)
+{
+ struct interface_tracker_block *itb;
+ struct interface_tracker_node *node;
+
+ switch (task->task_id) {
+ case interface_tracker_task_register:
+ node = interface_tracker_get_node(task->object, task->type,
+ true);
+ list_add_tail(&task->itb->list, &node->itb_head);
+
+ if (node->ifup)
+ task->itb->callback(task->itb, task->object, task->type,
+ true, node->data);
+ return;
+ case interface_tracker_task_unregister:
+ node = interface_tracker_get_node(task->object, task->type,
+ false);
+ if (WARN_ON(!node))
+ return;
+
+ list_for_each_entry(itb, &node->itb_head, list) {
+ if (itb != task->itb)
+ continue;
+ list_del(&itb->list);
+ if (list_empty(&node->itb_head)) {
+ list_del(&node->list);
+ kfree(node);
+ }
+ return;
+ }
+
+ WARN_ON(true);
+
+ return;
+ case interface_tracker_task_ifup:
+ node = interface_tracker_get_node(task->object, task->type,
+ true);
+
+ if (WARN_ON(node->ifup))
+ return;
+
+ node->ifup = true;
+ node->data = task->data;
+ list_for_each_entry(itb, &node->itb_head, list)
+ itb->callback(itb, task->object, task->type, true,
+ node->data);
+ return;
+ case interface_tracker_task_ifdown:
+ node = interface_tracker_get_node(task->object, task->type,
+ false);
+
+ if (WARN_ON(!node))
+ return;
+
+ WARN_ON(!node->ifup);
+
+ if (list_empty(&node->itb_head)) {
+ list_del(&node->list);
+ kfree(node);
+ return;
+ }
+
+ node->ifup = false;
+ node->data = task->data;
+
+ list_for_each_entry(itb, &node->itb_head, list)
+ itb->callback(itb, task->object, task->type, false,
+ node->data);
+ }
+}
+
+static int interface_tracker_process_queue(void)
+{
+ struct interface_tracker_task *task, *ptask = NULL;
+ unsigned long flags;
+ bool empty;
+
+ /* Queue non-emptiness is used as a sentinel to prevent processing
+ * by multiple threads, so we cannot delete entry from the queue
+ * until it is processed.
+ */
+ while (true) {
+ spin_lock_irqsave(&interface_tracker_queue_lock, flags);
+
+ if (ptask)
+ list_del(&ptask->list);
+ task = list_first_entry(&interface_tracker_queue,
+ struct interface_tracker_task, list);
+
+ empty = list_empty(&interface_tracker_queue);
+ if (empty)
+ complete_all(&interface_tracker_queue_completion);
+
+ spin_unlock_irqrestore(&interface_tracker_queue_lock, flags);
+
+ if (ptask)
+ kfree(ptask);
+
+ if (empty)
+ break;
+
+ interface_tracker_process_task(task);
+ ptask = task;
+ }
+
+ return 0;
+}
+
+static int interface_tracker_add_task(struct interface_tracker_task *task,
+ bool sync)
+{
+ unsigned long flags;
+ int ret = 0;
+ bool empty;
+
+ spin_lock_irqsave(&interface_tracker_queue_lock, flags);
+
+ empty = list_empty(&interface_tracker_queue);
+ if (empty)
+ reinit_completion(&interface_tracker_queue_completion);
+
+ list_add(&task->list, &interface_tracker_queue);
+
+ spin_unlock_irqrestore(&interface_tracker_queue_lock, flags);
+
+ if (empty) {
+ ret = interface_tracker_process_queue();
+ }else if (sync) {
+ wait_for_completion(&interface_tracker_queue_completion);
+ }
+
+ return ret;
+}
+
+int interface_tracker_register(const void *object, unsigned long type,
+ struct interface_tracker_block *itb)
+{
+ struct interface_tracker_task *task;
+
+ task = kmalloc(sizeof(*task), GFP_KERNEL);
+ if (!task)
+ return -ENOMEM;
+
+ task->task_id = interface_tracker_task_register;
+ task->object = object;
+ task->type = type;
+ task->itb = itb;
+
+ return interface_tracker_add_task(task, false);
+}
+
+int interface_tracker_unregister(const void *object, unsigned long type,
+ struct interface_tracker_block *itb)
+{
+ struct interface_tracker_task *task;
+
+ task = kmalloc(sizeof(*task), GFP_KERNEL);
+ if (!task)
+ return -ENOMEM;
+
+ task->task_id = interface_tracker_task_unregister;
+ task->object = object;
+ task->type = type;
+ task->itb = itb;
+
+ return interface_tracker_add_task(task, true);
+}
+
+int interface_tracker_ifup(const void *object, unsigned long type, void *data)
+{
+ struct interface_tracker_task *task;
+
+ task = kmalloc(sizeof(*task), GFP_KERNEL);
+ if (!task)
+ return -ENOMEM;
+
+ task->task_id = interface_tracker_task_ifup;
+ task->object = object;
+ task->type = type;
+ task->data = data;
+
+ return interface_tracker_add_task(task, false);
+}
+
+int interface_tracker_ifdown(const void *object, unsigned long type, void *data)
+{
+ struct interface_tracker_task *task;
+
+ task = kmalloc(sizeof(*task), GFP_KERNEL);
+ if (!task)
+ return -ENOMEM;
+
+ task->task_id = interface_tracker_task_ifdown;
+ task->object = object;
+ task->type = type;
+ task->data = data;
+
+ return interface_tracker_add_task(task, true);
+}
new file mode 100644
@@ -0,0 +1,27 @@
+#ifndef INTERFACE_TRACKER_H
+#define INTERFACE_TRACKER_H
+
+#include <linux/types.h>
+
+struct list_head;
+struct interface_tracker_block;
+
+typedef void (*interface_tracker_fn_t)(struct interface_tracker_block *itb,
+ const void *object, unsigned long type,
+ bool on, void *data);
+
+struct interface_tracker_block {
+ interface_tracker_fn_t callback;
+ struct list_head list;
+};
+
+extern int interface_tracker_register(const void *object, unsigned long type,
+ struct interface_tracker_block *itb);
+extern int interface_tracker_unregister(const void *object, unsigned long type,
+ struct interface_tracker_block *itb);
+extern int interface_tracker_ifup(const void *object, unsigned long type,
+ void *data);
+extern int interface_tracker_ifdown(const void *object, unsigned long type,
+ void *data);
+
+#endif /* INTERFACE_TRACKER_H */
interface_tracker is a generic framework which allows to track appearance and disappearance of different interfaces provided by kernel/driver code inside the kernel. Examples of such interfaces: clocks, phys, regulators, drm_panel... Interface is specified by a pair of object pointer and interface type. Object type depends on interface type, for example interface type drm_panel determines that object is a device_node. Object pointer is used to distinguish different interfaces of the same type and should identify object the interface is bound to. Signed-off-by: Andrzej Hajda <a.hajda@samsung.com> --- drivers/base/Makefile | 2 +- drivers/base/interface_tracker.c | 307 ++++++++++++++++++++++++++++++++++++++ include/linux/interface_tracker.h | 27 ++++ 3 files changed, 335 insertions(+), 1 deletion(-) create mode 100644 drivers/base/interface_tracker.c create mode 100644 include/linux/interface_tracker.h