From: Sakari Ailus <sakari.ailus@linux.intel.com>
To: linux-media@vger.kernel.org
Cc: bingbu.cao@linux.intel.com, stanislaw.gruszka@linux.intel.com,
tian.shu.qiu@intel.com, tomi.valkeinen@ideasonboard.com,
laurent.pinchart@ideasonboard.com
Subject: [PATCH 12/13] media: v4l2-mc: Introduce v4l2_mc_pipeline_enabled()
Date: Thu, 19 Jun 2025 11:15:45 +0300 [thread overview]
Message-ID: <20250619081546.1582969-13-sakari.ailus@linux.intel.com> (raw)
In-Reply-To: <20250619081546.1582969-1-sakari.ailus@linux.intel.com>
v4l2_mc_pipeline_enabled() helps solving a problem known for long but
lacked any sort of general solution: with multiple streams, when streaming
is started on video nodes one by one, when should streaming be started in
the source?
v4l2_mc_pipeline_enabled() traverses the pipeline towards the source,
queries the streams generated by the source and traces them back to the
video nodes.
Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com>
---
drivers/media/v4l2-core/v4l2-mc.c | 243 ++++++++++++++++++++++++++++++
include/media/v4l2-mc.h | 44 ++++++
2 files changed, 287 insertions(+)
diff --git a/drivers/media/v4l2-core/v4l2-mc.c b/drivers/media/v4l2-core/v4l2-mc.c
index 937d358697e1..1731088ad436 100644
--- a/drivers/media/v4l2-core/v4l2-mc.c
+++ b/drivers/media/v4l2-core/v4l2-mc.c
@@ -612,3 +612,246 @@ int v4l2_pipeline_link_notify(struct media_link *link, u32 flags,
return ret;
}
EXPORT_SYMBOL_GPL(v4l2_pipeline_link_notify);
+
+static int
+__v4l2_mc_pipeline_enabled(struct v4l2_subdev_state *state,
+ struct media_pad *src_pad, u64 __src_streams,
+ struct media_pad **__sink_pad, u64 *__sink_streams)
+{
+ struct v4l2_subdev_route *route;
+ u64 src_streams = 0, sink_streams = 0;
+ bool has_sink_pad = false;
+ unsigned int sink_pad;
+
+ dev_dbg(state->sd->dev, "%s: source enabled, pad/streams %u/%#llx\n",
+ state->sd->entity.name, src_pad->index, __src_streams);
+ for_each_active_route(&state->routing, route) {
+ dev_dbg(state->sd->dev, "%s: %u/%u -> %u/%u, flags %x\n",
+ state->sd->entity.name,
+ route->sink_pad, route->sink_stream, route->source_pad,
+ route->source_stream, route->flags);
+ if (route->source_pad != src_pad->index)
+ continue;
+
+ if (!(BIT_ULL(route->source_stream) & __src_streams))
+ continue;
+
+ if (!has_sink_pad) {
+ has_sink_pad = true;
+ sink_pad = route->sink_pad;
+ }
+
+ if (route->sink_pad != sink_pad) {
+ dev_dbg(state->sd->dev,
+ "sink pads (%u vs. %u) differ\n",
+ route->sink_pad, sink_pad);
+ return -EMLINK;
+ }
+
+ sink_streams |= BIT_ULL(route->sink_stream);
+ src_streams |= BIT_ULL(route->source_stream);
+ }
+
+ *__sink_pad = has_sink_pad ? &state->sd->entity.pads[sink_pad] : NULL;
+ *__sink_streams = sink_streams;
+
+ return 0;
+}
+
+static int v4l2_mc_downpath_enabled(struct media_pad *sink_pad,
+ unsigned int sink_stream,
+ bool (*func)(struct video_device *vdev),
+ struct media_pad **__sink_pad,
+ u64 *__sink_streams)
+{
+ struct v4l2_subdev_state *state;
+ struct v4l2_subdev_route *route;
+ struct v4l2_subdev *sd;
+ struct media_pad *source_pad, *tmp_pad;
+ u32 source_stream;
+
+ if (!is_media_entity_v4l2_subdev(sink_pad->entity))
+ return -ENXIO;
+
+ sd = media_entity_to_v4l2_subdev(sink_pad->entity);
+ dev_dbg(sd->dev, "path_enabled: found sub-device %s\n",
+ sd->entity.name);
+
+ state = v4l2_subdev_lock_and_get_active_state(sd);
+ route = v4l2_subdev_find_route(&state->routing, sink_pad->index,
+ sink_stream, true, 0);
+ if (IS_ERR(route)) {
+ v4l2_subdev_unlock_state(state);
+ dev_dbg(sd->dev,
+ "path_enabled: can't find opposite route for %s:%u/%u",
+ sd->entity.name, sink_pad->index, sink_stream);
+ return 2;
+ }
+
+ source_pad = &sd->entity.pads[route->source_pad];
+ v4l2_subdev_unlock_state(state);
+
+ tmp_pad = sink_pad;
+ sink_pad = media_pad_remote_pad_unique(source_pad);
+ if (IS_ERR(sink_pad)) {
+ dev_dbg(sd->dev,
+ "path_enabled: can't find remote source for %s:%u\n",
+ source_pad->entity->name, source_pad->index);
+ return PTR_ERR(sink_pad);
+ }
+
+ if (is_media_entity_v4l2_video_device(sink_pad->entity)) {
+ struct video_device *vdev;
+
+ vdev = media_entity_to_video_device(sink_pad->entity);
+ if (!vdev)
+ return -ENXIO;
+
+ dev_dbg(vdev->dev_parent,
+ "path_enabled: found video device %s\n",
+ vdev->name);
+
+ if (!*__sink_pad) {
+ *__sink_pad = tmp_pad;
+ dev_dbg(sd->dev, "path_enabled: sink %u/%u\n",
+ tmp_pad->index, sink_stream);
+ } else if (tmp_pad != *__sink_pad) {
+ dev_dbg(sd->dev,
+ "path_enabled: pads %s/%u and %s/%u differ\n",
+ tmp_pad->entity->name, tmp_pad->index,
+ (*__sink_pad)->entity->name,
+ (*__sink_pad)->index);
+ return -EXDEV;
+ }
+
+ *__sink_streams |= BIT_ULL(sink_stream);
+
+ return func(vdev);
+ }
+
+ return v4l2_mc_downpath_enabled(sink_pad, source_stream, func,
+ __sink_pad, __sink_streams);
+}
+
+static int v4l2_mc_source_get_streams(struct v4l2_subdev *sd, unsigned int pad,
+ u64 *__streams)
+{
+ struct v4l2_mbus_frame_desc desc;
+ u64 streams = 0;
+ int ret;
+
+ if (!__streams)
+ return -EINVAL;
+
+ ret = v4l2_subdev_call(sd, pad, get_frame_desc, pad, &desc);
+ if (ret == -ENOIOCTLCMD) {
+ *__streams = 1ULL;
+ return 0;
+ }
+ if (ret)
+ return ret;
+
+ for (unsigned int i = 0; i < desc.num_entries; i++) {
+ if (streams & BIT_ULL(desc.entry[i].stream))
+ return -EINVAL;
+
+ streams |= BIT_ULL(desc.entry[i].stream);
+ }
+
+ dev_dbg(sd->dev, "found streams %#llx based on streams %#llx\n",
+ *__streams, streams);
+ if (*__streams & ~streams)
+ return -EINVAL;
+
+ *__streams = streams;
+
+ return 0;
+}
+
+int v4l2_mc_pipeline_enabled(struct video_device *vdev,
+ bool (*func)(struct video_device *vdev),
+ struct media_pad **__sink_pad, u64 *__sink_streams)
+{
+ u64 sink_streams = 1U;
+ struct media_pad *src_pad;
+ u64 src_streams;
+ struct v4l2_subdev_state *state;
+ struct media_pad *sink_pad = vdev->entity.pads;
+ struct v4l2_subdev *sd = NULL;
+ bool streaming = true;
+ struct media_pad *tmp_pad;
+ u64 tmp_streams;
+ int ret;
+
+ if (!__sink_pad)
+ __sink_pad = &tmp_pad;
+ if (!__sink_streams)
+ __sink_streams = &tmp_streams;
+ *__sink_pad = NULL;
+ *__sink_streams = 0;
+
+ do {
+ src_pad = media_pad_remote_pad_unique(sink_pad);
+ if (IS_ERR(src_pad)) {
+ dev_dbg(sd ? sd->dev : vdev->dev_parent,
+ "no unique remote pad found from %s:%u\n",
+ sink_pad->entity->name, sink_pad->index);
+ return PTR_ERR(src_pad);
+ }
+
+ sd = media_entity_to_v4l2_subdev(src_pad->entity);
+ if (!sd) {
+ dev_dbg(sd->dev,
+ "media entity %s is not a V4L2 sub-device\n",
+ src_pad->entity->name);
+ return -ENXIO;
+ }
+
+ /* Source streams match sink. */
+ src_streams = sink_streams;
+
+ state = v4l2_subdev_lock_and_get_active_state(sd);
+ ret = __v4l2_mc_pipeline_enabled(state, src_pad,
+ src_streams, &sink_pad,
+ &sink_streams);
+ v4l2_subdev_unlock_state(state);
+ if (ret)
+ return ret;
+ } while (sink_pad);
+
+ ret = v4l2_mc_source_get_streams(sd, src_pad->index, &src_streams);
+ if (ret)
+ return ret;
+
+ sd = media_entity_to_v4l2_subdev(src_pad->entity);
+
+ dev_dbg(sd->dev, "following %s:%u/%#llx\n", sd->entity.name,
+ src_pad->index, src_streams);
+
+ for (unsigned int i = __ffs(src_streams); src_streams;
+ src_streams &= ~BIT_ULL(i), i = __ffs(src_streams)) {
+ sink_pad = media_pad_remote_pad_unique(src_pad);
+ if (IS_ERR(src_pad)) {
+ dev_dbg(sd->dev,
+ "no unique remote pad found from %s:%u\n",
+ sink_pad->entity->name, sink_pad->index);
+ return PTR_ERR(src_pad);
+ }
+
+ ret = v4l2_mc_downpath_enabled(sink_pad, i, func, __sink_pad,
+ __sink_streams);
+ if (ret == 2)
+ continue;
+ if (ret < 0)
+ return ret;
+ if (!ret)
+ streaming = false;
+ }
+
+ dev_dbg(media_entity_to_v4l2_subdev((*__sink_pad)->entity)->dev,
+ "sink pad %s:%u/%#llx\n", (*__sink_pad)->entity->name,
+ (*__sink_pad)->index, *__sink_streams);
+
+ return streaming;
+}
+EXPORT_SYMBOL_GPL(v4l2_mc_pipeline_enabled);
diff --git a/include/media/v4l2-mc.h b/include/media/v4l2-mc.h
index 1837c9fd78cf..e72c0f62fa34 100644
--- a/include/media/v4l2-mc.h
+++ b/include/media/v4l2-mc.h
@@ -193,6 +193,50 @@ void v4l2_pipeline_pm_put(struct media_entity *entity);
int v4l2_pipeline_link_notify(struct media_link *link, u32 flags,
unsigned int notification);
+/**
+ * v4l2_mc_pipeline_enabled - Tell when to start streaming
+ * @vdev: The video device
+ * @func: Caller-provided function to tell a video device's streaming state
+ * @__sink_pad: sink pad at the root of the local pipeline
+ * @__sink_streams: streams to start
+ *
+ * Use to tell whether streaming should start on a video node. @func returns
+ * true if streaming has been started on a given video node. @__sink_pad and
+ * @__sink_streams are filled with pad and streams on the sub-device closest to
+ * the video nodes, to be used for calling v4l2_subdev_enable_streams() and
+ * v4l2_subdev_disable_streams().
+ *
+ * Using v4l2_mc_pipeline_enabled() has a few limitations currently (consider it
+ * a to-do list):
+ * * only unbranched streams can be supported albeit adding support for
+ * downstream branches would be fairly trivial,
+ * * streams within a single source sub-device are considered to start at the
+ * same time, more control could be added in two ways: 1) for sources to
+ * determine stream starting, a control could be added to UAPI and 2) sources
+ * could tell which streams start at the same time using a sub-device
+ * operation,
+ * * CSI-2 VC framing is ignored currently, but VC-based stream starting could
+ * be implemented by letting the caller to provide a function to determine
+ * which streams are of interest and
+ * * routes leading to nowhere are ignored, on some hardware this is a problem,
+ * but this can also be rather trivially addressed.
+ *
+ * Return:
+ * * 0: Success, but don't start streaming yet
+ * * 1: Success, now it's time to start streaming
+ * * -ENXIO: Route traversal encountered a non-video device/sub-device entity
+ * * -ENOTUNIQ: No unique remote pad
+ * * -ENOLINK: No remote pad found
+ * * -ENOENT: Enabled upstream route not found
+ * * -EMLINK: No unique downstream route found
+ * * -EINVAL: Stream could not be followed to source or was not produced by
+ * the source
+ */
+int v4l2_mc_pipeline_enabled(struct video_device *vdev,
+ bool (*func)(struct video_device *vdev),
+ struct media_pad **__sink_pad,
+ u64 *__sink_streams);
+
#else /* CONFIG_MEDIA_CONTROLLER */
static inline int v4l2_mc_create_media_graph(struct media_device *mdev)
--
2.39.5
next prev parent reply other threads:[~2025-06-19 8:15 UTC|newest]
Thread overview: 68+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-06-19 8:15 [PATCH 00/13] Streaming control for MC with metadata or streams otherwise Sakari Ailus
2025-06-19 8:15 ` [PATCH 01/13] media: ipu6: Use correct pads for xlate_streams() Sakari Ailus
2025-06-19 13:27 ` Laurent Pinchart
2025-06-19 13:55 ` Sakari Ailus
2025-06-19 14:15 ` Laurent Pinchart
2025-06-19 14:28 ` Sakari Ailus
2025-06-19 15:08 ` Laurent Pinchart
2025-06-19 8:15 ` [PATCH 02/13] media: ipu6: Set minimum height to 1 Sakari Ailus
2025-06-19 13:27 ` Laurent Pinchart
2025-06-19 8:15 ` [PATCH 03/13] media: ipu6: Enable and disable each stream at CSI-2 subdev source pad Sakari Ailus
2025-06-19 12:23 ` kernel test robot
2025-06-19 12:48 ` Laurent Pinchart
2025-06-19 13:10 ` Sakari Ailus
2025-06-19 13:19 ` Laurent Pinchart
2025-06-19 13:52 ` Sakari Ailus
2025-06-19 8:15 ` [PATCH 04/13] media: v4l2-subdev: Add a helper to figure out the pad streaming state Sakari Ailus
2025-06-19 13:37 ` Laurent Pinchart
2025-06-19 8:15 ` [PATCH 05/13] media: v4l: Make media_entity_to_video_device() NULL-safe Sakari Ailus
2025-06-19 15:20 ` Laurent Pinchart
2025-06-19 16:14 ` Sakari Ailus
2025-07-08 11:56 ` Laurent Pinchart
2025-07-08 12:02 ` Sakari Ailus
2025-07-08 16:17 ` Laurent Pinchart
2025-07-09 20:03 ` Sakari Ailus
2025-07-09 20:54 ` Laurent Pinchart
2025-07-10 6:57 ` Sakari Ailus
2025-06-19 8:15 ` [PATCH 06/13] media: v4l2-subdev: Mark both streams of a route enabled Sakari Ailus
2025-06-19 16:56 ` Laurent Pinchart
2025-06-19 18:34 ` Sakari Ailus
2025-06-19 22:18 ` Laurent Pinchart
2025-06-25 16:10 ` Sakari Ailus
2025-06-26 15:22 ` Tomi Valkeinen
2025-06-26 19:13 ` Laurent Pinchart
2025-06-26 15:17 ` Tomi Valkeinen
2025-06-27 6:09 ` Sakari Ailus
2025-06-30 0:47 ` Laurent Pinchart
2025-06-19 8:15 ` [PATCH 07/13] media: ipu6: Set up CSI-2 receiver at correct moment Sakari Ailus
2025-06-19 17:00 ` Laurent Pinchart
2025-06-19 17:20 ` Sakari Ailus
2025-06-19 8:15 ` [PATCH 08/13] media: v4l2-subdev: Print early in v4l2_subdev_{enable,disable}_streams() Sakari Ailus
2025-06-19 17:03 ` Laurent Pinchart
2025-06-25 16:12 ` Sakari Ailus
2025-06-19 8:15 ` [PATCH 09/13] media: v4l2-subdev: Collect streams on source pads only Sakari Ailus
2025-06-19 17:07 ` Laurent Pinchart
2025-06-25 16:14 ` Sakari Ailus
2025-06-19 8:15 ` [PATCH 10/13] media: v4l2-subdev: Add debug prints to v4l2_subdev_collect_streams() Sakari Ailus
2025-06-19 22:23 ` Laurent Pinchart
2025-06-25 16:28 ` Sakari Ailus
2025-06-19 8:15 ` [PATCH 11/13] media: v4l2-subdev: Introduce v4l2_subdev_find_route() Sakari Ailus
2025-06-20 8:14 ` Jacopo Mondi
2025-06-25 16:53 ` Sakari Ailus
2025-06-26 22:20 ` Laurent Pinchart
2025-07-15 14:09 ` Sakari Ailus
2025-06-19 8:15 ` Sakari Ailus [this message]
2025-06-19 11:42 ` [PATCH 12/13] media: v4l2-mc: Introduce v4l2_mc_pipeline_enabled() kernel test robot
2025-06-20 3:58 ` Dan Carpenter
2025-06-20 8:53 ` Jacopo Mondi
2025-06-21 8:10 ` Sakari Ailus
2025-07-15 10:49 ` Sakari Ailus
2025-07-15 11:25 ` Laurent Pinchart
2025-07-15 11:32 ` Sakari Ailus
2025-07-15 18:18 ` Laurent Pinchart
2025-06-23 9:48 ` kernel test robot
2025-06-26 23:07 ` Laurent Pinchart
2025-08-04 11:32 ` Sakari Ailus
2025-08-04 11:46 ` Laurent Pinchart
2025-06-19 8:15 ` [PATCH 13/13] media: ipu6: isys: Rework stream starting and stopping Sakari Ailus
-- strict thread matches above, loose matches on Subject: below --
2025-06-20 3:32 [PATCH 12/13] media: v4l2-mc: Introduce v4l2_mc_pipeline_enabled() kernel test robot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250619081546.1582969-13-sakari.ailus@linux.intel.com \
--to=sakari.ailus@linux.intel.com \
--cc=bingbu.cao@linux.intel.com \
--cc=laurent.pinchart@ideasonboard.com \
--cc=linux-media@vger.kernel.org \
--cc=stanislaw.gruszka@linux.intel.com \
--cc=tian.shu.qiu@intel.com \
--cc=tomi.valkeinen@ideasonboard.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.