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@xxxxxxxxxxx> --- 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 diff --git a/drivers/base/Makefile b/drivers/base/Makefile index 04b314e..5a45c41 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -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/ diff --git a/drivers/base/interface_tracker.c b/drivers/base/interface_tracker.c new file mode 100644 index 0000000..3d36cba --- /dev/null +++ b/drivers/base/interface_tracker.c @@ -0,0 +1,307 @@ +/* + * Generic framework for tracking kernel interfaces + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd + * Andrzej Hajda <a.hajda@xxxxxxxxxxx> + * + * 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); +} diff --git a/include/linux/interface_tracker.h b/include/linux/interface_tracker.h new file mode 100644 index 0000000..e1eff00 --- /dev/null +++ b/include/linux/interface_tracker.h @@ -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 */ -- 1.8.3.2 -- To unsubscribe from this list: send the line "unsubscribe linux-samsung-soc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html