From: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
To: Sakari Ailus <sakari.ailus@linux.intel.com>
Cc: Jacopo Mondi <jacopo.mondi@ideasonboard.com>,
linux-media@vger.kernel.org, bingbu.cao@linux.intel.com,
stanislaw.gruszka@linux.intel.com, tian.shu.qiu@intel.com,
tomi.valkeinen@ideasonboard.com,
Daniel Scally <dan.scally@ideasonboard.com>
Subject: Re: [PATCH 12/13] media: v4l2-mc: Introduce v4l2_mc_pipeline_enabled()
Date: Tue, 15 Jul 2025 14:25:20 +0300 [thread overview]
Message-ID: <20250715112520.GH20231@pendragon.ideasonboard.com> (raw)
In-Reply-To: <aHYyPMcwt3WBzJur@kekkonen.localdomain>
On Tue, Jul 15, 2025 at 10:49:32AM +0000, Sakari Ailus wrote:
> On Fri, Jun 20, 2025 at 10:53:13AM +0200, Jacopo Mondi wrote:
> > On Thu, Jun 19, 2025 at 11:15:45AM +0300, Sakari Ailus wrote:
> > > 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?
> >
> > I tried quite some time to understand this, but if I'm not mistaken,
> > a stream-aware subdev, which links to vdev, will always "demux"
> > streams to different pads and will connect to the vdev from there
> >
> >
> > Source
> > subdev
> > +-----------------+
> > | (1/0) ------> vdev0
> > | |
> > (0)[1,2,3] (2/0 ------> vdev1
> > | |
> > | (3/0) ------> vdev2
> > +-----------------+
> >
> > With
> >
> > (0) multiplexed sink pad with 3 streams
> > (1) (2) and (3) source pad with a single stream
> >
> > Can't we relay on the media-link state between the source pads and the
> > video devices with something like what Dan has proposed here ?
> > https://patchwork.linuxtv.org/project/linux-media/patch/20250519140403.443915-2-dan.scally@ideasonboard.com/
>
> This isn't entirely the same thing: pipeline is specific to the pad but not
> streams, ad here we're interested in streams. Two streams may start at
> different points of time even if both are part of the same pipeline.
>
> We definitely should have just one way to figure this out.
But can we ? Isn't it dependent on use cases ? I can imagine an
application wanting to capture two image streams where one of them is
enabled all the time and the other one is regularly turned on and off,
in which case you would need to start the pipeline when the first stream
starts. A different application may instead want to capture the same two
streams and make sure it gets all frames on both, which possibly
requires delaying the start of capture until both video capture devices
are started.
> I'll send v2 with comments and before that also see if (or how) we could
> get rid of the callback.
>
> Cc Dan.
>
> > What am I missing ?
> >
> > > 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)
>
> --
> Kind regards,
>
> Sakari Ailus
--
Regards,
Laurent Pinchart
next prev parent reply other threads:[~2025-07-15 11:25 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 ` [PATCH 12/13] media: v4l2-mc: Introduce v4l2_mc_pipeline_enabled() Sakari Ailus
2025-06-19 11:42 ` 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 [this message]
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=20250715112520.GH20231@pendragon.ideasonboard.com \
--to=laurent.pinchart@ideasonboard.com \
--cc=bingbu.cao@linux.intel.com \
--cc=dan.scally@ideasonboard.com \
--cc=jacopo.mondi@ideasonboard.com \
--cc=linux-media@vger.kernel.org \
--cc=sakari.ailus@linux.intel.com \
--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.