All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH/RFC v1 0/2] Add support for out-of-order buffer dequeuing
@ 2010-04-21 16:10 Pawel Osciak
  2010-04-21 16:10 ` [PATCH v1 1/2] v4l: videobuf: " Pawel Osciak
  2010-04-21 16:10 ` [PATCH v1 2/2] v4l: vivi: adapt to out-of-order buffer dequeuing in videobuf Pawel Osciak
  0 siblings, 2 replies; 4+ messages in thread
From: Pawel Osciak @ 2010-04-21 16:10 UTC (permalink / raw)
  To: linux-media; +Cc: p.osciak, m.szyprowski, kyungmin.park

Hello,

this patch adds support for dequeuing video buffers out-of-order, i.e. not
in a FIFO order.

It is closely related to my previous RFC, to which no responses were given
(much to my disappointment, as an insight into what others think of the "done"
 waitqueues in videobuf_buffer could potentially shed a new light on the issue):
http://article.gmane.org/gmane.linux.drivers.video-input-infrastructure/17904


==================
Current behavior
==================
Currently, videobuf stores all the buffers passed from the userspace
on its "stream" list. They are inserted to it in the same order as
they are queued by the userspace.
When dequeuing/polling, the next (i.e. first queued) buffer from that list
is always used. In result, it is not possible to dequeue other buffers from
the stream queue first. If an another buffer is marked as finished by the
driver, it is not returned before all the buffers that have been queued
earlier, even if it takes more time for them to finish.

The videobuf_buffer structure includes a "done" waitqueue, on which we are
expected to sleep while waiting for each particular buffer to finish being
processed. The usefulness of having a separate waitqueue for every buffer
has been questioned in my previous RFC. As there was no response, I am
beginning to assume that there is no one who would like to speak in favor
of keeping them, although I am not removing them in this patch.

Dequeuing
----------------------------
videobuf_dqbuf() takes the next (i.e. first) buffer from the stream list and
checks the "state" member of videobuf_buffer to determine whether it has
already been marked as done (VIDEOBUF_DONE) or an error has occurred
(indicated by VIDEOBUF_ERROR state). If neither is true, we go to sleep on
that buffer's waitqueue.

Drivers mark buffers as done by:
1. setting their state to one of those constants and
2. waking up all processes sleeping on the "done" waitqueue.

Polling
----------------------------
videobuf_poll_stream() also takes the first buffer (if available) from
the stream list and behaves in a similar fashion to dqbuf.


Both dqbuf and poll do not check or return buffers other than the first
one. Even if a driver marks other buffers as finished first, they will not be
used (polled/dequeued) before their predecessors.

V4L2 API
----------------------------
>From the point of view of V4L2 API, no particular order of buffers is
specified. Applications are expected to check the index in struct v4l2_buffer
in order to determine which buffer has been dequeued.


==================
This patch
==================

Rationale
----------------------------
Although FIFO is enough for most hardware, it is essential for some types of
devices, particularly video codecs, to allow dequeuing buffers in an arbitrary
order. Video codecs may need to hold some buffers (reference frames) for longer
periods of time than others.

Moreover, even if not required, introducing such an ability may improve
performance of applications that utilize devices which have different
processing times for different buffers.

A different matter is that, in my opinion, exposing the nuts and bolts of
waking up processes sleeping on done waitqueues to drivers is slightly
inelegant and a bit too verbose. The drivers should be marking the buffers
as done with a more abstract call and not be exposed to the inner workings of
videobuf.

Finally, current situation ties up drivers to videobuf behavior a bit too
much in my opinion, as indicated in the videobuf_has_consumers() example below.


Changes
----------------------------
This patch adds the ability to return any buffer that has already been marked
as finished. Buffers are dequeued in FIFO order, but not based on the order
in which they were queued, but in the order in which the driver has marked
them as done instead.

This results in two main advantages:
1. Buffers are returned to userspace as soon as drivers finish with them
   (although curretnly drivers usually do return them in the same order as
    queued).
2. Out-of-order dequeuing becomes possible for drivers that require such an
   ability.


Two exported functions are introduced in this patch:

1. videobuf_has_consumers()
----------------------------
Returns 1 when userspace is waiting for buffers.

To achieve this in the previous version, a driver had to take the first
buffer that has been queued and check whether its done waitqueue is active.
An example from vivi:

 627        spin_lock_irqsave(&dev->slock, flags);
 628        if (list_empty(&dma_q->active)) {
 629                dprintk(dev, 1, "No active queue to serve\n");
 630                goto unlock;
 631        }
 632
 633        buf = list_entry(dma_q->active.next,
 634                         struct vivi_buffer, vb.queue);
 635
 636        /* Nobody is waiting on this buffer, return */
 637        if (!waitqueue_active(&buf->vb.done))
 638                goto unlock;
(...)
 650unlock:
 651        spin_unlock_irqrestore(&dev->slock, flags);

This worked, but required the checked buffer (active.next) to be the same as
stream.next in videobuf. This ties up videobuf and driver tightly together,
resulting in unclear dependencies and may cause obscure bugs.

Moreover, there is no more need to pass a particular buffer. We could also
introduce a similar function for explicitly passed buffers though.


2. videobuf_buf_finish()
----------------------------
This function should simply replace wake_up() calls in drivers. irq spinlock
should not be held when calling.


Compatibility
----------------------------
For this patch to be backwards-compatible, similar changes as in the included
vivi patch would have to be introduced. These are very simple (practically one 
line per driver) and fully automatic and include replacement of wake_up calls
with videobuf_buf_finish() and waitqueue_active() on done waitqueues with
videobuf_has_consumers(). The "done" waitqueues will work as previously.


==================
Next steps
==================
The next step could actually include getting rid of the "done" waitqueues from
the videobuf_buffer completely. As waiting is performed on only one of them
at one time anyway, I see little reason to keep them. During my initial
investigation I found no drivers that would depend on the ability to wait on
a particular buffer (instead of waiting for any buffer in general).


Comments highly appreciated!


This series includes:
[PATCH v1 1/2] v4l: videobuf: Add support for out-of-order buffer dequeuing.
[PATCH v1 2/2] v4l: vivi: adapt to out-of-order buffer dequeuing in videobuf.


Best regards,
--
Pawel Osciak
Linux Platform Group
Samsung Poland R&D Center

^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH v1 1/2] v4l: videobuf: Add support for out-of-order buffer dequeuing.
  2010-04-21 16:10 [PATCH/RFC v1 0/2] Add support for out-of-order buffer dequeuing Pawel Osciak
@ 2010-04-21 16:10 ` Pawel Osciak
  2010-04-22  6:59   ` Pawel Osciak
  2010-04-21 16:10 ` [PATCH v1 2/2] v4l: vivi: adapt to out-of-order buffer dequeuing in videobuf Pawel Osciak
  1 sibling, 1 reply; 4+ messages in thread
From: Pawel Osciak @ 2010-04-21 16:10 UTC (permalink / raw)
  To: linux-media; +Cc: p.osciak, m.szyprowski, kyungmin.park

Drivers can now finish processing on and return video buffers in
an arbitrary order. Before this patch, this was possible in a FIFO order
only. This is useful e.g. for video codecs, which often need to hold some
buffers (e.g. keyframes) for longer periods of time than others.

Signed-off-by: Pawel Osciak <p.osciak@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
---
 drivers/media/video/videobuf-core.c |  160 +++++++++++++++++++++++-----------
 include/media/videobuf-core.h       |    9 ++
 2 files changed, 117 insertions(+), 52 deletions(-)

diff --git a/drivers/media/video/videobuf-core.c b/drivers/media/video/videobuf-core.c
index 77899ca..ea5fd39 100644
--- a/drivers/media/video/videobuf-core.c
+++ b/drivers/media/video/videobuf-core.c
@@ -95,6 +95,27 @@ int videobuf_waiton(struct videobuf_buffer *vb, int non_blocking, int intr)
 }
 EXPORT_SYMBOL_GPL(videobuf_waiton);
 
+int videobuf_has_consumers(struct videobuf_queue *q)
+{
+	return waitqueue_active(&q->vb_done_wait);
+}
+EXPORT_SYMBOL_GPL(videobuf_has_consumers);
+
+void videobuf_buf_finish(struct videobuf_queue *q, struct videobuf_buffer *vb)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&q->vb_done_lock, flags);
+	list_add_tail(&vb->done_list, &q->vb_done_list);
+	spin_unlock_irqrestore(&q->vb_done_lock, flags);
+
+	spin_lock_irqsave(q->irqlock, flags);
+	wake_up(&vb->done);
+	wake_up_interruptible(&q->vb_done_wait);
+	spin_unlock_irqrestore(q->irqlock, flags);
+}
+EXPORT_SYMBOL_GPL(videobuf_buf_finish);
+
 int videobuf_iolock(struct videobuf_queue *q, struct videobuf_buffer *vb,
 		    struct v4l2_framebuffer *fbuf)
 {
@@ -153,7 +174,10 @@ void videobuf_queue_core_init(struct videobuf_queue *q,
 
 	mutex_init(&q->vb_lock);
 	init_waitqueue_head(&q->wait);
+	init_waitqueue_head(&q->vb_done_wait);
 	INIT_LIST_HEAD(&q->stream);
+	INIT_LIST_HEAD(&q->vb_done_list);
+	spin_lock_init(&q->vb_done_lock);
 }
 EXPORT_SYMBOL_GPL(videobuf_queue_core_init);
 
@@ -217,6 +241,7 @@ void videobuf_queue_cancel(struct videobuf_queue *q)
 			wake_up_all(&q->bufs[i]->done);
 		}
 	}
+	wake_up_all(&q->vb_done_wait);
 	spin_unlock_irqrestore(q->irqlock, flags);
 
 	/* free all buffers + clear queue */
@@ -603,67 +628,81 @@ done:
 EXPORT_SYMBOL_GPL(videobuf_qbuf);
 
 /* Locking: Caller holds q->vb_lock */
-static int stream_next_buffer_check_queue(struct videobuf_queue *q, int noblock)
+static int wait_for_buffer(struct videobuf_queue *q, int nonblocking)
 {
-	int retval;
+	int retval = 0;
 
 checks:
 	if (!q->streaming) {
-		dprintk(1, "next_buffer: Not streaming\n");
+		dprintk(1, "Not streaming\n");
 		retval = -EINVAL;
-		goto done;
+		goto end;
 	}
 
-	if (list_empty(&q->stream)) {
-		if (noblock) {
+	/*
+	 * Buffers may be added to vb_done_list without holding the vb_lock,
+	 * but removal is performed only while holding both vb_lock and the
+	 * vb_done_lock spinlock. Thus we can be sure that as long as we hold
+	 * vb_lock, the list will remain not empty if this check succeeds.
+	 */
+	if (list_empty(&q->vb_done_list)) {
+		if (nonblocking) {
+			dprintk(1, "Nonblocking and no buffers to dequeue\n");
 			retval = -EAGAIN;
-			dprintk(2, "next_buffer: no buffers to dequeue\n");
-			goto done;
-		} else {
-			dprintk(2, "next_buffer: waiting on buffer\n");
-
-			/* Drop lock to avoid deadlock with qbuf */
-			mutex_unlock(&q->vb_lock);
-
-			/* Checking list_empty and streaming is safe without
-			 * locks because we goto checks to validate while
-			 * holding locks before proceeding */
-			retval = wait_event_interruptible(q->wait,
-				!list_empty(&q->stream) || !q->streaming);
-			mutex_lock(&q->vb_lock);
-
-			if (retval)
-				goto done;
-
-			goto checks;
+			goto end;
 		}
-	}
 
-	retval = 0;
+		/*
+		 * We are streaming and nonblocking, wait for another buffer to
+		 * become ready or for streamoff. vb_lock is released to allow
+		 * streamoff and qbuf in parallel.
+		 */
+		mutex_unlock(&q->vb_lock);
+		/*
+		 * Although the mutex is released here, we will be reevaluating
+		 * both conditions again after reacquiring it.
+		 */
+		retval = wait_event_interruptible(q->vb_done_wait,
+				!list_empty(&q->vb_done_list) || !q->streaming);
+		mutex_lock(&q->vb_lock);
+
+		if (retval)
+			goto end;
+
+		goto checks;
+	}
 
-done:
+	/*
+	 * At least one buffer is on the vb_done_list and nothing can be removed
+	 * from it without acquiring the vb_lock, which we are holding now.
+	 */
+end:
 	return retval;
 }
 
 /* Locking: Caller holds q->vb_lock */
-static int stream_next_buffer(struct videobuf_queue *q,
-			struct videobuf_buffer **vb, int nonblocking)
+static int get_done_buffer(struct videobuf_queue *q,
+			   struct videobuf_buffer **vb, int nonblocking)
 {
-	int retval;
-	struct videobuf_buffer *buf = NULL;
-
-	retval = stream_next_buffer_check_queue(q, nonblocking);
-	if (retval)
-		goto done;
-
-	buf = list_entry(q->stream.next, struct videobuf_buffer, stream);
-	retval = videobuf_waiton(buf, nonblocking, 1);
-	if (retval < 0)
-		goto done;
-
-	*vb = buf;
-done:
-	return retval;
+	unsigned long flags;
+	int ret = 0;
+
+	ret = wait_for_buffer(q, nonblocking);
+	if (ret)
+		goto end;
+
+	/*
+	 * vb_lock has been held since we last verified that vb_done_list is
+	 * not empty, no need for another list_empty().
+	 */
+	spin_lock_irqsave(&q->vb_done_lock, flags);
+	*vb = list_first_entry(&q->vb_done_list, struct videobuf_buffer,
+				done_list);
+	list_del(&((*vb)->done_list));
+	spin_unlock_irqrestore(&q->vb_done_lock, flags);
+
+end:
+	return ret;
 }
 
 int videobuf_dqbuf(struct videobuf_queue *q,
@@ -676,7 +715,7 @@ int videobuf_dqbuf(struct videobuf_queue *q,
 
 	mutex_lock(&q->vb_lock);
 
-	retval = stream_next_buffer(q, &buf, nonblocking);
+	retval = get_done_buffer(q, &buf, nonblocking);
 	if (retval < 0) {
 		dprintk(1, "dqbuf: next_buffer error: %i\n", retval);
 		goto done;
@@ -1055,12 +1094,25 @@ unsigned int videobuf_poll_stream(struct file *file,
 {
 	struct videobuf_buffer *buf = NULL;
 	unsigned int rc = 0;
+	unsigned long flags;
 
 	mutex_lock(&q->vb_lock);
 	if (q->streaming) {
-		if (!list_empty(&q->stream))
-			buf = list_entry(q->stream.next,
-					 struct videobuf_buffer, stream);
+		if (list_empty(&q->stream)) {
+			rc = POLLERR;
+			goto end;
+		}
+
+		poll_wait(file, &q->vb_done_wait, wait);
+
+		spin_lock_irqsave(&q->vb_done_lock, flags);
+		if (!list_empty(&q->vb_done_list))
+			buf = list_first_entry(&q->vb_done_list,
+						struct videobuf_buffer,
+						done_list);
+		spin_unlock_irqrestore(&q->vb_done_lock, flags);
+		if (!buf)
+			goto end;
 	} else {
 		if (!q->reading)
 			__videobuf_read_start(q);
@@ -1074,12 +1126,15 @@ unsigned int videobuf_poll_stream(struct file *file,
 			q->read_off = 0;
 		}
 		buf = q->read_buf;
+		if (!buf) {
+			rc = POLLERR;
+			goto end;
+		}
+
+		poll_wait(file, &buf->done, wait);
 	}
-	if (!buf)
-		rc = POLLERR;
 
 	if (0 == rc) {
-		poll_wait(file, &buf->done, wait);
 		if (buf->state == VIDEOBUF_DONE ||
 		    buf->state == VIDEOBUF_ERROR) {
 			switch (q->type) {
@@ -1094,6 +1149,7 @@ unsigned int videobuf_poll_stream(struct file *file,
 			}
 		}
 	}
+end:
 	mutex_unlock(&q->vb_lock);
 	return rc;
 }
diff --git a/include/media/videobuf-core.h b/include/media/videobuf-core.h
index b1f7bf4..7b1cc94 100644
--- a/include/media/videobuf-core.h
+++ b/include/media/videobuf-core.h
@@ -82,6 +82,7 @@ struct videobuf_buffer {
 	enum v4l2_field         field;
 	enum videobuf_state     state;
 	struct list_head        stream;  /* QBUF/DQBUF list */
+	struct list_head	done_list;
 
 	/* touched by irq handler */
 	struct list_head        queue;
@@ -160,6 +161,10 @@ struct videobuf_queue {
 
 	wait_queue_head_t	   wait; /* wait if queue is empty */
 
+	wait_queue_head_t	   vb_done_wait;
+	struct list_head	   vb_done_list;
+	spinlock_t		   vb_done_lock;
+
 	enum v4l2_buf_type         type;
 	unsigned int               inputs; /* for V4L2_BUF_FLAG_INPUT */
 	unsigned int               msize;
@@ -206,6 +211,8 @@ void videobuf_queue_core_init(struct videobuf_queue *q,
 int  videobuf_queue_is_busy(struct videobuf_queue *q);
 void videobuf_queue_cancel(struct videobuf_queue *q);
 
+void videobuf_buf_finish(struct videobuf_queue *q, struct videobuf_buffer *vb);
+
 enum v4l2_field videobuf_next_field(struct videobuf_queue *q);
 int videobuf_reqbufs(struct videobuf_queue *q,
 		     struct v4l2_requestbuffers *req);
@@ -235,6 +242,8 @@ unsigned int videobuf_poll_stream(struct file *file,
 				  struct videobuf_queue *q,
 				  poll_table *wait);
 
+int videobuf_has_consumers(struct videobuf_queue *q);
+
 int videobuf_mmap_setup(struct videobuf_queue *q,
 			unsigned int bcount, unsigned int bsize,
 			enum v4l2_memory memory);
-- 
1.7.1.rc1.12.ga601


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH v1 2/2] v4l: vivi: adapt to out-of-order buffer dequeuing in videobuf.
  2010-04-21 16:10 [PATCH/RFC v1 0/2] Add support for out-of-order buffer dequeuing Pawel Osciak
  2010-04-21 16:10 ` [PATCH v1 1/2] v4l: videobuf: " Pawel Osciak
@ 2010-04-21 16:10 ` Pawel Osciak
  1 sibling, 0 replies; 4+ messages in thread
From: Pawel Osciak @ 2010-04-21 16:10 UTC (permalink / raw)
  To: linux-media; +Cc: p.osciak, m.szyprowski, kyungmin.park

Make vivi use new videobuf helpers for finishing processing a buffer and
checking for consumers.

Signed-off-by: Pawel Osciak <p.osciak@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
---
 drivers/media/video/vivi.c |   17 +++++++++--------
 1 files changed, 9 insertions(+), 8 deletions(-)

diff --git a/drivers/media/video/vivi.c b/drivers/media/video/vivi.c
index cdbe703..4a91761 100644
--- a/drivers/media/video/vivi.c
+++ b/drivers/media/video/vivi.c
@@ -630,13 +630,13 @@ static void vivi_thread_tick(struct vivi_fh *fh)
 		goto unlock;
 	}
 
-	buf = list_entry(dma_q->active.next,
-			 struct vivi_buffer, vb.queue);
-
-	/* Nobody is waiting on this buffer, return */
-	if (!waitqueue_active(&buf->vb.done))
+	if (!videobuf_has_consumers(&fh->vb_vidq)) {
+		dprintk(dev, 1, "No consumers\n");
 		goto unlock;
+	}
 
+	buf = list_entry(dma_q->active.next,
+			 struct vivi_buffer, vb.queue);
 	list_del(&buf->vb.queue);
 
 	do_gettimeofday(&buf->vb.ts);
@@ -645,11 +645,12 @@ static void vivi_thread_tick(struct vivi_fh *fh)
 	vivi_fillbuff(fh, buf);
 	dprintk(dev, 1, "filled buffer %p\n", buf);
 
-	wake_up(&buf->vb.done);
-	dprintk(dev, 2, "[%p/%d] wakeup\n", buf, buf->vb. i);
-unlock:
 	spin_unlock_irqrestore(&dev->slock, flags);
+	videobuf_buf_finish(&fh->vb_vidq, &buf->vb);
 	return;
+
+unlock:
+	spin_unlock_irqrestore(&dev->slock, flags);
 }
 
 #define frames_to_ms(frames)					\
-- 
1.7.1.rc1.12.ga601


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* RE: [PATCH v1 1/2] v4l: videobuf: Add support for out-of-order buffer dequeuing.
  2010-04-21 16:10 ` [PATCH v1 1/2] v4l: videobuf: " Pawel Osciak
@ 2010-04-22  6:59   ` Pawel Osciak
  0 siblings, 0 replies; 4+ messages in thread
From: Pawel Osciak @ 2010-04-22  6:59 UTC (permalink / raw)
  To: Pawel Osciak, linux-media; +Cc: Marek Szyprowski, kyungmin.park

Responding to my own e-mail here, but I just realized one little thing.


>Pawel Osciak <p.osciak@samsung.com> wrote:

[snip]

>+void videobuf_buf_finish(struct videobuf_queue *q, struct videobuf_buffer *vb)
>+{
>+	unsigned long flags;
>+
>+	spin_lock_irqsave(&q->vb_done_lock, flags);
>+	list_add_tail(&vb->done_list, &q->vb_done_list);
>+	spin_unlock_irqrestore(&q->vb_done_lock, flags);
>+
>+	spin_lock_irqsave(q->irqlock, flags);
>+	wake_up(&vb->done);
>+	wake_up_interruptible(&q->vb_done_wait);
>+	spin_unlock_irqrestore(q->irqlock, flags);
>+}
>+EXPORT_SYMBOL_GPL(videobuf_buf_finish);


There is a slight problem here if this function is not called from an interrupt
context (which is the case usually though). irqlock is not held for a period of
time and vb could potentially become NULL. So a recheck against vb == NULL is
required; alternatively the function could be called by driver while holding
the irqlock the whole time. So not really a problem, just a thing to point out.


Best regards
--
Pawel Osciak
Linux Platform Group
Samsung Poland R&D Center



^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2010-04-22  6:59 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-04-21 16:10 [PATCH/RFC v1 0/2] Add support for out-of-order buffer dequeuing Pawel Osciak
2010-04-21 16:10 ` [PATCH v1 1/2] v4l: videobuf: " Pawel Osciak
2010-04-22  6:59   ` Pawel Osciak
2010-04-21 16:10 ` [PATCH v1 2/2] v4l: vivi: adapt to out-of-order buffer dequeuing in videobuf Pawel Osciak

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.