RE: [PATCH 5/7] v4l: videobuf2: add read() emulator
Hello,
On Monday, October 25, 2010 2:13 AM Pawel Osciak wrote:
> Hi Marek,
> This is a pretty crafty patch, you've managed to make it nice and
> clean, without adding complexity to streaming code. Nice job. A few of
> my comments below.
Thanks!
> On Tue, Oct 19, 2010 at 23:41, Marek Szyprowski
> <m.szyprowski@xxxxxxxxxxx> wrote:
> > Add a generic read() emulator for videobuf2. It uses MMAP memory type
> > buffers and generic vb2 calls: req_bufs, qbuf and dqbuf. Video date is
> > being copied from mmap buffers to userspace with standard copy_to_user()
> > function. To add read() support to the driver, only one additional
> > structure should be provides which defines the default number of buffers
> > used by emulator and detemines the style of read() emulation
> > ('streaming' or 'one shot').
> >
> > Signed-off-by: Marek Szyprowski <m.szyprowski@xxxxxxxxxxx>
> > Signed-off-by: Kyungmin Park <kyungmin.park@xxxxxxxxxxx>
> > CC: Pawel Osciak <pawel@xxxxxxxxxx>
> > ---
> > drivers/media/video/videobuf2-core.c | 322 ++++++++++++++++++++++++++++++++--
> > include/media/videobuf2-core.h | 21 +++
> > 2 files changed, 325 insertions(+), 18 deletions(-)
> >
> > diff --git a/drivers/media/video/videobuf2-core.c b/drivers/media/video/videobuf2-core.c
> > index 4a29c49..ab00246 100644
> > --- a/drivers/media/video/videobuf2-core.c
> > +++ b/drivers/media/video/videobuf2-core.c
> > @@ -471,7 +471,7 @@ static bool __buffers_in_use(struct vb2_queue *q)
> > * The return values from this function are intended to be directly returned
> > * from vidioc_reqbufs handler in driver.
> > */
> > -int vb2_reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req)
> > +static int __vb2_reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req)
> > {
> > unsigned int num_buffers, num_planes;
> > int ret = 0;
> > @@ -482,8 +482,6 @@ int vb2_reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req)
> > return -EINVAL;
> > }
> >
> > - mutex_lock(&q->vb_lock);
> > -
> > if (req->type != q->type) {
> > dprintk(1, "reqbufs: queue type invalid\n");
> > ret = -EINVAL;
> > @@ -567,6 +565,15 @@ end:
> > mutex_unlock(&q->vb_lock);
> > return ret;
> > }
> > +
> > +int vb2_reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req)
> > +{
> > + int ret = 0;
> > + mutex_lock(&q->vb_lock);
> > + ret = (q->read_data) ? -EBUSY : __vb2_reqbufs(q, req);
> > + mutex_unlock(&q->vb_lock);
> > + return ret;
> > +}
> > EXPORT_SYMBOL_GPL(vb2_reqbufs);
> >
> > /**
> > @@ -821,14 +828,11 @@ static void __enqueue_in_driver(struct vb2_buffer *vb)
> > * The return values from this function are intended to be directly returned
> > * from vidioc_qbuf handler in driver.
> > */
> > -int vb2_qbuf(struct vb2_queue *q, struct v4l2_buffer *b)
> > +static int __vb2_qbuf(struct vb2_queue *q, struct v4l2_buffer *b)
> > {
> > struct vb2_buffer *vb;
> > - int ret;
> > -
> > - mutex_lock(&q->vb_lock);
> > + int ret = -EINVAL;
> >
> > - ret = -EINVAL;
> > if (b->type != q->type) {
> > dprintk(1, "qbuf: invalid buffer type\n");
> > goto done;
> > @@ -887,6 +891,14 @@ int vb2_qbuf(struct vb2_queue *q, struct v4l2_buffer *b)
> > dprintk(1, "qbuf of buffer %d succeeded\n", vb->v4l2_buf.index);
> > ret = 0;
> > done:
> > + return ret;
> > +}
> > +
> > +int vb2_qbuf(struct vb2_queue *q, struct v4l2_buffer *b)
> > +{
> > + int ret = 0;
> > + mutex_lock(&q->vb_lock);
> > + ret = (q->read_data) ? -EBUSY : __vb2_qbuf(q, b);
> > mutex_unlock(&q->vb_lock);
> > return ret;
> > }
> > @@ -996,13 +1008,11 @@ end:
> > * The return values from this function are intended to be directly returned
> > * from vidioc_dqbuf handler in driver.
> > */
> > -int vb2_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking)
> > +static int __vb2_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking)
> > {
> > struct vb2_buffer *vb = NULL;
> > int ret;
> >
> > - mutex_lock(&q->vb_lock);
> > -
> > if (b->type != q->type) {
> > dprintk(1, "dqbuf: invalid buffer type\n");
> > ret = -EINVAL;
> > @@ -1047,6 +1057,14 @@ int vb2_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking)
> > vb->state = VB2_BUF_STATE_DEQUEUED;
> >
> > done:
> > + return ret;
> > +}
> > +
> > +int vb2_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking)
> > +{
> > + int ret;
> > + mutex_lock(&q->vb_lock);
> > + ret = (q->read_data) ? -EBUSY : __vb2_dqbuf(q, b, nonblocking);
> > mutex_unlock(&q->vb_lock);
> > return ret;
> > }
> > @@ -1065,13 +1083,11 @@ EXPORT_SYMBOL_GPL(vb2_dqbuf);
> > * The return values from this function are intended to be directly returned
> > * from vidioc_streamon handler in the driver.
> > */
> > -int vb2_streamon(struct vb2_queue *q, enum v4l2_buf_type type)
> > +static int __vb2_streamon(struct vb2_queue *q, enum v4l2_buf_type type)
> > {
> > struct vb2_buffer *vb;
> > int ret = 0;
> >
> > - mutex_lock(&q->vb_lock);
> > -
> > if (type != q->type) {
> > dprintk(1, "streamon: invalid stream type\n");
> > ret = -EINVAL;
> > @@ -1113,6 +1129,14 @@ int vb2_streamon(struct vb2_queue *q, enum v4l2_buf_type type)
> >
> > dprintk(3, "Streamon successful\n");
> > done:
> > + return ret;
> > +}
> > +
> > +int vb2_streamon(struct vb2_queue *q, enum v4l2_buf_type type)
> > +{
> > + int ret;
> > + mutex_lock(&q->vb_lock);
> > + ret = (q->read_data) ? -EBUSY : __vb2_streamon(q, type);
> > mutex_unlock(&q->vb_lock);
> > return ret;
> > }
> > @@ -1161,12 +1185,10 @@ static void __vb2_queue_cancel(struct vb2_queue *q)
> > * The return values from this function are intended to be directly returned
> > * from vidioc_streamoff handler in the driver
> > */
> > -int vb2_streamoff(struct vb2_queue *q, enum v4l2_buf_type type)
> > +static int __vb2_streamoff(struct vb2_queue *q, enum v4l2_buf_type type)
> > {
> > int ret = 0;
> >
> > - mutex_lock(&q->vb_lock);
> > -
> > if (type != q->type) {
> > dprintk(1, "streamoff: invalid stream type\n");
> > ret = -EINVAL;
> > @@ -1187,6 +1209,14 @@ int vb2_streamoff(struct vb2_queue *q, enum v4l2_buf_type type)
> >
> > dprintk(3, "Streamoff successful\n");
> > end:
> > + return ret;
> > +}
> > +
> > +int vb2_streamoff(struct vb2_queue *q, enum v4l2_buf_type type)
> > +{
> > + int ret;
> > + mutex_lock(&q->vb_lock);
> > + ret = (q->read_data) ? -EBUSY : __vb2_streamoff(q, type);
> > mutex_unlock(&q->vb_lock);
> > return ret;
> > }
> > @@ -1311,6 +1341,9 @@ bool vb2_has_consumers(struct vb2_queue *q)
> > }
> > EXPORT_SYMBOL_GPL(vb2_has_consumers);
> >
> > +static int __vb2_init_read(struct vb2_queue *q);
> > +static int __vb2_cleanup_read(struct vb2_queue *q);
> > +
> > /**
> > * vb2_poll() - implements poll userspace operation
> > * @q: videobuf2 queue
> > @@ -1336,6 +1369,15 @@ unsigned int vb2_poll(struct vb2_queue *q, struct file *file, poll_table
> *wait)
> > mutex_lock(&q->vb_lock);
> >
> > /*
> > + * Start read() emulator if streaming api has not been used yet.
> > + */
> > + if (q->num_buffers == 0 && q->read_data == NULL && q->read_ctx) {
> > + ret = __vb2_init_read(q);
> > + if (ret)
> > + goto end;
> > + }
> > +
> > + /*
> > * There is nothing to wait for if no buffers have already been queued.
> > */
> > if (list_empty(&q->queued_list)) {
> > @@ -1378,12 +1420,15 @@ EXPORT_SYMBOL_GPL(vb2_poll);
> > * the given context will be used for memory allocation on all
> > * planes and buffers; it is possible to assign different contexts
> > * per plane, use vb2_set_alloc_ctx() for that
> > + * @read_ctx: parameters for read() api to be used; can be NULL if no read
> > + * callback is used
> > * @type: queue type
> > * @drv_priv: driver private data, may be NULL; it can be used by driver in
> > * driver-specific callbacks when issued
> > */
> > int vb2_queue_init(struct vb2_queue *q, const struct vb2_ops *ops,
> > const struct vb2_alloc_ctx *alloc_ctx,
> > + const struct vb2_read_ctx *read_ctx,
> > enum v4l2_buf_type type, void *drv_priv)
> > {
> > unsigned int i;
> > @@ -1403,6 +1448,7 @@ int vb2_queue_init(struct vb2_queue *q, const struct vb2_ops *ops,
> > for (i = 0; i < VIDEO_MAX_PLANES; ++i)
> > q->alloc_ctx[i] = alloc_ctx;
> >
> > + q->read_ctx = read_ctx;
> > q->type = type;
> > q->drv_priv = drv_priv;
> >
> > @@ -1428,6 +1474,7 @@ void vb2_queue_release(struct vb2_queue *q)
> > {
> > mutex_lock(&q->vb_lock);
> >
> > + __vb2_cleanup_read(q);
> > __vb2_queue_cancel(q);
> > __vb2_queue_free(q);
> >
> > @@ -1435,6 +1482,245 @@ void vb2_queue_release(struct vb2_queue *q)
> > }
> > EXPORT_SYMBOL_GPL(vb2_queue_release);
> >
> > +/**
> > + * struct vb2_read_data - internal structure used by read() emulator
> > + *
> > + * vb2 provides a compatibility layer and emulator of read() calls on top
> > + * of streaming api. For proper operation it required this structure to
> > + * save the driver state between each call of the read function.
> > + */
> > +struct vb2_read_data {
> > + struct v4l2_requestbuffers req;
> > + struct v4l2_buffer b;
> > + void *buff_vaddr[VIDEO_MAX_FRAME];
> > + unsigned cur_offset;
> > + unsigned cur_bufsize;
> > + unsigned read_offset;
> > + int cur_buffer;
> > + int buffer_count;
> > +};
> > +
> > +/**
> > + * __vb2_init_read() - initialize read() emulator
> > + * @q: videobuf2 queue
> > + */
> > +static int __vb2_init_read(struct vb2_queue *q)
> > +{
> > + struct vb2_read_data *rd;
> > + int i, ret;
> > +
> > + if (!q->read_ctx)
> > + BUG();
> > +
> > + /*
> > + * Check if device supports mapping buffers to kernel virtual space
> > + */
> > + if (!q->alloc_ctx[0]->mem_ops->vaddr)
> > + return -EBUSY;
> > +
> > + /*
> > + * Check if steaming api has not been already activated.
> > + */
> > + if (q->streaming || q->num_buffers > 0)
> > + return -EBUSY;
> > +
> > + /*
> > + * Basic checks done, lets try to set up read emulator
> > + */
> > + dprintk(3, "setting up read() emulator\n");
> > +
> > + q->read_data = kmalloc(sizeof(struct vb2_read_data), GFP_KERNEL);
> > + if (q->read_data == NULL)
> > + return -ENOMEM;
> > +
> > + memset(q->read_data, 0, sizeof(*q->read_data));
>
> kzalloc() ?
Right
>
> > + rd = q->read_data;
> > +
> > + /*
> > + * Request buffers and use MMAP type to force driver
> > + * to allocate buffers by itself.
> > + */
> > + rd->req.count = q->read_ctx->num_bufs;
> > + rd->req.memory = V4L2_MEMORY_MMAP;
> > + rd->req.type = q->type;
> > + ret = __vb2_reqbufs(q, &rd->req);
> > + if (ret)
> > + goto err_kfree;
> > +
> > + /*
> > + * Check if plane_count is correct
> > + * (multiplane buffers are not supported).
> > + */
> > + if (q->bufs[0]->num_planes != 1) {
> > + rd->req.count = 0;
> > + ret = -EBUSY;
> > + goto err_reqbufs;
> > + }
> > +
> > + /*
> > + * Get kernel address of each buffer.
> > + */
> > + for (i = 0; i < q->num_buffers; i++)
> > + rd->buff_vaddr[i] = vb2_plane_vaddr(q->bufs[i], 0);
> > +
>
> Even if the driver provided a vaddr() callback, it may still return a
> NULL. I'd be better to verify that here.
Right, I will fix this.
> > + /*
> > + * Queue all buffers.
> > + */
> > + for (i = 0; i < q->num_buffers; i++) {
> > + memset(&rd->b, 0, sizeof(rd->b));
> > + rd->b.type = q->type;
> > + rd->b.memory = q->memory;
> > + rd->b.index = i;
> > + ret = __vb2_qbuf(q, &rd->b);
> > + if (ret)
> > + goto err_reqbufs;
> > + }
> > +
> > + /*
> > + * Start streaming.
> > + */
> > + ret = __vb2_streamon(q, q->type);
> > + if (ret)
> > + goto err_reqbufs;
> > +
> > + return ret;
> > +
> > +err_reqbufs:
> > + __vb2_reqbufs(q, &rd->req);
> > +
> > +err_kfree:
> > + kfree(q->read_data);
> > + return ret;
> > +}
> > +
> > +/**
> > + * __vb2_cleanup_read() - free resourced used by read() emulator
> > + * @q: videobuf2 queue
> > + */
> > +static int __vb2_cleanup_read(struct vb2_queue *q)
> > +{
> > + struct vb2_read_data *rd = q->read_data;
> > + if (rd) {
> > + __vb2_streamoff(q, q->type);
> > + rd->req.count = 0;
> > + __vb2_reqbufs(q, &rd->req);
> > + kfree(rd);
> > + q->read_data = NULL;
> > + dprintk(3, "read() emulator released\n");
> > + }
> > + return 0;
> > +}
> > +
> > +size_t vb2_read(struct vb2_queue *q, char __user *data, size_t count,
> > + loff_t *ppos, int nonblocking)
> > +{
> > + struct vb2_read_data *rd;
> > + int ret;
> > +
> > + dprintk(3, "read: offset %ld, count %d, %sblocking\n", (long)*ppos,
> > + count, nonblocking ? "non" : "");
> > +
> > + if (!data)
> > + return -EINVAL;
> > +
> > + mutex_lock(&q->vb_lock);
> > +
> > + /*
> > + * Initialize emulator on first read() call.
> > + */
> > + if (!q->read_data) {
> > + ret = __vb2_init_read(q);
> > + dprintk(3, "read: vb2_init_read result: %d\n", ret);
> > + if (ret)
> > + goto end;
> > + }
> > +
> > + rd = q->read_data;
> > +
> > + /*
> > + * Current buffer is empty...
> > + */
> > + if (rd->cur_offset == 0 && rd->cur_bufsize == 0) {
> > + /*
> > + * ... check if this was the last buffer to read.
> > + */
> > + if (q->read_ctx->read_once &&
> > + rd->buffer_count == q->read_ctx->num_bufs) {
> > + ret = __vb2_cleanup_read(q);
> > + goto end;
> > + }
> > +
> > + /*
> > + * ... or call vb2_dqbuf to get next buffer with data.
> > + */
> > + memset(&rd->b, 0, sizeof(rd->b));
> > + rd->b.type = q->type;
> > + rd->b.memory = q->memory;
> > + rd->b.index = rd->cur_buffer;
> > + ret = __vb2_dqbuf(q, &rd->b, nonblocking);
> > + dprintk(5, "read: vb2_dqbuf result: %d\n", ret);
> > + if (ret)
> > + goto end;
> > + rd->buffer_count += 1;
> > + rd->cur_bufsize = rd->b.length;
>
> This is tricky. Since you are supporting both non-multiplanar types
> and 1-plane multiplanar, in the latter case this value will be in
> b.m.planes[0]->length instead.
Well, in fact I should use b.bytesused here. I will update the code to read
it from q->bufs->planes[0].bytesused.
>
> > + }
> > +
> > + /*
> > + * Limit count on last few bytes of the buffer.
> > + */
>
> This comment is confusing. Maybe something like "If the user requested
> more bytes than we can fit in buffer, reduce that number" would be
> better?
OK
> > + if (count + rd->cur_offset > rd->cur_bufsize) {
> > + count = rd->cur_bufsize - rd->cur_offset;
> > + dprintk(5, "reducing read count: %d\n", count);
> > + }
> > +
> > + /*
> > + * Transfer data to userspace.
> > + */
> > + dprintk(3, "read: copying %d data from buffer %d (offset %d bytes)\n",
> > + count, rd->cur_buffer, rd->cur_offset);
> > + if (copy_to_user(data,
> > + rd->buff_vaddr[rd->cur_buffer] + rd->cur_offset,
> > + count)) {
> > + dprintk(3, "read: error copying data\n");
> > + ret = -EFAULT;
> > + goto end;
> > + }
> > +
> > + /*
> > + * Update counters.
> > + */
> > + rd->cur_offset += count;
> > + rd->read_offset += count;
> > + *ppos += count;
> > +
> > + /*
> > + * Queue next buffer if required.
> > + */
> > + if (rd->cur_offset == rd->cur_bufsize) {
>
> This should actually be rd->cur_offset == rd->b.planes[0].bytesused
> for 1-plane multiplanar and rd->b.bytesused for non-multiplanar. (See
> also my comment about length above). You want to get the size of
> actual video data, which may be smaller than buffer size.
Right, I used wrong field here.
> By the way, vb2 internally always stores those values in planes[0],
> even for non-multiplanar types, which makes things more consistent,
> but you are using dqbuf and everything gets moved back to v4l2_buffer
> struct there.
Right, I will use it then to simplify the code;
> <snip>
Best regards
--
Marek Szyprowski
Samsung Poland R&D Center
--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
[Linux Input]
[Video for Linux]
[Mplayer Users]
[Linux USB Devel]
[Linux Audio Users]
[Photos]
[Yosemite Photos]
[Linux Kernel]
[Linux SCSI]
[XFree86]
[Devices]
[Yosemite Backpacking]
[Linux Input]