Skip to content

Commit

Permalink
media: v4l2-mem2mem: handle draining, stopped and next-buf-is-last st…
Browse files Browse the repository at this point in the history
…ates

Since the draining and stop phase of the HW decoder mem2mem bahaviour is
now clearly defined, we can move handling of the following states to the
common v4l2-mem2mem core code:
- draining
- stopped
- next-buf-is-last

By introducing the following v4l2-mem2mem APIs:
- v4l2_m2m_encoder_cmd/v4l2_m2m_ioctl_encoder_cmd to handle start/stop command
- v4l2_m2m_decoder_cmd/v4l2_m2m_ioctl_decoder_cmd to handle start/stop command
- v4l2_m2m_update_start_streaming_state to update state on start of streaming
of the de/encoder queue
- v4l2_m2m_update_stop_streaming_state to update state on stop of streaming
of the de/encoder queue
- v4l2_m2m_last_buffer_done to make the current dest buffer as the last one

And inline helpers:
- v4l2_m2m_mark_stopped to mark the de/encoding process as stopped
- v4l2_m2m_clear_state to clear the de/encoding state
- v4l2_m2m_dst_buf_is_last to detect the current dequeued dst_buf is the last
- v4l2_m2m_has_stopped to detect the de/encoding stopped state
- v4l2_m2m_is_last_draining_src_buf to detect the current source buffer should
 be the last processing before stopping the de/encoding process

The special next-buf-is-last when min_buffers != 1 case is also handled
in v4l2_m2m_qbuf() by reusing the other introduced APIs.

This state management has been stolen from the vicodec implementation,
and is no-op for drivers not calling the v4l2_m2m_encoder_cmd or
v4l2_m2m_decoder_cmd and v4l2_m2m_update_start/stop_streaming_state.

The vicodec will be the first one to be converted as an example.

Signed-off-by: Neil Armstrong <[email protected]>
Signed-off-by: Hans Verkuil <[email protected]>
Signed-off-by: Mauro Carvalho Chehab <[email protected]>
  • Loading branch information
superna9999 authored and mchehab committed Mar 5, 2020
1 parent 2fae4d6 commit 2b48e11
Show file tree
Hide file tree
Showing 2 changed files with 352 additions and 2 deletions.
221 changes: 219 additions & 2 deletions drivers/media/v4l2-core/v4l2-mem2mem.c
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,11 @@ static void __v4l2_m2m_try_queue(struct v4l2_m2m_dev *m2m_dev,
m2m_ctx->new_frame = !dst->vb2_buf.copied_timestamp ||
dst->vb2_buf.timestamp != src->vb2_buf.timestamp;

if (m2m_ctx->has_stopped) {
dprintk("Device has stopped\n");
goto job_unlock;
}

if (m2m_dev->m2m_ops->job_ready
&& (!m2m_dev->m2m_ops->job_ready(m2m_ctx->priv))) {
dprintk("Driver not ready\n");
Expand Down Expand Up @@ -556,6 +561,140 @@ int v4l2_m2m_querybuf(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
}
EXPORT_SYMBOL_GPL(v4l2_m2m_querybuf);

/*
* This will add the LAST flag and mark the buffer management
* state as stopped.
* This is called when the last capture buffer must be flagged as LAST
* in draining mode from the encoder/decoder driver buf_queue() callback
* or from v4l2_update_last_buf_state() when a capture buffer is available.
*/
void v4l2_m2m_last_buffer_done(struct v4l2_m2m_ctx *m2m_ctx,
struct vb2_v4l2_buffer *vbuf)
{
vbuf->flags |= V4L2_BUF_FLAG_LAST;
vb2_buffer_done(&vbuf->vb2_buf, VB2_BUF_STATE_DONE);

v4l2_m2m_mark_stopped(m2m_ctx);
}
EXPORT_SYMBOL_GPL(v4l2_m2m_last_buffer_done);

/* When stop command is issued, update buffer management state */
static int v4l2_update_last_buf_state(struct v4l2_m2m_ctx *m2m_ctx)
{
struct vb2_v4l2_buffer *next_dst_buf;

if (m2m_ctx->is_draining)
return -EBUSY;

if (m2m_ctx->has_stopped)
return 0;

m2m_ctx->last_src_buf = v4l2_m2m_last_src_buf(m2m_ctx);
m2m_ctx->is_draining = true;

/*
* The processing of the last output buffer queued before
* the STOP command is expected to mark the buffer management
* state as stopped with v4l2_m2m_mark_stopped().
*/
if (m2m_ctx->last_src_buf)
return 0;

/*
* In case the output queue is empty, try to mark the last capture
* buffer as LAST.
*/
next_dst_buf = v4l2_m2m_dst_buf_remove(m2m_ctx);
if (!next_dst_buf) {
/*
* Wait for the next queued one in encoder/decoder driver
* buf_queue() callback using the v4l2_m2m_dst_buf_is_last()
* helper or in v4l2_m2m_qbuf() if encoder/decoder is not yet
* streaming.
*/
m2m_ctx->next_buf_last = true;
return 0;
}

v4l2_m2m_last_buffer_done(m2m_ctx, next_dst_buf);

return 0;
}

/*
* Updates the encoding/decoding buffer management state, should
* be called from encoder/decoder drivers start_streaming()
*/
void v4l2_m2m_update_start_streaming_state(struct v4l2_m2m_ctx *m2m_ctx,
struct vb2_queue *q)
{
/* If start streaming again, untag the last output buffer */
if (V4L2_TYPE_IS_OUTPUT(q->type))
m2m_ctx->last_src_buf = NULL;
}
EXPORT_SYMBOL_GPL(v4l2_m2m_update_start_streaming_state);

/*
* Updates the encoding/decoding buffer management state, should
* be called from encoder/decoder driver stop_streaming()
*/
void v4l2_m2m_update_stop_streaming_state(struct v4l2_m2m_ctx *m2m_ctx,
struct vb2_queue *q)
{
if (V4L2_TYPE_IS_OUTPUT(q->type)) {
/*
* If in draining state, either mark next dst buffer as
* done or flag next one to be marked as done either
* in encoder/decoder driver buf_queue() callback using
* the v4l2_m2m_dst_buf_is_last() helper or in v4l2_m2m_qbuf()
* if encoder/decoder is not yet streaming
*/
if (m2m_ctx->is_draining) {
struct vb2_v4l2_buffer *next_dst_buf;

m2m_ctx->last_src_buf = NULL;
next_dst_buf = v4l2_m2m_dst_buf_remove(m2m_ctx);
if (!next_dst_buf)
m2m_ctx->next_buf_last = true;
else
v4l2_m2m_last_buffer_done(m2m_ctx,
next_dst_buf);
}
} else {
v4l2_m2m_clear_state(m2m_ctx);
}
}
EXPORT_SYMBOL_GPL(v4l2_m2m_update_stop_streaming_state);

static void v4l2_m2m_force_last_buf_done(struct v4l2_m2m_ctx *m2m_ctx,
struct vb2_queue *q)
{
struct vb2_buffer *vb;
struct vb2_v4l2_buffer *vbuf;
unsigned int i;

if (WARN_ON(q->is_output))
return;
if (list_empty(&q->queued_list))
return;

vb = list_first_entry(&q->queued_list, struct vb2_buffer, queued_entry);
for (i = 0; i < vb->num_planes; i++)
vb2_set_plane_payload(vb, i, 0);

/*
* Since the buffer hasn't been queued to the ready queue,
* mark is active and owned before marking it LAST and DONE
*/
vb->state = VB2_BUF_STATE_ACTIVE;
atomic_inc(&q->owned_by_drv_count);

vbuf = to_vb2_v4l2_buffer(vb);
vbuf->field = V4L2_FIELD_NONE;

v4l2_m2m_last_buffer_done(m2m_ctx, vbuf);
}

int v4l2_m2m_qbuf(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
struct v4l2_buffer *buf)
{
Expand All @@ -570,11 +709,25 @@ int v4l2_m2m_qbuf(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
__func__);
return -EPERM;
}

ret = vb2_qbuf(vq, vdev->v4l2_dev->mdev, buf);
if (!ret && !(buf->flags & V4L2_BUF_FLAG_IN_REQUEST))
if (ret)
return ret;

/*
* If the capture queue is streaming, but streaming hasn't started
* on the device, but was asked to stop, mark the previously queued
* buffer as DONE with LAST flag since it won't be queued on the
* device.
*/
if (!V4L2_TYPE_IS_OUTPUT(vq->type) &&
vb2_is_streaming(vq) && !vb2_start_streaming_called(vq) &&
(v4l2_m2m_has_stopped(m2m_ctx) || v4l2_m2m_dst_buf_is_last(m2m_ctx)))
v4l2_m2m_force_last_buf_done(m2m_ctx, vq);
else if (!(buf->flags & V4L2_BUF_FLAG_IN_REQUEST))
v4l2_m2m_try_schedule(m2m_ctx);

return ret;
return 0;
}
EXPORT_SYMBOL_GPL(v4l2_m2m_qbuf);

Expand Down Expand Up @@ -1225,6 +1378,70 @@ int v4l2_m2m_ioctl_try_decoder_cmd(struct file *file, void *fh,
}
EXPORT_SYMBOL_GPL(v4l2_m2m_ioctl_try_decoder_cmd);

/*
* Updates the encoding state on ENC_CMD_STOP/ENC_CMD_START
* Should be called from the encoder driver encoder_cmd() callback
*/
int v4l2_m2m_encoder_cmd(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
struct v4l2_encoder_cmd *ec)
{
if (ec->cmd != V4L2_ENC_CMD_STOP && ec->cmd != V4L2_ENC_CMD_START)
return -EINVAL;

if (ec->cmd == V4L2_ENC_CMD_STOP)
return v4l2_update_last_buf_state(m2m_ctx);

if (m2m_ctx->is_draining)
return -EBUSY;

if (m2m_ctx->has_stopped)
m2m_ctx->has_stopped = false;

return 0;
}
EXPORT_SYMBOL_GPL(v4l2_m2m_encoder_cmd);

/*
* Updates the decoding state on DEC_CMD_STOP/DEC_CMD_START
* Should be called from the decoder driver decoder_cmd() callback
*/
int v4l2_m2m_decoder_cmd(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
struct v4l2_decoder_cmd *dc)
{
if (dc->cmd != V4L2_DEC_CMD_STOP && dc->cmd != V4L2_DEC_CMD_START)
return -EINVAL;

if (dc->cmd == V4L2_DEC_CMD_STOP)
return v4l2_update_last_buf_state(m2m_ctx);

if (m2m_ctx->is_draining)
return -EBUSY;

if (m2m_ctx->has_stopped)
m2m_ctx->has_stopped = false;

return 0;
}
EXPORT_SYMBOL_GPL(v4l2_m2m_decoder_cmd);

int v4l2_m2m_ioctl_encoder_cmd(struct file *file, void *priv,
struct v4l2_encoder_cmd *ec)
{
struct v4l2_fh *fh = file->private_data;

return v4l2_m2m_encoder_cmd(file, fh->m2m_ctx, ec);
}
EXPORT_SYMBOL_GPL(v4l2_m2m_ioctl_encoder_cmd);

int v4l2_m2m_ioctl_decoder_cmd(struct file *file, void *priv,
struct v4l2_decoder_cmd *dc)
{
struct v4l2_fh *fh = file->private_data;

return v4l2_m2m_decoder_cmd(file, fh->m2m_ctx, dc);
}
EXPORT_SYMBOL_GPL(v4l2_m2m_ioctl_decoder_cmd);

int v4l2_m2m_ioctl_stateless_try_decoder_cmd(struct file *file, void *fh,
struct v4l2_decoder_cmd *dc)
{
Expand Down
Loading

0 comments on commit 2b48e11

Please sign in to comment.