2020-04-15 22:01:41

by Helen Koike

[permalink] [raw]
Subject: [PATCH v3 0/4] media: add v4l2_pipeline_stream_{enable,disable} helpers

Hi,

Media drivers need to iterate through the pipeline and call .s_stream()
callbacks in the subdevices.

Instead of repeating code, add helpers for this.

These helpers will go walk through the pipeline only visiting entities
that participates in the stream, i.e. it follows links from sink to source
(and not the opposite).

Which means that in a topology like this https://bit.ly/3b2MxjI
calling v4l2_pipeline_stream_enable() from rkisp1_mainpath won't call
.s_stream(true) for rkisp1_resizer_selfpath.

stream_count variable was added in v4l2_subdevice to handle nested calls
to the helpers.
This is useful when the driver allows streaming from more then one
capture device sharing subdevices.

This patch came from the error I was facing when multistreaming from
rkisp1 driver, where stoping one capture would call s_stream(false) in
the pipeline, causing a stall in the second capture device.

Also, the vimc patch https://patchwork.kernel.org/patch/10948833/ won't
be required with this patchset.

This patchset was tested on rkisp1 and vimc drivers.

Other cleanup might be possible (but I won't add in this patchset as I
don't have the hw to test):
https://git.linuxtv.org/media_tree.git/tree/drivers/media/platform/qcom/camss/camss-video.c#n430
https://git.linuxtv.org/media_tree.git/tree/drivers/media/platform/omap3isp/isp.c#n697
https://git.linuxtv.org/media_tree.git/tree/drivers/media/platform/stm32/stm32-dcmi.c#n680
https://git.linuxtv.org/media_tree.git/tree/drivers/media/platform/xilinx/xilinx-dma.c#n97

Changes in V3:
====================
Following up Niklas' comments in V2 https://patchwork.kernel.org/patch/11473681/#23270823

* I removed the limitation in topologies with entities with multiple enabled
links to its sink pads in the topology.
Now it enables all subdevs in the pipeline that have an enabled link going
from sink to source while walking from the video device, so it can be
also useful for rcar-vin driver.

To implement this, I added back in the series the patch from v1:
"media: mc-entity.c: add media_graph_walk_next_stream()"

* "size" was renamed to "max_size" in function v4l2_pipeline_subdevs_get()
to reflect the maximum number of elements that can fit in the subdevs array,
with proper documentation.

* v4l2_pipeline_subdevs_get() returns a negative number for error, instead
of returning 0 and printing a warning.

* I also add if defined(CONFIG_MEDIA_CONTROLLER) around helpers to avoid
compiling errors.

Overview of patches in V3:
--------------------------

Patch 1/4 adds a new iterator function to follow links from sink to
source only.

Path 2/4 adds the helpers in v4l2-common.c, allowing nested calls by
adding stream_count in the subdevice struct.

Patch 3/4 cleanup rkisp1 driver to use the helpers.

Patch 4/4 cleanup vimc driver to use the helpers.

Changes in V2:
====================
The first version was calling the s_stream() callbacks from sensor to
capture.

This was generating errors in the Scarlet Chromebook, when the sensor
was being enabled before the ISP.

It make sense to enable subdevices from capture to sensor instead (which
is what most drivers do already).

This v2 drops the changes from mc-entity.c, and re-implement helpers in
v4l2-common.c

Overview of patches in V2:
--------------------------

Path 1/3 adds the helpers in v4l2-common.c, allowing nested calls by
adding stream_count in the subdevice struct.

Patch 2/3 cleanup rkisp1 driver to use the helpers.

Patch 3/3 cleanup vimc driver to use the helpers.

Helen Koike (4):
media: mc-entity.c: add media_graph_walk_next_stream()
media: v4l2-common: add helper functions to call s_stream() callbacks
media: staging: rkisp1: use v4l2_pipeline_stream_{enable,disable}
helpers
media: vimc: use v4l2_pipeline_stream_{enable,disable} helpers

drivers/media/mc/mc-entity.c | 34 ++++-
.../media/test_drivers/vimc/vimc-capture.c | 28 ++--
.../media/test_drivers/vimc/vimc-streamer.c | 49 +------
drivers/media/v4l2-core/v4l2-common.c | 125 ++++++++++++++++++
drivers/staging/media/rkisp1/rkisp1-capture.c | 76 +----------
include/media/media-entity.h | 15 +++
include/media/v4l2-common.h | 43 ++++++
include/media/v4l2-subdev.h | 2 +
8 files changed, 242 insertions(+), 130 deletions(-)

--
2.26.0


2020-04-15 22:03:13

by Helen Koike

[permalink] [raw]
Subject: [PATCH v3 3/4] media: staging: rkisp1: use v4l2_pipeline_stream_{enable,disable} helpers

Use v4l2_pipeline_stream_{enable,disable} to call .s_stream() subdevice
callbacks through the pipeline.

Tested by streaming on Scarlet Chromebook.

Signed-off-by: Helen Koike <[email protected]>

---

Changes in v3:
- rebase on top of new helpers prototypes

Changes in v2:
- rebase on top of new helpers prototypes

drivers/staging/media/rkisp1/rkisp1-capture.c | 76 +------------------
1 file changed, 3 insertions(+), 73 deletions(-)

diff --git a/drivers/staging/media/rkisp1/rkisp1-capture.c b/drivers/staging/media/rkisp1/rkisp1-capture.c
index 24fe6a7888aa4..a18f1668e3563 100644
--- a/drivers/staging/media/rkisp1/rkisp1-capture.c
+++ b/drivers/staging/media/rkisp1/rkisp1-capture.c
@@ -838,71 +838,6 @@ static void rkisp1_return_all_buffers(struct rkisp1_capture *cap,
spin_unlock_irqrestore(&cap->buf.lock, flags);
}

-/*
- * rkisp1_pipeline_sink_walk - Walk through the pipeline and call cb
- * @from: entity at which to start pipeline walk
- * @until: entity at which to stop pipeline walk
- *
- * Walk the entities chain starting at the pipeline video node and stop
- * all subdevices in the chain.
- *
- * If the until argument isn't NULL, stop the pipeline walk when reaching the
- * until entity. This is used to disable a partially started pipeline due to a
- * subdev start error.
- */
-static int rkisp1_pipeline_sink_walk(struct media_entity *from,
- struct media_entity *until,
- int (*cb)(struct media_entity *from,
- struct media_entity *curr))
-{
- struct media_entity *entity = from;
- struct media_pad *pad;
- unsigned int i;
- int ret;
-
- while (1) {
- pad = NULL;
- /* Find remote source pad */
- for (i = 0; i < entity->num_pads; i++) {
- struct media_pad *spad = &entity->pads[i];
-
- if (!(spad->flags & MEDIA_PAD_FL_SINK))
- continue;
- pad = media_entity_remote_pad(spad);
- if (pad && is_media_entity_v4l2_subdev(pad->entity))
- break;
- }
- if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
- break;
-
- entity = pad->entity;
- if (entity == until)
- break;
-
- ret = cb(from, entity);
- if (ret)
- return ret;
- }
-
- return 0;
-}
-
-static int rkisp1_pipeline_disable_cb(struct media_entity *from,
- struct media_entity *curr)
-{
- struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(curr);
-
- return v4l2_subdev_call(sd, video, s_stream, false);
-}
-
-static int rkisp1_pipeline_enable_cb(struct media_entity *from,
- struct media_entity *curr)
-{
- struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(curr);
-
- return v4l2_subdev_call(sd, video, s_stream, true);
-}
-
static void rkisp1_stream_stop(struct rkisp1_capture *cap)
{
int ret;
@@ -929,11 +864,7 @@ static void rkisp1_vb2_stop_streaming(struct vb2_queue *queue)

rkisp1_stream_stop(cap);
media_pipeline_stop(&node->vdev.entity);
- ret = rkisp1_pipeline_sink_walk(&node->vdev.entity, NULL,
- rkisp1_pipeline_disable_cb);
- if (ret)
- dev_err(rkisp1->dev,
- "pipeline stream-off failed error:%d\n", ret);
+ v4l2_pipeline_stream_disable(&node->vdev, &cap->rkisp1->pipe);

rkisp1_return_all_buffers(cap, VB2_BUF_STATE_ERROR);

@@ -1005,8 +936,7 @@ rkisp1_vb2_start_streaming(struct vb2_queue *queue, unsigned int count)
rkisp1_stream_start(cap);

/* start sub-devices */
- ret = rkisp1_pipeline_sink_walk(entity, NULL,
- rkisp1_pipeline_enable_cb);
+ ret = v4l2_pipeline_stream_enable(&cap->vnode.vdev, &cap->rkisp1->pipe);
if (ret)
goto err_stop_stream;

@@ -1019,7 +949,7 @@ rkisp1_vb2_start_streaming(struct vb2_queue *queue, unsigned int count)
return 0;

err_pipe_disable:
- rkisp1_pipeline_sink_walk(entity, NULL, rkisp1_pipeline_disable_cb);
+ v4l2_pipeline_stream_disable(&cap->vnode.vdev, &cap->rkisp1->pipe);
err_stop_stream:
rkisp1_stream_stop(cap);
v4l2_pipeline_pm_put(entity);
--
2.26.0

2020-04-15 22:03:24

by Helen Koike

[permalink] [raw]
Subject: [PATCH v3 4/4] media: vimc: use v4l2_pipeline_stream_{enable,disable} helpers

Use v4l2_pipeline_stream_{enable,disable} to call .s_stream() subdevice
callbacks through the pipeline.

Tested streaming works with:

media-ctl -d /dev/media0 -V '"Sensor A":0[fmt:SBGGR8_1X8/640x480]'
media-ctl -d /dev/media0 -V '"Debayer A":0[fmt:SBGGR8_1X8/640x480]'
media-ctl -d /dev/media0 -V '"Sensor B":0[fmt:SBGGR8_1X8/640x480]'
media-ctl -d /dev/media0 -V '"Debayer B":0[fmt:SBGGR8_1X8/640x480]'
media-ctl -d /dev/media0 -V '"Scaler":0[fmt:RGB888_1X24/640x480]'
media-ctl -d /dev/media0 -V '"Scaler":0[crop:(100,50)/400x150]'
media-ctl -d /dev/media0 -V '"Scaler":1[fmt:RGB888_1X24/1920x1440]'
v4l2-ctl -d /dev/video2 -v width=1200,height=450
v4l2-ctl -d /dev/video0 -v pixelformat=BA81
v4l2-ctl -d /dev/video1 -v pixelformat=BA81
v4l2-ctl --stream-mmap --stream-count=10 -d /dev/video2 --stream-to=/tmp/test.raw

Signed-off-by: Helen Koike <[email protected]>

---

Changes in v3:
- rebase on top of new helpers prototypes

Changes in v2:
- rebase on top of new helpers prototypes

.../media/test_drivers/vimc/vimc-capture.c | 28 +++++++----
.../media/test_drivers/vimc/vimc-streamer.c | 49 ++-----------------
2 files changed, 23 insertions(+), 54 deletions(-)

diff --git a/drivers/media/test_drivers/vimc/vimc-capture.c b/drivers/media/test_drivers/vimc/vimc-capture.c
index 5315c201314c9..73707634010e9 100644
--- a/drivers/media/test_drivers/vimc/vimc-capture.c
+++ b/drivers/media/test_drivers/vimc/vimc-capture.c
@@ -233,21 +233,27 @@ static int vimc_cap_start_streaming(struct vb2_queue *vq, unsigned int count)

vcap->sequence = 0;

- /* Start the media pipeline */
ret = media_pipeline_start(entity, &vcap->stream.pipe);
- if (ret) {
- vimc_cap_return_all_buffers(vcap, VB2_BUF_STATE_QUEUED);
- return ret;
- }
+ if (ret)
+ goto err_return_all_buffers;
+
+ ret = v4l2_pipeline_stream_enable(&vcap->vdev, &vcap->stream.pipe);
+ if (ret)
+ goto err_stop_media_pipe;

ret = vimc_streamer_s_stream(&vcap->stream, &vcap->ved, 1);
- if (ret) {
- media_pipeline_stop(entity);
- vimc_cap_return_all_buffers(vcap, VB2_BUF_STATE_QUEUED);
- return ret;
- }
+ if (ret)
+ goto err_stop_stream;

return 0;
+
+err_stop_stream:
+ v4l2_pipeline_stream_disable(&vcap->vdev, &vcap->stream.pipe);
+err_stop_media_pipe:
+ media_pipeline_stop(entity);
+err_return_all_buffers:
+ vimc_cap_return_all_buffers(vcap, VB2_BUF_STATE_QUEUED);
+ return ret;
}

/*
@@ -260,6 +266,8 @@ static void vimc_cap_stop_streaming(struct vb2_queue *vq)

vimc_streamer_s_stream(&vcap->stream, &vcap->ved, 0);

+ v4l2_pipeline_stream_disable(&vcap->vdev, &vcap->stream.pipe);
+
/* Stop the media pipeline */
media_pipeline_stop(&vcap->vdev.entity);

diff --git a/drivers/media/test_drivers/vimc/vimc-streamer.c b/drivers/media/test_drivers/vimc/vimc-streamer.c
index 65feb3c596db5..c0085f4695c2f 100644
--- a/drivers/media/test_drivers/vimc/vimc-streamer.c
+++ b/drivers/media/test_drivers/vimc/vimc-streamer.c
@@ -36,33 +36,6 @@ static struct media_entity *vimc_get_source_entity(struct media_entity *ent)
return NULL;
}

-/**
- * vimc_streamer_pipeline_terminate - Disable stream in all ved in stream
- *
- * @stream: the pointer to the stream structure with the pipeline to be
- * disabled.
- *
- * Calls s_stream to disable the stream in each entity of the pipeline
- *
- */
-static void vimc_streamer_pipeline_terminate(struct vimc_stream *stream)
-{
- struct vimc_ent_device *ved;
- struct v4l2_subdev *sd;
-
- while (stream->pipe_size) {
- stream->pipe_size--;
- ved = stream->ved_pipeline[stream->pipe_size];
- stream->ved_pipeline[stream->pipe_size] = NULL;
-
- if (!is_media_entity_v4l2_subdev(ved->ent))
- continue;
-
- sd = media_entity_to_v4l2_subdev(ved->ent);
- v4l2_subdev_call(sd, video, s_stream, 0);
- }
-}
-
/**
* vimc_streamer_pipeline_init - Initializes the stream structure
*
@@ -82,27 +55,15 @@ static int vimc_streamer_pipeline_init(struct vimc_stream *stream,
struct media_entity *entity;
struct video_device *vdev;
struct v4l2_subdev *sd;
- int ret = 0;

stream->pipe_size = 0;
while (stream->pipe_size < VIMC_STREAMER_PIPELINE_MAX_SIZE) {
if (!ved) {
- vimc_streamer_pipeline_terminate(stream);
+ stream->pipe_size = 0;
return -EINVAL;
}
stream->ved_pipeline[stream->pipe_size++] = ved;

- if (is_media_entity_v4l2_subdev(ved->ent)) {
- sd = media_entity_to_v4l2_subdev(ved->ent);
- ret = v4l2_subdev_call(sd, video, s_stream, 1);
- if (ret && ret != -ENOIOCTLCMD) {
- dev_err(ved->dev, "subdev_call error %s\n",
- ved->ent->name);
- vimc_streamer_pipeline_terminate(stream);
- return ret;
- }
- }
-
entity = vimc_get_source_entity(ved->ent);
/* Check if the end of the pipeline was reached */
if (!entity) {
@@ -111,7 +72,7 @@ static int vimc_streamer_pipeline_init(struct vimc_stream *stream,
dev_err(ved->dev,
"first entity in the pipe '%s' is not a source\n",
ved->ent->name);
- vimc_streamer_pipeline_terminate(stream);
+ stream->pipe_size = 0;
return -EPIPE;
}
return 0;
@@ -129,7 +90,7 @@ static int vimc_streamer_pipeline_init(struct vimc_stream *stream,
}
}

- vimc_streamer_pipeline_terminate(stream);
+ stream->pipe_size = 0;
return -EINVAL;
}

@@ -210,7 +171,7 @@ int vimc_streamer_s_stream(struct vimc_stream *stream,
if (IS_ERR(stream->kthread)) {
ret = PTR_ERR(stream->kthread);
dev_err(ved->dev, "kthread_run failed with %d\n", ret);
- vimc_streamer_pipeline_terminate(stream);
+ stream->pipe_size = 0;
stream->kthread = NULL;
return ret;
}
@@ -231,7 +192,7 @@ int vimc_streamer_s_stream(struct vimc_stream *stream,

stream->kthread = NULL;

- vimc_streamer_pipeline_terminate(stream);
+ stream->pipe_size = 0;
}

return 0;
--
2.26.0

2020-04-15 23:38:15

by Helen Koike

[permalink] [raw]
Subject: [PATCH v3 1/4] media: mc-entity.c: add media_graph_walk_next_stream()

Add media_graph_walk_next_stream() function to follow links only from
sink to source (not the opposite) to allow iteration only through the
entities participating in a given stream.

This is useful to allow calling .s_stream() callback only in the
subdevices that requires to be enabled/disabled, and avoid calling this
callback when not required.

Signed-off-by: Helen Koike <[email protected]>

---

Changes in v3:
- Patch re-added in the series from version 1

Changes in v2: None

drivers/media/mc/mc-entity.c | 34 +++++++++++++++++++++++++++++++---
include/media/media-entity.h | 15 +++++++++++++++
2 files changed, 46 insertions(+), 3 deletions(-)

diff --git a/drivers/media/mc/mc-entity.c b/drivers/media/mc/mc-entity.c
index 211279c5fd77d..0d44c2de23e6f 100644
--- a/drivers/media/mc/mc-entity.c
+++ b/drivers/media/mc/mc-entity.c
@@ -228,6 +228,11 @@ EXPORT_SYMBOL_GPL(media_entity_pads_init);
* Graph traversal
*/

+enum media_graph_walk_type {
+ MEDIA_GRAPH_WALK_CONNECTED_NODES,
+ MEDIA_GRAPH_WALK_STREAM_NODES,
+};
+
static struct media_entity *
media_entity_other(struct media_entity *entity, struct media_link *link)
{
@@ -305,7 +310,8 @@ void media_graph_walk_start(struct media_graph *graph,
}
EXPORT_SYMBOL_GPL(media_graph_walk_start);

-static void media_graph_walk_iter(struct media_graph *graph)
+static void media_graph_walk_iter(struct media_graph *graph,
+ enum media_graph_walk_type type)
{
struct media_entity *entity = stack_top(graph);
struct media_link *link;
@@ -326,6 +332,15 @@ static void media_graph_walk_iter(struct media_graph *graph)
/* Get the entity in the other end of the link . */
next = media_entity_other(entity, link);

+ if (type == MEDIA_GRAPH_WALK_STREAM_NODES
+ && next == link->sink->entity) {
+ link_top(graph) = link_top(graph)->next;
+ dev_dbg(entity->graph_obj.mdev->dev,
+ "walk: skipping '%s' (outside of the stream path)\n",
+ link->sink->entity->name);
+ return;
+ }
+
/* Has the entity already been visited? */
if (media_entity_enum_test_and_set(&graph->ent_enum, next)) {
link_top(graph) = link_top(graph)->next;
@@ -342,7 +357,9 @@ static void media_graph_walk_iter(struct media_graph *graph)
next->name);
}

-struct media_entity *media_graph_walk_next(struct media_graph *graph)
+static struct media_entity *
+__media_graph_walk_next(struct media_graph *graph,
+ enum media_graph_walk_type type)
{
struct media_entity *entity;

@@ -355,7 +372,7 @@ struct media_entity *media_graph_walk_next(struct media_graph *graph)
* found.
*/
while (link_top(graph) != &stack_top(graph)->links)
- media_graph_walk_iter(graph);
+ media_graph_walk_iter(graph, type);

entity = stack_pop(graph);
dev_dbg(entity->graph_obj.mdev->dev,
@@ -363,8 +380,19 @@ struct media_entity *media_graph_walk_next(struct media_graph *graph)

return entity;
}
+
+struct media_entity *media_graph_walk_next(struct media_graph *graph)
+{
+ return __media_graph_walk_next(graph, MEDIA_GRAPH_WALK_CONNECTED_NODES);
+}
EXPORT_SYMBOL_GPL(media_graph_walk_next);

+struct media_entity *media_graph_walk_next_stream(struct media_graph *graph)
+{
+ return __media_graph_walk_next(graph, MEDIA_GRAPH_WALK_STREAM_NODES);
+}
+EXPORT_SYMBOL_GPL(media_graph_walk_next_stream);
+
int media_entity_get_fwnode_pad(struct media_entity *entity,
struct fwnode_handle *fwnode,
unsigned long direction_flags)
diff --git a/include/media/media-entity.h b/include/media/media-entity.h
index 8cb2c504a05c7..f17a5180ce524 100644
--- a/include/media/media-entity.h
+++ b/include/media/media-entity.h
@@ -927,6 +927,21 @@ void media_graph_walk_start(struct media_graph *graph,
*/
struct media_entity *media_graph_walk_next(struct media_graph *graph);

+/**
+ * media_graph_walk_next_stream - Get the next entity in the graph
+ * @graph: Media graph structure
+ *
+ * Perform a depth-first traversal of the given media entities graph only
+ * following links from sink to source (and not the opposite).
+ *
+ * The graph structure must have been previously initialized with a call to
+ * media_graph_walk_start().
+ *
+ * Return: returns the next entity in the graph in the stream path
+ * or %NULL if the whole stream path have been traversed.
+ */
+struct media_entity *media_graph_walk_next_stream(struct media_graph *graph);
+
/**
* media_pipeline_start - Mark a pipeline as streaming
* @entity: Starting entity
--
2.26.0

2020-04-15 23:38:41

by Helen Koike

[permalink] [raw]
Subject: [PATCH v3 2/4] media: v4l2-common: add helper functions to call s_stream() callbacks

Add v4l2_pipeline_stream_{enable,disable} helper functions to iterate
through the subdevices in a given stream (i.e following links from sink
to source) and call .s_stream() callback.

Add stream_count on the subdevice object for simultaneous streaming from
different video devices which shares subdevices.

Prevent calling .s_stream(true) if it was already called previously by
another stream.

Prevent calling .s_stream(false) from one stream when there are still
others active.

If .s_stream(true) fails, call .s_stream(false) in the reverse order.

Signed-off-by: Helen Koike <[email protected]>
---

Changes in v3:
- re-write helpers to use media walkers as in v1, but with
v4l2_pipeline_subdevs_get() to reverse the order we call s_stream(true)
in subdevices.
- rename size to max_size (and update docs) in v4l2_pipeline_subdevs_get() to
reflect the meaning of the argument.
- add if defined(CONFIG_MEDIA_CONTROLLER) around helpers

Changes in v2:
- re-write helpers to not use media walkers

drivers/media/v4l2-core/v4l2-common.c | 125 ++++++++++++++++++++++++++
include/media/v4l2-common.h | 43 +++++++++
include/media/v4l2-subdev.h | 2 +
3 files changed, 170 insertions(+)

diff --git a/drivers/media/v4l2-core/v4l2-common.c b/drivers/media/v4l2-core/v4l2-common.c
index 9e8eb45a5b03c..2f991a1a57d7c 100644
--- a/drivers/media/v4l2-core/v4l2-common.c
+++ b/drivers/media/v4l2-core/v4l2-common.c
@@ -442,3 +442,128 @@ int v4l2_fill_pixfmt(struct v4l2_pix_format *pixfmt, u32 pixelformat,
return 0;
}
EXPORT_SYMBOL_GPL(v4l2_fill_pixfmt);
+
+#if defined(CONFIG_MEDIA_CONTROLLER)
+
+/*
+ * v4l2_pipeline_subdevs_get - Assemble a list of subdevices in a pipeline
+ * @subdevs: the array to be filled.
+ * @max_size: max number of elements that can fit in subdevs array.
+ *
+ * Walk from a video node, following links from sink to source and fill the
+ * array with subdevices in the path.
+ * The walker performs a depth-first traversal, which means that, in a topology
+ * sd1->sd2->sd3->vdev, sd1 will be the first element placed in the array.
+ *
+ * Return the number of subdevices filled in the array.
+ */
+static int v4l2_pipeline_subdevs_get(struct video_device *vdev,
+ struct media_pipeline *pipe,
+ struct v4l2_subdev **subdevs,
+ unsigned int max_size)
+{
+ struct media_entity *entity = &vdev->entity;
+ struct media_device *mdev = entity->graph_obj.mdev;
+ int idx = 0;
+ int ret;
+
+ mutex_lock(&mdev->graph_mutex);
+
+ if (!pipe->streaming_count) {
+ ret = media_graph_walk_init(&pipe->graph, mdev);
+ if (ret) {
+ mutex_unlock(&mdev->graph_mutex);
+ return ret;
+ }
+ }
+
+ media_graph_walk_start(&pipe->graph, entity);
+
+ while ((entity = media_graph_walk_next_stream(&pipe->graph))) {
+ if (!is_media_entity_v4l2_subdev(entity))
+ continue;
+ if (WARN_ON(idx >= max_size)) {
+ mutex_unlock(&mdev->graph_mutex);
+ return -EINVAL;
+ }
+ subdevs[idx++] = media_entity_to_v4l2_subdev(entity);
+ }
+
+ if (!pipe->streaming_count)
+ media_graph_walk_cleanup(&pipe->graph);
+
+ mutex_unlock(&mdev->graph_mutex);
+
+ return idx;
+}
+
+__must_check int v4l2_pipeline_stream_enable(struct video_device *vdev,
+ struct media_pipeline *pipe)
+{
+ struct media_device *mdev = vdev->entity.graph_obj.mdev;
+ struct v4l2_subdev *subdevs[MEDIA_ENTITY_ENUM_MAX_DEPTH];
+ struct v4l2_subdev *sd;
+ int i, size, ret;
+
+ size = v4l2_pipeline_subdevs_get(vdev, pipe,
+ subdevs, ARRAY_SIZE(subdevs));
+ if (size <= 0)
+ return size;
+
+ /* Call s_stream() in reverse order to enable sensors last */
+ for (i = size - 1; i >= 0; i--) {
+ sd = subdevs[i];
+ if (sd->stream_count++)
+ continue;
+ dev_dbg(mdev->dev,
+ "enabling stream for '%s'\n", sd->entity.name);
+ ret = v4l2_subdev_call(sd, video, s_stream, true);
+ if (ret && ret != -ENOIOCTLCMD) {
+ sd->stream_count = 0;
+ goto err_stream_disable;
+ }
+ }
+
+ return 0;
+
+err_stream_disable:
+ for (i++; i < size; i++) {
+ sd = subdevs[i];
+ if (--sd->stream_count)
+ continue;
+ dev_dbg(mdev->dev,
+ "disabling stream for '%s'\n", sd->entity.name);
+ v4l2_subdev_call(sd, video, s_stream, false);
+ };
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(v4l2_pipeline_stream_enable);
+
+void v4l2_pipeline_stream_disable(struct video_device *vdev,
+ struct media_pipeline *pipe)
+{
+ struct media_device *mdev = vdev->entity.graph_obj.mdev;
+ struct v4l2_subdev *subdevs[MEDIA_ENTITY_ENUM_MAX_DEPTH];
+ unsigned int i;
+ int size;
+
+ size = v4l2_pipeline_subdevs_get(vdev, pipe,
+ subdevs, ARRAY_SIZE(subdevs));
+ if (WARN_ON(size < 0))
+ return;
+
+ /* Call s_stream() in order to disable sensors first */
+ for (i = 0; i < size; i++) {
+ struct v4l2_subdev *sd = subdevs[i];
+
+ if (--sd->stream_count)
+ continue;
+ dev_dbg(mdev->dev,
+ "disabling stream for '%s'\n", sd->entity.name);
+ v4l2_subdev_call(sd, video, s_stream, false);
+ }
+}
+EXPORT_SYMBOL_GPL(v4l2_pipeline_stream_disable);
+
+#endif /* CONFIG_MEDIA_CONTROLLER */
diff --git a/include/media/v4l2-common.h b/include/media/v4l2-common.h
index 150ee16ebd811..dc46812120cdc 100644
--- a/include/media/v4l2-common.h
+++ b/include/media/v4l2-common.h
@@ -539,4 +539,47 @@ static inline void v4l2_buffer_set_timestamp(struct v4l2_buffer *buf,
buf->timestamp.tv_usec = ts.tv_nsec / NSEC_PER_USEC;
}

+#if defined(CONFIG_MEDIA_CONTROLLER)
+
+/**
+ * v4l2_pipeline_stream_enable - Call .s_stream(true) callbacks in the stream
+ * @vdev: Starting video device.
+ * @pipe: Pipeline this entity belongs to.
+ *
+ * Call .s_stream(true) callback in all the subdevices participating in the
+ * stream, i.e. following links from sink to source.
+ *
+ * .s_stream(true) is also called from sink to source, i.e. in a topology
+ * sd1->sd2->sd3->vdev, .s_stream(true) of sd3 is called first.
+ *
+ * Calls to this function can be nested, in which case the same number of
+ * v4l2_pipeline_stream_disable() calls will be required to disable streaming
+ * through subdevices in the pipeline.
+ * The pipeline pointer must be identical for all nested calls to
+ * v4l2_pipeline_stream_enable().
+ */
+__must_check int v4l2_pipeline_stream_enable(struct video_device *vdev,
+ struct media_pipeline *pipe);
+
+/**
+ * v4l2_pipeline_stream_disable - Call .s_stream(false) callbacks in the stream
+ * @vdev: Starting video device.
+ * @pipe: Pipeline this entity belongs to.
+ *
+ * Call .s_stream(false) callback in all the subdevices participating in the
+ * stream, i.e. following links from sink to source.
+ *
+ * s_stream(false) is called in a reverse order from
+ * v4l2_pipeline_stream_enable(), i.e. in a topology sd1->sd2->sd3->vdev,
+ * .s_stream(false) of sd1 is called first.
+ *
+ * If multiple calls to v4l2_pipeline_stream_enable() have been made, the same
+ * number of calls to this function are required to disable streaming through
+ * subdevices in the pipeline.
+ */
+void v4l2_pipeline_stream_disable(struct video_device *vdev,
+ struct media_pipeline *pipe);
+
+#endif /* CONFIG_MEDIA_CONTROLLER */
+
#endif /* V4L2_COMMON_H_ */
diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h
index a4848de598521..20f913a9f70c5 100644
--- a/include/media/v4l2-subdev.h
+++ b/include/media/v4l2-subdev.h
@@ -838,6 +838,7 @@ struct v4l2_subdev_platform_data {
* @subdev_notifier: A sub-device notifier implicitly registered for the sub-
* device using v4l2_device_register_sensor_subdev().
* @pdata: common part of subdevice platform data
+ * @stream_count: Stream count for the subdevice.
*
* Each instance of a subdev driver should create this struct, either
* stand-alone or embedded in a larger struct.
@@ -869,6 +870,7 @@ struct v4l2_subdev {
struct v4l2_async_notifier *notifier;
struct v4l2_async_notifier *subdev_notifier;
struct v4l2_subdev_platform_data *pdata;
+ unsigned int stream_count;
};


--
2.26.0

2020-04-24 13:44:29

by Niklas Söderlund

[permalink] [raw]
Subject: Re: [PATCH v3 2/4] media: v4l2-common: add helper functions to call s_stream() callbacks

Hi Helen,

Thanks for your work.

On 2020-04-14 22:30:42 -0300, Helen Koike wrote:
> Add v4l2_pipeline_stream_{enable,disable} helper functions to iterate
> through the subdevices in a given stream (i.e following links from sink
> to source) and call .s_stream() callback.
>
> Add stream_count on the subdevice object for simultaneous streaming from
> different video devices which shares subdevices.
>
> Prevent calling .s_stream(true) if it was already called previously by
> another stream.
>
> Prevent calling .s_stream(false) from one stream when there are still
> others active.
>
> If .s_stream(true) fails, call .s_stream(false) in the reverse order.
>
> Signed-off-by: Helen Koike <[email protected]>
> ---
>
> Changes in v3:
> - re-write helpers to use media walkers as in v1, but with
> v4l2_pipeline_subdevs_get() to reverse the order we call s_stream(true)
> in subdevices.
> - rename size to max_size (and update docs) in v4l2_pipeline_subdevs_get() to
> reflect the meaning of the argument.
> - add if defined(CONFIG_MEDIA_CONTROLLER) around helpers
>
> Changes in v2:
> - re-write helpers to not use media walkers
>
> drivers/media/v4l2-core/v4l2-common.c | 125 ++++++++++++++++++++++++++
> include/media/v4l2-common.h | 43 +++++++++
> include/media/v4l2-subdev.h | 2 +
> 3 files changed, 170 insertions(+)
>
> diff --git a/drivers/media/v4l2-core/v4l2-common.c b/drivers/media/v4l2-core/v4l2-common.c
> index 9e8eb45a5b03c..2f991a1a57d7c 100644
> --- a/drivers/media/v4l2-core/v4l2-common.c
> +++ b/drivers/media/v4l2-core/v4l2-common.c
> @@ -442,3 +442,128 @@ int v4l2_fill_pixfmt(struct v4l2_pix_format *pixfmt, u32 pixelformat,
> return 0;
> }
> EXPORT_SYMBOL_GPL(v4l2_fill_pixfmt);
> +
> +#if defined(CONFIG_MEDIA_CONTROLLER)
> +
> +/*
> + * v4l2_pipeline_subdevs_get - Assemble a list of subdevices in a pipeline
> + * @subdevs: the array to be filled.
> + * @max_size: max number of elements that can fit in subdevs array.
> + *
> + * Walk from a video node, following links from sink to source and fill the
> + * array with subdevices in the path.
> + * The walker performs a depth-first traversal, which means that, in a topology
> + * sd1->sd2->sd3->vdev, sd1 will be the first element placed in the array.
> + *
> + * Return the number of subdevices filled in the array.
> + */
> +static int v4l2_pipeline_subdevs_get(struct video_device *vdev,
> + struct media_pipeline *pipe,
> + struct v4l2_subdev **subdevs,
> + unsigned int max_size)
> +{
> + struct media_entity *entity = &vdev->entity;
> + struct media_device *mdev = entity->graph_obj.mdev;
> + int idx = 0;
> + int ret;
> +
> + mutex_lock(&mdev->graph_mutex);
> +
> + if (!pipe->streaming_count) {
> + ret = media_graph_walk_init(&pipe->graph, mdev);
> + if (ret) {
> + mutex_unlock(&mdev->graph_mutex);
> + return ret;
> + }
> + }

This confuses me a bit. Why is this conditional on streaming_count ?
Looking how it's used in mc-entity.c the count is also
increased/decreased with this pattern.

Would it make sens to create a local 'struct media_graph graph' here and
always init and clean it up ?

> +
> + media_graph_walk_start(&pipe->graph, entity);
> +
> + while ((entity = media_graph_walk_next_stream(&pipe->graph))) {
> + if (!is_media_entity_v4l2_subdev(entity))
> + continue;
> + if (WARN_ON(idx >= max_size)) {
> + mutex_unlock(&mdev->graph_mutex);
> + return -EINVAL;
> + }
> + subdevs[idx++] = media_entity_to_v4l2_subdev(entity);
> + }
> +
> + if (!pipe->streaming_count)
> + media_graph_walk_cleanup(&pipe->graph);
> +
> + mutex_unlock(&mdev->graph_mutex);
> +
> + return idx;
> +}
> +
> +__must_check int v4l2_pipeline_stream_enable(struct video_device *vdev,
> + struct media_pipeline *pipe)
> +{
> + struct media_device *mdev = vdev->entity.graph_obj.mdev;
> + struct v4l2_subdev *subdevs[MEDIA_ENTITY_ENUM_MAX_DEPTH];
> + struct v4l2_subdev *sd;
> + int i, size, ret;
> +
> + size = v4l2_pipeline_subdevs_get(vdev, pipe,
> + subdevs, ARRAY_SIZE(subdevs));
> + if (size <= 0)
> + return size;
> +
> + /* Call s_stream() in reverse order to enable sensors last */
> + for (i = size - 1; i >= 0; i--) {
> + sd = subdevs[i];
+ if (sd->stream_count++)
> + continue;
> + dev_dbg(mdev->dev,
> + "enabling stream for '%s'\n", sd->entity.name);
> + ret = v4l2_subdev_call(sd, video, s_stream, true);
> + if (ret && ret != -ENOIOCTLCMD) {
> + sd->stream_count = 0;
> + goto err_stream_disable;
> + }
> + }
> +
> + return 0;
> +
> +err_stream_disable:
> + for (i++; i < size; i++) {
> + sd = subdevs[i];
> + if (--sd->stream_count)
> + continue;
> + dev_dbg(mdev->dev,
> + "disabling stream for '%s'\n", sd->entity.name);
> + v4l2_subdev_call(sd, video, s_stream, false);
> + };
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(v4l2_pipeline_stream_enable);
> +
> +void v4l2_pipeline_stream_disable(struct video_device *vdev,
> + struct media_pipeline *pipe)
> +{
> + struct media_device *mdev = vdev->entity.graph_obj.mdev;
> + struct v4l2_subdev *subdevs[MEDIA_ENTITY_ENUM_MAX_DEPTH];
> + unsigned int i;
> + int size;
> +
> + size = v4l2_pipeline_subdevs_get(vdev, pipe,
> + subdevs, ARRAY_SIZE(subdevs));
> + if (WARN_ON(size < 0))
> + return;
> +
> + /* Call s_stream() in order to disable sensors first */
> + for (i = 0; i < size; i++) {
> + struct v4l2_subdev *sd = subdevs[i];
> +
> + if (--sd->stream_count)
> + continue;
> + dev_dbg(mdev->dev,
> + "disabling stream for '%s'\n", sd->entity.name);
> + v4l2_subdev_call(sd, video, s_stream, false);

small nit, maybe this can be extracted to a helper as the code is
duplicated here and in the error path v4l2_pipeline_stream_enable ?

> + }
> +}
> +EXPORT_SYMBOL_GPL(v4l2_pipeline_stream_disable);
> +
> +#endif /* CONFIG_MEDIA_CONTROLLER */
> diff --git a/include/media/v4l2-common.h b/include/media/v4l2-common.h
> index 150ee16ebd811..dc46812120cdc 100644
> --- a/include/media/v4l2-common.h
> +++ b/include/media/v4l2-common.h
> @@ -539,4 +539,47 @@ static inline void v4l2_buffer_set_timestamp(struct v4l2_buffer *buf,
> buf->timestamp.tv_usec = ts.tv_nsec / NSEC_PER_USEC;
> }
>
> +#if defined(CONFIG_MEDIA_CONTROLLER)
> +
> +/**
> + * v4l2_pipeline_stream_enable - Call .s_stream(true) callbacks in the stream
> + * @vdev: Starting video device.
> + * @pipe: Pipeline this entity belongs to.
> + *
> + * Call .s_stream(true) callback in all the subdevices participating in the
> + * stream, i.e. following links from sink to source.
> + *
> + * .s_stream(true) is also called from sink to source, i.e. in a topology
> + * sd1->sd2->sd3->vdev, .s_stream(true) of sd3 is called first.
> + *
> + * Calls to this function can be nested, in which case the same number of
> + * v4l2_pipeline_stream_disable() calls will be required to disable streaming
> + * through subdevices in the pipeline.
> + * The pipeline pointer must be identical for all nested calls to
> + * v4l2_pipeline_stream_enable().
> + */
> +__must_check int v4l2_pipeline_stream_enable(struct video_device *vdev,
> + struct media_pipeline *pipe);
> +
> +/**
> + * v4l2_pipeline_stream_disable - Call .s_stream(false) callbacks in the stream
> + * @vdev: Starting video device.
> + * @pipe: Pipeline this entity belongs to.
> + *
> + * Call .s_stream(false) callback in all the subdevices participating in the
> + * stream, i.e. following links from sink to source.
> + *
> + * s_stream(false) is called in a reverse order from
> + * v4l2_pipeline_stream_enable(), i.e. in a topology sd1->sd2->sd3->vdev,
> + * .s_stream(false) of sd1 is called first.
> + *
> + * If multiple calls to v4l2_pipeline_stream_enable() have been made, the same
> + * number of calls to this function are required to disable streaming through
> + * subdevices in the pipeline.
> + */
> +void v4l2_pipeline_stream_disable(struct video_device *vdev,
> + struct media_pipeline *pipe);
> +
> +#endif /* CONFIG_MEDIA_CONTROLLER */
> +
> #endif /* V4L2_COMMON_H_ */
> diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h
> index a4848de598521..20f913a9f70c5 100644
> --- a/include/media/v4l2-subdev.h
> +++ b/include/media/v4l2-subdev.h
> @@ -838,6 +838,7 @@ struct v4l2_subdev_platform_data {
> * @subdev_notifier: A sub-device notifier implicitly registered for the sub-
> * device using v4l2_device_register_sensor_subdev().
> * @pdata: common part of subdevice platform data
> + * @stream_count: Stream count for the subdevice.
> *
> * Each instance of a subdev driver should create this struct, either
> * stand-alone or embedded in a larger struct.
> @@ -869,6 +870,7 @@ struct v4l2_subdev {
> struct v4l2_async_notifier *notifier;
> struct v4l2_async_notifier *subdev_notifier;
> struct v4l2_subdev_platform_data *pdata;
> + unsigned int stream_count;
> };
>
>
> --
> 2.26.0
>

--
Regards,
Niklas S?derlund

2020-04-24 13:45:26

by Niklas Söderlund

[permalink] [raw]
Subject: Re: [PATCH v3 3/4] media: staging: rkisp1: use v4l2_pipeline_stream_{enable,disable} helpers

Hi Helen,

Thanks for your work.

On 2020-04-14 22:30:43 -0300, Helen Koike wrote:
> Use v4l2_pipeline_stream_{enable,disable} to call .s_stream() subdevice
> callbacks through the pipeline.
>
> Tested by streaming on Scarlet Chromebook.
>
> Signed-off-by: Helen Koike <[email protected]>

Reviewed-by: Niklas S?derlund <[email protected]>

>
> ---
>
> Changes in v3:
> - rebase on top of new helpers prototypes
>
> Changes in v2:
> - rebase on top of new helpers prototypes
>
> drivers/staging/media/rkisp1/rkisp1-capture.c | 76 +------------------
> 1 file changed, 3 insertions(+), 73 deletions(-)
>
> diff --git a/drivers/staging/media/rkisp1/rkisp1-capture.c b/drivers/staging/media/rkisp1/rkisp1-capture.c
> index 24fe6a7888aa4..a18f1668e3563 100644
> --- a/drivers/staging/media/rkisp1/rkisp1-capture.c
> +++ b/drivers/staging/media/rkisp1/rkisp1-capture.c
> @@ -838,71 +838,6 @@ static void rkisp1_return_all_buffers(struct rkisp1_capture *cap,
> spin_unlock_irqrestore(&cap->buf.lock, flags);
> }
>
> -/*
> - * rkisp1_pipeline_sink_walk - Walk through the pipeline and call cb
> - * @from: entity at which to start pipeline walk
> - * @until: entity at which to stop pipeline walk
> - *
> - * Walk the entities chain starting at the pipeline video node and stop
> - * all subdevices in the chain.
> - *
> - * If the until argument isn't NULL, stop the pipeline walk when reaching the
> - * until entity. This is used to disable a partially started pipeline due to a
> - * subdev start error.
> - */
> -static int rkisp1_pipeline_sink_walk(struct media_entity *from,
> - struct media_entity *until,
> - int (*cb)(struct media_entity *from,
> - struct media_entity *curr))
> -{
> - struct media_entity *entity = from;
> - struct media_pad *pad;
> - unsigned int i;
> - int ret;
> -
> - while (1) {
> - pad = NULL;
> - /* Find remote source pad */
> - for (i = 0; i < entity->num_pads; i++) {
> - struct media_pad *spad = &entity->pads[i];
> -
> - if (!(spad->flags & MEDIA_PAD_FL_SINK))
> - continue;
> - pad = media_entity_remote_pad(spad);
> - if (pad && is_media_entity_v4l2_subdev(pad->entity))
> - break;
> - }
> - if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
> - break;
> -
> - entity = pad->entity;
> - if (entity == until)
> - break;
> -
> - ret = cb(from, entity);
> - if (ret)
> - return ret;
> - }
> -
> - return 0;
> -}
> -
> -static int rkisp1_pipeline_disable_cb(struct media_entity *from,
> - struct media_entity *curr)
> -{
> - struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(curr);
> -
> - return v4l2_subdev_call(sd, video, s_stream, false);
> -}
> -
> -static int rkisp1_pipeline_enable_cb(struct media_entity *from,
> - struct media_entity *curr)
> -{
> - struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(curr);
> -
> - return v4l2_subdev_call(sd, video, s_stream, true);
> -}
> -
> static void rkisp1_stream_stop(struct rkisp1_capture *cap)
> {
> int ret;
> @@ -929,11 +864,7 @@ static void rkisp1_vb2_stop_streaming(struct vb2_queue *queue)
>
> rkisp1_stream_stop(cap);
> media_pipeline_stop(&node->vdev.entity);
> - ret = rkisp1_pipeline_sink_walk(&node->vdev.entity, NULL,
> - rkisp1_pipeline_disable_cb);
> - if (ret)
> - dev_err(rkisp1->dev,
> - "pipeline stream-off failed error:%d\n", ret);
> + v4l2_pipeline_stream_disable(&node->vdev, &cap->rkisp1->pipe);
>
> rkisp1_return_all_buffers(cap, VB2_BUF_STATE_ERROR);
>
> @@ -1005,8 +936,7 @@ rkisp1_vb2_start_streaming(struct vb2_queue *queue, unsigned int count)
> rkisp1_stream_start(cap);
>
> /* start sub-devices */
> - ret = rkisp1_pipeline_sink_walk(entity, NULL,
> - rkisp1_pipeline_enable_cb);
> + ret = v4l2_pipeline_stream_enable(&cap->vnode.vdev, &cap->rkisp1->pipe);
> if (ret)
> goto err_stop_stream;
>
> @@ -1019,7 +949,7 @@ rkisp1_vb2_start_streaming(struct vb2_queue *queue, unsigned int count)
> return 0;
>
> err_pipe_disable:
> - rkisp1_pipeline_sink_walk(entity, NULL, rkisp1_pipeline_disable_cb);
> + v4l2_pipeline_stream_disable(&cap->vnode.vdev, &cap->rkisp1->pipe);
> err_stop_stream:
> rkisp1_stream_stop(cap);
> v4l2_pipeline_pm_put(entity);
> --
> 2.26.0
>

--
Regards,
Niklas S?derlund

2020-04-24 13:49:59

by Niklas Söderlund

[permalink] [raw]
Subject: Re: [PATCH v3 1/4] media: mc-entity.c: add media_graph_walk_next_stream()

Hi Helen,

Thanks for your work.

On 2020-04-14 22:30:41 -0300, Helen Koike wrote:
> Add media_graph_walk_next_stream() function to follow links only from
> sink to source (not the opposite) to allow iteration only through the
> entities participating in a given stream.
>
> This is useful to allow calling .s_stream() callback only in the
> subdevices that requires to be enabled/disabled, and avoid calling this
> callback when not required.
>
> Signed-off-by: Helen Koike <[email protected]>
>
> ---
>
> Changes in v3:
> - Patch re-added in the series from version 1
>
> Changes in v2: None
>
> drivers/media/mc/mc-entity.c | 34 +++++++++++++++++++++++++++++++---
> include/media/media-entity.h | 15 +++++++++++++++
> 2 files changed, 46 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/media/mc/mc-entity.c b/drivers/media/mc/mc-entity.c
> index 211279c5fd77d..0d44c2de23e6f 100644
> --- a/drivers/media/mc/mc-entity.c
> +++ b/drivers/media/mc/mc-entity.c
> @@ -228,6 +228,11 @@ EXPORT_SYMBOL_GPL(media_entity_pads_init);
> * Graph traversal
> */
>
> +enum media_graph_walk_type {
> + MEDIA_GRAPH_WALK_CONNECTED_NODES,
> + MEDIA_GRAPH_WALK_STREAM_NODES,
> +};
> +
> static struct media_entity *
> media_entity_other(struct media_entity *entity, struct media_link *link)
> {
> @@ -305,7 +310,8 @@ void media_graph_walk_start(struct media_graph *graph,
> }
> EXPORT_SYMBOL_GPL(media_graph_walk_start);
>
> -static void media_graph_walk_iter(struct media_graph *graph)
> +static void media_graph_walk_iter(struct media_graph *graph,
> + enum media_graph_walk_type type)
> {
> struct media_entity *entity = stack_top(graph);
> struct media_link *link;
> @@ -326,6 +332,15 @@ static void media_graph_walk_iter(struct media_graph *graph)
> /* Get the entity in the other end of the link . */
> next = media_entity_other(entity, link);
>
> + if (type == MEDIA_GRAPH_WALK_STREAM_NODES
> + && next == link->sink->entity) {
> + link_top(graph) = link_top(graph)->next;
> + dev_dbg(entity->graph_obj.mdev->dev,
> + "walk: skipping '%s' (outside of the stream path)\n",
> + link->sink->entity->name);
> + return;
> + }
> +
> /* Has the entity already been visited? */
> if (media_entity_enum_test_and_set(&graph->ent_enum, next)) {
> link_top(graph) = link_top(graph)->next;
> @@ -342,7 +357,9 @@ static void media_graph_walk_iter(struct media_graph *graph)
> next->name);
> }
>
> -struct media_entity *media_graph_walk_next(struct media_graph *graph)
> +static struct media_entity *
> +__media_graph_walk_next(struct media_graph *graph,
> + enum media_graph_walk_type type)
> {
> struct media_entity *entity;
>
> @@ -355,7 +372,7 @@ struct media_entity *media_graph_walk_next(struct media_graph *graph)
> * found.
> */
> while (link_top(graph) != &stack_top(graph)->links)
> - media_graph_walk_iter(graph);
> + media_graph_walk_iter(graph, type);
>
> entity = stack_pop(graph);
> dev_dbg(entity->graph_obj.mdev->dev,
> @@ -363,8 +380,19 @@ struct media_entity *media_graph_walk_next(struct media_graph *graph)
>
> return entity;
> }
> +
> +struct media_entity *media_graph_walk_next(struct media_graph *graph)
> +{
> + return __media_graph_walk_next(graph, MEDIA_GRAPH_WALK_CONNECTED_NODES);
> +}
> EXPORT_SYMBOL_GPL(media_graph_walk_next);
>
> +struct media_entity *media_graph_walk_next_stream(struct media_graph *graph)
> +{
> + return __media_graph_walk_next(graph, MEDIA_GRAPH_WALK_STREAM_NODES);

One space to much after the ','. With this fixed,

Reviewed-by: Niklas S?derlund <[email protected]>

> +}
> +EXPORT_SYMBOL_GPL(media_graph_walk_next_stream);
> +
> int media_entity_get_fwnode_pad(struct media_entity *entity,
> struct fwnode_handle *fwnode,
> unsigned long direction_flags)
> diff --git a/include/media/media-entity.h b/include/media/media-entity.h
> index 8cb2c504a05c7..f17a5180ce524 100644
> --- a/include/media/media-entity.h
> +++ b/include/media/media-entity.h
> @@ -927,6 +927,21 @@ void media_graph_walk_start(struct media_graph *graph,
> */
> struct media_entity *media_graph_walk_next(struct media_graph *graph);
>
> +/**
> + * media_graph_walk_next_stream - Get the next entity in the graph
> + * @graph: Media graph structure
> + *
> + * Perform a depth-first traversal of the given media entities graph only
> + * following links from sink to source (and not the opposite).
> + *
> + * The graph structure must have been previously initialized with a call to
> + * media_graph_walk_start().
> + *
> + * Return: returns the next entity in the graph in the stream path
> + * or %NULL if the whole stream path have been traversed.
> + */
> +struct media_entity *media_graph_walk_next_stream(struct media_graph *graph);
> +
> /**
> * media_pipeline_start - Mark a pipeline as streaming
> * @entity: Starting entity
> --
> 2.26.0
>

--
Regards,
Niklas S?derlund

2020-04-27 13:51:45

by Helen Koike

[permalink] [raw]
Subject: Re: [PATCH v3 2/4] media: v4l2-common: add helper functions to call s_stream() callbacks



On 4/24/20 10:41 AM, Niklas Söderlund wrote:
> Hi Helen,
>
> Thanks for your work.
>
> On 2020-04-14 22:30:42 -0300, Helen Koike wrote:
>> Add v4l2_pipeline_stream_{enable,disable} helper functions to iterate
>> through the subdevices in a given stream (i.e following links from sink
>> to source) and call .s_stream() callback.
>>
>> Add stream_count on the subdevice object for simultaneous streaming from
>> different video devices which shares subdevices.
>>
>> Prevent calling .s_stream(true) if it was already called previously by
>> another stream.
>>
>> Prevent calling .s_stream(false) from one stream when there are still
>> others active.
>>
>> If .s_stream(true) fails, call .s_stream(false) in the reverse order.
>>
>> Signed-off-by: Helen Koike <[email protected]>
>> ---
>>
>> Changes in v3:
>> - re-write helpers to use media walkers as in v1, but with
>> v4l2_pipeline_subdevs_get() to reverse the order we call s_stream(true)
>> in subdevices.
>> - rename size to max_size (and update docs) in v4l2_pipeline_subdevs_get() to
>> reflect the meaning of the argument.
>> - add if defined(CONFIG_MEDIA_CONTROLLER) around helpers
>>
>> Changes in v2:
>> - re-write helpers to not use media walkers
>>
>> drivers/media/v4l2-core/v4l2-common.c | 125 ++++++++++++++++++++++++++
>> include/media/v4l2-common.h | 43 +++++++++
>> include/media/v4l2-subdev.h | 2 +
>> 3 files changed, 170 insertions(+)
>>
>> diff --git a/drivers/media/v4l2-core/v4l2-common.c b/drivers/media/v4l2-core/v4l2-common.c
>> index 9e8eb45a5b03c..2f991a1a57d7c 100644
>> --- a/drivers/media/v4l2-core/v4l2-common.c
>> +++ b/drivers/media/v4l2-core/v4l2-common.c
>> @@ -442,3 +442,128 @@ int v4l2_fill_pixfmt(struct v4l2_pix_format *pixfmt, u32 pixelformat,
>> return 0;
>> }
>> EXPORT_SYMBOL_GPL(v4l2_fill_pixfmt);
>> +
>> +#if defined(CONFIG_MEDIA_CONTROLLER)
>> +
>> +/*
>> + * v4l2_pipeline_subdevs_get - Assemble a list of subdevices in a pipeline
>> + * @subdevs: the array to be filled.
>> + * @max_size: max number of elements that can fit in subdevs array.
>> + *
>> + * Walk from a video node, following links from sink to source and fill the
>> + * array with subdevices in the path.
>> + * The walker performs a depth-first traversal, which means that, in a topology
>> + * sd1->sd2->sd3->vdev, sd1 will be the first element placed in the array.
>> + *
>> + * Return the number of subdevices filled in the array.
>> + */
>> +static int v4l2_pipeline_subdevs_get(struct video_device *vdev,
>> + struct media_pipeline *pipe,
>> + struct v4l2_subdev **subdevs,
>> + unsigned int max_size)
>> +{
>> + struct media_entity *entity = &vdev->entity;
>> + struct media_device *mdev = entity->graph_obj.mdev;
>> + int idx = 0;
>> + int ret;
>> +
>> + mutex_lock(&mdev->graph_mutex);
>> +
>> + if (!pipe->streaming_count) {
>> + ret = media_graph_walk_init(&pipe->graph, mdev);
>> + if (ret) {
>> + mutex_unlock(&mdev->graph_mutex);
>> + return ret;
>> + }
>> + }
>
> This confuses me a bit. Why is this conditional on streaming_count ?
> Looking how it's used in mc-entity.c the count is also
> increased/decreased with this pattern.
>
> Would it make sens to create a local 'struct media_graph graph' here and
> always init and clean it up ?

media_pipeline_start() initialize the walker by calling media_graph_walk_init() when
pipe->stream_count increases to 1, and it leaves to media_pipeline_stop() to call
media_graph_walk_cleanup() when pipe->streaming_count reaches 0.

So, if this function is called before any call to media_pipeline_start(), i.e pipe->streaming_count == 0,
it needs to init and clean the walker up, otherwise it shouldn't do it.
If it is called after the last media_pipeline_stop() is called, i.e pipe->streaming_count == 0 again,
it should also init and clean up.

This is what the conditional checks, if the walker is already initialized or not.

But I think I could use a local struct media_graph to have an independent walker from the media_pipeline_{start,stop}(),
instead or reusing the struct media_pipeline that is being given (and I could remove the pipe parameter of these functions).
I was wondering if there was any issue of having an independent walker, but I don't think so, let me do this and re-send the series.

>
>> +
>> + media_graph_walk_start(&pipe->graph, entity);
>> +
>> + while ((entity = media_graph_walk_next_stream(&pipe->graph))) {
>> + if (!is_media_entity_v4l2_subdev(entity))
>> + continue;
>> + if (WARN_ON(idx >= max_size)) {
>> + mutex_unlock(&mdev->graph_mutex);
>> + return -EINVAL;
>> + }
>> + subdevs[idx++] = media_entity_to_v4l2_subdev(entity);
>> + }
>> +
>> + if (!pipe->streaming_count)
>> + media_graph_walk_cleanup(&pipe->graph);
>> +
>> + mutex_unlock(&mdev->graph_mutex);
>> +
>> + return idx;
>> +}
>> +
>> +__must_check int v4l2_pipeline_stream_enable(struct video_device *vdev,
>> + struct media_pipeline *pipe)
>> +{
>> + struct media_device *mdev = vdev->entity.graph_obj.mdev;
>> + struct v4l2_subdev *subdevs[MEDIA_ENTITY_ENUM_MAX_DEPTH];
>> + struct v4l2_subdev *sd;
>> + int i, size, ret;
>> +
>> + size = v4l2_pipeline_subdevs_get(vdev, pipe,
>> + subdevs, ARRAY_SIZE(subdevs));
>> + if (size <= 0)
>> + return size;
>> +
>> + /* Call s_stream() in reverse order to enable sensors last */
>> + for (i = size - 1; i >= 0; i--) {
>> + sd = subdevs[i];
> + if (sd->stream_count++)
>> + continue;
>> + dev_dbg(mdev->dev,
>> + "enabling stream for '%s'\n", sd->entity.name);
>> + ret = v4l2_subdev_call(sd, video, s_stream, true);
>> + if (ret && ret != -ENOIOCTLCMD) {
>> + sd->stream_count = 0;
>> + goto err_stream_disable;
>> + }
>> + }
>> +
>> + return 0;
>> +
>> +err_stream_disable:
>> + for (i++; i < size; i++) {
>> + sd = subdevs[i];
>> + if (--sd->stream_count)
>> + continue;
>> + dev_dbg(mdev->dev,
>> + "disabling stream for '%s'\n", sd->entity.name);
>> + v4l2_subdev_call(sd, video, s_stream, false);
>> + };
>> +
>> + return ret;
>> +}
>> +EXPORT_SYMBOL_GPL(v4l2_pipeline_stream_enable);
>> +
>> +void v4l2_pipeline_stream_disable(struct video_device *vdev,
>> + struct media_pipeline *pipe)
>> +{
>> + struct media_device *mdev = vdev->entity.graph_obj.mdev;
>> + struct v4l2_subdev *subdevs[MEDIA_ENTITY_ENUM_MAX_DEPTH];
>> + unsigned int i;
>> + int size;
>> +
>> + size = v4l2_pipeline_subdevs_get(vdev, pipe,
>> + subdevs, ARRAY_SIZE(subdevs));
>> + if (WARN_ON(size < 0))
>> + return;
>> +
>> + /* Call s_stream() in order to disable sensors first */
>> + for (i = 0; i < size; i++) {
>> + struct v4l2_subdev *sd = subdevs[i];
>> +
>> + if (--sd->stream_count)
>> + continue;
>> + dev_dbg(mdev->dev,
>> + "disabling stream for '%s'\n", sd->entity.name);
>> + v4l2_subdev_call(sd, video, s_stream, false);
>
> small nit, maybe this can be extracted to a helper as the code is
> duplicated here and in the error path v4l2_pipeline_stream_enable ?

ack, I'll see how I can improve this.


Thanks for reviewing!
Helen

>
>> + }
>> +}
>> +EXPORT_SYMBOL_GPL(v4l2_pipeline_stream_disable);
>> +
>> +#endif /* CONFIG_MEDIA_CONTROLLER */
>> diff --git a/include/media/v4l2-common.h b/include/media/v4l2-common.h
>> index 150ee16ebd811..dc46812120cdc 100644
>> --- a/include/media/v4l2-common.h
>> +++ b/include/media/v4l2-common.h
>> @@ -539,4 +539,47 @@ static inline void v4l2_buffer_set_timestamp(struct v4l2_buffer *buf,
>> buf->timestamp.tv_usec = ts.tv_nsec / NSEC_PER_USEC;
>> }
>>
>> +#if defined(CONFIG_MEDIA_CONTROLLER)
>> +
>> +/**
>> + * v4l2_pipeline_stream_enable - Call .s_stream(true) callbacks in the stream
>> + * @vdev: Starting video device.
>> + * @pipe: Pipeline this entity belongs to.
>> + *
>> + * Call .s_stream(true) callback in all the subdevices participating in the
>> + * stream, i.e. following links from sink to source.
>> + *
>> + * .s_stream(true) is also called from sink to source, i.e. in a topology
>> + * sd1->sd2->sd3->vdev, .s_stream(true) of sd3 is called first.
>> + *
>> + * Calls to this function can be nested, in which case the same number of
>> + * v4l2_pipeline_stream_disable() calls will be required to disable streaming
>> + * through subdevices in the pipeline.
>> + * The pipeline pointer must be identical for all nested calls to
>> + * v4l2_pipeline_stream_enable().
>> + */
>> +__must_check int v4l2_pipeline_stream_enable(struct video_device *vdev,
>> + struct media_pipeline *pipe);
>> +
>> +/**
>> + * v4l2_pipeline_stream_disable - Call .s_stream(false) callbacks in the stream
>> + * @vdev: Starting video device.
>> + * @pipe: Pipeline this entity belongs to.
>> + *
>> + * Call .s_stream(false) callback in all the subdevices participating in the
>> + * stream, i.e. following links from sink to source.
>> + *
>> + * s_stream(false) is called in a reverse order from
>> + * v4l2_pipeline_stream_enable(), i.e. in a topology sd1->sd2->sd3->vdev,
>> + * .s_stream(false) of sd1 is called first.
>> + *
>> + * If multiple calls to v4l2_pipeline_stream_enable() have been made, the same
>> + * number of calls to this function are required to disable streaming through
>> + * subdevices in the pipeline.
>> + */
>> +void v4l2_pipeline_stream_disable(struct video_device *vdev,
>> + struct media_pipeline *pipe);
>> +
>> +#endif /* CONFIG_MEDIA_CONTROLLER */
>> +
>> #endif /* V4L2_COMMON_H_ */
>> diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h
>> index a4848de598521..20f913a9f70c5 100644
>> --- a/include/media/v4l2-subdev.h
>> +++ b/include/media/v4l2-subdev.h
>> @@ -838,6 +838,7 @@ struct v4l2_subdev_platform_data {
>> * @subdev_notifier: A sub-device notifier implicitly registered for the sub-
>> * device using v4l2_device_register_sensor_subdev().
>> * @pdata: common part of subdevice platform data
>> + * @stream_count: Stream count for the subdevice.
>> *
>> * Each instance of a subdev driver should create this struct, either
>> * stand-alone or embedded in a larger struct.
>> @@ -869,6 +870,7 @@ struct v4l2_subdev {
>> struct v4l2_async_notifier *notifier;
>> struct v4l2_async_notifier *subdev_notifier;
>> struct v4l2_subdev_platform_data *pdata;
>> + unsigned int stream_count;
>> };
>>
>>
>> --
>> 2.26.0
>>
>

2020-05-21 12:05:33

by Dafna Hirschfeld

[permalink] [raw]
Subject: Re: [PATCH v3 2/4] media: v4l2-common: add helper functions to call s_stream() callbacks

Hi

On 15.04.20 03:30, Helen Koike wrote:
> Add v4l2_pipeline_stream_{enable,disable} helper functions to iterate
> through the subdevices in a given stream (i.e following links from sink
> to source) and call .s_stream() callback.
>
> Add stream_count on the subdevice object for simultaneous streaming from
> different video devices which shares subdevices.
>
> Prevent calling .s_stream(true) if it was already called previously by
> another stream.
>
> Prevent calling .s_stream(false) from one stream when there are still
> others active.
>
> If .s_stream(true) fails, call .s_stream(false) in the reverse order.
>
> Signed-off-by: Helen Koike <[email protected]>
> ---
>
> Changes in v3:
> - re-write helpers to use media walkers as in v1, but with
> v4l2_pipeline_subdevs_get() to reverse the order we call s_stream(true)
> in subdevices.
> - rename size to max_size (and update docs) in v4l2_pipeline_subdevs_get() to
> reflect the meaning of the argument.
> - add if defined(CONFIG_MEDIA_CONTROLLER) around helpers
>
> Changes in v2:
> - re-write helpers to not use media walkers
>
> drivers/media/v4l2-core/v4l2-common.c | 125 ++++++++++++++++++++++++++
> include/media/v4l2-common.h | 43 +++++++++
> include/media/v4l2-subdev.h | 2 +
> 3 files changed, 170 insertions(+)
>
> diff --git a/drivers/media/v4l2-core/v4l2-common.c b/drivers/media/v4l2-core/v4l2-common.c
> index 9e8eb45a5b03c..2f991a1a57d7c 100644
> --- a/drivers/media/v4l2-core/v4l2-common.c
> +++ b/drivers/media/v4l2-core/v4l2-common.c
> @@ -442,3 +442,128 @@ int v4l2_fill_pixfmt(struct v4l2_pix_format *pixfmt, u32 pixelformat,
> return 0;
> }
> EXPORT_SYMBOL_GPL(v4l2_fill_pixfmt);
> +
> +#if defined(CONFIG_MEDIA_CONTROLLER)
> +
> +/*
> + * v4l2_pipeline_subdevs_get - Assemble a list of subdevices in a pipeline
> + * @subdevs: the array to be filled.
> + * @max_size: max number of elements that can fit in subdevs array.
> + *
> + * Walk from a video node, following links from sink to source and fill the
> + * array with subdevices in the path.
> + * The walker performs a depth-first traversal, which means that, in a topology
> + * sd1->sd2->sd3->vdev, sd1 will be the first element placed in the array.
> + *
> + * Return the number of subdevices filled in the array.
> + */
> +static int v4l2_pipeline_subdevs_get(struct video_device *vdev,
> + struct media_pipeline *pipe,
> + struct v4l2_subdev **subdevs,
> + unsigned int max_size)
> +{
> + struct media_entity *entity = &vdev->entity;
> + struct media_device *mdev = entity->graph_obj.mdev;
> + int idx = 0;
> + int ret;
> +
> + mutex_lock(&mdev->graph_mutex);
> +
> + if (!pipe->streaming_count) {
> + ret = media_graph_walk_init(&pipe->graph, mdev);
> + if (ret) {
> + mutex_unlock(&mdev->graph_mutex);
> + return ret;
> + }
> + }
> +
> + media_graph_walk_start(&pipe->graph, entity);
> +
> + while ((entity = media_graph_walk_next_stream(&pipe->graph))) {
> + if (!is_media_entity_v4l2_subdev(entity))
> + continue;
> + if (WARN_ON(idx >= max_size)) {
> + mutex_unlock(&mdev->graph_mutex);
> + return -EINVAL;
> + }
> + subdevs[idx++] = media_entity_to_v4l2_subdev(entity);
> + }
> +
> + if (!pipe->streaming_count)
> + media_graph_walk_cleanup(&pipe->graph);
> +
> + mutex_unlock(&mdev->graph_mutex);
> +
> + return idx;
> +}
> +
> +__must_check int v4l2_pipeline_stream_enable(struct video_device *vdev,
> + struct media_pipeline *pipe)
> +{
> + struct media_device *mdev = vdev->entity.graph_obj.mdev;
> + struct v4l2_subdev *subdevs[MEDIA_ENTITY_ENUM_MAX_DEPTH];
> + struct v4l2_subdev *sd;
> + int i, size, ret;
> +
> + size = v4l2_pipeline_subdevs_get(vdev, pipe,
> + subdevs, ARRAY_SIZE(subdevs));
> + if (size <= 0)
> + return size;
> +
> + /* Call s_stream() in reverse order to enable sensors last */
> + for (i = size - 1; i >= 0; i--) {
> + sd = subdevs[i];
> + if (sd->stream_count++)
> + continue;
> + dev_dbg(mdev->dev,
> + "enabling stream for '%s'\n", sd->entity.name);
> + ret = v4l2_subdev_call(sd, video, s_stream, true);
> + if (ret && ret != -ENOIOCTLCMD) {
> + sd->stream_count = 0;
> + goto err_stream_disable;
> + }
> + }
> +
> + return 0;
> +
> +err_stream_disable:
> + for (i++; i < size; i++) {
> + sd = subdevs[i];
> + if (--sd->stream_count)
> + continue;
> + dev_dbg(mdev->dev,
> + "disabling stream for '%s'\n", sd->entity.name);
> + v4l2_subdev_call(sd, video, s_stream, false);
> + };
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(v4l2_pipeline_stream_enable);
> +
> +void v4l2_pipeline_stream_disable(struct video_device *vdev,
> + struct media_pipeline *pipe)
> +{
> + struct media_device *mdev = vdev->entity.graph_obj.mdev;
> + struct v4l2_subdev *subdevs[MEDIA_ENTITY_ENUM_MAX_DEPTH];
> + unsigned int i;
> + int size;
> +
> + size = v4l2_pipeline_subdevs_get(vdev, pipe,
> + subdevs, ARRAY_SIZE(subdevs));
> + if (WARN_ON(size < 0))
> + return;
> +
> + /* Call s_stream() in order to disable sensors first */
> + for (i = 0; i < size; i++) {
> + struct v4l2_subdev *sd = subdevs[i];
> +
> + if (--sd->stream_count)
> + continue;
> + dev_dbg(mdev->dev,
> + "disabling stream for '%s'\n", sd->entity.name);
> + v4l2_subdev_call(sd, video, s_stream, false);
> + }
> +}
> +EXPORT_SYMBOL_GPL(v4l2_pipeline_stream_disable);
> +
> +#endif /* CONFIG_MEDIA_CONTROLLER */
> diff --git a/include/media/v4l2-common.h b/include/media/v4l2-common.h
> index 150ee16ebd811..dc46812120cdc 100644
> --- a/include/media/v4l2-common.h
> +++ b/include/media/v4l2-common.h
> @@ -539,4 +539,47 @@ static inline void v4l2_buffer_set_timestamp(struct v4l2_buffer *buf,
> buf->timestamp.tv_usec = ts.tv_nsec / NSEC_PER_USEC;
> }
>
> +#if defined(CONFIG_MEDIA_CONTROLLER)
> +
> +/**
> + * v4l2_pipeline_stream_enable - Call .s_stream(true) callbacks in the stream
> + * @vdev: Starting video device.
> + * @pipe: Pipeline this entity belongs to.
> + *
> + * Call .s_stream(true) callback in all the subdevices participating in the
> + * stream, i.e. following links from sink to source.
> + *
> + * .s_stream(true) is also called from sink to source, i.e. in a topology
> + * sd1->sd2->sd3->vdev, .s_stream(true) of sd3 is called first.
> + *
> + * Calls to this function can be nested, in which case the same number of
> + * v4l2_pipeline_stream_disable() calls will be required to disable streaming
> + * through subdevices in the pipeline.
> + * The pipeline pointer must be identical for all nested calls to
> + * v4l2_pipeline_stream_enable().
> + */
> +__must_check int v4l2_pipeline_stream_enable(struct video_device *vdev,
> + struct media_pipeline *pipe);
> +
> +/**
> + * v4l2_pipeline_stream_disable - Call .s_stream(false) callbacks in the stream
> + * @vdev: Starting video device.
> + * @pipe: Pipeline this entity belongs to.
> + *
> + * Call .s_stream(false) callback in all the subdevices participating in the
> + * stream, i.e. following links from sink to source.
> + *
> + * s_stream(false) is called in a reverse order from
> + * v4l2_pipeline_stream_enable(), i.e. in a topology sd1->sd2->sd3->vdev,
> + * .s_stream(false) of sd1 is called first.
> + *
> + * If multiple calls to v4l2_pipeline_stream_enable() have been made, the same
> + * number of calls to this function are required to disable streaming through
> + * subdevices in the pipeline.
> + */
> +void v4l2_pipeline_stream_disable(struct video_device *vdev,
> + struct media_pipeline *pipe);
> +
> +#endif /* CONFIG_MEDIA_CONTROLLER */
> +
> #endif /* V4L2_COMMON_H_ */
> diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h
> index a4848de598521..20f913a9f70c5 100644
> --- a/include/media/v4l2-subdev.h
> +++ b/include/media/v4l2-subdev.h
> @@ -838,6 +838,7 @@ struct v4l2_subdev_platform_data {
> * @subdev_notifier: A sub-device notifier implicitly registered for the sub-
> * device using v4l2_device_register_sensor_subdev().
> * @pdata: common part of subdevice platform data
> + * @stream_count: Stream count for the subdevice.
> *
> * Each instance of a subdev driver should create this struct, either
> * stand-alone or embedded in a larger struct.
> @@ -869,6 +870,7 @@ struct v4l2_subdev {
> struct v4l2_async_notifier *notifier;
> struct v4l2_async_notifier *subdev_notifier;
> struct v4l2_subdev_platform_data *pdata;
> + unsigned int stream_count;
I wonder if it is worth it to add a new field to v4l2_subdev that is used only
for the specific case, it seems that except of rkisp1 and vimc, the other drivers you pointed to that
could use the helpers don't need this counter and they call s_stream without checking if it was already called.
This counter is updated only by the new introduced functions and not when s_stream is called from other places.
Maybe we can add a helper that just return the next subdevice connected to the current entity as a source, and drivers can
iterate it and can decide themselves if s_stream should be called or not

Thanks,
Dafna

> };
>
>
>

2020-05-21 12:57:29

by Helen Koike

[permalink] [raw]
Subject: Re: [PATCH v3 2/4] media: v4l2-common: add helper functions to call s_stream() callbacks

Hi Dafna,

On 5/21/20 9:03 AM, Dafna Hirschfeld wrote:
> Hi
>
> On 15.04.20 03:30, Helen Koike wrote:
>> Add v4l2_pipeline_stream_{enable,disable} helper functions to iterate
>> through the subdevices in a given stream (i.e following links from sink
>> to source) and call .s_stream() callback.
>>
>> Add stream_count on the subdevice object for simultaneous streaming from
>> different video devices which shares subdevices.
>>
>> Prevent calling .s_stream(true) if it was already called previously by
>> another stream.
>>
>> Prevent calling .s_stream(false) from one stream when there are still
>> others active.
>>
>> If .s_stream(true) fails, call .s_stream(false) in the reverse order.
>>
>> Signed-off-by: Helen Koike <[email protected]>
>> ---
>>
>> Changes in v3:
>> - re-write helpers to use media walkers as in v1, but with
>> v4l2_pipeline_subdevs_get() to reverse the order we call s_stream(true)
>> in subdevices.
>> - rename size to max_size (and update docs) in v4l2_pipeline_subdevs_get() to
>> reflect the meaning of the argument.
>> - add if defined(CONFIG_MEDIA_CONTROLLER) around helpers
>>
>> Changes in v2:
>> - re-write helpers to not use media walkers
>>
>>   drivers/media/v4l2-core/v4l2-common.c | 125 ++++++++++++++++++++++++++
>>   include/media/v4l2-common.h           |  43 +++++++++
>>   include/media/v4l2-subdev.h           |   2 +
>>   3 files changed, 170 insertions(+)
>>
>> diff --git a/drivers/media/v4l2-core/v4l2-common.c b/drivers/media/v4l2-core/v4l2-common.c
>> index 9e8eb45a5b03c..2f991a1a57d7c 100644
>> --- a/drivers/media/v4l2-core/v4l2-common.c
>> +++ b/drivers/media/v4l2-core/v4l2-common.c
>> @@ -442,3 +442,128 @@ int v4l2_fill_pixfmt(struct v4l2_pix_format *pixfmt, u32 pixelformat,
>>       return 0;
>>   }
>>   EXPORT_SYMBOL_GPL(v4l2_fill_pixfmt);
>> +
>> +#if defined(CONFIG_MEDIA_CONTROLLER)
>> +
>> +/*
>> + * v4l2_pipeline_subdevs_get - Assemble a list of subdevices in a pipeline
>> + * @subdevs: the array to be filled.
>> + * @max_size: max number of elements that can fit in subdevs array.
>> + *
>> + * Walk from a video node, following links from sink to source and fill the
>> + * array with subdevices in the path.
>> + * The walker performs a depth-first traversal, which means that, in a topology
>> + * sd1->sd2->sd3->vdev, sd1 will be the first element placed in the array.
>> + *
>> + * Return the number of subdevices filled in the array.
>> + */
>> +static int v4l2_pipeline_subdevs_get(struct video_device *vdev,
>> +                     struct media_pipeline *pipe,
>> +                     struct v4l2_subdev **subdevs,
>> +                     unsigned int max_size)
>> +{
>> +    struct media_entity *entity = &vdev->entity;
>> +    struct media_device *mdev = entity->graph_obj.mdev;
>> +    int idx = 0;
>> +    int ret;
>> +
>> +    mutex_lock(&mdev->graph_mutex);
>> +
>> +    if (!pipe->streaming_count) {
>> +        ret = media_graph_walk_init(&pipe->graph, mdev);
>> +        if (ret) {
>> +            mutex_unlock(&mdev->graph_mutex);
>> +            return ret;
>> +        }
>> +    }
>> +
>> +    media_graph_walk_start(&pipe->graph, entity);
>> +
>> +    while ((entity = media_graph_walk_next_stream(&pipe->graph))) {
>> +        if (!is_media_entity_v4l2_subdev(entity))
>> +            continue;
>> +        if (WARN_ON(idx >= max_size)) {
>> +            mutex_unlock(&mdev->graph_mutex);
>> +            return -EINVAL;
>> +        }
>> +        subdevs[idx++] = media_entity_to_v4l2_subdev(entity);
>> +    }
>> +
>> +    if (!pipe->streaming_count)
>> +        media_graph_walk_cleanup(&pipe->graph);
>> +
>> +    mutex_unlock(&mdev->graph_mutex);
>> +
>> +    return idx;
>> +}
>> +
>> +__must_check int v4l2_pipeline_stream_enable(struct video_device *vdev,
>> +                         struct media_pipeline *pipe)
>> +{
>> +    struct media_device *mdev = vdev->entity.graph_obj.mdev;
>> +    struct v4l2_subdev *subdevs[MEDIA_ENTITY_ENUM_MAX_DEPTH];
>> +    struct v4l2_subdev *sd;
>> +    int i, size, ret;
>> +
>> +    size = v4l2_pipeline_subdevs_get(vdev, pipe,
>> +                     subdevs, ARRAY_SIZE(subdevs));
>> +    if (size <= 0)
>> +        return size;
>> +
>> +    /* Call s_stream() in reverse order to enable sensors last */
>> +    for (i = size - 1; i >= 0; i--) {
>> +        sd = subdevs[i];
>> +        if (sd->stream_count++)
>> +            continue;
>> +        dev_dbg(mdev->dev,
>> +            "enabling stream for '%s'\n", sd->entity.name);
>> +        ret = v4l2_subdev_call(sd, video, s_stream, true);
>> +        if (ret && ret != -ENOIOCTLCMD) {
>> +            sd->stream_count = 0;
>> +            goto err_stream_disable;
>> +        }
>> +    }
>> +
>> +    return 0;
>> +
>> +err_stream_disable:
>> +    for (i++; i < size; i++) {
>> +        sd = subdevs[i];
>> +        if (--sd->stream_count)
>> +            continue;
>> +        dev_dbg(mdev->dev,
>> +            "disabling stream for '%s'\n", sd->entity.name);
>> +        v4l2_subdev_call(sd, video, s_stream, false);
>> +    };
>> +
>> +    return ret;
>> +}
>> +EXPORT_SYMBOL_GPL(v4l2_pipeline_stream_enable);
>> +
>> +void v4l2_pipeline_stream_disable(struct video_device *vdev,
>> +                  struct media_pipeline *pipe)
>> +{
>> +    struct media_device *mdev = vdev->entity.graph_obj.mdev;
>> +    struct v4l2_subdev *subdevs[MEDIA_ENTITY_ENUM_MAX_DEPTH];
>> +    unsigned int i;
>> +    int size;
>> +
>> +    size = v4l2_pipeline_subdevs_get(vdev, pipe,
>> +                     subdevs, ARRAY_SIZE(subdevs));
>> +    if (WARN_ON(size < 0))
>> +        return;
>> +
>> +    /* Call s_stream() in order to disable sensors first */
>> +    for (i = 0; i < size; i++) {
>> +        struct v4l2_subdev *sd = subdevs[i];
>> +
>> +        if (--sd->stream_count)
>> +            continue;
>> +        dev_dbg(mdev->dev,
>> +            "disabling stream for '%s'\n", sd->entity.name);
>> +        v4l2_subdev_call(sd, video, s_stream, false);
>> +    }
>> +}
>> +EXPORT_SYMBOL_GPL(v4l2_pipeline_stream_disable);
>> +
>> +#endif /* CONFIG_MEDIA_CONTROLLER */
>> diff --git a/include/media/v4l2-common.h b/include/media/v4l2-common.h
>> index 150ee16ebd811..dc46812120cdc 100644
>> --- a/include/media/v4l2-common.h
>> +++ b/include/media/v4l2-common.h
>> @@ -539,4 +539,47 @@ static inline void v4l2_buffer_set_timestamp(struct v4l2_buffer *buf,
>>       buf->timestamp.tv_usec = ts.tv_nsec / NSEC_PER_USEC;
>>   }
>>   +#if defined(CONFIG_MEDIA_CONTROLLER)
>> +
>> +/**
>> + * v4l2_pipeline_stream_enable - Call .s_stream(true) callbacks in the stream
>> + * @vdev: Starting video device.
>> + * @pipe: Pipeline this entity belongs to.
>> + *
>> + * Call .s_stream(true) callback in all the subdevices participating in the
>> + * stream, i.e. following links from sink to source.
>> + *
>> + * .s_stream(true) is also called from sink to source, i.e. in a topology
>> + * sd1->sd2->sd3->vdev, .s_stream(true) of sd3 is called first.
>> + *
>> + * Calls to this function can be nested, in which case the same number of
>> + * v4l2_pipeline_stream_disable() calls will be required to disable streaming
>> + * through subdevices in the pipeline.
>> + * The  pipeline pointer must be identical for all nested calls to
>> + * v4l2_pipeline_stream_enable().
>> + */
>> +__must_check int v4l2_pipeline_stream_enable(struct video_device *vdev,
>> +                         struct media_pipeline *pipe);
>> +
>> +/**
>> + * v4l2_pipeline_stream_disable - Call .s_stream(false) callbacks in the stream
>> + * @vdev: Starting video device.
>> + * @pipe: Pipeline this entity belongs to.
>> + *
>> + * Call .s_stream(false) callback in all the subdevices participating in the
>> + * stream, i.e. following links from sink to source.
>> + *
>> + * s_stream(false) is called in a reverse order from
>> + * v4l2_pipeline_stream_enable(), i.e. in a topology sd1->sd2->sd3->vdev,
>> + * .s_stream(false) of sd1 is called first.
>> + *
>> + * If multiple calls to v4l2_pipeline_stream_enable() have been made, the same
>> + * number of calls to this function are required to disable streaming through
>> + * subdevices in the pipeline.
>> + */
>> +void v4l2_pipeline_stream_disable(struct video_device *vdev,
>> +                  struct media_pipeline *pipe);
>> +
>> +#endif /* CONFIG_MEDIA_CONTROLLER */
>> +
>>   #endif /* V4L2_COMMON_H_ */
>> diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h
>> index a4848de598521..20f913a9f70c5 100644
>> --- a/include/media/v4l2-subdev.h
>> +++ b/include/media/v4l2-subdev.h
>> @@ -838,6 +838,7 @@ struct v4l2_subdev_platform_data {
>>    * @subdev_notifier: A sub-device notifier implicitly registered for the sub-
>>    *             device using v4l2_device_register_sensor_subdev().
>>    * @pdata: common part of subdevice platform data
>> + * @stream_count: Stream count for the subdevice.
>>    *
>>    * Each instance of a subdev driver should create this struct, either
>>    * stand-alone or embedded in a larger struct.
>> @@ -869,6 +870,7 @@ struct v4l2_subdev {
>>       struct v4l2_async_notifier *notifier;
>>       struct v4l2_async_notifier *subdev_notifier;
>>       struct v4l2_subdev_platform_data *pdata;
>> +    unsigned int stream_count;
> I wonder if it is worth it to add a new field to v4l2_subdev that is used only
> for the specific case, it seems that except of rkisp1 and vimc, the other drivers you pointed to that
> could use the helpers don't need this counter and they call s_stream without checking if it was already called.
> This counter is updated only by the new introduced functions and not when s_stream is called from other places.

Other drivers implement their own loop that navigates through the pipeline calling .s_stream().
They are very similar. The only difference from rkisp1/vimc is that they don't support simultaneous streaming from different
capture nodes.

Also, drivers usually don't handle errors very well, in cases of failures where .s_stream() should be called in reversed order.

Adding this counter + helpers allows a common generic implementation that can be used for both cases.
So we re-use a code that should be well tested in the core and avoid re-implementing it.

> Maybe we can add a helper that just return the next subdevice connected to the current entity as a source, and drivers can
> iterate it and can decide themselves if s_stream should be called or not

Patch 1/4 in the series provides this iterator :)
Unless if you think we could add helpers to make things easier.

Regards,
Helen

>
> Thanks,
> Dafna
>
>>   };
>>