OSDN Git Service

media: v4l2-mem2mem: handle draining, stopped and next-buf-is-last states
authorNeil Armstrong <narmstrong@baylibre.com>
Tue, 3 Mar 2020 14:33:17 +0000 (15:33 +0100)
committerMauro Carvalho Chehab <mchehab+huawei@kernel.org>
Thu, 5 Mar 2020 21:54:49 +0000 (22:54 +0100)
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 <narmstrong@baylibre.com>
Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
drivers/media/v4l2-core/v4l2-mem2mem.c
include/media/v4l2-mem2mem.h

index cc34c5a..8986c31 100644 (file)
@@ -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");
@@ -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)
 {
@@ -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);
 
@@ -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)
 {
index 1d85e24..98753f0 100644 (file)
@@ -80,6 +80,10 @@ struct v4l2_m2m_queue_ctx {
  *             for an existing frame. This is always true unless
  *             V4L2_BUF_CAP_SUPPORTS_M2M_HOLD_CAPTURE_BUF is set, which
  *             indicates slicing support.
+ * @is_draining: indicates device is in draining phase
+ * @last_src_buf: indicate the last source buffer for draining
+ * @next_buf_last: next capture queud buffer will be tagged as last
+ * @has_stopped: indicate the device has been stopped
  * @m2m_dev: opaque pointer to the internal data to handle M2M context
  * @cap_q_ctx: Capture (output to memory) queue context
  * @out_q_ctx: Output (input from memory) queue context
@@ -98,6 +102,11 @@ struct v4l2_m2m_ctx {
 
        bool                            new_frame;
 
+       bool                            is_draining;
+       struct vb2_v4l2_buffer          *last_src_buf;
+       bool                            next_buf_last;
+       bool                            has_stopped;
+
        /* internal use only */
        struct v4l2_m2m_dev             *m2m_dev;
 
@@ -216,6 +225,86 @@ v4l2_m2m_buf_done(struct vb2_v4l2_buffer *buf, enum vb2_buffer_state state)
 }
 
 /**
+ * v4l2_m2m_clear_state() - clear encoding/decoding state
+ *
+ * @m2m_ctx: m2m context assigned to the instance given by struct &v4l2_m2m_ctx
+ */
+static inline void
+v4l2_m2m_clear_state(struct v4l2_m2m_ctx *m2m_ctx)
+{
+       m2m_ctx->next_buf_last = false;
+       m2m_ctx->is_draining = false;
+       m2m_ctx->has_stopped = false;
+}
+
+/**
+ * v4l2_m2m_mark_stopped() - set current encoding/decoding state as stopped
+ *
+ * @m2m_ctx: m2m context assigned to the instance given by struct &v4l2_m2m_ctx
+ */
+static inline void
+v4l2_m2m_mark_stopped(struct v4l2_m2m_ctx *m2m_ctx)
+{
+       m2m_ctx->next_buf_last = false;
+       m2m_ctx->is_draining = false;
+       m2m_ctx->has_stopped = true;
+}
+
+/**
+ * v4l2_m2m_dst_buf_is_last() - return the current encoding/decoding session
+ * draining management state of next queued capture buffer
+ *
+ * This last capture buffer should be tagged with V4L2_BUF_FLAG_LAST to notify
+ * the end of the capture session.
+ *
+ * @m2m_ctx: m2m context assigned to the instance given by struct &v4l2_m2m_ctx
+ */
+static inline bool
+v4l2_m2m_dst_buf_is_last(struct v4l2_m2m_ctx *m2m_ctx)
+{
+       return m2m_ctx->is_draining && m2m_ctx->next_buf_last;
+}
+
+/**
+ * v4l2_m2m_has_stopped() - return the current encoding/decoding session
+ * stopped state
+ *
+ * @m2m_ctx: m2m context assigned to the instance given by struct &v4l2_m2m_ctx
+ */
+static inline bool
+v4l2_m2m_has_stopped(struct v4l2_m2m_ctx *m2m_ctx)
+{
+       return m2m_ctx->has_stopped;
+}
+
+/**
+ * v4l2_m2m_is_last_draining_src_buf() - return the output buffer draining
+ * state in the current encoding/decoding session
+ *
+ * This will identify the last output buffer queued before a session stop
+ * was required, leading to an actual encoding/decoding session stop state
+ * in the encoding/decoding process after being processed.
+ *
+ * @m2m_ctx: m2m context assigned to the instance given by struct &v4l2_m2m_ctx
+ * @vbuf: pointer to struct &v4l2_buffer
+ */
+static inline bool
+v4l2_m2m_is_last_draining_src_buf(struct v4l2_m2m_ctx *m2m_ctx,
+                                 struct vb2_v4l2_buffer *vbuf)
+{
+       return m2m_ctx->is_draining && vbuf == m2m_ctx->last_src_buf;
+}
+
+/**
+ * v4l2_m2m_last_buffer_done() - marks the buffer with LAST flag and DONE
+ *
+ * @m2m_ctx: m2m context assigned to the instance given by struct &v4l2_m2m_ctx
+ * @vbuf: pointer to struct &v4l2_buffer
+ */
+void v4l2_m2m_last_buffer_done(struct v4l2_m2m_ctx *m2m_ctx,
+                              struct vb2_v4l2_buffer *vbuf);
+
+/**
  * v4l2_m2m_reqbufs() - multi-queue-aware REQBUFS multiplexer
  *
  * @file: pointer to struct &file
@@ -313,6 +402,46 @@ int v4l2_m2m_streamoff(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
                       enum v4l2_buf_type type);
 
 /**
+ * v4l2_m2m_update_start_streaming_state() - update the encoding/decoding
+ * session state when a start of streaming of a video queue is requested
+ *
+ * @m2m_ctx: m2m context assigned to the instance given by struct &v4l2_m2m_ctx
+ * @q: queue
+ */
+void v4l2_m2m_update_start_streaming_state(struct v4l2_m2m_ctx *m2m_ctx,
+                                          struct vb2_queue *q);
+
+/**
+ * v4l2_m2m_update_stop_streaming_state() -  update the encoding/decoding
+ * session state when a stop of streaming of a video queue is requested
+ *
+ * @m2m_ctx: m2m context assigned to the instance given by struct &v4l2_m2m_ctx
+ * @q: queue
+ */
+void v4l2_m2m_update_stop_streaming_state(struct v4l2_m2m_ctx *m2m_ctx,
+                                         struct vb2_queue *q);
+
+/**
+ * v4l2_m2m_encoder_cmd() - execute an encoder command
+ *
+ * @file: pointer to struct &file
+ * @m2m_ctx: m2m context assigned to the instance given by struct &v4l2_m2m_ctx
+ * @ec: pointer to the encoder command
+ */
+int v4l2_m2m_encoder_cmd(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
+                        struct v4l2_encoder_cmd *ec);
+
+/**
+ * v4l2_m2m_decoder_cmd() - execute a decoder command
+ *
+ * @file: pointer to struct &file
+ * @m2m_ctx: m2m context assigned to the instance given by struct &v4l2_m2m_ctx
+ * @dc: pointer to the decoder command
+ */
+int v4l2_m2m_decoder_cmd(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
+                        struct v4l2_decoder_cmd *dc);
+
+/**
  * v4l2_m2m_poll() - poll replacement, for destination buffers only
  *
  * @file: pointer to struct &file
@@ -704,6 +833,10 @@ int v4l2_m2m_ioctl_streamon(struct file *file, void *fh,
                                enum v4l2_buf_type type);
 int v4l2_m2m_ioctl_streamoff(struct file *file, void *fh,
                                enum v4l2_buf_type type);
+int v4l2_m2m_ioctl_encoder_cmd(struct file *file, void *fh,
+                              struct v4l2_encoder_cmd *ec);
+int v4l2_m2m_ioctl_decoder_cmd(struct file *file, void *fh,
+                              struct v4l2_decoder_cmd *dc);
 int v4l2_m2m_ioctl_try_encoder_cmd(struct file *file, void *fh,
                                   struct v4l2_encoder_cmd *ec);
 int v4l2_m2m_ioctl_try_decoder_cmd(struct file *file, void *fh,