Re: [PATCH v3 1/2] v4l: Add memory-to-memory device helper framework for videobuf.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Here is my review...

On Monday 29 March 2010 09:36:46 Pawel Osciak wrote:
> A mem-to-mem device is a device that uses memory buffers passed by
> userspace applications for both their source and destination data. This
> is different from existing drivers, which utilize memory buffers for either
> input or output, but not both.
> 
> In terms of V4L2 such a device would be both of OUTPUT and CAPTURE type.
> 
> Examples of such devices would be: image 'resizers', 'rotators',
> 'colorspace converters', etc.
> 
> This patch adds a separate Kconfig sub-menu for mem-to-mem devices as well.
> 
> Signed-off-by: Pawel Osciak <p.osciak@xxxxxxxxxxx>
> Signed-off-by: Marek Szyprowski <m.szyprowski@xxxxxxxxxxx>
> Reviewed-by: Kyungmin Park <kyungmin.park@xxxxxxxxxxx>
> ---
>  drivers/media/video/Kconfig        |   14 +
>  drivers/media/video/Makefile       |    2 +
>  drivers/media/video/v4l2-mem2mem.c |  685 ++++++++++++++++++++++++++++++++++++
>  include/media/v4l2-mem2mem.h       |  154 ++++++++
>  4 files changed, 855 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/media/video/v4l2-mem2mem.c
>  create mode 100644 include/media/v4l2-mem2mem.h
> 
> diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig
> index f8fc865..5fd041e 100644
> --- a/drivers/media/video/Kconfig
> +++ b/drivers/media/video/Kconfig
> @@ -45,6 +45,10 @@ config VIDEO_TUNER
>  	tristate
>  	depends on MEDIA_TUNER
>  
> +config V4L2_MEM2MEM_DEV
> +	tristate
> +	depends on VIDEOBUF_GEN
> +
>  #
>  # Multimedia Video device configuration
>  #
> @@ -1107,3 +1111,13 @@ config USB_S2255
>  
>  endif # V4L_USB_DRIVERS
>  endif # VIDEO_CAPTURE_DRIVERS
> +
> +menuconfig V4L_MEM2MEM_DRIVERS
> +	bool "Memory-to-memory multimedia devices"
> +	depends on VIDEO_V4L2
> +	default n
> +	---help---
> +	  Say Y here to enable selecting drivers for V4L devices that
> +	  use system memory for both source and destination buffers, as opposed
> +	  to capture and output drivers, which use memory buffers for just
> +	  one of those.
> diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile
> index b88b617..e974680 100644
> --- a/drivers/media/video/Makefile
> +++ b/drivers/media/video/Makefile
> @@ -117,6 +117,8 @@ obj-$(CONFIG_VIDEOBUF_VMALLOC) += videobuf-vmalloc.o
>  obj-$(CONFIG_VIDEOBUF_DVB) += videobuf-dvb.o
>  obj-$(CONFIG_VIDEO_BTCX)  += btcx-risc.o
>  
> +obj-$(CONFIG_V4L2_MEM2MEM_DEV) += v4l2-mem2mem.o
> +
>  obj-$(CONFIG_VIDEO_M32R_AR_M64278) += arv.o
>  
>  obj-$(CONFIG_VIDEO_CX2341X) += cx2341x.o
> diff --git a/drivers/media/video/v4l2-mem2mem.c b/drivers/media/video/v4l2-mem2mem.c
> new file mode 100644
> index 0000000..a78157f
> --- /dev/null
> +++ b/drivers/media/video/v4l2-mem2mem.c
> @@ -0,0 +1,685 @@
> +/*
> + * Memory-to-memory device framework for Video for Linux 2 and videobuf.
> + *
> + * Helper functions for devices that use videobuf buffers for both their
> + * source and destination.
> + *
> + * Copyright (c) 2009-2010 Samsung Electronics Co., Ltd.
> + * Pawel Osciak, <p.osciak@xxxxxxxxxxx>
> + * Marek Szyprowski, <m.szyprowski@xxxxxxxxxxx>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by the
> + * Free Software Foundation; either version 2 of the License, or (at your
> + * option) any later version.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/sched.h>
> +#include <media/videobuf-core.h>
> +#include <media/v4l2-mem2mem.h>
> +
> +MODULE_DESCRIPTION("Mem to mem device framework for videobuf");
> +MODULE_AUTHOR("Pawel Osciak, <p.osciak@xxxxxxxxxxx>");
> +MODULE_LICENSE("GPL");
> +
> +static int debug;
> +module_param(debug, int, 0644);
> +
> +#define dprintk(fmt, arg...)						\
> +	do {								\
> +		if (debug >= 1)						\
> +			printk(KERN_DEBUG "%s: " fmt, __func__, ## arg);\
> +	} while (0)
> +
> +
> +/* Instance is already queued on the jobqueue */
> +#define TRANS_QUEUED		(1 << 0)
> +/* Instance is currently running in hardware */
> +#define TRANS_RUNNING		(1 << 1)
> +
> +
> +/* Offset base for buffers on the destination queue - used to distinguish
> + * between source and destination buffers when mmapping - they receive the same
> + * offsets but for different queues */
> +#define DST_QUEUE_OFF_BASE	(1 << 30)
> +
> +
> +/**
> + * struct v4l2_m2m_dev - per-device context
> + * @curr_ctx:		currently running instance
> + * @jobqueue:		instances queued to run
> + * @job_spinlock:	protects jobqueue
> + * @m2m_ops:		driver callbacks
> + */
> +struct v4l2_m2m_dev {
> +	struct v4l2_m2m_ctx	*curr_ctx;
> +
> +	struct list_head	jobqueue;

Using job_queue is a bit more consistent as you also use an '_' in job_spinlock.

> +	spinlock_t		job_spinlock;
> +
> +	struct v4l2_m2m_ops	*m2m_ops;
> +};
> +
> +static struct v4l2_m2m_queue_ctx *get_queue_ctx(struct v4l2_m2m_ctx *m2m_ctx,
> +						enum v4l2_buf_type type)
> +{
> +	switch (type) {
> +	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
> +		return &m2m_ctx->cap_q_ctx;
> +	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
> +		return &m2m_ctx->out_q_ctx;
> +	default:
> +		printk(KERN_ERR "Invalid buffer type\n");
> +		return NULL;
> +	}
> +}
> +
> +/**
> + * v4l2_m2m_get_vq() - return videobuf_queue for the given type
> + */
> +struct videobuf_queue *v4l2_m2m_get_vq(struct v4l2_m2m_ctx *m2m_ctx,
> +				       enum v4l2_buf_type type)
> +{
> +	struct v4l2_m2m_queue_ctx *q_ctx;
> +
> +	q_ctx = get_queue_ctx(m2m_ctx, type);
> +	if (!q_ctx)
> +		return NULL;
> +
> +	return &q_ctx->q;

Shorter:

	struct v4l2_m2m_queue_ctx *q_ctx = get_queue_ctx(m2m_ctx, type);

	return q_ctx ? &q_ctx->q : NULL;

> +}
> +EXPORT_SYMBOL(v4l2_m2m_get_vq);
> +
> +/**
> + * v4l2_m2m_get_src_vq() - return videobuf_queue for source buffers
> + */
> +struct videobuf_queue *v4l2_m2m_get_src_vq(struct v4l2_m2m_ctx *m2m_ctx)
> +{
> +	return v4l2_m2m_get_vq(m2m_ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT);
> +}
> +EXPORT_SYMBOL(v4l2_m2m_get_src_vq);
> +
> +/**
> + * v4l2_m2m_get_dst_vq() - return videobuf_queue for destination buffers
> + */
> +struct videobuf_queue *v4l2_m2m_get_dst_vq(struct v4l2_m2m_ctx *m2m_ctx)
> +{
> +	return v4l2_m2m_get_vq(m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE);
> +}
> +EXPORT_SYMBOL(v4l2_m2m_get_dst_vq);
> +
> +/**
> + * v4l2_m2m_next_buf() - return next buffer from the list of ready buffers
> + */
> +static void *v4l2_m2m_next_buf(struct v4l2_m2m_ctx *m2m_ctx,
> +			       enum v4l2_buf_type type)
> +{
> +	struct v4l2_m2m_queue_ctx *q_ctx;
> +	struct videobuf_buffer *vb = NULL;
> +	unsigned long flags;
> +
> +	q_ctx = get_queue_ctx(m2m_ctx, type);
> +	if (!q_ctx)
> +		return NULL;
> +
> +	spin_lock_irqsave(q_ctx->q.irqlock, flags);
> +
> +	if (list_empty(&q_ctx->rdy_queue))
> +		goto end;
> +
> +	vb = list_entry(q_ctx->rdy_queue.next, struct videobuf_buffer, queue);
> +	vb->state = VIDEOBUF_ACTIVE;
> +
> +end:
> +	spin_unlock_irqrestore(q_ctx->q.irqlock, flags);
> +	return vb;
> +}
> +
> +/**
> + * v4l2_m2m_next_src_buf() - return next source buffer from the list of ready
> + * buffers
> + */
> +inline void *v4l2_m2m_next_src_buf(struct v4l2_m2m_ctx *m2m_ctx)

inline makes no sense for an exported function.

You can also move this to the header and make it static inline. That would be
a valid option for a lot of these small one-liner functions.

> +{
> +	return v4l2_m2m_next_buf(m2m_ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT);
> +}
> +EXPORT_SYMBOL(v4l2_m2m_next_src_buf);
> +
> +/**
> + * v4l2_m2m_next_dst_buf() - return next destination buffer from the list of
> + * ready buffers
> + */
> +inline void *v4l2_m2m_next_dst_buf(struct v4l2_m2m_ctx *m2m_ctx)
> +{
> +	return v4l2_m2m_next_buf(m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE);
> +}
> +EXPORT_SYMBOL(v4l2_m2m_next_dst_buf);
> +
> +/**
> + * v4l2_m2m_buf_remove() - take off a buffer from the list of ready buffers and
> + * return it
> + */
> +static void *v4l2_m2m_buf_remove(struct v4l2_m2m_ctx *m2m_ctx,
> +				 enum v4l2_buf_type type)
> +{
> +	struct v4l2_m2m_queue_ctx *q_ctx;
> +	struct videobuf_buffer *vb = NULL;
> +	unsigned long flags;
> +
> +	q_ctx = get_queue_ctx(m2m_ctx, type);
> +	if (!q_ctx)
> +		return NULL;
> +
> +	spin_lock_irqsave(q_ctx->q.irqlock, flags);
> +	if (!list_empty(&q_ctx->rdy_queue)) {
> +		vb = list_entry(q_ctx->rdy_queue.next, struct videobuf_buffer,
> +				queue);
> +		list_del(&vb->queue);
> +		q_ctx->num_rdy--;
> +	}
> +	spin_unlock_irqrestore(q_ctx->q.irqlock, flags);
> +
> +	return vb;
> +}
> +
> +/**
> + * v4l2_m2m_src_buf_remove() - take off a source buffer from the list of ready
> + * buffers and return it
> + */
> +void *v4l2_m2m_src_buf_remove(struct v4l2_m2m_ctx *m2m_ctx)
> +{
> +	return v4l2_m2m_buf_remove(m2m_ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT);
> +}
> +EXPORT_SYMBOL(v4l2_m2m_src_buf_remove);
> +
> +/**
> + * v4l2_m2m_dst_buf_remove() - take off a destination buffer from the list of
> + * ready buffers and return it
> + */
> +void *v4l2_m2m_dst_buf_remove(struct v4l2_m2m_ctx *m2m_ctx)
> +{
> +	return v4l2_m2m_buf_remove(m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE);
> +}
> +EXPORT_SYMBOL(v4l2_m2m_dst_buf_remove);
> +
> +
> +/*
> + * Scheduling handlers
> + */
> +
> +/**
> + * v4l2_m2m_get_curr_priv() - return driver private data for the currently
> + * running instance or NULL if no instance is running
> + */
> +void *v4l2_m2m_get_curr_priv(struct v4l2_m2m_dev *m2m_dev)
> +{
> +	unsigned long flags;
> +	void *ret;
> +
> +	spin_lock_irqsave(&m2m_dev->job_spinlock, flags);
> +	if (!m2m_dev->curr_ctx)
> +		ret = NULL;
> +	else
> +		ret = m2m_dev->curr_ctx->priv;

Shorter:

	ret = m2m_dev->curr_ctx ? m2m_dev->curr_ctx->priv : NULL;

> +	spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL(v4l2_m2m_get_curr_priv);
> +
> +/**
> + * v4l2_m2m_try_run() - select next job to perform and run it if possible
> + *
> + * Get next transaction (if present) from the waiting jobs list and run it.
> + */
> +static void v4l2_m2m_try_run(struct v4l2_m2m_dev *m2m_dev)
> +{
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&m2m_dev->job_spinlock, flags);
> +	if (NULL != m2m_dev->curr_ctx) {
> +		spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags);
> +		dprintk("Another instance is running, won't run now\n");
> +		return;
> +	}
> +
> +	if (list_empty(&m2m_dev->jobqueue)) {
> +		spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags);
> +		dprintk("No job pending\n");
> +		return;
> +	}
> +
> +	m2m_dev->curr_ctx = list_entry(m2m_dev->jobqueue.next,
> +				   struct v4l2_m2m_ctx, queue);
> +	m2m_dev->curr_ctx->job_flags |= TRANS_RUNNING;
> +	spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags);
> +
> +	m2m_dev->m2m_ops->device_run(m2m_dev->curr_ctx->priv);
> +
> +	return;

Bogus return.

> +}
> +
> +/**
> + * v4l2_m2m_try_schedule() - check whether an instance is ready to be added to
> + * the pending job queue and add it if so.
> + * @m2m_ctx:	m2m context assigned to the instance to be checked
> + *
> + * There are three basic requirements an instance has to meet to be able to run:
> + * 1) at least one source buffer has to be queued,
> + * 2) at least one destination buffer has to be queued,
> + * 3) streaming has to be on.
> + *
> + * There may also be additional, custom requirements. In such case the driver
> + * should supply a custom callback (job_ready in v4l2_m2m_ops) that should
> + * return 1 if the instance is ready.
> + * An example of the above could be an instance that requires more than one
> + * src/dst buffer per transaction.
> + */
> +static void v4l2_m2m_try_schedule(struct v4l2_m2m_ctx *m2m_ctx)
> +{
> +	struct v4l2_m2m_dev *m2m_dev;
> +	unsigned long flags_job, flags;
> +
> +	m2m_dev = m2m_ctx->m2m_dev;
> +	dprintk("Trying to schedule a job for m2m_ctx: %p\n", m2m_ctx);
> +
> +	if (!m2m_ctx->out_q_ctx.q.streaming
> +	    || !m2m_ctx->cap_q_ctx.q.streaming) {
> +		dprintk("Streaming needs to be on for both queues\n");
> +		return;
> +	}
> +
> +	spin_lock_irqsave(&m2m_dev->job_spinlock, flags_job);
> +	if (m2m_ctx->job_flags & TRANS_QUEUED) {
> +		spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags_job);
> +		dprintk("On job queue already\n");
> +		return;
> +	}
> +
> +	spin_lock_irqsave(m2m_ctx->out_q_ctx.q.irqlock, flags);

Two spin_lock_irqsave's? I think it will work, but it is very unusual.

Does job_spinlock really have to be held with interrupts turned off?

> +	if (list_empty(&m2m_ctx->out_q_ctx.rdy_queue)) {
> +		spin_unlock_irqrestore(m2m_ctx->out_q_ctx.q.irqlock, flags);
> +		spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags_job);
> +		dprintk("No input buffers available\n");
> +		return;
> +	}
> +	if (list_empty(&m2m_ctx->cap_q_ctx.rdy_queue)) {
> +		spin_unlock_irqrestore(m2m_ctx->out_q_ctx.q.irqlock, flags);
> +		spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags_job);
> +		dprintk("No output buffers available\n");
> +		return;
> +	}
> +	spin_unlock_irqrestore(m2m_ctx->out_q_ctx.q.irqlock, flags);
> +
> +	if (m2m_dev->m2m_ops->job_ready
> +		&& (!m2m_dev->m2m_ops->job_ready(m2m_ctx->priv))) {
> +		spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags_job);
> +		dprintk("Driver not ready\n");
> +		return;
> +	}
> +
> +	if (!(m2m_ctx->job_flags & TRANS_QUEUED)) {
> +		list_add_tail(&m2m_ctx->queue, &m2m_dev->jobqueue);
> +		m2m_ctx->job_flags |= TRANS_QUEUED;
> +	}
> +	spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags_job);
> +
> +	v4l2_m2m_try_run(m2m_dev);
> +}
> +
> +/**
> + * v4l2_m2m_job_finish() - inform the framework that a job has been finished
> + * and have it clean up
> + *
> + * Called by a driver to yield back the device after it has finished with it.
> + * Should be called as soon as possible after reaching a state which allows
> + * other instances to take control of the device.
> + *
> + * This function has to be called only after device_run() callback has been
> + * called on the driver. To prevent recursion, it should not be called directly
> + * from the device_run() callback though.
> + */
> +void v4l2_m2m_job_finish(struct v4l2_m2m_dev *m2m_dev,
> +			 struct v4l2_m2m_ctx *m2m_ctx)
> +{
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&m2m_dev->job_spinlock, flags);
> +	if (!m2m_dev->curr_ctx || m2m_dev->curr_ctx != m2m_ctx) {
> +		spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags);
> +		dprintk("Called by an instance not currently running\n");
> +		return;
> +	}
> +
> +	list_del(&m2m_dev->curr_ctx->queue);
> +	m2m_dev->curr_ctx->job_flags &= ~(TRANS_QUEUED | TRANS_RUNNING);
> +	m2m_dev->curr_ctx = NULL;
> +
> +	spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags);
> +
> +	/* This instance might have more buffers ready, but since we do not
> +	 * allow more than one job on the jobqueue per instance, each has
> +	 * to be scheduled separately after the previous one finishes. */
> +	v4l2_m2m_try_schedule(m2m_ctx);
> +	v4l2_m2m_try_run(m2m_dev);
> +}
> +EXPORT_SYMBOL(v4l2_m2m_job_finish);
> +
> +/**
> + * v4l2_m2m_reqbufs() - multi-queue-aware REQBUFS multiplexer
> + */
> +int v4l2_m2m_reqbufs(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
> +		     struct v4l2_requestbuffers *reqbufs)
> +{
> +	struct videobuf_queue *vq;
> +
> +	vq = v4l2_m2m_get_vq(m2m_ctx, reqbufs->type);
> +	return videobuf_reqbufs(vq, reqbufs);
> +}
> +EXPORT_SYMBOL_GPL(v4l2_m2m_reqbufs);
> +
> +/**
> + * v4l2_m2m_querybuf() - multi-queue-aware QUERYBUF multiplexer
> + *
> + * See v4l2_m2m_mmap() documentation for details.
> + */
> +int v4l2_m2m_querybuf(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
> +		      struct v4l2_buffer *buf)
> +{
> +	struct videobuf_queue *vq;
> +	int ret;
> +
> +	vq = v4l2_m2m_get_vq(m2m_ctx, buf->type);
> +	ret = videobuf_querybuf(vq, buf);
> +
> +	if (buf->memory == V4L2_MEMORY_MMAP
> +	    && vq->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
> +		buf->m.offset += DST_QUEUE_OFF_BASE;
> +	}
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL_GPL(v4l2_m2m_querybuf);
> +
> +/**
> + * v4l2_m2m_qbuf() - enqueue a source or destination buffer, depending on
> + * the type
> + */
> +int v4l2_m2m_qbuf(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
> +		  struct v4l2_buffer *buf)
> +{
> +	struct videobuf_queue *vq;
> +	int ret;
> +
> +	vq = v4l2_m2m_get_vq(m2m_ctx, buf->type);
> +	ret = videobuf_qbuf(vq, buf);
> +	if (ret)
> +		return ret;
> +
> +	v4l2_m2m_try_schedule(m2m_ctx);
> +
> +	return 0;

Simplify:

	ret = videobuf_qbuf(vq, buf);
	if (!ret)
		v4l2_m2m_try_schedule(m2m_ctx);
	return ret;


> +}
> +EXPORT_SYMBOL_GPL(v4l2_m2m_qbuf);
> +
> +/**
> + * v4l2_m2m_dqbuf() - dequeue a source or destination buffer, depending on
> + * the type
> + */
> +int v4l2_m2m_dqbuf(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
> +		   struct v4l2_buffer *buf)
> +{
> +	struct videobuf_queue *vq;
> +
> +	vq = v4l2_m2m_get_vq(m2m_ctx, buf->type);
> +	return videobuf_dqbuf(vq, buf, file->f_flags & O_NONBLOCK);
> +}
> +EXPORT_SYMBOL_GPL(v4l2_m2m_dqbuf);
> +
> +/**
> + * v4l2_m2m_streamon() - turn on streaming for a video queue
> + */
> +int v4l2_m2m_streamon(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
> +		      enum v4l2_buf_type type)
> +{
> +	struct videobuf_queue *vq;
> +	int ret;
> +
> +	vq = v4l2_m2m_get_vq(m2m_ctx, type);
> +	ret = videobuf_streamon(vq);
> +	if (ret)
> +		return ret;
> +
> +	v4l2_m2m_try_schedule(m2m_ctx);

Same as above.

> +
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(v4l2_m2m_streamon);
> +
> +/**
> + * v4l2_m2m_streamoff() - turn off streaming for a video queue
> + */
> +int v4l2_m2m_streamoff(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
> +		       enum v4l2_buf_type type)
> +{
> +	struct videobuf_queue *vq;
> +
> +	vq = v4l2_m2m_get_vq(m2m_ctx, type);
> +
> +	return videobuf_streamoff(vq);

Shorter:

	return videobuf_streamoff(v4l2_m2m_get_vq(m2m_ctx, type));

> +}
> +EXPORT_SYMBOL_GPL(v4l2_m2m_streamoff);
> +
> +/**
> + * v4l2_m2m_poll() - poll replacement, for destination buffers only
> + *
> + * Call from driver's poll() function. Will poll the destination queue only.
> + */
> +unsigned int v4l2_m2m_poll(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
> +			   struct poll_table_struct *wait)
> +{
> +	struct videobuf_queue *dst_q;
> +	struct videobuf_buffer *vb = NULL;
> +	unsigned int rc = 0;
> +
> +	dst_q = v4l2_m2m_get_dst_vq(m2m_ctx);
> +
> +	mutex_lock(&dst_q->vb_lock);
> +
> +	if (dst_q->streaming) {
> +		if (!list_empty(&dst_q->stream))
> +			vb = list_entry(dst_q->stream.next,
> +					struct videobuf_buffer, stream);
> +	}
> +
> +	if (!vb)
> +		rc = POLLERR;
> +
> +	if (0 == rc) {
> +		poll_wait(file, &vb->done, wait);
> +		if (vb->state == VIDEOBUF_DONE || vb->state == VIDEOBUF_ERROR)
> +			rc = POLLOUT | POLLRDNORM;
> +	}
> +
> +	mutex_unlock(&dst_q->vb_lock);

Would there be any need for polling for writing?

> +	return rc;
> +}
> +EXPORT_SYMBOL_GPL(v4l2_m2m_poll);
> +
> +/**
> + * v4l2_m2m_mmap() - source and destination queues-aware mmap multiplexer
> + *
> + * Call from driver's mmap() function. Will handle mmap() for both queues
> + * seamlessly for videobuffer, which will receive normal per-queue offsets and
> + * proper videobuf queue pointers. The differentiation is made outside videobuf
> + * by adding a predefined offset to buffers from one of the queues and
> + * subtracting it before passing it back to videobuf. Only drivers (and
> + * thus applications) receive modified offsets.
> + */
> +int v4l2_m2m_mmap(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
> +			 struct vm_area_struct *vma)
> +{
> +	unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
> +	struct videobuf_queue *vq;
> +
> +	if (offset < DST_QUEUE_OFF_BASE) {
> +		vq = v4l2_m2m_get_src_vq(m2m_ctx);
> +	} else {
> +		vq = v4l2_m2m_get_dst_vq(m2m_ctx);
> +		vma->vm_pgoff -= (DST_QUEUE_OFF_BASE >> PAGE_SHIFT);
> +	}
> +
> +	return videobuf_mmap_mapper(vq, vma);
> +}
> +EXPORT_SYMBOL(v4l2_m2m_mmap);
> +
> +/**
> + * v4l2_m2m_init() - initialize per-driver m2m data
> + *
> + * Usually called from driver's probe() function.
> + */
> +struct v4l2_m2m_dev *v4l2_m2m_init(struct v4l2_m2m_ops *m2m_ops)
> +{
> +	struct v4l2_m2m_dev *m2m_dev;
> +
> +	if (!m2m_ops)
> +		return ERR_PTR(-EINVAL);
> +
> +	BUG_ON(!m2m_ops->device_run);
> +	BUG_ON(!m2m_ops->job_abort);

No need to crash. Use WARN_ON and return an error.

Learned something today: I didn't know ERR_PTR existed! Nice.

> +
> +	m2m_dev = kzalloc(sizeof *m2m_dev, GFP_KERNEL);
> +	if (!m2m_dev)
> +		return ERR_PTR(-ENOMEM);
> +
> +	m2m_dev->curr_ctx = NULL;
> +	m2m_dev->m2m_ops = m2m_ops;
> +	INIT_LIST_HEAD(&m2m_dev->jobqueue);
> +	spin_lock_init(&m2m_dev->job_spinlock);
> +
> +	return m2m_dev;
> +}
> +EXPORT_SYMBOL_GPL(v4l2_m2m_init);
> +
> +/**
> + * v4l2_m2m_release() - cleans up and frees a m2m_dev structure
> + *
> + * Usually called from driver's remove() function.
> + */
> +void v4l2_m2m_release(struct v4l2_m2m_dev *m2m_dev)
> +{
> +	kfree(m2m_dev);
> +}
> +EXPORT_SYMBOL_GPL(v4l2_m2m_release);

Wouldn't it make sense to embed this struct in a filehandle structure?
Then there is no need to allocate anything, you just need an init function.

I like embedding structures: it's quite clean.

> +
> +/**
> + * v4l2_m2m_ctx_init() - allocate and initialize a m2m context
> + * @priv - driver's instance private data
> + * @m2m_dev - a previously initialized m2m_dev struct
> + * @vq_init - a callback for queue type-specific initialization function to be
> + * used for initializing videobuf_queues
> + *
> + * Usually called from driver's open() function.
> + */
> +struct v4l2_m2m_ctx *v4l2_m2m_ctx_init(void *priv, struct v4l2_m2m_dev *m2m_dev,
> +			void (*vq_init)(void *priv, struct videobuf_queue *,
> +					enum v4l2_buf_type))
> +{
> +	struct v4l2_m2m_ctx *m2m_ctx;
> +	struct v4l2_m2m_queue_ctx *out_q_ctx;
> +	struct v4l2_m2m_queue_ctx *cap_q_ctx;
> +
> +	if (!vq_init)
> +		return ERR_PTR(-EINVAL);
> +
> +	m2m_ctx = kzalloc(sizeof *m2m_ctx, GFP_KERNEL);
> +	if (!m2m_ctx)
> +		return ERR_PTR(-ENOMEM);
> +
> +	m2m_ctx->priv = priv;
> +	m2m_ctx->m2m_dev = m2m_dev;
> +
> +	out_q_ctx = get_queue_ctx(m2m_ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT);
> +	cap_q_ctx = get_queue_ctx(m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE);
> +
> +	INIT_LIST_HEAD(&out_q_ctx->rdy_queue);
> +	INIT_LIST_HEAD(&cap_q_ctx->rdy_queue);
> +
> +	INIT_LIST_HEAD(&m2m_ctx->queue);
> +
> +	vq_init(priv, &out_q_ctx->q, V4L2_BUF_TYPE_VIDEO_OUTPUT);
> +	vq_init(priv, &cap_q_ctx->q, V4L2_BUF_TYPE_VIDEO_CAPTURE);
> +	out_q_ctx->q.priv_data = cap_q_ctx->q.priv_data = priv;
> +
> +	return m2m_ctx;
> +}
> +EXPORT_SYMBOL_GPL(v4l2_m2m_ctx_init);
> +
> +/**
> + * v4l2_m2m_ctx_release() - release m2m context
> + *
> + * Usually called from driver's release() function.
> + */
> +void v4l2_m2m_ctx_release(struct v4l2_m2m_ctx *m2m_ctx)
> +{
> +	struct v4l2_m2m_dev *m2m_dev;
> +	struct videobuf_buffer *vb;
> +	unsigned long flags;
> +
> +	m2m_dev = m2m_ctx->m2m_dev;
> +
> +	spin_lock_irqsave(&m2m_dev->job_spinlock, flags);
> +	if (m2m_ctx->job_flags & TRANS_RUNNING) {
> +		spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags);
> +		m2m_dev->m2m_ops->job_abort(m2m_ctx->priv);
> +		dprintk("m2m_ctx %p running, will wait to complete", m2m_ctx);
> +		vb = v4l2_m2m_next_dst_buf(m2m_ctx);
> +		BUG_ON(NULL == vb);
> +		wait_event(vb->done, vb->state != VIDEOBUF_ACTIVE
> +				     && vb->state != VIDEOBUF_QUEUED);
> +	} else if (m2m_ctx->job_flags & TRANS_QUEUED) {
> +		list_del(&m2m_ctx->queue);
> +		m2m_ctx->job_flags &= ~(TRANS_QUEUED | TRANS_RUNNING);
> +		spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags);
> +		dprintk("m2m_ctx: %p had been on queue and was removed\n",
> +			m2m_ctx);
> +	} else {
> +		/* Do nothing, was not on queue/running */
> +		spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags);
> +	}
> +
> +	videobuf_stop(&m2m_ctx->cap_q_ctx.q);
> +	videobuf_stop(&m2m_ctx->out_q_ctx.q);
> +
> +	videobuf_mmap_free(&m2m_ctx->cap_q_ctx.q);
> +	videobuf_mmap_free(&m2m_ctx->out_q_ctx.q);
> +
> +	kfree(m2m_ctx);
> +}
> +EXPORT_SYMBOL_GPL(v4l2_m2m_ctx_release);
> +
> +/**
> + * v4l2_m2m_buf_queue() - add a buffer to the proper ready buffers list.
> + *
> + * Call from withing buf_queue() videobuf_queue_ops callback.
> + *
> + * Locking: Caller holds q->irqlock (taken by videobuf before calling buf_queue
> + * callback in the driver).
> + */
> +void v4l2_m2m_buf_queue(struct v4l2_m2m_ctx *m2m_ctx, struct videobuf_queue *vq,
> +			struct videobuf_buffer *vb)
> +{
> +	struct v4l2_m2m_queue_ctx *q_ctx;
> +
> +	q_ctx = get_queue_ctx(m2m_ctx, vq->type);
> +	if (!q_ctx)
> +		return;
> +
> +	list_add_tail(&vb->queue, &q_ctx->rdy_queue);
> +	q_ctx->num_rdy++;
> +
> +	vb->state = VIDEOBUF_QUEUED;
> +}
> +EXPORT_SYMBOL_GPL(v4l2_m2m_buf_queue);
> +
> diff --git a/include/media/v4l2-mem2mem.h b/include/media/v4l2-mem2mem.h
> new file mode 100644
> index 0000000..33fe9f3
> --- /dev/null
> +++ b/include/media/v4l2-mem2mem.h
> @@ -0,0 +1,154 @@
> +/*
> + * Memory-to-memory device framework for Video for Linux 2.
> + *
> + * Helper functions for devices that use memory buffers for both source
> + * and destination.
> + *
> + * Copyright (c) 2009 Samsung Electronics Co., Ltd.
> + * Pawel Osciak, <p.osciak@xxxxxxxxxxx>
> + * Marek Szyprowski, <m.szyprowski@xxxxxxxxxxx>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by the
> + * Free Software Foundation; either version 2 of the
> + * License, or (at your option) any later version
> + */
> +
> +#ifndef _MEDIA_V4L2_MEM2MEM_H
> +#define _MEDIA_V4L2_MEM2MEM_H
> +
> +#include <media/videobuf-core.h>
> +
> +/**
> + * struct v4l2_m2m_ops - mem-to-mem device driver callbacks
> + * @device_run:	required. Begin the actual job (transaction) inside this
> + *		callback.
> + *		The job does NOT have to end before this callback returns
> + *		(and it will be the usual case). When the job finishes,
> + *		v4l2_m2m_job_finish() has to be called.
> + * @job_ready:	optional. Should return 0 if the driver does not have a job
> + *		fully prepared to run yet (i.e. it will not be able to finish a
> + *		transaction without sleeping). If not provided, it will be
> + *		assumed that one source and one destination buffer are all
> + *		that is required for the driver to perform one full transaction.
> + *		This method may not sleep.
> + * @job_abort:	required. Informs the driver that it has to abort the currently
> + *		running transaction as soon as possible (i.e. as soon as it can
> + *		stop the device safely; e.g. in the next interrupt handler),
> + *		even if the transaction would not have been finished by then.
> + *		After the driver performs the necessary steps, it has to call
> + *		v4l2_m2m_job_finish() (as if the transaction ended normally).
> + *		This function does not have to (and will usually not) wait
> + *		until the device enters a state when it can be stopped.
> + */
> +struct v4l2_m2m_ops {
> +	void (*device_run)(void *priv);
> +	int (*job_ready)(void *priv);
> +	void (*job_abort)(void *priv);
> +};
> +
> +struct v4l2_m2m_dev;
> +
> +struct v4l2_m2m_queue_ctx {
> +/* private: internal use only */
> +	struct videobuf_queue	q;
> +
> +	/* Base value for offsets of mmaped buffers on this queue */
> +	unsigned long		offset_base;
> +
> +	/* Queue for buffers ready to be processed as soon as this
> +	 * instance receives access to the device */
> +	struct list_head	rdy_queue;
> +	u8			num_rdy;
> +};
> +
> +struct v4l2_m2m_ctx {
> +/* private: internal use only */
> +	struct v4l2_m2m_dev		*m2m_dev;
> +
> +	/* Capture (output to memory) queue context */
> +	struct v4l2_m2m_queue_ctx	cap_q_ctx;
> +
> +	/* Output (input from memory) queue context */
> +	struct v4l2_m2m_queue_ctx	out_q_ctx;
> +
> +	/* For device job queue */
> +	struct list_head		queue;
> +	unsigned long			job_flags;
> +
> +	/* Instance private data */
> +	void				*priv;
> +};
> +
> +void *v4l2_m2m_get_curr_priv(struct v4l2_m2m_dev *m2m_dev);
> +
> +struct videobuf_queue *v4l2_m2m_get_src_vq(struct v4l2_m2m_ctx *m2m_ctx);
> +struct videobuf_queue *v4l2_m2m_get_dst_vq(struct v4l2_m2m_ctx *m2m_ctx);
> +struct videobuf_queue *v4l2_m2m_get_vq(struct v4l2_m2m_ctx *m2m_ctx,
> +				       enum v4l2_buf_type type);
> +
> +void v4l2_m2m_job_finish(struct v4l2_m2m_dev *m2m_dev,
> +			 struct v4l2_m2m_ctx *m2m_ctx);
> +
> +void *v4l2_m2m_next_src_buf(struct v4l2_m2m_ctx *m2m_ctx);
> +void *v4l2_m2m_next_dst_buf(struct v4l2_m2m_ctx *m2m_ctx);
> +
> +void *v4l2_m2m_src_buf_remove(struct v4l2_m2m_ctx *m2m_ctx);
> +void *v4l2_m2m_dst_buf_remove(struct v4l2_m2m_ctx *m2m_ctx);
> +
> +
> +int v4l2_m2m_reqbufs(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
> +		     struct v4l2_requestbuffers *reqbufs);
> +
> +int v4l2_m2m_querybuf(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
> +		      struct v4l2_buffer *buf);
> +
> +int v4l2_m2m_qbuf(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
> +		  struct v4l2_buffer *buf);
> +int v4l2_m2m_dqbuf(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
> +		   struct v4l2_buffer *buf);
> +
> +int v4l2_m2m_streamon(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
> +		      enum v4l2_buf_type type);
> +int v4l2_m2m_streamoff(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
> +		       enum v4l2_buf_type type);
> +
> +unsigned int v4l2_m2m_poll(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
> +			   struct poll_table_struct *wait);
> +
> +int v4l2_m2m_mmap(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
> +		  struct vm_area_struct *vma);
> +
> +struct v4l2_m2m_dev *v4l2_m2m_init(struct v4l2_m2m_ops *m2m_ops);
> +void v4l2_m2m_release(struct v4l2_m2m_dev *m2m_dev);
> +
> +struct v4l2_m2m_ctx *v4l2_m2m_ctx_init(void *priv, struct v4l2_m2m_dev *m2m_dev,
> +			void (*vq_init)(void *priv, struct videobuf_queue *,
> +					enum v4l2_buf_type));
> +void v4l2_m2m_ctx_release(struct v4l2_m2m_ctx *m2m_ctx);
> +
> +void v4l2_m2m_buf_queue(struct v4l2_m2m_ctx *m2m_ctx, struct videobuf_queue *vq,
> +			struct videobuf_buffer *vb);
> +
> +/**
> + * v4l2_m2m_num_src_bufs_ready() - return the number of source buffers ready for
> + * use
> + */
> +static inline
> +unsigned int v4l2_m2m_num_src_bufs_ready(struct v4l2_m2m_ctx *m2m_ctx)
> +{
> +	return m2m_ctx->cap_q_ctx.num_rdy;
> +}
> +
> +/**
> + * v4l2_m2m_num_src_bufs_ready() - return the number of destination buffers
> + * ready for use
> + */
> +static inline
> +unsigned int v4l2_m2m_num_dst_bufs_ready(struct v4l2_m2m_ctx *m2m_ctx)
> +{
> +	return m2m_ctx->out_q_ctx.num_rdy;
> +}
> +
> +#endif /* _MEDIA_V4L2_MEM2MEM_H */
> +
> 

Regards,

	Hans

-- 
Hans Verkuil - video4linux developer - sponsored by TANDBERG
--
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

[Index of Archives]     [Linux Input]     [Video for Linux]     [Gstreamer Embedded]     [Mplayer Users]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]
  Powered by Linux