Merge branch 'v4l_for_linus' of git://linuxtv.org/mchehab/for_linus
[pandora-kernel.git] / drivers / media / video / v4l2-event.c
index 670f2f8..53b190c 100644 (file)
 #include <linux/sched.h>
 #include <linux/slab.h>
 
-static void v4l2_event_unsubscribe_all(struct v4l2_fh *fh);
-
-int v4l2_event_init(struct v4l2_fh *fh)
-{
-       fh->events = kzalloc(sizeof(*fh->events), GFP_KERNEL);
-       if (fh->events == NULL)
-               return -ENOMEM;
-
-       init_waitqueue_head(&fh->events->wait);
-
-       INIT_LIST_HEAD(&fh->events->free);
-       INIT_LIST_HEAD(&fh->events->available);
-       INIT_LIST_HEAD(&fh->events->subscribed);
-
-       fh->events->sequence = -1;
-
-       return 0;
-}
-EXPORT_SYMBOL_GPL(v4l2_event_init);
-
-int v4l2_event_alloc(struct v4l2_fh *fh, unsigned int n)
-{
-       struct v4l2_events *events = fh->events;
-       unsigned long flags;
-
-       if (!events) {
-               WARN_ON(1);
-               return -ENOMEM;
-       }
-
-       while (events->nallocated < n) {
-               struct v4l2_kevent *kev;
-
-               kev = kzalloc(sizeof(*kev), GFP_KERNEL);
-               if (kev == NULL)
-                       return -ENOMEM;
-
-               spin_lock_irqsave(&fh->vdev->fh_lock, flags);
-               list_add_tail(&kev->list, &events->free);
-               events->nallocated++;
-               spin_unlock_irqrestore(&fh->vdev->fh_lock, flags);
-       }
-
-       return 0;
-}
-EXPORT_SYMBOL_GPL(v4l2_event_alloc);
-
-#define list_kfree(list, type, member)                         \
-       while (!list_empty(list)) {                             \
-               type *hi;                                       \
-               hi = list_first_entry(list, type, member);      \
-               list_del(&hi->member);                          \
-               kfree(hi);                                      \
-       }
-
-void v4l2_event_free(struct v4l2_fh *fh)
+static unsigned sev_pos(const struct v4l2_subscribed_event *sev, unsigned idx)
 {
-       struct v4l2_events *events = fh->events;
-
-       if (!events)
-               return;
-
-       list_kfree(&events->free, struct v4l2_kevent, list);
-       list_kfree(&events->available, struct v4l2_kevent, list);
-       v4l2_event_unsubscribe_all(fh);
-
-       kfree(events);
-       fh->events = NULL;
+       idx += sev->first;
+       return idx >= sev->elems ? idx - sev->elems : idx;
 }
-EXPORT_SYMBOL_GPL(v4l2_event_free);
 
 static int __v4l2_event_dequeue(struct v4l2_fh *fh, struct v4l2_event *event)
 {
-       struct v4l2_events *events = fh->events;
        struct v4l2_kevent *kev;
        unsigned long flags;
 
        spin_lock_irqsave(&fh->vdev->fh_lock, flags);
 
-       if (list_empty(&events->available)) {
+       if (list_empty(&fh->available)) {
                spin_unlock_irqrestore(&fh->vdev->fh_lock, flags);
                return -ENOENT;
        }
 
-       WARN_ON(events->navailable == 0);
+       WARN_ON(fh->navailable == 0);
 
-       kev = list_first_entry(&events->available, struct v4l2_kevent, list);
-       list_move(&kev->list, &events->free);
-       events->navailable--;
+       kev = list_first_entry(&fh->available, struct v4l2_kevent, list);
+       list_del(&kev->list);
+       fh->navailable--;
 
-       kev->event.pending = events->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);
 
@@ -131,7 +67,6 @@ static int __v4l2_event_dequeue(struct v4l2_fh *fh, struct v4l2_event *event)
 int v4l2_event_dequeue(struct v4l2_fh *fh, struct v4l2_event *event,
                       int nonblocking)
 {
-       struct v4l2_events *events = fh->events;
        int ret;
 
        if (nonblocking)
@@ -142,8 +77,8 @@ int v4l2_event_dequeue(struct v4l2_fh *fh, struct v4l2_event *event,
                mutex_unlock(fh->vdev->lock);
 
        do {
-               ret = wait_event_interruptible(events->wait,
-                                              events->navailable != 0);
+               ret = wait_event_interruptible(fh->wait,
+                                              fh->navailable != 0);
                if (ret < 0)
                        break;
 
@@ -161,15 +96,13 @@ EXPORT_SYMBOL_GPL(v4l2_event_dequeue);
 static struct v4l2_subscribed_event *v4l2_event_subscribed(
                struct v4l2_fh *fh, u32 type, u32 id)
 {
-       struct v4l2_events *events = fh->events;
        struct v4l2_subscribed_event *sev;
 
        assert_spin_locked(&fh->vdev->fh_lock);
 
-       list_for_each_entry(sev, &events->subscribed, list) {
+       list_for_each_entry(sev, &fh->subscribed, list)
                if (sev->type == type && sev->id == id)
                        return sev;
-       }
 
        return NULL;
 }
@@ -177,9 +110,9 @@ static struct v4l2_subscribed_event *v4l2_event_subscribed(
 static void __v4l2_event_queue_fh(struct v4l2_fh *fh, const struct v4l2_event *ev,
                const struct timespec *ts)
 {
-       struct v4l2_events *events = fh->events;
        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);
@@ -187,24 +120,42 @@ static void __v4l2_event_queue_fh(struct v4l2_fh *fh, const struct v4l2_event *e
                return;
 
        /* Increase event sequence number on fh. */
-       events->sequence++;
+       fh->sequence++;
 
        /* Do we have any free events? */
-       if (list_empty(&events->free))
-               return;
+       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->replace) {
+                               sev->replace(&kev->event, ev);
+                               copy_payload = false;
+                       }
+               } else if (sev->merge) {
+                       struct v4l2_kevent *second_oldest =
+                               sev->events + sev_pos(sev, 0);
+                       sev->merge(&kev->event, &second_oldest->event);
+               }
+       }
 
        /* Take one and fill it. */
-       kev = list_first_entry(&events->free, struct v4l2_kevent, list);
+       kev = sev->events + sev_pos(sev, sev->in_use);
        kev->event.type = ev->type;
-       kev->event.u = ev->u;
+       if (copy_payload)
+               kev->event.u = ev->u;
        kev->event.id = ev->id;
        kev->event.timestamp = *ts;
-       kev->event.sequence = events->sequence;
-       list_move_tail(&kev->list, &events->available);
+       kev->event.sequence = fh->sequence;
+       sev->in_use++;
+       list_add_tail(&kev->list, &fh->available);
 
-       events->navailable++;
+       fh->navailable++;
 
-       wake_up_all(&events->wait);
+       wake_up_all(&fh->wait);
 }
 
 void v4l2_event_queue(struct video_device *vdev, const struct v4l2_event *ev)
@@ -217,9 +168,8 @@ void v4l2_event_queue(struct video_device *vdev, const struct v4l2_event *ev)
 
        spin_lock_irqsave(&vdev->fh_lock, flags);
 
-       list_for_each_entry(fh, &vdev->fh_list, list) {
+       list_for_each_entry(fh, &vdev->fh_list, list)
                __v4l2_event_queue_fh(fh, ev, &timestamp);
-       }
 
        spin_unlock_irqrestore(&vdev->fh_lock, flags);
 }
@@ -240,73 +190,72 @@ EXPORT_SYMBOL_GPL(v4l2_event_queue_fh);
 
 int v4l2_event_pending(struct v4l2_fh *fh)
 {
-       return fh->events->navailable;
+       return fh->navailable;
 }
 EXPORT_SYMBOL_GPL(v4l2_event_pending);
 
+static void ctrls_replace(struct v4l2_event *old, const struct v4l2_event *new)
+{
+       u32 old_changes = old->u.ctrl.changes;
+
+       old->u.ctrl = new->u.ctrl;
+       old->u.ctrl.changes |= old_changes;
+}
+
+static void ctrls_merge(const struct v4l2_event *old, struct v4l2_event *new)
+{
+       new->u.ctrl.changes |= old->u.ctrl.changes;
+}
+
 int v4l2_event_subscribe(struct v4l2_fh *fh,
-                        struct v4l2_event_subscription *sub)
+                        struct v4l2_event_subscription *sub, unsigned elems)
 {
-       struct v4l2_events *events = fh->events;
        struct v4l2_subscribed_event *sev, *found_ev;
        struct v4l2_ctrl *ctrl = NULL;
-       struct v4l2_ctrl_fh *ctrl_fh = NULL;
        unsigned long flags;
+       unsigned i;
 
-       if (fh->events == NULL) {
-               WARN_ON(1);
-               return -ENOMEM;
-       }
-
+       if (elems < 1)
+               elems = 1;
        if (sub->type == V4L2_EVENT_CTRL) {
                ctrl = v4l2_ctrl_find(fh->ctrl_handler, sub->id);
                if (ctrl == NULL)
                        return -EINVAL;
        }
 
-       sev = kmalloc(sizeof(*sev), GFP_KERNEL);
+       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->elems = elems;
        if (ctrl) {
-               ctrl_fh = kzalloc(sizeof(*ctrl_fh), GFP_KERNEL);
-               if (!ctrl_fh) {
-                       kfree(sev);
-                       return -ENOMEM;
-               }
-               ctrl_fh->fh = fh;
+               sev->replace = ctrls_replace;
+               sev->merge = ctrls_merge;
        }
 
        spin_lock_irqsave(&fh->vdev->fh_lock, flags);
-
        found_ev = v4l2_event_subscribed(fh, sub->type, sub->id);
-       if (!found_ev) {
-               INIT_LIST_HEAD(&sev->list);
-               sev->type = sub->type;
-               sev->id = sub->id;
-
-               list_add(&sev->list, &events->subscribed);
-               sev = NULL;
-       }
-
+       if (!found_ev)
+               list_add(&sev->list, &fh->subscribed);
        spin_unlock_irqrestore(&fh->vdev->fh_lock, flags);
 
-       /* v4l2_ctrl_add_fh uses a mutex, so do this outside the spin lock */
-       if (ctrl) {
-               if (found_ev)
-                       kfree(ctrl_fh);
-               else
-                       v4l2_ctrl_add_fh(fh->ctrl_handler, ctrl_fh, sub);
-       }
-
-       kfree(sev);
+       /* v4l2_ctrl_add_event uses a mutex, so do this outside the spin lock */
+       if (found_ev)
+               kfree(sev);
+       else if (ctrl)
+               v4l2_ctrl_add_event(ctrl, sev);
 
        return 0;
 }
 EXPORT_SYMBOL_GPL(v4l2_event_subscribe);
 
-static void v4l2_event_unsubscribe_all(struct v4l2_fh *fh)
+void v4l2_event_unsubscribe_all(struct v4l2_fh *fh)
 {
-       struct v4l2_events *events = fh->events;
        struct v4l2_event_subscription sub;
        struct v4l2_subscribed_event *sev;
        unsigned long flags;
@@ -315,8 +264,8 @@ static void v4l2_event_unsubscribe_all(struct v4l2_fh *fh)
                sev = NULL;
 
                spin_lock_irqsave(&fh->vdev->fh_lock, flags);
-               if (!list_empty(&events->subscribed)) {
-                       sev = list_first_entry(&events->subscribed,
+               if (!list_empty(&fh->subscribed)) {
+                       sev = list_first_entry(&fh->subscribed,
                                        struct v4l2_subscribed_event, list);
                        sub.type = sev->type;
                        sub.id = sev->id;
@@ -326,6 +275,7 @@ static void v4l2_event_unsubscribe_all(struct v4l2_fh *fh)
                        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)
@@ -341,15 +291,17 @@ int v4l2_event_unsubscribe(struct v4l2_fh *fh,
        spin_lock_irqsave(&fh->vdev->fh_lock, flags);
 
        sev = v4l2_event_subscribed(fh, sub->type, sub->id);
-       if (sev != NULL)
+       if (sev != NULL) {
                list_del(&sev->list);
+               sev->fh = NULL;
+       }
 
        spin_unlock_irqrestore(&fh->vdev->fh_lock, flags);
-       if (sev->type == V4L2_EVENT_CTRL) {
+       if (sev && sev->type == V4L2_EVENT_CTRL) {
                struct v4l2_ctrl *ctrl = v4l2_ctrl_find(fh->ctrl_handler, sev->id);
 
                if (ctrl)
-                       v4l2_ctrl_del_fh(ctrl, fh);
+                       v4l2_ctrl_del_event(ctrl, sev);
        }
 
        kfree(sev);