summary refs log tree commit diff
path: root/drivers/media/v4l2-core/v4l2-event.c
diff options
context:
space:
mode:
authorMauro Carvalho Chehab <mchehab@redhat.com>2012-06-14 16:35:52 -0300
committerMauro Carvalho Chehab <mchehab@redhat.com>2012-08-13 23:02:38 -0300
commit5bc3cb743bbab408792c1b4ef31adf6268aa4b7e (patch)
tree94faf3260c80a9626b450a6472780828cdf03b26 /drivers/media/v4l2-core/v4l2-event.c
parent2ea4b442589b30210a166b9630c2547ebbe2cb82 (diff)
downloadlinux-5bc3cb743bbab408792c1b4ef31adf6268aa4b7e.tar.gz
[media] v4l: move v4l2 core into a separate directory
Currently, the v4l2 core is mixed together with other non-core drivers.
Move them into a separate directory.

Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Diffstat (limited to 'drivers/media/v4l2-core/v4l2-event.c')
-rw-r--r--drivers/media/v4l2-core/v4l2-event.c313
1 files changed, 313 insertions, 0 deletions
diff --git a/drivers/media/v4l2-core/v4l2-event.c b/drivers/media/v4l2-core/v4l2-event.c
new file mode 100644
index 000000000000..ef2a33c94045
--- /dev/null
+++ b/drivers/media/v4l2-core/v4l2-event.c
@@ -0,0 +1,313 @@
+/*
+ * v4l2-event.c
+ *
+ * V4L2 events.
+ *
+ * Copyright (C) 2009--2010 Nokia Corporation.
+ *
+ * Contact: Sakari Ailus <sakari.ailus@maxwell.research.nokia.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.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include <media/v4l2-dev.h>
+#include <media/v4l2-fh.h>
+#include <media/v4l2-event.h>
+
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/export.h>
+
+static unsigned sev_pos(const struct v4l2_subscribed_event *sev, unsigned idx)
+{
+	idx += sev->first;
+	return idx >= sev->elems ? idx - sev->elems : idx;
+}
+
+static int __v4l2_event_dequeue(struct v4l2_fh *fh, struct v4l2_event *event)
+{
+	struct v4l2_kevent *kev;
+	unsigned long flags;
+
+	spin_lock_irqsave(&fh->vdev->fh_lock, flags);
+
+	if (list_empty(&fh->available)) {
+		spin_unlock_irqrestore(&fh->vdev->fh_lock, flags);
+		return -ENOENT;
+	}
+
+	WARN_ON(fh->navailable == 0);
+
+	kev = list_first_entry(&fh->available, struct v4l2_kevent, list);
+	list_del(&kev->list);
+	fh->navailable--;
+
+	kev->event.pending = fh->navailable;
+	*event = kev->event;
+	kev->sev->first = sev_pos(kev->sev, 1);
+	kev->sev->in_use--;
+
+	spin_unlock_irqrestore(&fh->vdev->fh_lock, flags);
+
+	return 0;
+}
+
+int v4l2_event_dequeue(struct v4l2_fh *fh, struct v4l2_event *event,
+		       int nonblocking)
+{
+	int ret;
+
+	if (nonblocking)
+		return __v4l2_event_dequeue(fh, event);
+
+	/* Release the vdev lock while waiting */
+	if (fh->vdev->lock)
+		mutex_unlock(fh->vdev->lock);
+
+	do {
+		ret = wait_event_interruptible(fh->wait,
+					       fh->navailable != 0);
+		if (ret < 0)
+			break;
+
+		ret = __v4l2_event_dequeue(fh, event);
+	} while (ret == -ENOENT);
+
+	if (fh->vdev->lock)
+		mutex_lock(fh->vdev->lock);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(v4l2_event_dequeue);
+
+/* Caller must hold fh->vdev->fh_lock! */
+static struct v4l2_subscribed_event *v4l2_event_subscribed(
+		struct v4l2_fh *fh, u32 type, u32 id)
+{
+	struct v4l2_subscribed_event *sev;
+
+	assert_spin_locked(&fh->vdev->fh_lock);
+
+	list_for_each_entry(sev, &fh->subscribed, list)
+		if (sev->type == type && sev->id == id)
+			return sev;
+
+	return NULL;
+}
+
+static void __v4l2_event_queue_fh(struct v4l2_fh *fh, const struct v4l2_event *ev,
+		const struct timespec *ts)
+{
+	struct v4l2_subscribed_event *sev;
+	struct v4l2_kevent *kev;
+	bool copy_payload = true;
+
+	/* Are we subscribed? */
+	sev = v4l2_event_subscribed(fh, ev->type, ev->id);
+	if (sev == NULL)
+		return;
+
+	/*
+	 * If the event has been added to the fh->subscribed list, but its
+	 * add op has not completed yet elems will be 0, treat this as
+	 * not being subscribed.
+	 */
+	if (!sev->elems)
+		return;
+
+	/* Increase event sequence number on fh. */
+	fh->sequence++;
+
+	/* Do we have any free events? */
+	if (sev->in_use == sev->elems) {
+		/* no, remove the oldest one */
+		kev = sev->events + sev_pos(sev, 0);
+		list_del(&kev->list);
+		sev->in_use--;
+		sev->first = sev_pos(sev, 1);
+		fh->navailable--;
+		if (sev->elems == 1) {
+			if (sev->ops && sev->ops->replace) {
+				sev->ops->replace(&kev->event, ev);
+				copy_payload = false;
+			}
+		} else if (sev->ops && sev->ops->merge) {
+			struct v4l2_kevent *second_oldest =
+				sev->events + sev_pos(sev, 0);
+			sev->ops->merge(&kev->event, &second_oldest->event);
+		}
+	}
+
+	/* Take one and fill it. */
+	kev = sev->events + sev_pos(sev, sev->in_use);
+	kev->event.type = ev->type;
+	if (copy_payload)
+		kev->event.u = ev->u;
+	kev->event.id = ev->id;
+	kev->event.timestamp = *ts;
+	kev->event.sequence = fh->sequence;
+	sev->in_use++;
+	list_add_tail(&kev->list, &fh->available);
+
+	fh->navailable++;
+
+	wake_up_all(&fh->wait);
+}
+
+void v4l2_event_queue(struct video_device *vdev, const struct v4l2_event *ev)
+{
+	struct v4l2_fh *fh;
+	unsigned long flags;
+	struct timespec timestamp;
+
+	ktime_get_ts(&timestamp);
+
+	spin_lock_irqsave(&vdev->fh_lock, flags);
+
+	list_for_each_entry(fh, &vdev->fh_list, list)
+		__v4l2_event_queue_fh(fh, ev, &timestamp);
+
+	spin_unlock_irqrestore(&vdev->fh_lock, flags);
+}
+EXPORT_SYMBOL_GPL(v4l2_event_queue);
+
+void v4l2_event_queue_fh(struct v4l2_fh *fh, const struct v4l2_event *ev)
+{
+	unsigned long flags;
+	struct timespec timestamp;
+
+	ktime_get_ts(&timestamp);
+
+	spin_lock_irqsave(&fh->vdev->fh_lock, flags);
+	__v4l2_event_queue_fh(fh, ev, &timestamp);
+	spin_unlock_irqrestore(&fh->vdev->fh_lock, flags);
+}
+EXPORT_SYMBOL_GPL(v4l2_event_queue_fh);
+
+int v4l2_event_pending(struct v4l2_fh *fh)
+{
+	return fh->navailable;
+}
+EXPORT_SYMBOL_GPL(v4l2_event_pending);
+
+int v4l2_event_subscribe(struct v4l2_fh *fh,
+			 struct v4l2_event_subscription *sub, unsigned elems,
+			 const struct v4l2_subscribed_event_ops *ops)
+{
+	struct v4l2_subscribed_event *sev, *found_ev;
+	unsigned long flags;
+	unsigned i;
+
+	if (sub->type == V4L2_EVENT_ALL)
+		return -EINVAL;
+
+	if (elems < 1)
+		elems = 1;
+
+	sev = kzalloc(sizeof(*sev) + sizeof(struct v4l2_kevent) * elems, GFP_KERNEL);
+	if (!sev)
+		return -ENOMEM;
+	for (i = 0; i < elems; i++)
+		sev->events[i].sev = sev;
+	sev->type = sub->type;
+	sev->id = sub->id;
+	sev->flags = sub->flags;
+	sev->fh = fh;
+	sev->ops = ops;
+
+	spin_lock_irqsave(&fh->vdev->fh_lock, flags);
+	found_ev = v4l2_event_subscribed(fh, sub->type, sub->id);
+	if (!found_ev)
+		list_add(&sev->list, &fh->subscribed);
+	spin_unlock_irqrestore(&fh->vdev->fh_lock, flags);
+
+	if (found_ev) {
+		kfree(sev);
+		return 0; /* Already listening */
+	}
+
+	if (sev->ops && sev->ops->add) {
+		int ret = sev->ops->add(sev, elems);
+		if (ret) {
+			sev->ops = NULL;
+			v4l2_event_unsubscribe(fh, sub);
+			return ret;
+		}
+	}
+
+	/* Mark as ready for use */
+	sev->elems = elems;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(v4l2_event_subscribe);
+
+void v4l2_event_unsubscribe_all(struct v4l2_fh *fh)
+{
+	struct v4l2_event_subscription sub;
+	struct v4l2_subscribed_event *sev;
+	unsigned long flags;
+
+	do {
+		sev = NULL;
+
+		spin_lock_irqsave(&fh->vdev->fh_lock, flags);
+		if (!list_empty(&fh->subscribed)) {
+			sev = list_first_entry(&fh->subscribed,
+					struct v4l2_subscribed_event, list);
+			sub.type = sev->type;
+			sub.id = sev->id;
+		}
+		spin_unlock_irqrestore(&fh->vdev->fh_lock, flags);
+		if (sev)
+			v4l2_event_unsubscribe(fh, &sub);
+	} while (sev);
+}
+EXPORT_SYMBOL_GPL(v4l2_event_unsubscribe_all);
+
+int v4l2_event_unsubscribe(struct v4l2_fh *fh,
+			   struct v4l2_event_subscription *sub)
+{
+	struct v4l2_subscribed_event *sev;
+	unsigned long flags;
+	int i;
+
+	if (sub->type == V4L2_EVENT_ALL) {
+		v4l2_event_unsubscribe_all(fh);
+		return 0;
+	}
+
+	spin_lock_irqsave(&fh->vdev->fh_lock, flags);
+
+	sev = v4l2_event_subscribed(fh, sub->type, sub->id);
+	if (sev != NULL) {
+		/* Remove any pending events for this subscription */
+		for (i = 0; i < sev->in_use; i++) {
+			list_del(&sev->events[sev_pos(sev, i)].list);
+			fh->navailable--;
+		}
+		list_del(&sev->list);
+	}
+
+	spin_unlock_irqrestore(&fh->vdev->fh_lock, flags);
+
+	if (sev && sev->ops && sev->ops->del)
+		sev->ops->del(sev);
+
+	kfree(sev);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(v4l2_event_unsubscribe);