linux-media.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v5 0/7] v4l-utils: Support multiplexed streams
@ 2023-05-29  8:49 Tomi Valkeinen
  2023-05-29  8:49 ` [PATCH v5 1/7] v4l2-ctl: Add routing and streams support Tomi Valkeinen
                   ` (6 more replies)
  0 siblings, 7 replies; 15+ messages in thread
From: Tomi Valkeinen @ 2023-05-29  8:49 UTC (permalink / raw)
  To: linux-media, sakari.ailus, Jacopo Mondi, Laurent Pinchart,
	niklas.soderlund+renesas, Mauro Carvalho Chehab, Hans Verkuil,
	satish.nagireddy
  Cc: Tomi Valkeinen

Hi,

v4 can be found from:

https://lore.kernel.org/all/20230421124428.393261-1-tomi.valkeinen@ideasonboard.com/

v5 is rebased on top of latest master, and I have addressed most of
Laurent's comment. I did not start refactoring ARRAY_SIZE() nor
print_flags(), nor did I rewrite everything so that a real parser would
be used.

A diff to v4 is included below to help the reviews.

 Tomi

Tomi Valkeinen (7):
  v4l2-ctl: Add routing and streams support
  media-ctl: Add support for routes and streams
  v4l2-ctl/compliance: Add routing and streams multiplexed streams
  v4l2-ctl/compliance: Add simple routing test
  media-ctl: Check for Streams API support
  utils/common: Set V4L2_SUBDEV_CLIENT_CAP_STREAMS for subdevs
  v4l2-ctl: Check for Streams API support

 utils/common/cv4l-helpers.h                 |   1 +
 utils/common/v4l-helpers.h                  |  18 +
 utils/media-ctl/libmediactl.c               |  43 +++
 utils/media-ctl/libv4l2subdev.c             | 339 ++++++++++++++++--
 utils/media-ctl/media-ctl.c                 | 113 +++++-
 utils/media-ctl/mediactl-priv.h             |   1 +
 utils/media-ctl/mediactl.h                  |  16 +
 utils/media-ctl/options.c                   |  15 +-
 utils/media-ctl/options.h                   |   1 +
 utils/media-ctl/v4l2subdev.h                |  66 +++-
 utils/v4l2-compliance/v4l2-compliance.cpp   | 132 +++++--
 utils/v4l2-compliance/v4l2-compliance.h     |   9 +-
 utils/v4l2-compliance/v4l2-test-subdevs.cpp |  59 +++-
 utils/v4l2-ctl/v4l2-ctl-subdev.cpp          | 365 ++++++++++++++++++--
 utils/v4l2-ctl/v4l2-ctl.cpp                 |   2 +
 utils/v4l2-ctl/v4l2-ctl.h                   |   2 +
 16 files changed, 1062 insertions(+), 120 deletions(-)

Interdiff against v4:
diff --git a/utils/common/cv4l-helpers.h b/utils/common/cv4l-helpers.h
index 502df6ac..91a04146 100644
--- a/utils/common/cv4l-helpers.h
+++ b/utils/common/cv4l-helpers.h
@@ -82,7 +82,7 @@ public:
 	bool has_rw() const { return v4l_has_rw(this); }
 	bool has_streaming() const { return v4l_has_streaming(this); }
 	bool has_ext_pix_format() const { return v4l_has_ext_pix_format(this); }
-	bool has_streams_support() const { return subdev_supports_streams; }
+	bool has_streams() const { return have_streams; }
 
 	int querycap(v4l2_capability &cap, bool force = false)
 	{
diff --git a/utils/common/v4l-helpers.h b/utils/common/v4l-helpers.h
index 2dd7f061..f8e96d58 100644
--- a/utils/common/v4l-helpers.h
+++ b/utils/common/v4l-helpers.h
@@ -9,6 +9,7 @@
 #ifndef _V4L_HELPERS_H_
 #define _V4L_HELPERS_H_
 
+#include <linux/v4l2-subdev.h>
 #include <linux/videodev2.h>
 #include <string.h>
 #include <stdlib.h>
@@ -39,7 +40,7 @@ struct v4l_fd {
 	bool have_selection;
 	bool is_subdev;
 	bool is_media;
-	bool subdev_supports_streams;
+	bool have_streams;
 
 	int (*open)(struct v4l_fd *f, const char *file, int oflag, ...);
 	int (*close)(struct v4l_fd *f);
@@ -543,7 +544,7 @@ static inline int v4l_subdev_s_fd(struct v4l_fd *f, int fd, const char *devname)
 	ret = ioctl(f->fd, VIDIOC_SUBDEV_S_CLIENT_CAP, &clientcap);
 	client_streams = !ret && (clientcap.capabilities & V4L2_SUBDEV_CLIENT_CAP_STREAMS);
 
-	f->subdev_supports_streams = subdev_streams && client_streams;
+	f->have_streams = subdev_streams && client_streams;
 
 	return f->fd;
 }
diff --git a/utils/media-ctl/libmediactl.c b/utils/media-ctl/libmediactl.c
index c32fe56a..64ac8cf1 100644
--- a/utils/media-ctl/libmediactl.c
+++ b/utils/media-ctl/libmediactl.c
@@ -909,6 +909,8 @@ struct media_pad *media_parse_pad_stream(struct media_device *media,
 		*stream = 0;
 	}
 
+	for (; isspace(*p); ++p);
+
 	if (endp)
 		*endp = (char*)p;
 
diff --git a/utils/media-ctl/libv4l2subdev.c b/utils/media-ctl/libv4l2subdev.c
index 186708ff..750796cc 100644
--- a/utils/media-ctl/libv4l2subdev.c
+++ b/utils/media-ctl/libv4l2subdev.c
@@ -231,33 +231,6 @@ int v4l2_subdev_set_selection(struct media_entity *entity,
 	return 0;
 }
 
-int v4l2_subdev_set_routing(struct media_entity *entity,
-			    struct v4l2_subdev_route *routes,
-			    unsigned int num_routes)
-{
-	struct v4l2_subdev_routing routing = {
-		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
-		.routes = (uintptr_t)routes,
-		.num_routes = num_routes,
-	};
-	int ret;
-
-	ret = v4l2_subdev_open(entity);
-	if (ret < 0)
-		return ret;
-
-	if (!entity->supports_streams) {
-		media_dbg(entity->media, "Streams API not supported\n");
-		return -ENOTSUP;
-	}
-
-	ret = ioctl(entity->fd, VIDIOC_SUBDEV_S_ROUTING, &routing);
-	if (ret == -1)
-		return -errno;
-
-	return 0;
-}
-
 int v4l2_subdev_get_routing(struct media_entity *entity,
 			    struct v4l2_subdev_route **routes,
 			    unsigned int *num_routes)
@@ -272,8 +245,10 @@ int v4l2_subdev_get_routing(struct media_entity *entity,
 	if (ret < 0)
 		return ret;
 
-	if (!entity->supports_streams)
+	if (!entity->supports_streams) {
+		media_dbg(entity->media, "Streams API not supported\n");
 		return -ENOTSUP;
+	}
 
 	ret = ioctl(entity->fd, VIDIOC_SUBDEV_G_ROUTING, &routing);
 	if (ret == -1 && errno != ENOSPC)
@@ -302,6 +277,33 @@ int v4l2_subdev_get_routing(struct media_entity *entity,
 	return 0;
 }
 
+int v4l2_subdev_set_routing(struct media_entity *entity,
+			    struct v4l2_subdev_route *routes,
+			    unsigned int num_routes)
+{
+	struct v4l2_subdev_routing routing = {
+		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
+		.routes = (uintptr_t)routes,
+		.num_routes = num_routes,
+	};
+	int ret;
+
+	ret = v4l2_subdev_open(entity);
+	if (ret < 0)
+		return ret;
+
+	if (!entity->supports_streams) {
+		media_dbg(entity->media, "Streams API not supported\n");
+		return -ENOTSUP;
+	}
+
+	ret = ioctl(entity->fd, VIDIOC_SUBDEV_S_ROUTING, &routing);
+	if (ret == -1)
+		return -errno;
+
+	return 0;
+}
+
 int v4l2_subdev_get_dv_timings_caps(struct media_entity *entity,
 	struct v4l2_dv_timings_cap *caps)
 {
@@ -496,6 +498,8 @@ static int v4l2_subdev_parse_setup_route(struct media_device *media,
 	}
 	end++;
 
+	for (; isspace(*end); ++end);
+
 	*endp = end;
 
 	return 0;
@@ -506,9 +510,9 @@ int v4l2_subdev_parse_setup_routes(struct media_device *media, const char *p)
 	struct media_entity *entity;
 	struct v4l2_subdev_route *routes;
 	unsigned int num_routes;
+	unsigned int i;
 	char *end;
 	int ret;
-	int i;
 
 	entity = media_parse_entity(media, p, &end);
 	if (!entity)
@@ -558,7 +562,7 @@ int v4l2_subdev_parse_setup_routes(struct media_device *media, const char *p)
 		struct v4l2_subdev_route *r = &routes[i];
 
 		media_dbg(entity->media,
-			  "Setting up route %s : %u/%u -> %u/%u, flags 0x%8.8x\n",
+			  "Setting up route %s : %u/%u -> %u/%u [0x%08x]\n",
 			  entity->info.name,
 			  r->sink_pad, r->sink_stream,
 			  r->source_pad, r->source_stream,
diff --git a/utils/media-ctl/media-ctl.c b/utils/media-ctl/media-ctl.c
index 831136a0..1531cffa 100644
--- a/utils/media-ctl/media-ctl.c
+++ b/utils/media-ctl/media-ctl.c
@@ -82,16 +82,16 @@ static void v4l2_subdev_print_routes(struct media_entity *entity,
 {
 	unsigned int i;
 
-	for (i = 0; i < num_routes; i++) {
-		const struct v4l2_subdev_route *r = &routes[i];
+	if (num_routes)
+		printf("\troutes:\n");
 
-		if (i == 0)
-			printf("\troutes:\n");
+	for (i = 0; i < num_routes; i++) {
+		const struct v4l2_subdev_route *route = &routes[i];
 
 		printf("\t\t%u/%u -> %u/%u [%s]\n",
-		       r->sink_pad, r->sink_stream,
-		       r->source_pad, r->source_stream,
-		       r->flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE ? "ACTIVE" : "INACTIVE");
+		       route->sink_pad, route->sink_stream,
+		       route->source_pad, route->source_stream,
+		       route->flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE ? "ACTIVE" : "INACTIVE");
 	}
 }
 
@@ -480,54 +480,49 @@ static void media_print_pad_text(struct media_entity *entity,
 				 struct v4l2_subdev_route *routes,
 				 unsigned int num_routes)
 {
+	uint64_t printed_streams_mask = 0;
 	unsigned int i;
-	uint64_t printed_streams_mask;
 
 	if (media_entity_type(entity) != MEDIA_ENT_T_V4L2_SUBDEV)
 		return;
 
 	if (!routes) {
-		v4l2_subdev_print_format(entity, pad->index, 0, V4L2_SUBDEV_FORMAT_ACTIVE);
-		v4l2_subdev_print_pad_dv(entity, pad->index, V4L2_SUBDEV_FORMAT_ACTIVE);
-
-		if (pad->flags & MEDIA_PAD_FL_SOURCE)
-			v4l2_subdev_print_subdev_dv(entity);
+		v4l2_subdev_print_format(entity, pad->index, 0,
+					 V4L2_SUBDEV_FORMAT_ACTIVE);
+	} else {
+		for (i = 0; i < num_routes; ++i) {
+			const struct v4l2_subdev_route *route = &routes[i];
+			unsigned int stream;
 
-		return;
-	}
+			if (!(route->flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE))
+				continue;
 
-	printed_streams_mask = 0;
+			if (pad->flags & MEDIA_PAD_FL_SINK) {
+				if (route->sink_pad != pad->index)
+					continue;
 
-	for (i = 0; i < num_routes; ++i) {
-		const struct v4l2_subdev_route *r = &routes[i];
-		unsigned int stream;
+				stream = route->sink_stream;
+			} else {
+				if (route->source_pad != pad->index)
+					continue;
 
-		if (!(r->flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE))
-			continue;
+				stream = route->source_stream;
+			}
 
-		if (pad->flags & MEDIA_PAD_FL_SINK) {
-			if (r->sink_pad != pad->index)
+			if (printed_streams_mask & (1 << stream))
 				continue;
 
-			stream = r->sink_stream;
-		} else {
-			if (r->source_pad != pad->index)
-				continue;
+			v4l2_subdev_print_format(entity, pad->index, stream,
+						 V4L2_SUBDEV_FORMAT_ACTIVE);
 
-			stream = r->source_stream;
+			printed_streams_mask |= (1 << stream);
 		}
+	}
 
-		if (printed_streams_mask & (1 << stream))
-			continue;
-
-		v4l2_subdev_print_format(entity, pad->index, stream, V4L2_SUBDEV_FORMAT_ACTIVE);
-		v4l2_subdev_print_pad_dv(entity, pad->index, V4L2_SUBDEV_FORMAT_ACTIVE);
-
-		if (pad->flags & MEDIA_PAD_FL_SOURCE)
-			v4l2_subdev_print_subdev_dv(entity);
+	v4l2_subdev_print_pad_dv(entity, pad->index, V4L2_SUBDEV_FORMAT_ACTIVE);
 
-		printed_streams_mask |= (1 << stream);
-	}
+	if (pad->flags & MEDIA_PAD_FL_SOURCE)
+		v4l2_subdev_print_subdev_dv(entity);
 }
 
 static void media_print_topology_text_entity(struct media_device *media,
@@ -541,19 +536,24 @@ static void media_print_topology_text_entity(struct media_device *media,
 	const struct media_entity_desc *info = media_entity_get_info(entity);
 	const char *devname = media_entity_get_devname(entity);
 	unsigned int num_links = media_entity_get_links_count(entity);
-	unsigned int j, k;
-	unsigned int padding;
 	struct v4l2_subdev_route *routes = NULL;
 	unsigned int num_routes = 0;
+	unsigned int j, k;
+	unsigned int padding;
 
 	if (media_entity_type(entity) == MEDIA_ENT_T_V4L2_SUBDEV)
 		v4l2_subdev_get_routing(entity, &routes, &num_routes);
 
 	padding = printf("- entity %u: ", info->id);
-	printf("%s (%u pad%s, %u link%s, %u route%s)\n", info->name,
+	printf("%s (%u pad%s, %u link%s", info->name,
 	       info->pads, info->pads > 1 ? "s" : "",
-	       num_links, num_links > 1 ? "s" : "",
-	       num_routes, num_routes > 1 ? "s" : "");
+	       num_links, num_links > 1 ? "s" : "");
+
+	if (media_entity_type(entity) == MEDIA_ENT_T_V4L2_SUBDEV)
+		printf(", %u route%s", num_routes, num_routes != 1 ? "s" : "");
+
+	printf(")\n");
+
 	printf("%*ctype %s subtype %s flags %x\n", padding, ' ',
 	       media_entity_type_to_string(info->type),
 	       media_entity_subtype_to_string(info->type),
diff --git a/utils/media-ctl/v4l2subdev.h b/utils/media-ctl/v4l2subdev.h
index a8a6e7ad..1277040b 100644
--- a/utils/media-ctl/v4l2subdev.h
+++ b/utils/media-ctl/v4l2subdev.h
@@ -52,6 +52,7 @@ void v4l2_subdev_close(struct media_entity *entity);
  * @param entity - subdev-device media entity.
  * @param format - format to be filled.
  * @param pad - pad number.
+ * @param stream - stream number.
  * @param which - identifier of the format to get.
  *
  * Retrieve the current format on the @a entity @a pad and store it in the
@@ -72,6 +73,7 @@ int v4l2_subdev_get_format(struct media_entity *entity,
  * @param entity - subdev-device media entity.
  * @param format - format.
  * @param pad - pad number.
+ * @param stream - stream number.
  * @param which - identifier of the format to set.
  *
  * Set the format on the @a entity @a pad to @a format. The driver is allowed to
@@ -94,6 +96,7 @@ int v4l2_subdev_set_format(struct media_entity *entity,
  * @param entity - subdev-device media entity.
  * @param r - rectangle to be filled.
  * @param pad - pad number.
+ * @param stream - stream number.
  * @param target - selection target
  * @param which - identifier of the format to get.
  *
@@ -116,6 +119,7 @@ int v4l2_subdev_get_selection(struct media_entity *entity,
  * @param entity - subdev-device media entity.
  * @param rect - crop rectangle.
  * @param pad - pad number.
+ * @param stream - stream number.
  * @param target - selection target
  * @param which - identifier of the format to set.
  *
@@ -222,6 +226,8 @@ int v4l2_subdev_set_dv_timings(struct media_entity *entity,
  * @brief Retrieve the frame interval on a sub-device.
  * @param entity - subdev-device media entity.
  * @param interval - frame interval to be filled.
+ * @param pad - pad number.
+ * @param stream - stream number.
  *
  * Retrieve the current frame interval on subdev @a entity and store it in the
  * @a interval structure.
@@ -239,6 +245,8 @@ int v4l2_subdev_get_frame_interval(struct media_entity *entity,
  * @brief Set the frame interval on a sub-device.
  * @param entity - subdev-device media entity.
  * @param interval - frame interval.
+ * @param pad - pad number.
+ * @param stream - stream number.
  *
  * Set the frame interval on subdev @a entity to @a interval. The driver is
  * allowed to modify the requested frame interval, in which case @a interval is
diff --git a/utils/v4l2-compliance/v4l2-compliance.cpp b/utils/v4l2-compliance/v4l2-compliance.cpp
index f96f5972..f082f569 100644
--- a/utils/v4l2-compliance/v4l2-compliance.cpp
+++ b/utils/v4l2-compliance/v4l2-compliance.cpp
@@ -1255,8 +1255,8 @@ void testNode(struct node &node, struct node &node_m2m_cap, struct node &expbuf_
 				which <= V4L2_SUBDEV_FORMAT_ACTIVE; which++) {
 
 				printf("\ttest %s VIDIOC_SUBDEV_G_ROUTING/VIDIOC_SUBDEV_S_ROUTING: %s\n",
-						       which ? "Active" : "Try",
-						       ok(testSubDevRouting(&node, which)));
+				       which ? "Active" : "Try",
+				       ok(testSubDevRouting(&node, which)));
 			}
 
 			printf("\n");
diff --git a/utils/v4l2-ctl/v4l2-ctl-subdev.cpp b/utils/v4l2-ctl/v4l2-ctl-subdev.cpp
index ec70b52b..8539c416 100644
--- a/utils/v4l2-ctl/v4l2-ctl-subdev.cpp
+++ b/utils/v4l2-ctl/v4l2-ctl-subdev.cpp
@@ -569,7 +569,7 @@ void subdev_set(cv4l_fd &_fd)
 	if (options[OptSetSubDevFormat] || options[OptTrySubDevFormat]) {
 		struct v4l2_subdev_format fmt;
 
-		if (!_fd.has_streams_support() && set_fmt_stream) {
+		if (!_fd.has_streams() && set_fmt_stream) {
 			printf("Streams API not supported.\n");
 			return;
 		}
@@ -622,7 +622,7 @@ void subdev_set(cv4l_fd &_fd)
 	if (options[OptSetSubDevSelection] || options[OptTrySubDevSelection]) {
 		struct v4l2_subdev_selection sel;
 
-		if (!_fd.has_streams_support() && vsel.stream) {
+		if (!_fd.has_streams() && vsel.stream) {
 			printf("Streams API not supported.\n");
 			return;
 		}
@@ -659,7 +659,7 @@ void subdev_set(cv4l_fd &_fd)
 	if (options[OptSetSubDevFPS]) {
 		struct v4l2_subdev_frame_interval fival;
 
-		if (!_fd.has_streams_support() && set_fps_stream) {
+		if (!_fd.has_streams() && set_fps_stream) {
 			printf("Streams API not supported.\n");
 			return;
 		}
@@ -689,7 +689,7 @@ void subdev_set(cv4l_fd &_fd)
 		}
 	}
 	if (options[OptSetRouting]) {
-		if (!_fd.has_streams_support()) {
+		if (!_fd.has_streams()) {
 			printf("Streams API not supported.\n");
 			return;
 		}
@@ -736,7 +736,7 @@ static void print_routes(const struct v4l2_subdev_routing *r)
 	};
 
 	for (i = 0; i < r->num_routes; i++) {
-		printf("%d/%d -> %d/%d [",
+		printf("%u/%u -> %u/%u [",
 		       routes[i].sink_pad, routes[i].sink_stream,
 		       routes[i].source_pad, routes[i].source_stream);
 		print_flags(route_flags, ARRAY_SIZE(route_flags), routes[i].flags);
@@ -751,7 +751,7 @@ void subdev_get(cv4l_fd &_fd)
 	if (options[OptGetSubDevFormat]) {
 		struct v4l2_subdev_format fmt;
 
-		if (!_fd.has_streams_support() && get_fmt_stream) {
+		if (!_fd.has_streams() && get_fmt_stream) {
 			printf("Streams API not supported.\n");
 			return;
 		}
@@ -761,7 +761,7 @@ void subdev_get(cv4l_fd &_fd)
 		fmt.pad = get_fmt_pad;
 		fmt.stream = get_fmt_stream;
 
-		printf("ioctl: VIDIOC_SUBDEV_G_FMT (pad=%u, stream=%u)\n", fmt.pad, fmt.stream);
+		printf("ioctl: VIDIOC_SUBDEV_G_FMT (pad=%u,stream=%u)\n", fmt.pad, fmt.stream);
 		if (doioctl(fd, VIDIOC_SUBDEV_G_FMT, &fmt) == 0)
 			print_framefmt(fmt.format);
 	}
@@ -770,7 +770,7 @@ void subdev_get(cv4l_fd &_fd)
 		struct v4l2_subdev_selection sel;
 		unsigned idx = 0;
 
-		if (!_fd.has_streams_support() && get_sel_stream) {
+		if (!_fd.has_streams() && get_sel_stream) {
 			printf("Streams API not supported.\n");
 			return;
 		}
@@ -797,7 +797,7 @@ void subdev_get(cv4l_fd &_fd)
 	if (options[OptGetSubDevFPS]) {
 		struct v4l2_subdev_frame_interval fival;
 
-		if (!_fd.has_streams_support() && get_fps_stream) {
+		if (!_fd.has_streams() && get_fps_stream) {
 			printf("Streams API not supported.\n");
 			return;
 		}
@@ -819,7 +819,7 @@ void subdev_get(cv4l_fd &_fd)
 	}
 
 	if (options[OptGetRouting]) {
-		if (!_fd.has_streams_support()) {
+		if (!_fd.has_streams()) {
 			printf("Streams API not supported.\n");
 			return;
 		}
@@ -907,7 +907,7 @@ void subdev_list(cv4l_fd &_fd)
 	int fd = _fd.g_fd();
 
 	if (options[OptListSubDevMBusCodes]) {
-		if (!_fd.has_streams_support() && list_mbus_codes_stream) {
+		if (!_fd.has_streams() && list_mbus_codes_stream) {
 			printf("Streams API not supported.\n");
 			return;
 		}
@@ -917,7 +917,7 @@ void subdev_list(cv4l_fd &_fd)
 		print_mbus_codes(fd, list_mbus_codes_pad, list_mbus_codes_stream);
 	}
 	if (options[OptListSubDevFrameSizes]) {
-		if (!_fd.has_streams_support() && frmsize.stream) {
+		if (!_fd.has_streams() && frmsize.stream) {
 			printf("Streams API not supported.\n");
 			return;
 		}
@@ -932,7 +932,7 @@ void subdev_list(cv4l_fd &_fd)
 		}
 	}
 	if (options[OptListSubDevFrameIntervals]) {
-		if (!_fd.has_streams_support() && frmival.stream) {
+		if (!_fd.has_streams() && frmival.stream) {
 			printf("Streams API not supported.\n");
 			return;
 		}
diff --git a/utils/v4l2-ctl/v4l2-ctl.cpp b/utils/v4l2-ctl/v4l2-ctl.cpp
index 1cfb50f7..52974b40 100644
--- a/utils/v4l2-ctl/v4l2-ctl.cpp
+++ b/utils/v4l2-ctl/v4l2-ctl.cpp
@@ -64,8 +64,8 @@ static struct option long_options[] = {
 	{"get-fmt-video-out", no_argument, nullptr, OptGetVideoOutFormat},
 	{"set-fmt-video-out", required_argument, nullptr, OptSetVideoOutFormat},
 	{"try-fmt-video-out", required_argument, nullptr, OptTryVideoOutFormat},
-	{"set-routing", required_argument, 0, OptSetRouting},
 	{"get-routing", no_argument, 0, OptGetRouting},
+	{"set-routing", required_argument, 0, OptSetRouting},
 	{"help", no_argument, nullptr, OptHelp},
 	{"help-tuner", no_argument, nullptr, OptHelpTuner},
 	{"help-io", no_argument, nullptr, OptHelpIO},
diff --git a/utils/v4l2-ctl/v4l2-ctl.h b/utils/v4l2-ctl/v4l2-ctl.h
index 9396c974..bf519c3f 100644
--- a/utils/v4l2-ctl/v4l2-ctl.h
+++ b/utils/v4l2-ctl/v4l2-ctl.h
@@ -191,8 +191,8 @@ enum Option {
 	OptInfoEdid,
 	OptShowEdid,
 	OptFixEdidChecksums,
-	OptSetRouting,
 	OptGetRouting,
+	OptSetRouting,
 	OptFreqSeek,
 	OptEncoderCmd,
 	OptTryEncoderCmd,
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v5 1/7] v4l2-ctl: Add routing and streams support
  2023-05-29  8:49 [PATCH v5 0/7] v4l-utils: Support multiplexed streams Tomi Valkeinen
@ 2023-05-29  8:49 ` Tomi Valkeinen
  2023-06-07 11:28   ` Hans Verkuil
  2023-05-29  8:49 ` [PATCH v5 2/7] media-ctl: Add support for routes and streams Tomi Valkeinen
                   ` (5 subsequent siblings)
  6 siblings, 1 reply; 15+ messages in thread
From: Tomi Valkeinen @ 2023-05-29  8:49 UTC (permalink / raw)
  To: linux-media, sakari.ailus, Jacopo Mondi, Laurent Pinchart,
	niklas.soderlund+renesas, Mauro Carvalho Chehab, Hans Verkuil,
	satish.nagireddy
  Cc: Tomi Valkeinen

Add support to get and set subdev routes and to get and set
configurations per stream.

Based on work from Jacopo Mondi <jacopo@jmondi.org> and
Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>.

Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
---
 utils/v4l2-ctl/v4l2-ctl-subdev.cpp | 310 ++++++++++++++++++++++++++---
 utils/v4l2-ctl/v4l2-ctl.cpp        |   2 +
 utils/v4l2-ctl/v4l2-ctl.h          |   2 +
 3 files changed, 281 insertions(+), 33 deletions(-)

diff --git a/utils/v4l2-ctl/v4l2-ctl-subdev.cpp b/utils/v4l2-ctl/v4l2-ctl-subdev.cpp
index 33cc1342..fafb7d92 100644
--- a/utils/v4l2-ctl/v4l2-ctl-subdev.cpp
+++ b/utils/v4l2-ctl/v4l2-ctl-subdev.cpp
@@ -1,5 +1,13 @@
 #include "v4l2-ctl.h"
 
+#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
+
+/*
+ * The max value comes from a check in the kernel source code
+ * drivers/media/v4l2-core/v4l2-ioctl.c check_array_args()
+ */
+#define NUM_ROUTES_MAX 256
+
 struct mbus_name {
 	const char *name;
 	__u32 code;
@@ -19,45 +27,57 @@ static const struct mbus_name mbus_names[] = {
 #define SelectionFlags 		(1L<<4)
 
 static __u32 list_mbus_codes_pad;
+static __u32 list_mbus_codes_stream = 0;
 static __u32 get_fmt_pad;
+static __u32 get_fmt_stream = 0;
 static __u32 get_sel_pad;
+static __u32 get_sel_stream = 0;
 static __u32 get_fps_pad;
+static __u32 get_fps_stream = 0;
 static int get_sel_target = -1;
 static unsigned int set_selection;
 static struct v4l2_subdev_selection vsel;
 static unsigned int set_fmt;
 static __u32 set_fmt_pad;
+static __u32 set_fmt_stream = 0;
 static struct v4l2_mbus_framefmt ffmt;
 static struct v4l2_subdev_frame_size_enum frmsize;
 static struct v4l2_subdev_frame_interval_enum frmival;
 static __u32 set_fps_pad;
+static __u32 set_fps_stream = 0;
 static double set_fps;
+static struct v4l2_subdev_routing routing;
+static struct v4l2_subdev_route routes[NUM_ROUTES_MAX];
 
 void subdev_usage()
 {
 	printf("\nSub-Device options:\n"
-	       "  --list-subdev-mbus-codes <pad>\n"
+	       "  --list-subdev-mbus-codes pad=<pad>,stream=<stream>\n"
 	       "                      display supported mediabus codes for this pad (0 is default)\n"
 	       "                      [VIDIOC_SUBDEV_ENUM_MBUS_CODE]\n"
-	       "  --list-subdev-framesizes pad=<pad>,code=<code>\n"
+	       "  --list-subdev-framesizes pad=<pad>,stream=<stream>,code=<code>\n"
 	       "                     list supported framesizes for this pad and code\n"
 	       "                     [VIDIOC_SUBDEV_ENUM_FRAME_SIZE]\n"
 	       "                     <code> is the value of the mediabus code\n"
-	       "  --list-subdev-frameintervals pad=<pad>,width=<w>,height=<h>,code=<code>\n"
+	       "  --list-subdev-frameintervals pad=<pad>,stream=<stream>,width=<w>,height=<h>,code=<code>\n"
 	       "                     list supported frame intervals for this pad and code and\n"
 	       "                     the given width and height [VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL]\n"
 	       "                     <code> is the value of the mediabus code\n"
-	       "  --get-subdev-fmt [<pad>]\n"
-	       "     		     query the frame format for the given pad [VIDIOC_SUBDEV_G_FMT]\n"
-	       "  --get-subdev-selection pad=<pad>,target=<target>\n"
+	       "  --get-subdev-fmt pad=<pad>,stream=<stream>\n"
+	       "     		     query the frame format for the given pad and optional stream [VIDIOC_SUBDEV_G_FMT]\n"
+	       "		     <pad> the pad to get the format from\n"
+	       "		     <stream> the stream to get the format from (0 if not specified)\n"
+	       "  --get-subdev-selection pad=<pad>,stream=<stream>,target=<target>\n"
 	       "                     query the frame selection rectangle [VIDIOC_SUBDEV_G_SELECTION]\n"
 	       "                     See --set-subdev-selection command for the valid <target> values.\n"
-	       "  --get-subdev-fps [<pad>]\n"
+	       "  --get-subdev-fps pad=<pad>,stream=<stream>\n"
 	       "                     query the frame rate [VIDIOC_SUBDEV_G_FRAME_INTERVAL]\n"
 	       "  --set-subdev-fmt   (for testing only, otherwise use media-ctl)\n"
-	       "  --try-subdev-fmt pad=<pad>,width=<w>,height=<h>,code=<code>,field=<f>,colorspace=<c>,\n"
+	       "  --try-subdev-fmt pad=<pad>,stream=<stream>,width=<w>,height=<h>,code=<code>,field=<f>,colorspace=<c>,\n"
 	       "                   xfer=<xf>,ycbcr=<y>,hsv=<hsv>,quantization=<q>\n"
-	       "                     set the frame format [VIDIOC_SUBDEV_S_FMT]\n"
+	       "                     set the frame format for the given pad and optional stream [VIDIOC_SUBDEV_S_FMT]\n"
+	       "                     <pad> the pad to get the format from\n"
+	       "                     <stream> the stream to get the format from (0 if not specified)\n"
 	       "                     <code> is the value of the mediabus code\n"
 	       "                     <f> can be one of the following field layouts:\n"
 	       "                       any, none, top, bottom, interlaced, seq_tb, seq_bt,\n"
@@ -74,31 +94,74 @@ void subdev_usage()
 	       "                     <q> can be one of the following quantization methods:\n"
 	       "                       default, full-range, lim-range\n"
 	       "  --set-subdev-selection (for testing only, otherwise use media-ctl)\n"
-	       "  --try-subdev-selection pad=<pad>,target=<target>,flags=<flags>,\n"
+	       "  --try-subdev-selection pad=<pad>,stream=<stream>,target=<target>,flags=<flags>,\n"
 	       "                         top=<x>,left=<y>,width=<w>,height=<h>\n"
 	       "                     set the video capture selection rectangle [VIDIOC_SUBDEV_S_SELECTION]\n"
 	       "                     target=crop|crop_bounds|crop_default|compose|compose_bounds|\n"
 	       "                            compose_default|compose_padded|native_size\n"
 	       "                     flags=le|ge|keep-config\n"
-	       "  --set-subdev-fps pad=<pad>,fps=<fps> (for testing only, otherwise use media-ctl)\n"
+	       "  --set-subdev-fps pad=<pad>,stream=<stream>,fps=<fps> (for testing only, otherwise use media-ctl)\n"
 	       "                     set the frame rate [VIDIOC_SUBDEV_S_FRAME_INTERVAL]\n"
+	       "  --get-routing      Print the route topology\n"
+	       "  --set-routing <routes>\n"
+	       "                     Comma-separated list of route descriptors to setup\n"
+	       "\n"
+	       "Routes are defined as\n"
+	       "	routes		= route { ',' route } ;\n"
+	       "	route		= sink '->' source '[' flags ']' ;\n"
+	       "	sink		= sink-pad '/' sink-stream ;\n"
+	       "	source		= source-pad '/' source-stream ;\n"
+	       "\n"
+	       "where\n"
+	       "	sink-pad	= Pad numeric identifier for sink\n"
+	       "	sink-stream	= Stream numeric identifier for sink\n"
+	       "	source-pad	= Pad numeric identifier for source\n"
+	       "	source-stream	= Stream numeric identifier for source\n"
+	       "	flags		= Route flags (0: inactive, 1: active)\n"
 	       );
 }
 
 void subdev_cmd(int ch, char *optarg)
 {
 	char *value, *subs;
+	char *endp;
 
 	switch (ch) {
 	case OptListSubDevMBusCodes:
-		if (optarg)
-			list_mbus_codes_pad = strtoul(optarg, nullptr, 0);
+		if (optarg) {
+			/* Legacy pad-only parsing */
+			list_mbus_codes_pad = strtoul(optarg, &endp, 0);
+			if (*endp == 0)
+				break;
+		}
+
+		subs = optarg;
+		while (subs && *subs != '\0') {
+			static constexpr const char *subopts[] = {
+				"pad",
+				"stream",
+				nullptr
+			};
+
+			switch (parse_subopt(&subs, subopts, &value)) {
+			case 0:
+				list_mbus_codes_pad = strtoul(value, nullptr, 0);
+				break;
+			case 1:
+				list_mbus_codes_stream = strtoul(value, nullptr, 0);
+				break;
+			default:
+				subdev_usage();
+				std::exit(EXIT_FAILURE);
+			}
+		}
 		break;
 	case OptListSubDevFrameSizes:
 		subs = optarg;
 		while (*subs != '\0') {
 			static constexpr const char *subopts[] = {
 				"pad",
+				"stream",
 				"code",
 				nullptr
 			};
@@ -108,6 +171,9 @@ void subdev_cmd(int ch, char *optarg)
 				frmsize.pad = strtoul(value, nullptr, 0);
 				break;
 			case 1:
+				frmsize.stream = strtoul(value, nullptr, 0);
+				break;
+			case 2:
 				frmsize.code = strtoul(value, nullptr, 0);
 				break;
 			default:
@@ -121,6 +187,7 @@ void subdev_cmd(int ch, char *optarg)
 		while (*subs != '\0') {
 			static constexpr const char *subopts[] = {
 				"pad",
+				"stream",
 				"code",
 				"width",
 				"height",
@@ -132,12 +199,15 @@ void subdev_cmd(int ch, char *optarg)
 				frmival.pad = strtoul(value, nullptr, 0);
 				break;
 			case 1:
-				frmival.code = strtoul(value, nullptr, 0);
+				frmival.stream = strtoul(value, nullptr, 0);
 				break;
 			case 2:
-				frmival.width = strtoul(value, nullptr, 0);
+				frmival.code = strtoul(value, nullptr, 0);
 				break;
 			case 3:
+				frmival.width = strtoul(value, nullptr, 0);
+				break;
+			case 4:
 				frmival.height = strtoul(value, nullptr, 0);
 				break;
 			default:
@@ -147,14 +217,40 @@ void subdev_cmd(int ch, char *optarg)
 		}
 		break;
 	case OptGetSubDevFormat:
-		if (optarg)
-			get_fmt_pad = strtoul(optarg, nullptr, 0);
+		if (optarg) {
+			/* Legacy pad-only parsing */
+			get_fmt_pad = strtoul(optarg, &endp, 0);
+			if (*endp == 0)
+				break;
+		}
+
+		subs = optarg;
+		while (subs && *subs != '\0') {
+			static constexpr const char *subopts[] = {
+				"pad",
+				"stream",
+				nullptr
+			};
+
+			switch (parse_subopt(&subs, subopts, &value)) {
+			case 0:
+				get_fmt_pad = strtoul(value, nullptr, 0);
+				break;
+			case 1:
+				get_fmt_stream = strtoul(value, nullptr, 0);
+				break;
+			default:
+				subdev_usage();
+				std::exit(EXIT_FAILURE);
+			}
+		}
 		break;
 	case OptGetSubDevSelection:
 		subs = optarg;
 		while (*subs != '\0') {
 			static constexpr const char *subopts[] = {
 				"pad",
+				"stream",
 				"target",
 				nullptr
 			};
@@ -165,6 +261,9 @@ void subdev_cmd(int ch, char *optarg)
 				get_sel_pad = strtoul(value, nullptr, 0);
 				break;
 			case 1:
+				get_sel_stream = strtoul(value, nullptr, 0);
+				break;
+			case 2:
 				if (parse_selection_target(value, target)) {
 					fprintf(stderr, "Unknown selection target\n");
 					subdev_usage();
@@ -179,8 +278,33 @@ void subdev_cmd(int ch, char *optarg)
 		}
 		break;
 	case OptGetSubDevFPS:
-		if (optarg)
-			get_fps_pad = strtoul(optarg, nullptr, 0);
+		if (optarg) {
+			/* Legacy pad-only parsing */
+			get_fps_pad = strtoul(optarg, &endp, 0);
+			if (*endp == 0)
+				break;
+		}
+
+		subs = optarg;
+		while (subs && *subs != '\0') {
+			static constexpr const char *subopts[] = {
+				"pad",
+				"stream",
+				nullptr
+			};
+
+			switch (parse_subopt(&subs, subopts, &value)) {
+			case 0:
+				get_fps_pad = strtoul(value, nullptr, 0);
+				break;
+			case 1:
+				get_fps_stream = strtoul(value, nullptr, 0);
+				break;
+			default:
+				subdev_usage();
+				std::exit(EXIT_FAILURE);
+			}
+		}
 		break;
 	case OptSetSubDevFormat:
 	case OptTrySubDevFormat:
@@ -198,6 +322,7 @@ void subdev_cmd(int ch, char *optarg)
 				"quantization",
 				"xfer",
 				"pad",
+				"stream",
 				nullptr
 			};
 
@@ -244,6 +369,9 @@ void subdev_cmd(int ch, char *optarg)
 			case 9:
 				set_fmt_pad = strtoul(value, nullptr, 0);
 				break;
+			case 10:
+				set_fmt_stream = strtoul(value, nullptr, 0);
+				break;
 			default:
 				fprintf(stderr, "Unknown option\n");
 				subdev_usage();
@@ -264,6 +392,7 @@ void subdev_cmd(int ch, char *optarg)
 				"width",
 				"height",
 				"pad",
+				"stream",
 				nullptr
 			};
 
@@ -298,6 +427,9 @@ void subdev_cmd(int ch, char *optarg)
 			case 6:
 				vsel.pad = strtoul(value, nullptr, 0);
 				break;
+			case 7:
+				vsel.stream = strtoul(value, nullptr, 0);
+				break;
 			default:
 				fprintf(stderr, "Unknown option\n");
 				subdev_usage();
@@ -311,6 +443,7 @@ void subdev_cmd(int ch, char *optarg)
 		while (*subs != '\0') {
 			static constexpr const char *subopts[] = {
 				"pad",
+				"stream",
 				"fps",
 				nullptr
 			};
@@ -320,6 +453,9 @@ void subdev_cmd(int ch, char *optarg)
 				set_fps_pad = strtoul(value, nullptr, 0);
 				break;
 			case 1:
+				set_fps_stream = strtoul(value, nullptr, 0);
+				break;
+			case 2:
 				set_fps = strtod(value, nullptr);
 				break;
 			default:
@@ -329,6 +465,47 @@ void subdev_cmd(int ch, char *optarg)
 			}
 		}
 		break;
+	case OptSetRouting: {
+		struct v4l2_subdev_route *r;
+		char *end, *ref, *tok;
+		unsigned int flags;
+
+		memset(&routing, 0, sizeof(routing));
+		memset(routes, 0, sizeof(routes[0]) * NUM_ROUTES_MAX);
+		routing.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+		routing.num_routes = 0;
+		routing.routes = (__u64)routes;
+
+		if (!optarg)
+			break;
+
+		r = (v4l2_subdev_route *)routing.routes;
+		ref = end = strdup(optarg);
+		while ((tok = strsep(&end, ",")) != NULL) {
+			if (sscanf(tok, "%u/%u -> %u/%u [%u]",
+				   &r->sink_pad, &r->sink_stream,
+				   &r->source_pad, &r->source_stream,
+				   &flags) != 5) {
+				free(ref);
+				fprintf(stderr, "Invalid route information specified\n");
+				subdev_usage();
+				std::exit(EXIT_FAILURE);
+			}
+
+			if (flags & ~(V4L2_SUBDEV_ROUTE_FL_ACTIVE)) {
+				fprintf(stderr, "Invalid route flags specified: %#x\n", flags);
+				subdev_usage();
+				std::exit(EXIT_FAILURE);
+			}
+
+			r->flags = flags;
+
+			r++;
+			routing.num_routes++;
+		}
+		free(ref);
+		break;
+	}
 	default:
 		break;
 	}
@@ -394,6 +571,7 @@ void subdev_set(cv4l_fd &_fd)
 
 		memset(&fmt, 0, sizeof(fmt));
 		fmt.pad = set_fmt_pad;
+		fmt.stream = set_fmt_stream;
 		fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
 
 		if (doioctl(fd, VIDIOC_SUBDEV_G_FMT, &fmt) == 0) {
@@ -430,7 +608,7 @@ void subdev_set(cv4l_fd &_fd)
 			else
 				fmt.which = V4L2_SUBDEV_FORMAT_TRY;
 
-			printf("ioctl: VIDIOC_SUBDEV_S_FMT (pad=%u)\n", fmt.pad);
+			printf("ioctl: VIDIOC_SUBDEV_S_FMT (pad=%u,stream=%u)\n", fmt.pad, fmt.stream);
 			ret = doioctl(fd, VIDIOC_SUBDEV_S_FMT, &fmt);
 			if (ret == 0 && (verbose || !options[OptSetSubDevFormat]))
 				print_framefmt(fmt.format);
@@ -441,6 +619,7 @@ void subdev_set(cv4l_fd &_fd)
 
 		memset(&sel, 0, sizeof(sel));
 		sel.pad = vsel.pad;
+		sel.stream = vsel.stream;
 		sel.which = V4L2_SUBDEV_FORMAT_ACTIVE;
 		sel.target = vsel.target;
 
@@ -461,7 +640,7 @@ void subdev_set(cv4l_fd &_fd)
 			else
 				sel.which = V4L2_SUBDEV_FORMAT_TRY;
 
-			printf("ioctl: VIDIOC_SUBDEV_S_SELECTION (pad=%u)\n", sel.pad);
+			printf("ioctl: VIDIOC_SUBDEV_S_SELECTION (pad=%u,stream=%u)\n", sel.pad, sel.stream);
 			int ret = doioctl(fd, VIDIOC_SUBDEV_S_SELECTION, &sel);
 			if (ret == 0 && (verbose || !options[OptSetSubDevSelection]))
 				print_subdev_selection(sel);
@@ -472,6 +651,7 @@ void subdev_set(cv4l_fd &_fd)
 
 		memset(&fival, 0, sizeof(fival));
 		fival.pad = set_fps_pad;
+		fival.stream = set_fps_stream;
 
 		if (set_fps <= 0) {
 			fprintf(stderr, "invalid fps %f\n", set_fps);
@@ -482,7 +662,7 @@ void subdev_set(cv4l_fd &_fd)
 		fival.interval.denominator = static_cast<uint32_t>(set_fps * fival.interval.numerator);
 		printf("Note: --set-subdev-fps is only for testing.\n"
 		       "Normally media-ctl is used to configure the video pipeline.\n");
-		printf("ioctl: VIDIOC_SUBDEV_S_FRAME_INTERVAL (pad=%u)\n", fival.pad);
+		printf("ioctl: VIDIOC_SUBDEV_S_FRAME_INTERVAL (pad=%u,stream=%u)\n", fival.pad, fival.stream);
 		if (doioctl(fd, VIDIOC_SUBDEV_S_FRAME_INTERVAL, &fival) == 0) {
 			if (!fival.interval.denominator || !fival.interval.numerator)
 				printf("\tFrames per second: invalid (%d/%d)\n",
@@ -493,6 +673,55 @@ void subdev_set(cv4l_fd &_fd)
 					fival.interval.denominator, fival.interval.numerator);
 		}
 	}
+	if (options[OptSetRouting]) {
+		if (doioctl(fd, VIDIOC_SUBDEV_S_ROUTING, &routing) == 0)
+			printf("Routing set\n");
+	}
+}
+
+struct flag_name {
+	__u32 flag;
+	const char *name;
+};
+
+static void print_flags(const struct flag_name *flag_names, unsigned int num_entries, __u32 flags)
+{
+	bool first = true;
+	unsigned int i;
+
+	for (i = 0; i < num_entries; i++) {
+		if (!(flags & flag_names[i].flag))
+			continue;
+		if (!first)
+			printf(",");
+		printf("%s", flag_names[i].name);
+		flags &= ~flag_names[i].flag;
+		first = false;
+	}
+
+	if (flags) {
+		if (!first)
+			printf(",");
+		printf("0x%x", flags);
+	}
+}
+
+static void print_routes(const struct v4l2_subdev_routing *r)
+{
+	unsigned int i;
+	struct v4l2_subdev_route *routes = (struct v4l2_subdev_route *)r->routes;
+
+	static const struct flag_name route_flags[] = {
+		{ V4L2_SUBDEV_ROUTE_FL_ACTIVE, "ACTIVE" },
+	};
+
+	for (i = 0; i < r->num_routes; i++) {
+		printf("%u/%u -> %u/%u [",
+		       routes[i].sink_pad, routes[i].sink_stream,
+		       routes[i].source_pad, routes[i].source_stream);
+		print_flags(route_flags, ARRAY_SIZE(route_flags), routes[i].flags);
+		printf("]\n");
+	}
 }
 
 void subdev_get(cv4l_fd &_fd)
@@ -505,8 +734,9 @@ void subdev_get(cv4l_fd &_fd)
 		memset(&fmt, 0, sizeof(fmt));
 		fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
 		fmt.pad = get_fmt_pad;
+		fmt.stream = get_fmt_stream;
 
-		printf("ioctl: VIDIOC_SUBDEV_G_FMT (pad=%u)\n", fmt.pad);
+		printf("ioctl: VIDIOC_SUBDEV_G_FMT (pad=%u,stream=%u)\n", fmt.pad, fmt.stream);
 		if (doioctl(fd, VIDIOC_SUBDEV_G_FMT, &fmt) == 0)
 			print_framefmt(fmt.format);
 	}
@@ -518,8 +748,9 @@ void subdev_get(cv4l_fd &_fd)
 		memset(&sel, 0, sizeof(sel));
 		sel.which = V4L2_SUBDEV_FORMAT_ACTIVE;
 		sel.pad = get_sel_pad;
+		sel.stream = get_sel_stream;
 
-		printf("ioctl: VIDIOC_SUBDEV_G_SELECTION (pad=%u)\n", sel.pad);
+		printf("ioctl: VIDIOC_SUBDEV_G_SELECTION (pad=%u,stream=%u)\n", sel.pad, sel.stream);
 		if (options[OptAll] || get_sel_target == -1) {
 			while (valid_seltarget_at_idx(idx)) {
 				sel.target = seltarget_at_idx(idx);
@@ -538,8 +769,9 @@ void subdev_get(cv4l_fd &_fd)
 
 		memset(&fival, 0, sizeof(fival));
 		fival.pad = get_fps_pad;
+		fival.stream = get_fps_stream;
 
-		printf("ioctl: VIDIOC_SUBDEV_G_FRAME_INTERVAL (pad=%u)\n", fival.pad);
+		printf("ioctl: VIDIOC_SUBDEV_G_FRAME_INTERVAL (pad=%u,stream=%u)\n", fival.pad, fival.stream);
 		if (doioctl(fd, VIDIOC_SUBDEV_G_FRAME_INTERVAL, &fival) == 0) {
 			if (!fival.interval.denominator || !fival.interval.numerator)
 				printf("\tFrames per second: invalid (%d/%d)\n",
@@ -550,6 +782,17 @@ void subdev_get(cv4l_fd &_fd)
 					fival.interval.denominator, fival.interval.numerator);
 		}
 	}
+
+	if (options[OptGetRouting]) {
+		memset(&routing, 0, sizeof(routing));
+		memset(routes, 0, sizeof(routes[0]) * NUM_ROUTES_MAX);
+		routing.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+		routing.num_routes = NUM_ROUTES_MAX;
+		routing.routes = (__u64)routes;
+
+		if (doioctl(fd, VIDIOC_SUBDEV_G_ROUTING, &routing) == 0)
+			print_routes(&routing);
+	}
 }
 
 static void print_mbus_code(__u32 code)
@@ -566,11 +809,12 @@ static void print_mbus_code(__u32 code)
 		printf("\t0x%04x", code);
 }
 
-static void print_mbus_codes(int fd, __u32 pad)
+static void print_mbus_codes(int fd, __u32 pad, __u32 stream)
 {
 	struct v4l2_subdev_mbus_code_enum mbus_code = {};
 
 	mbus_code.pad = pad;
+	mbus_code.stream = stream;
 	mbus_code.which = V4L2_SUBDEV_FORMAT_TRY;
 
 	for (;;) {
@@ -623,13 +867,13 @@ void subdev_list(cv4l_fd &_fd)
 	int fd = _fd.g_fd();
 
 	if (options[OptListSubDevMBusCodes]) {
-		printf("ioctl: VIDIOC_SUBDEV_ENUM_MBUS_CODE (pad=%u)\n",
-		       list_mbus_codes_pad);
-		print_mbus_codes(fd, list_mbus_codes_pad);
+		printf("ioctl: VIDIOC_SUBDEV_ENUM_MBUS_CODE (pad=%u,stream=%u)\n",
+		       list_mbus_codes_pad, list_mbus_codes_stream);
+		print_mbus_codes(fd, list_mbus_codes_pad, list_mbus_codes_stream);
 	}
 	if (options[OptListSubDevFrameSizes]) {
-		printf("ioctl: VIDIOC_SUBDEV_ENUM_FRAME_SIZE (pad=%u)\n",
-		       frmsize.pad);
+		printf("ioctl: VIDIOC_SUBDEV_ENUM_FRAME_SIZE (pad=%u,stream=%u)\n",
+		       frmsize.pad, frmsize.stream);
 		frmsize.index = 0;
 		frmsize.which = V4L2_SUBDEV_FORMAT_TRY;
 		while (test_ioctl(fd, VIDIOC_SUBDEV_ENUM_FRAME_SIZE, &frmsize) >= 0) {
@@ -638,8 +882,8 @@ void subdev_list(cv4l_fd &_fd)
 		}
 	}
 	if (options[OptListSubDevFrameIntervals]) {
-		printf("ioctl: VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL (pad=%u)\n",
-		       frmival.pad);
+		printf("ioctl: VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL (pad=%u,stream=%u)\n",
+		       frmival.pad, frmival.stream);
 		frmival.index = 0;
 		frmival.which = V4L2_SUBDEV_FORMAT_TRY;
 		while (test_ioctl(fd, VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL, &frmival) >= 0) {
diff --git a/utils/v4l2-ctl/v4l2-ctl.cpp b/utils/v4l2-ctl/v4l2-ctl.cpp
index 8585278f..52974b40 100644
--- a/utils/v4l2-ctl/v4l2-ctl.cpp
+++ b/utils/v4l2-ctl/v4l2-ctl.cpp
@@ -64,6 +64,8 @@ static struct option long_options[] = {
 	{"get-fmt-video-out", no_argument, nullptr, OptGetVideoOutFormat},
 	{"set-fmt-video-out", required_argument, nullptr, OptSetVideoOutFormat},
 	{"try-fmt-video-out", required_argument, nullptr, OptTryVideoOutFormat},
+	{"get-routing", no_argument, 0, OptGetRouting},
+	{"set-routing", required_argument, 0, OptSetRouting},
 	{"help", no_argument, nullptr, OptHelp},
 	{"help-tuner", no_argument, nullptr, OptHelpTuner},
 	{"help-io", no_argument, nullptr, OptHelpIO},
diff --git a/utils/v4l2-ctl/v4l2-ctl.h b/utils/v4l2-ctl/v4l2-ctl.h
index 8f2726ea..bf519c3f 100644
--- a/utils/v4l2-ctl/v4l2-ctl.h
+++ b/utils/v4l2-ctl/v4l2-ctl.h
@@ -191,6 +191,8 @@ enum Option {
 	OptInfoEdid,
 	OptShowEdid,
 	OptFixEdidChecksums,
+	OptGetRouting,
+	OptSetRouting,
 	OptFreqSeek,
 	OptEncoderCmd,
 	OptTryEncoderCmd,
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v5 2/7] media-ctl: Add support for routes and streams
  2023-05-29  8:49 [PATCH v5 0/7] v4l-utils: Support multiplexed streams Tomi Valkeinen
  2023-05-29  8:49 ` [PATCH v5 1/7] v4l2-ctl: Add routing and streams support Tomi Valkeinen
@ 2023-05-29  8:49 ` Tomi Valkeinen
  2023-05-29  8:49 ` [PATCH v5 3/7] v4l2-ctl/compliance: Add routing and streams multiplexed streams Tomi Valkeinen
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 15+ messages in thread
From: Tomi Valkeinen @ 2023-05-29  8:49 UTC (permalink / raw)
  To: linux-media, sakari.ailus, Jacopo Mondi, Laurent Pinchart,
	niklas.soderlund+renesas, Mauro Carvalho Chehab, Hans Verkuil,
	satish.nagireddy
  Cc: Tomi Valkeinen

Add support to get and set subdev routes and to get and set
configurations per stream.

Based on work from Sakari Ailus <sakari.ailus@linux.intel.com>.

Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
---
 utils/media-ctl/libmediactl.c   |  43 +++++
 utils/media-ctl/libv4l2subdev.c | 283 ++++++++++++++++++++++++++++----
 utils/media-ctl/media-ctl.c     | 113 +++++++++++--
 utils/media-ctl/mediactl.h      |  16 ++
 utils/media-ctl/options.c       |  15 +-
 utils/media-ctl/options.h       |   1 +
 utils/media-ctl/v4l2subdev.h    |  66 +++++++-
 7 files changed, 484 insertions(+), 53 deletions(-)

diff --git a/utils/media-ctl/libmediactl.c b/utils/media-ctl/libmediactl.c
index a18b063e..64ac8cf1 100644
--- a/utils/media-ctl/libmediactl.c
+++ b/utils/media-ctl/libmediactl.c
@@ -874,6 +874,49 @@ struct media_pad *media_parse_pad(struct media_device *media,
 	return &entity->pads[pad];
 }
 
+struct media_pad *media_parse_pad_stream(struct media_device *media,
+					 const char *p, unsigned int *stream,
+					 char **endp)
+{
+	struct media_pad *pad;
+	const char *orig_p = p;
+	char *ep;
+
+	pad = media_parse_pad(media, p, &ep);
+	if (pad == NULL)
+		return NULL;
+
+	p = ep;
+
+	if (*p == '/') {
+		unsigned int s;
+
+		p++;
+
+		s = strtoul(p, &ep, 10);
+
+		if (ep == p) {
+			media_dbg(media, "Unable to parse stream: '%s'\n", orig_p);
+			if (endp)
+				*endp = (char*)p;
+			return NULL;
+		}
+
+		*stream = s;
+
+		p++;
+	} else {
+		*stream = 0;
+	}
+
+	for (; isspace(*p); ++p);
+
+	if (endp)
+		*endp = (char*)p;
+
+	return pad;
+}
+
 struct media_link *media_parse_link(struct media_device *media,
 				    const char *p, char **endp)
 {
diff --git a/utils/media-ctl/libv4l2subdev.c b/utils/media-ctl/libv4l2subdev.c
index 63bb3d75..51d30e61 100644
--- a/utils/media-ctl/libv4l2subdev.c
+++ b/utils/media-ctl/libv4l2subdev.c
@@ -64,7 +64,7 @@ void v4l2_subdev_close(struct media_entity *entity)
 }
 
 int v4l2_subdev_get_format(struct media_entity *entity,
-	struct v4l2_mbus_framefmt *format, unsigned int pad,
+	struct v4l2_mbus_framefmt *format, unsigned int pad, unsigned int stream,
 	enum v4l2_subdev_format_whence which)
 {
 	struct v4l2_subdev_format fmt;
@@ -76,6 +76,7 @@ int v4l2_subdev_get_format(struct media_entity *entity,
 
 	memset(&fmt, 0, sizeof(fmt));
 	fmt.pad = pad;
+	fmt.stream = stream;
 	fmt.which = which;
 
 	ret = ioctl(entity->fd, VIDIOC_SUBDEV_G_FMT, &fmt);
@@ -88,6 +89,7 @@ int v4l2_subdev_get_format(struct media_entity *entity,
 
 int v4l2_subdev_set_format(struct media_entity *entity,
 	struct v4l2_mbus_framefmt *format, unsigned int pad,
+	unsigned int stream,
 	enum v4l2_subdev_format_whence which)
 {
 	struct v4l2_subdev_format fmt;
@@ -99,6 +101,7 @@ int v4l2_subdev_set_format(struct media_entity *entity,
 
 	memset(&fmt, 0, sizeof(fmt));
 	fmt.pad = pad;
+	fmt.stream = stream;
 	fmt.which = which;
 	fmt.format = *format;
 
@@ -111,8 +114,8 @@ int v4l2_subdev_set_format(struct media_entity *entity,
 }
 
 int v4l2_subdev_get_selection(struct media_entity *entity,
-	struct v4l2_rect *rect, unsigned int pad, unsigned int target,
-	enum v4l2_subdev_format_whence which)
+	struct v4l2_rect *rect, unsigned int pad, unsigned int stream,
+	unsigned int target, enum v4l2_subdev_format_whence which)
 {
 	union {
 		struct v4l2_subdev_selection sel;
@@ -150,8 +153,8 @@ int v4l2_subdev_get_selection(struct media_entity *entity,
 }
 
 int v4l2_subdev_set_selection(struct media_entity *entity,
-	struct v4l2_rect *rect, unsigned int pad, unsigned int target,
-	enum v4l2_subdev_format_whence which)
+	struct v4l2_rect *rect, unsigned int pad, unsigned int stream,
+	unsigned int target, enum v4l2_subdev_format_whence which)
 {
 	union {
 		struct v4l2_subdev_selection sel;
@@ -165,6 +168,7 @@ int v4l2_subdev_set_selection(struct media_entity *entity,
 
 	memset(&u.sel, 0, sizeof(u.sel));
 	u.sel.pad = pad;
+	u.sel.stream = stream;
 	u.sel.target = target;
 	u.sel.which = which;
 	u.sel.r = *rect;
@@ -179,6 +183,7 @@ int v4l2_subdev_set_selection(struct media_entity *entity,
 
 	memset(&u.crop, 0, sizeof(u.crop));
 	u.crop.pad = pad;
+	u.crop.stream = stream;
 	u.crop.which = which;
 	u.crop.rect = *rect;
 
@@ -190,6 +195,69 @@ int v4l2_subdev_set_selection(struct media_entity *entity,
 	return 0;
 }
 
+int v4l2_subdev_get_routing(struct media_entity *entity,
+			    struct v4l2_subdev_route **routes,
+			    unsigned int *num_routes)
+{
+	struct v4l2_subdev_routing routing = {
+		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
+	};
+	struct v4l2_subdev_route *r;
+	int ret;
+
+	ret = v4l2_subdev_open(entity);
+	if (ret < 0)
+		return ret;
+
+	ret = ioctl(entity->fd, VIDIOC_SUBDEV_G_ROUTING, &routing);
+	if (ret == -1 && errno != ENOSPC)
+		return -errno;
+
+	if (!routing.num_routes) {
+		*routes = NULL;
+		*num_routes = 0;
+		return 0;
+	}
+
+	r = calloc(routing.num_routes, sizeof(*r));
+	if (!r)
+		return -ENOMEM;
+
+	routing.routes = (uintptr_t)r;
+	ret = ioctl(entity->fd, VIDIOC_SUBDEV_G_ROUTING, &routing);
+	if (ret) {
+		free(r);
+		return ret;
+	}
+
+	*num_routes = routing.num_routes;
+	*routes = r;
+
+	return 0;
+}
+
+int v4l2_subdev_set_routing(struct media_entity *entity,
+			    struct v4l2_subdev_route *routes,
+			    unsigned int num_routes)
+{
+	struct v4l2_subdev_routing routing = {
+		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
+		.routes = (uintptr_t)routes,
+		.num_routes = num_routes,
+	};
+	int ret;
+
+	ret = v4l2_subdev_open(entity);
+	if (ret < 0)
+		return ret;
+
+	ret = ioctl(entity->fd, VIDIOC_SUBDEV_S_ROUTING, &routing);
+	if (ret == -1)
+		return -errno;
+
+	return 0;
+}
+
 int v4l2_subdev_get_dv_timings_caps(struct media_entity *entity,
 	struct v4l2_dv_timings_cap *caps)
 {
@@ -264,7 +332,7 @@ int v4l2_subdev_set_dv_timings(struct media_entity *entity,
 
 int v4l2_subdev_get_frame_interval(struct media_entity *entity,
 				   struct v4l2_fract *interval,
-				   unsigned int pad)
+				   unsigned int pad, unsigned int stream)
 {
 	struct v4l2_subdev_frame_interval ival;
 	int ret;
@@ -275,6 +343,7 @@ int v4l2_subdev_get_frame_interval(struct media_entity *entity,
 
 	memset(&ival, 0, sizeof(ival));
 	ival.pad = pad;
+	ival.stream = stream;
 
 	ret = ioctl(entity->fd, VIDIOC_SUBDEV_G_FRAME_INTERVAL, &ival);
 	if (ret < 0)
@@ -286,7 +355,7 @@ int v4l2_subdev_get_frame_interval(struct media_entity *entity,
 
 int v4l2_subdev_set_frame_interval(struct media_entity *entity,
 				   struct v4l2_fract *interval,
-				   unsigned int pad)
+				   unsigned int pad, unsigned int stream)
 {
 	struct v4l2_subdev_frame_interval ival;
 	int ret;
@@ -297,6 +366,7 @@ int v4l2_subdev_set_frame_interval(struct media_entity *entity,
 
 	memset(&ival, 0, sizeof(ival));
 	ival.pad = pad;
+	ival.stream = stream;
 	ival.interval = *interval;
 
 	ret = ioctl(entity->fd, VIDIOC_SUBDEV_S_FRAME_INTERVAL, &ival);
@@ -307,6 +377,155 @@ int v4l2_subdev_set_frame_interval(struct media_entity *entity,
 	return 0;
 }
 
+static int v4l2_subdev_parse_setup_route(struct media_device *media,
+					 struct v4l2_subdev_route *r,
+					 const char *p, char **endp)
+{
+	char *end;
+
+	/* sink pad/stream */
+
+	r->sink_pad = strtoul(p, &end, 10);
+	if (*end != '/') {
+		media_dbg(media, "Expected '/'\n");
+		return -EINVAL;
+	}
+
+	p = end + 1;
+
+	r->sink_stream = strtoul(p, &end, 10);
+
+	for (; isspace(*end); ++end);
+
+	if (end[0] != '-' || end[1] != '>') {
+		media_dbg(media, "Expected '->'\n");
+		return -EINVAL;
+	}
+	p = end + 2;
+
+	/* source pad/stream */
+
+	r->source_pad = strtoul(p, &end, 10);
+	if (*end != '/') {
+		media_dbg(media, "Expected '/'\n");
+		return -EINVAL;
+	}
+
+	p = end + 1;
+
+	r->source_stream = strtoul(p, &end, 10);
+
+	/* flags */
+
+	for (; isspace(*end); ++end);
+
+	if (*end != '[') {
+		media_dbg(media, "Expected '['\n");
+		return -EINVAL;
+	}
+
+	for (end++; isspace(*end); ++end);
+
+	p = end;
+
+	r->flags = strtoul(p, &end, 0);
+	if (r->flags & ~(V4L2_SUBDEV_ROUTE_FL_ACTIVE)) {
+		media_dbg(media, "Bad route flags %#x\n", r->flags);
+		return -EINVAL;
+	}
+
+	for (; isspace(*end); ++end);
+
+	if (*end != ']') {
+		media_dbg(media, "Expected ']'\n");
+		return -EINVAL;
+	}
+	end++;
+
+	for (; isspace(*end); ++end);
+
+	*endp = end;
+
+	return 0;
+}
+
+int v4l2_subdev_parse_setup_routes(struct media_device *media, const char *p)
+{
+	struct media_entity *entity;
+	struct v4l2_subdev_route *routes;
+	unsigned int num_routes;
+	unsigned int i;
+	char *end;
+	int ret;
+
+	entity = media_parse_entity(media, p, &end);
+	if (!entity)
+		return -EINVAL;
+
+	p = end;
+
+	if (*p != '[') {
+		media_dbg(media, "Expected '['\n");
+		return -EINVAL;
+	}
+
+	p++;
+
+	routes = calloc(256, sizeof(routes[0]));
+	if (!routes)
+		return -ENOMEM;
+
+	num_routes = 0;
+
+	while (*p != 0) {
+		struct v4l2_subdev_route *r = &routes[num_routes];
+
+		ret = v4l2_subdev_parse_setup_route(media, r, p, &end);
+		if (ret)
+			goto out;
+
+		p = end;
+
+		num_routes++;
+
+		if (*p == ',') {
+			p++;
+			continue;
+		}
+
+		break;
+	}
+
+	if (*p != ']') {
+		media_dbg(media, "Expected ']'\n");
+		ret = -EINVAL;
+		goto out;
+	}
+
+	for (i = 0; i < num_routes; ++i) {
+		struct v4l2_subdev_route *r = &routes[i];
+
+		media_dbg(entity->media,
+			  "Setting up route %s : %u/%u -> %u/%u [0x%08x]\n",
+			  entity->info.name,
+			  r->sink_pad, r->sink_stream,
+			  r->source_pad, r->source_stream,
+			  r->flags);
+	}
+
+	ret = v4l2_subdev_set_routing(entity, routes, num_routes);
+	if (ret) {
+		media_dbg(entity->media, "VIDIOC_SUBDEV_S_ROUTING failed: %d\n",
+			  ret);
+		goto out;
+	}
+
+out:
+	free(routes);
+
+	return ret;
+}
+
 static int v4l2_subdev_parse_format(struct media_device *media,
 				    struct v4l2_mbus_framefmt *format,
 				    const char *p, char **endp)
@@ -442,7 +661,8 @@ static bool strhazit(const char *str, const char **p)
 }
 
 static struct media_pad *v4l2_subdev_parse_pad_format(
-	struct media_device *media, struct v4l2_mbus_framefmt *format,
+	struct media_device *media, unsigned int *stream,
+	struct v4l2_mbus_framefmt *format,
 	struct v4l2_rect *crop, struct v4l2_rect *compose,
 	struct v4l2_fract *interval, const char *p, char **endp)
 {
@@ -453,7 +673,7 @@ static struct media_pad *v4l2_subdev_parse_pad_format(
 
 	for (; isspace(*p); ++p);
 
-	pad = media_parse_pad(media, p, &end);
+	pad = media_parse_pad_stream(media, p, stream, &end);
 	if (pad == NULL) {
 		*endp = end;
 		return NULL;
@@ -675,6 +895,7 @@ static struct media_pad *v4l2_subdev_parse_pad_format(
 }
 
 static int set_format(struct media_pad *pad,
+		      unsigned int stream,
 		      struct v4l2_mbus_framefmt *format)
 {
 	int ret;
@@ -683,12 +904,12 @@ static int set_format(struct media_pad *pad,
 		return 0;
 
 	media_dbg(pad->entity->media,
-		  "Setting up format %s %ux%u on pad %s/%u\n",
+		  "Setting up format %s %ux%u on pad %s/%u/%u\n",
 		  v4l2_subdev_pixelcode_to_string(format->code),
 		  format->width, format->height,
-		  pad->entity->info.name, pad->index);
+		  pad->entity->info.name, pad->index, stream);
 
-	ret = v4l2_subdev_set_format(pad->entity, format, pad->index,
+	ret = v4l2_subdev_set_format(pad->entity, format, pad->index, stream,
 				     V4L2_SUBDEV_FORMAT_ACTIVE);
 	if (ret < 0) {
 		media_dbg(pad->entity->media,
@@ -705,8 +926,8 @@ static int set_format(struct media_pad *pad,
 	return 0;
 }
 
-static int set_selection(struct media_pad *pad, unsigned int target,
-			 struct v4l2_rect *rect)
+static int set_selection(struct media_pad *pad, unsigned int stream,
+			 unsigned int target, struct v4l2_rect *rect)
 {
 	int ret;
 
@@ -714,11 +935,11 @@ static int set_selection(struct media_pad *pad, unsigned int target,
 		return 0;
 
 	media_dbg(pad->entity->media,
-		  "Setting up selection target %u rectangle (%u,%u)/%ux%u on pad %s/%u\n",
+		  "Setting up selection target %u rectangle (%u,%u)/%ux%u on pad %s/%u/%u\n",
 		  target, rect->left, rect->top, rect->width, rect->height,
-		  pad->entity->info.name, pad->index);
+		  pad->entity->info.name, pad->index, stream);
 
-	ret = v4l2_subdev_set_selection(pad->entity, rect, pad->index,
+	ret = v4l2_subdev_set_selection(pad->entity, rect, pad->index, stream,
 					target, V4L2_SUBDEV_FORMAT_ACTIVE);
 	if (ret < 0) {
 		media_dbg(pad->entity->media,
@@ -734,7 +955,7 @@ static int set_selection(struct media_pad *pad, unsigned int target,
 	return 0;
 }
 
-static int set_frame_interval(struct media_pad *pad,
+static int set_frame_interval(struct media_pad *pad, unsigned int stream,
 			      struct v4l2_fract *interval)
 {
 	int ret;
@@ -743,11 +964,12 @@ static int set_frame_interval(struct media_pad *pad,
 		return 0;
 
 	media_dbg(pad->entity->media,
-		  "Setting up frame interval %u/%u on pad %s/%u\n",
+		  "Setting up frame interval %u/%u on pad %s/%u/%u\n",
 		  interval->numerator, interval->denominator,
-		  pad->entity->info.name, pad->index);
+		  pad->entity->info.name, pad->index, stream);
 
-	ret = v4l2_subdev_set_frame_interval(pad->entity, interval, pad->index);
+	ret = v4l2_subdev_set_frame_interval(pad->entity, interval, pad->index,
+					     stream);
 	if (ret < 0) {
 		media_dbg(pad->entity->media,
 			  "Unable to set frame interval: %s (%d)",
@@ -770,11 +992,13 @@ static int v4l2_subdev_parse_setup_format(struct media_device *media,
 	struct v4l2_rect crop = { -1, -1, -1, -1 };
 	struct v4l2_rect compose = crop;
 	struct v4l2_fract interval = { 0, 0 };
+	unsigned int stream;
 	unsigned int i;
 	char *end;
 	int ret;
 
-	pad = v4l2_subdev_parse_pad_format(media, &format, &crop, &compose,
+	pad = v4l2_subdev_parse_pad_format(media, &stream,
+					   &format, &crop, &compose,
 					   &interval, p, &end);
 	if (pad == NULL) {
 		media_print_streampos(media, p, end);
@@ -783,30 +1007,29 @@ static int v4l2_subdev_parse_setup_format(struct media_device *media,
 	}
 
 	if (pad->flags & MEDIA_PAD_FL_SINK) {
-		ret = set_format(pad, &format);
+		ret = set_format(pad, stream, &format);
 		if (ret < 0)
 			return ret;
 	}
 
-	ret = set_selection(pad, V4L2_SEL_TGT_CROP, &crop);
+	ret = set_selection(pad, stream, V4L2_SEL_TGT_CROP, &crop);
 	if (ret < 0)
 		return ret;
 
-	ret = set_selection(pad, V4L2_SEL_TGT_COMPOSE, &compose);
+	ret = set_selection(pad, stream, V4L2_SEL_TGT_COMPOSE, &compose);
 	if (ret < 0)
 		return ret;
 
 	if (pad->flags & MEDIA_PAD_FL_SOURCE) {
-		ret = set_format(pad, &format);
+		ret = set_format(pad, stream, &format);
 		if (ret < 0)
 			return ret;
 	}
 
-	ret = set_frame_interval(pad, &interval);
+	ret = set_frame_interval(pad, stream, &interval);
 	if (ret < 0)
 		return ret;
 
-
 	/* If the pad is an output pad, automatically set the same format and
 	 * frame interval on the remote subdev input pads, if any.
 	 */
@@ -821,9 +1044,9 @@ static int v4l2_subdev_parse_setup_format(struct media_device *media,
 			if (link->source == pad &&
 			    link->sink->entity->info.type == MEDIA_ENT_T_V4L2_SUBDEV) {
 				remote_format = format;
-				set_format(link->sink, &remote_format);
+				set_format(link->sink, stream, &remote_format);
 
-				ret = set_frame_interval(link->sink, &interval);
+				ret = set_frame_interval(link->sink, stream, &interval);
 				if (ret < 0 && ret != -EINVAL && ret != -ENOTTY)
 					return ret;
 			}
diff --git a/utils/media-ctl/media-ctl.c b/utils/media-ctl/media-ctl.c
index 84ee7a83..1531cffa 100644
--- a/utils/media-ctl/media-ctl.c
+++ b/utils/media-ctl/media-ctl.c
@@ -28,6 +28,7 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <stdbool.h>
+#include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -75,23 +76,43 @@ static void print_flags(const struct flag_name *flag_names, unsigned int num_ent
 	}
 }
 
+static void v4l2_subdev_print_routes(struct media_entity *entity,
+				     struct v4l2_subdev_route *routes,
+				     unsigned int num_routes)
+{
+	unsigned int i;
+
+	if (num_routes)
+		printf("\troutes:\n");
+
+	for (i = 0; i < num_routes; i++) {
+		const struct v4l2_subdev_route *route = &routes[i];
+
+		printf("\t\t%u/%u -> %u/%u [%s]\n",
+		       route->sink_pad, route->sink_stream,
+		       route->source_pad, route->source_stream,
+		       route->flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE ? "ACTIVE" : "INACTIVE");
+	}
+}
+
 static void v4l2_subdev_print_format(struct media_entity *entity,
-	unsigned int pad, enum v4l2_subdev_format_whence which)
+	unsigned int pad, unsigned int stream,
+	enum v4l2_subdev_format_whence which)
 {
 	struct v4l2_mbus_framefmt format;
 	struct v4l2_fract interval = { 0, 0 };
 	struct v4l2_rect rect;
 	int ret;
 
-	ret = v4l2_subdev_get_format(entity, &format, pad, which);
+	ret = v4l2_subdev_get_format(entity, &format, pad, stream, which);
 	if (ret != 0)
 		return;
 
-	ret = v4l2_subdev_get_frame_interval(entity, &interval, pad);
+	ret = v4l2_subdev_get_frame_interval(entity, &interval, pad, stream);
 	if (ret != 0 && ret != -ENOTTY && ret != -EINVAL)
 		return;
 
-	printf("\t\t[fmt:%s/%ux%u",
+	printf("\t\t[stream:%u fmt:%s/%ux%u", stream,
 	       v4l2_subdev_pixelcode_to_string(format.code),
 	       format.width, format.height);
 
@@ -118,28 +139,28 @@ static void v4l2_subdev_print_format(struct media_entity *entity,
 			       v4l2_subdev_quantization_to_string(format.quantization));
 	}
 
-	ret = v4l2_subdev_get_selection(entity, &rect, pad,
+	ret = v4l2_subdev_get_selection(entity, &rect, pad, stream,
 					V4L2_SEL_TGT_CROP_BOUNDS,
 					which);
 	if (ret == 0)
 		printf("\n\t\t crop.bounds:(%u,%u)/%ux%u", rect.left, rect.top,
 		       rect.width, rect.height);
 
-	ret = v4l2_subdev_get_selection(entity, &rect, pad,
+	ret = v4l2_subdev_get_selection(entity, &rect, pad, stream,
 					V4L2_SEL_TGT_CROP,
 					which);
 	if (ret == 0)
 		printf("\n\t\t crop:(%u,%u)/%ux%u", rect.left, rect.top,
 		       rect.width, rect.height);
 
-	ret = v4l2_subdev_get_selection(entity, &rect, pad,
+	ret = v4l2_subdev_get_selection(entity, &rect, pad, stream,
 					V4L2_SEL_TGT_COMPOSE_BOUNDS,
 					which);
 	if (ret == 0)
 		printf("\n\t\t compose.bounds:(%u,%u)/%ux%u",
 		       rect.left, rect.top, rect.width, rect.height);
 
-	ret = v4l2_subdev_get_selection(entity, &rect, pad,
+	ret = v4l2_subdev_get_selection(entity, &rect, pad, stream,
 					V4L2_SEL_TGT_COMPOSE,
 					which);
 	if (ret == 0)
@@ -455,12 +476,49 @@ static void media_print_topology_dot(struct media_device *media)
 }
 
 static void media_print_pad_text(struct media_entity *entity,
-				 const struct media_pad *pad)
+				 const struct media_pad *pad,
+				 struct v4l2_subdev_route *routes,
+				 unsigned int num_routes)
 {
+	uint64_t printed_streams_mask = 0;
+	unsigned int i;
+
 	if (media_entity_type(entity) != MEDIA_ENT_T_V4L2_SUBDEV)
 		return;
 
-	v4l2_subdev_print_format(entity, pad->index, V4L2_SUBDEV_FORMAT_ACTIVE);
+	if (!routes) {
+		v4l2_subdev_print_format(entity, pad->index, 0,
+					 V4L2_SUBDEV_FORMAT_ACTIVE);
+	} else {
+		for (i = 0; i < num_routes; ++i) {
+			const struct v4l2_subdev_route *route = &routes[i];
+			unsigned int stream;
+
+			if (!(route->flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE))
+				continue;
+
+			if (pad->flags & MEDIA_PAD_FL_SINK) {
+				if (route->sink_pad != pad->index)
+					continue;
+
+				stream = route->sink_stream;
+			} else {
+				if (route->source_pad != pad->index)
+					continue;
+
+				stream = route->source_stream;
+			}
+
+			if (printed_streams_mask & (1 << stream))
+				continue;
+
+			v4l2_subdev_print_format(entity, pad->index, stream,
+						 V4L2_SUBDEV_FORMAT_ACTIVE);
+
+			printed_streams_mask |= (1 << stream);
+		}
+	}
+
 	v4l2_subdev_print_pad_dv(entity, pad->index, V4L2_SUBDEV_FORMAT_ACTIVE);
 
 	if (pad->flags & MEDIA_PAD_FL_SOURCE)
@@ -478,13 +536,24 @@ static void media_print_topology_text_entity(struct media_device *media,
 	const struct media_entity_desc *info = media_entity_get_info(entity);
 	const char *devname = media_entity_get_devname(entity);
 	unsigned int num_links = media_entity_get_links_count(entity);
+	struct v4l2_subdev_route *routes = NULL;
+	unsigned int num_routes = 0;
 	unsigned int j, k;
 	unsigned int padding;
 
+	if (media_entity_type(entity) == MEDIA_ENT_T_V4L2_SUBDEV)
+		v4l2_subdev_get_routing(entity, &routes, &num_routes);
+
 	padding = printf("- entity %u: ", info->id);
-	printf("%s (%u pad%s, %u link%s)\n", info->name,
+	printf("%s (%u pad%s, %u link%s", info->name,
 	       info->pads, info->pads > 1 ? "s" : "",
 	       num_links, num_links > 1 ? "s" : "");
+
+	if (media_entity_type(entity) == MEDIA_ENT_T_V4L2_SUBDEV)
+		printf(", %u route%s", num_routes, num_routes != 1 ? "s" : "");
+
+	printf(")\n");
+
 	printf("%*ctype %s subtype %s flags %x\n", padding, ' ',
 	       media_entity_type_to_string(info->type),
 	       media_entity_subtype_to_string(info->type),
@@ -492,12 +561,15 @@ static void media_print_topology_text_entity(struct media_device *media,
 	if (devname)
 		printf("%*cdevice node name %s\n", padding, ' ', devname);
 
+	if (media_entity_type(entity) == MEDIA_ENT_T_V4L2_SUBDEV)
+		v4l2_subdev_print_routes(entity, routes, num_routes);
+
 	for (j = 0; j < info->pads; j++) {
 		const struct media_pad *pad = media_entity_get_pad(entity, j);
 
 		printf("\tpad%u: %s\n", j, media_pad_type_to_string(pad->flags));
 
-		media_print_pad_text(entity, pad);
+		media_print_pad_text(entity, pad, routes, num_routes);
 
 		for (k = 0; k < num_links; k++) {
 			const struct media_link *link = media_entity_get_link(entity, k);
@@ -521,6 +593,8 @@ static void media_print_topology_text_entity(struct media_device *media,
 		}
 	}
 	printf("\n");
+
+	free(routes);
 }
 
 static void media_print_topology_text(struct media_device *media)
@@ -594,14 +668,16 @@ int main(int argc, char **argv)
 
 	if (media_opts.fmt_pad) {
 		struct media_pad *pad;
+		unsigned int stream;
+		char *p;
 
-		pad = media_parse_pad(media, media_opts.fmt_pad, NULL);
+		pad = media_parse_pad_stream(media, media_opts.fmt_pad, &stream, &p);
 		if (pad == NULL) {
 			printf("Pad '%s' not found\n", media_opts.fmt_pad);
 			goto out;
 		}
 
-		v4l2_subdev_print_format(pad->entity, pad->index,
+		v4l2_subdev_print_format(pad->entity, pad->index, stream,
 					 V4L2_SUBDEV_FORMAT_ACTIVE);
 	}
 
@@ -685,6 +761,15 @@ int main(int argc, char **argv)
 		}
 	}
 
+	if (media_opts.routes) {
+		ret = v4l2_subdev_parse_setup_routes(media, media_opts.routes);
+		if (ret) {
+			printf("Unable to setup routes: %s (%d)\n",
+			       strerror(-ret), -ret);
+			goto out;
+		}
+	}
+
 	if (media_opts.interactive) {
 		while (1) {
 			char buffer[32];
diff --git a/utils/media-ctl/mediactl.h b/utils/media-ctl/mediactl.h
index af360518..c0fc2962 100644
--- a/utils/media-ctl/mediactl.h
+++ b/utils/media-ctl/mediactl.h
@@ -394,6 +394,22 @@ struct media_entity *media_parse_entity(struct media_device *media,
 struct media_pad *media_parse_pad(struct media_device *media,
 				  const char *p, char **endp);
 
+/**
+ * @brief Parse string to a pad and stream on the media device.
+ * @param media - media device.
+ * @param p - input string
+ * @param stream - pointer to uint where the stream number is stored
+ * @param endp - pointer to string where parsing ended
+ *
+ * Parse NULL terminated string describing a pad and stream and return its struct
+ * media_pad instance and the stream number.
+ *
+ * @return Pointer to struct media_pad on success, NULL on failure.
+ */
+struct media_pad *media_parse_pad_stream(struct media_device *media,
+					 const char *p, unsigned int *stream,
+					 char **endp);
+
 /**
  * @brief Parse string to a link on the media device.
  * @param media - media device.
diff --git a/utils/media-ctl/options.c b/utils/media-ctl/options.c
index 6d30d3dc..3c408a1b 100644
--- a/utils/media-ctl/options.c
+++ b/utils/media-ctl/options.c
@@ -63,6 +63,7 @@ static void usage(const char *argv0)
 	printf("    --get-v4l2 pad	Print the active format on a given pad\n");
 	printf("    --get-dv pad        Print detected and current DV timings on a given pad\n");
 	printf("    --set-dv pad	Configure DV timings on a given pad\n");
+	printf("-R, --set-routes routes Configure routes on a given subdev entity\n");
 	printf("-h, --help		Show verbose help and exit\n");
 	printf("-i, --interactive	Modify links interactively\n");
 	printf("-l, --links links	Comma-separated list of link descriptors to setup\n");
@@ -78,7 +79,7 @@ static void usage(const char *argv0)
 	printf("Links and formats are defined as\n");
 	printf("\tlinks           = link { ',' link } ;\n");
 	printf("\tlink            = pad '->' pad '[' flags ']' ;\n");
-	printf("\tpad             = entity ':' pad-number ;\n");
+	printf("\tpad             = entity ':' pad-number { '/' stream-number } ;\n");
 	printf("\tentity          = entity-number | ( '\"' entity-name '\"' ) ;\n");
 	printf("\n");
 	printf("\tv4l2            = pad '[' v4l2-properties ']' ;\n");
@@ -95,11 +96,16 @@ static void usage(const char *argv0)
 	printf("\trectangle       = '(' left ',' top, ')' '/' size ;\n");
 	printf("\tsize            = width 'x' height ;\n");
 	printf("\n");
+	printf("\troutes          = entity '[' route { ',' route } ']' ;\n");
+	printf("\troute           = pad-number '/' stream-number '->' pad-number '/' stream-number '[' route-flags ']' ;\n");
+	printf("\n");
 	printf("where the fields are\n");
 	printf("\tentity-number   Entity numeric identifier\n");
 	printf("\tentity-name     Entity name (string) \n");
 	printf("\tpad-number      Pad numeric identifier\n");
+	printf("\tstream-number   Stream numeric identifier\n");
 	printf("\tflags           Link flags (0: inactive, 1: active)\n");
+	printf("\troute-flags     Route flags (bitmask of route flags: active - 0x1)\n");
 	printf("\tfcc             Format FourCC\n");
 	printf("\twidth           Image width in pixels\n");
 	printf("\theight          Image height in pixels\n");
@@ -152,6 +158,7 @@ static struct option opts[] = {
 	{"get-v4l2", 1, 0, OPT_GET_FORMAT},
 	{"get-dv", 1, 0, OPT_GET_DV},
 	{"set-dv", 1, 0, OPT_SET_DV},
+	{"set-routes", 1, 0, 'R'},
 	{"help", 0, 0, 'h'},
 	{"interactive", 0, 0, 'i'},
 	{"links", 1, 0, 'l'},
@@ -237,7 +244,7 @@ int parse_cmdline(int argc, char **argv)
 	}
 
 	/* parse options */
-	while ((opt = getopt_long(argc, argv, "d:e:f:hil:prvV:",
+	while ((opt = getopt_long(argc, argv, "d:e:f:hil:prvV:R:",
 				  opts, NULL)) != -1) {
 		switch (opt) {
 		case 'd':
@@ -283,6 +290,10 @@ int parse_cmdline(int argc, char **argv)
 			media_opts.verbose = 1;
 			break;
 
+		case 'R':
+			media_opts.routes = optarg;
+			break;
+
 		case OPT_PRINT_DOT:
 			media_opts.print_dot = 1;
 			break;
diff --git a/utils/media-ctl/options.h b/utils/media-ctl/options.h
index 7e0556fc..753d0934 100644
--- a/utils/media-ctl/options.h
+++ b/utils/media-ctl/options.h
@@ -36,6 +36,7 @@ struct media_options
 	const char *fmt_pad;
 	const char *get_dv_pad;
 	const char *dv_pad;
+	const char *routes;
 };
 
 extern struct media_options media_opts;
diff --git a/utils/media-ctl/v4l2subdev.h b/utils/media-ctl/v4l2subdev.h
index a1813911..1277040b 100644
--- a/utils/media-ctl/v4l2subdev.h
+++ b/utils/media-ctl/v4l2subdev.h
@@ -52,6 +52,7 @@ void v4l2_subdev_close(struct media_entity *entity);
  * @param entity - subdev-device media entity.
  * @param format - format to be filled.
  * @param pad - pad number.
+ * @param stream - stream number.
  * @param which - identifier of the format to get.
  *
  * Retrieve the current format on the @a entity @a pad and store it in the
@@ -64,7 +65,7 @@ void v4l2_subdev_close(struct media_entity *entity);
  * @return 0 on success, or a negative error code on failure.
  */
 int v4l2_subdev_get_format(struct media_entity *entity,
-	struct v4l2_mbus_framefmt *format, unsigned int pad,
+	struct v4l2_mbus_framefmt *format, unsigned int pad, unsigned int stream,
 	enum v4l2_subdev_format_whence which);
 
 /**
@@ -72,6 +73,7 @@ int v4l2_subdev_get_format(struct media_entity *entity,
  * @param entity - subdev-device media entity.
  * @param format - format.
  * @param pad - pad number.
+ * @param stream - stream number.
  * @param which - identifier of the format to set.
  *
  * Set the format on the @a entity @a pad to @a format. The driver is allowed to
@@ -86,6 +88,7 @@ int v4l2_subdev_get_format(struct media_entity *entity,
  */
 int v4l2_subdev_set_format(struct media_entity *entity,
 	struct v4l2_mbus_framefmt *format, unsigned int pad,
+	unsigned int stream,
 	enum v4l2_subdev_format_whence which);
 
 /**
@@ -93,6 +96,7 @@ int v4l2_subdev_set_format(struct media_entity *entity,
  * @param entity - subdev-device media entity.
  * @param r - rectangle to be filled.
  * @param pad - pad number.
+ * @param stream - stream number.
  * @param target - selection target
  * @param which - identifier of the format to get.
  *
@@ -107,14 +111,15 @@ int v4l2_subdev_set_format(struct media_entity *entity,
  * @return 0 on success, or a negative error code on failure.
  */
 int v4l2_subdev_get_selection(struct media_entity *entity,
-	struct v4l2_rect *rect, unsigned int pad, unsigned int target,
-	enum v4l2_subdev_format_whence which);
+	struct v4l2_rect *rect, unsigned int pad, unsigned int stream,
+	unsigned int target, enum v4l2_subdev_format_whence which);
 
 /**
  * @brief Set a selection rectangle on a pad.
  * @param entity - subdev-device media entity.
  * @param rect - crop rectangle.
  * @param pad - pad number.
+ * @param stream - stream number.
  * @param target - selection target
  * @param which - identifier of the format to set.
  *
@@ -129,8 +134,40 @@ int v4l2_subdev_get_selection(struct media_entity *entity,
  * @return 0 on success, or a negative error code on failure.
  */
 int v4l2_subdev_set_selection(struct media_entity *entity,
-	struct v4l2_rect *rect, unsigned int pad, unsigned int target,
-	enum v4l2_subdev_format_whence which);
+	struct v4l2_rect *rect, unsigned int pad, unsigned int stream,
+	unsigned int target, enum v4l2_subdev_format_whence which);
+
+/**
+ * @brief Get the routing table of a subdev media entity.
+ * @param entity - subdev-device media entity.
+ * @param routes - routes of the subdev.
+ * @param num_routes - number of routes.
+ *
+ * Get the routes of @a entity and return them in an allocated array in @a routes
+ * and the number of routes in @a num_routes.
+ *
+ * The caller is responsible for freeing the routes array after use.
+ *
+ * @return 0 on success, or a negative error code on failure.
+ */
+int v4l2_subdev_get_routing(struct media_entity *entity,
+			    struct v4l2_subdev_route **routes,
+			    unsigned int *num_routes);
+
+/**
+ * @brief Set the routing table of a subdev media entity.
+ * @param entity - subdev-device media entity.
+ * @param routes - routes of the subdev.
+ * @param num_routes - number of routes.
+ *
+ * Set the routes of @a entity. The routes are given in @a routes with the
+ * length of @a num_routes.
+ *
+ * @return 0 on success, or a negative error code on failure.
+ */
+int v4l2_subdev_set_routing(struct media_entity *entity,
+			    struct v4l2_subdev_route *route,
+			    unsigned int num_routes);
 
 /**
  * @brief Query the digital video capabilities of a pad.
@@ -189,6 +226,8 @@ int v4l2_subdev_set_dv_timings(struct media_entity *entity,
  * @brief Retrieve the frame interval on a sub-device.
  * @param entity - subdev-device media entity.
  * @param interval - frame interval to be filled.
+ * @param pad - pad number.
+ * @param stream - stream number.
  *
  * Retrieve the current frame interval on subdev @a entity and store it in the
  * @a interval structure.
@@ -200,12 +239,14 @@ int v4l2_subdev_set_dv_timings(struct media_entity *entity,
  */
 
 int v4l2_subdev_get_frame_interval(struct media_entity *entity,
-	struct v4l2_fract *interval, unsigned int pad);
+	struct v4l2_fract *interval, unsigned int pad, unsigned int stream);
 
 /**
  * @brief Set the frame interval on a sub-device.
  * @param entity - subdev-device media entity.
  * @param interval - frame interval.
+ * @param pad - pad number.
+ * @param stream - stream number.
  *
  * Set the frame interval on subdev @a entity to @a interval. The driver is
  * allowed to modify the requested frame interval, in which case @a interval is
@@ -217,7 +258,7 @@ int v4l2_subdev_get_frame_interval(struct media_entity *entity,
  * @return 0 on success, or a negative error code on failure.
  */
 int v4l2_subdev_set_frame_interval(struct media_entity *entity,
-	struct v4l2_fract *interval, unsigned int pad);
+	struct v4l2_fract *interval, unsigned int pad, unsigned int stream);
 
 /**
  * @brief Parse a string and apply format, crop and frame interval settings.
@@ -235,6 +276,17 @@ int v4l2_subdev_set_frame_interval(struct media_entity *entity,
  */
 int v4l2_subdev_parse_setup_formats(struct media_device *media, const char *p);
 
+/**
+ * @brief Parse a string and apply route settings.
+ * @param media - media device.
+ * @param p - input string
+ *
+ * Parse string @a p and apply route settings to a subdev.
+ *
+ * @return 0 on success, or a negative error code on failure.
+ */
+int v4l2_subdev_parse_setup_routes(struct media_device *media, const char *p);
+
 /**
  * @brief Convert media bus pixel code to string.
  * @param code - input string
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v5 3/7] v4l2-ctl/compliance: Add routing and streams multiplexed streams
  2023-05-29  8:49 [PATCH v5 0/7] v4l-utils: Support multiplexed streams Tomi Valkeinen
  2023-05-29  8:49 ` [PATCH v5 1/7] v4l2-ctl: Add routing and streams support Tomi Valkeinen
  2023-05-29  8:49 ` [PATCH v5 2/7] media-ctl: Add support for routes and streams Tomi Valkeinen
@ 2023-05-29  8:49 ` Tomi Valkeinen
  2023-06-07 11:34   ` Hans Verkuil
  2023-05-29  8:50 ` [PATCH v5 4/7] v4l2-ctl/compliance: Add simple routing test Tomi Valkeinen
                   ` (3 subsequent siblings)
  6 siblings, 1 reply; 15+ messages in thread
From: Tomi Valkeinen @ 2023-05-29  8:49 UTC (permalink / raw)
  To: linux-media, sakari.ailus, Jacopo Mondi, Laurent Pinchart,
	niklas.soderlund+renesas, Mauro Carvalho Chehab, Hans Verkuil,
	satish.nagireddy
  Cc: Tomi Valkeinen

Add basic support for routing and streams.

Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
---
 utils/v4l2-compliance/v4l2-compliance.cpp   | 120 ++++++++++++++++----
 utils/v4l2-compliance/v4l2-compliance.h     |   8 +-
 utils/v4l2-compliance/v4l2-test-subdevs.cpp |  43 ++++++-
 3 files changed, 137 insertions(+), 34 deletions(-)

diff --git a/utils/v4l2-compliance/v4l2-compliance.cpp b/utils/v4l2-compliance/v4l2-compliance.cpp
index e3556b1f..d7c10482 100644
--- a/utils/v4l2-compliance/v4l2-compliance.cpp
+++ b/utils/v4l2-compliance/v4l2-compliance.cpp
@@ -1233,6 +1233,10 @@ void testNode(struct node &node, struct node &node_m2m_cap, struct node &expbuf_
 	if (node.is_subdev()) {
 		bool has_source = false;
 		bool has_sink = false;
+		struct v4l2_subdev_routing sd_routing[2] = {};
+		struct v4l2_subdev_route sd_routes[2][256] = {};
+		bool has_routes = !!(subdevcap.capabilities & V4L2_SUBDEV_CAP_STREAMS);
+		int ret;
 
 		node.frame_interval_pad = -1;
 		node.enum_frame_interval_pad = -1;
@@ -1244,6 +1248,22 @@ void testNode(struct node &node, struct node &node_m2m_cap, struct node &expbuf_
 		}
 		node.is_passthrough_subdev = has_source && has_sink;
 
+		if (has_routes) {
+			for (unsigned which = V4L2_SUBDEV_FORMAT_TRY;
+				which <= V4L2_SUBDEV_FORMAT_ACTIVE; which++) {
+
+				sd_routing[which].which = which;
+				sd_routing[which].routes = (__u64)sd_routes[which];
+				sd_routing[which].num_routes = 256;
+
+				ret = doioctl(&node, VIDIOC_SUBDEV_G_ROUTING, &sd_routing[which]);
+				if (ret) {
+					fail("VIDIOC_SUBDEV_G_ROUTING: failed to get routing\n");
+					sd_routing[which].num_routes = 0;
+				}
+			}
+		}
+
 		for (unsigned pad = 0; pad < node.entity.pads; pad++) {
 			printf("Sub-Device ioctls (%s Pad %u):\n",
 			       (node.pads[pad].flags & MEDIA_PAD_FL_SINK) ?
@@ -1253,32 +1273,82 @@ void testNode(struct node &node, struct node &node_m2m_cap, struct node &expbuf_
 			node.has_subdev_enum_fival = 0;
 			for (unsigned which = V4L2_SUBDEV_FORMAT_TRY;
 			     which <= V4L2_SUBDEV_FORMAT_ACTIVE; which++) {
-				printf("\ttest %s VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: %s\n",
-				       which ? "Active" : "Try",
-				       ok(testSubDevEnum(&node, which, pad)));
-				printf("\ttest %s VIDIOC_SUBDEV_G/S_FMT: %s\n",
-				       which ? "Active" : "Try",
-				       ok(testSubDevFormat(&node, which, pad)));
-				printf("\ttest %s VIDIOC_SUBDEV_G/S_SELECTION/CROP: %s\n",
-				       which ? "Active" : "Try",
-				       ok(testSubDevSelection(&node, which, pad)));
-				if (which)
-					printf("\ttest VIDIOC_SUBDEV_G/S_FRAME_INTERVAL: %s\n",
-					       ok(testSubDevFrameInterval(&node, pad)));
+				struct v4l2_subdev_routing dummy_routing;
+				struct v4l2_subdev_route dummy_routes[1];
+
+				const struct v4l2_subdev_routing *routing;
+				const struct v4l2_subdev_route *routes;
+
+				if (has_routes) {
+					routing = &sd_routing[which];
+					routes = sd_routes[which];
+				} else {
+					dummy_routing.num_routes = 1;
+					dummy_routing.routes = (__u64)&dummy_routes;
+					dummy_routes[0].source_pad = pad;
+					dummy_routes[0].source_stream = 0;
+					dummy_routes[0].sink_pad = pad;
+					dummy_routes[0].sink_stream = 0;
+					dummy_routes[0].flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
+
+					routing = &dummy_routing;
+					routes = dummy_routes;
+				}
+
+				for (unsigned i = 0; i < routing->num_routes; ++i) {
+					const struct v4l2_subdev_route *r = &routes[i];
+					unsigned stream;
+
+					if (!(r->flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE))
+						continue;
+
+					if ((node.pads[pad].flags & MEDIA_PAD_FL_SINK) &&
+					    (r->sink_pad == pad))
+						stream = r->sink_stream;
+					else if ((node.pads[pad].flags & MEDIA_PAD_FL_SOURCE) &&
+					    (r->source_pad == pad))
+						stream = r->source_stream;
+					else
+						continue;
+
+					printf("\t%s Stream %u\n",which ? "Active" : "Try",
+					       stream);
+
+					printf("\ttest %s VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: %s\n",
+					       which ? "Active" : "Try",
+					       ok(testSubDevEnum(&node, which, pad, stream)));
+					printf("\ttest %s VIDIOC_SUBDEV_G/S_FMT: %s\n",
+					       which ? "Active" : "Try",
+					       ok(testSubDevFormat(&node, which, pad, stream)));
+					printf("\ttest %s VIDIOC_SUBDEV_G/S_SELECTION/CROP: %s\n",
+					       which ? "Active" : "Try",
+					       ok(testSubDevSelection(&node, which, pad, stream)));
+					if (which)
+						printf("\ttest VIDIOC_SUBDEV_G/S_FRAME_INTERVAL: %s\n",
+						       ok(testSubDevFrameInterval(&node, pad, stream)));
+				}
+			}
+
+			/*
+			 * These tests do not make sense for subdevs with multiplexed streams,
+			 * as the try & active cases may have different routing and thus different
+			 * behavior.
+			 */
+			if (!has_routes) {
+				if (node.has_subdev_enum_code && node.has_subdev_enum_code < 3)
+					fail("VIDIOC_SUBDEV_ENUM_MBUS_CODE: try/active mismatch\n");
+				if (node.has_subdev_enum_fsize && node.has_subdev_enum_fsize < 3)
+					fail("VIDIOC_SUBDEV_ENUM_FRAME_SIZE: try/active mismatch\n");
+				if (node.has_subdev_enum_fival && node.has_subdev_enum_fival < 3)
+					fail("VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL: try/active mismatch\n");
+				if (node.has_subdev_fmt && node.has_subdev_fmt < 3)
+					fail("VIDIOC_SUBDEV_G/S_FMT: try/active mismatch\n");
+				if (node.has_subdev_selection && node.has_subdev_selection < 3)
+					fail("VIDIOC_SUBDEV_G/S_SELECTION: try/active mismatch\n");
+				if (node.has_subdev_selection &&
+				    node.has_subdev_selection != node.has_subdev_fmt)
+					fail("VIDIOC_SUBDEV_G/S_SELECTION: fmt/selection mismatch\n");
 			}
-			if (node.has_subdev_enum_code && node.has_subdev_enum_code < 3)
-				fail("VIDIOC_SUBDEV_ENUM_MBUS_CODE: try/active mismatch\n");
-			if (node.has_subdev_enum_fsize && node.has_subdev_enum_fsize < 3)
-				fail("VIDIOC_SUBDEV_ENUM_FRAME_SIZE: try/active mismatch\n");
-			if (node.has_subdev_enum_fival && node.has_subdev_enum_fival < 3)
-				fail("VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL: try/active mismatch\n");
-			if (node.has_subdev_fmt && node.has_subdev_fmt < 3)
-				fail("VIDIOC_SUBDEV_G/S_FMT: try/active mismatch\n");
-			if (node.has_subdev_selection && node.has_subdev_selection < 3)
-				fail("VIDIOC_SUBDEV_G/S_SELECTION: try/active mismatch\n");
-			if (node.has_subdev_selection &&
-			    node.has_subdev_selection != node.has_subdev_fmt)
-				fail("VIDIOC_SUBDEV_G/S_SELECTION: fmt/selection mismatch\n");
 			printf("\n");
 		}
 	}
diff --git a/utils/v4l2-compliance/v4l2-compliance.h b/utils/v4l2-compliance/v4l2-compliance.h
index 88b792cd..0cd43980 100644
--- a/utils/v4l2-compliance/v4l2-compliance.h
+++ b/utils/v4l2-compliance/v4l2-compliance.h
@@ -371,10 +371,10 @@ int testDecoder(struct node *node);
 
 // SubDev ioctl tests
 int testSubDevCap(struct node *node);
-int testSubDevEnum(struct node *node, unsigned which, unsigned pad);
-int testSubDevFormat(struct node *node, unsigned which, unsigned pad);
-int testSubDevSelection(struct node *node, unsigned which, unsigned pad);
-int testSubDevFrameInterval(struct node *node, unsigned pad);
+int testSubDevEnum(struct node *node, unsigned which, unsigned pad, unsigned stream);
+int testSubDevFormat(struct node *node, unsigned which, unsigned pad, unsigned stream);
+int testSubDevSelection(struct node *node, unsigned which, unsigned pad, unsigned stream);
+int testSubDevFrameInterval(struct node *node, unsigned pad, unsigned stream);
 
 // Buffer ioctl tests
 int testReqBufs(struct node *node);
diff --git a/utils/v4l2-compliance/v4l2-test-subdevs.cpp b/utils/v4l2-compliance/v4l2-test-subdevs.cpp
index f3d85771..07192bda 100644
--- a/utils/v4l2-compliance/v4l2-test-subdevs.cpp
+++ b/utils/v4l2-compliance/v4l2-test-subdevs.cpp
@@ -25,7 +25,7 @@
 
 #include "v4l2-compliance.h"
 
-#define VALID_SUBDEV_CAPS (V4L2_SUBDEV_CAP_RO_SUBDEV)
+#define VALID_SUBDEV_CAPS (V4L2_SUBDEV_CAP_RO_SUBDEV | V4L2_SUBDEV_CAP_STREAMS)
 
 int testSubDevCap(struct node *node)
 {
@@ -54,6 +54,7 @@ static int testSubDevEnumFrameInterval(struct node *node, unsigned which,
 	memset(&fie, 0, sizeof(fie));
 	fie.which = which;
 	fie.pad = pad;
+	fie.stream = 0;
 	fie.code = code;
 	fie.width = width;
 	fie.height = height;
@@ -83,6 +84,7 @@ static int testSubDevEnumFrameInterval(struct node *node, unsigned which,
 	memset(&fie, 0xff, sizeof(fie));
 	fie.which = which;
 	fie.pad = pad;
+	fie.stream = 0;
 	fie.code = code;
 	fie.width = width;
 	fie.height = height;
@@ -128,6 +130,7 @@ static int testSubDevEnumFrameSize(struct node *node, unsigned which,
 	memset(&fse, 0, sizeof(fse));
 	fse.which = which;
 	fse.pad = pad;
+	fse.stream = 0;
 	fse.code = code;
 	ret = doioctl(node, VIDIOC_SUBDEV_ENUM_FRAME_SIZE, &fse);
 	node->has_subdev_enum_fsize |= (ret != ENOTTY) << which;
@@ -137,6 +140,7 @@ static int testSubDevEnumFrameSize(struct node *node, unsigned which,
 		memset(&fie, 0, sizeof(fie));
 		fie.which = which;
 		fie.pad = pad;
+		fie.stream = 0;
 		fie.code = code;
 		fail_on_test(doioctl(node, VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL, &fie) != ENOTTY);
 		return ret;
@@ -152,6 +156,7 @@ static int testSubDevEnumFrameSize(struct node *node, unsigned which,
 	memset(&fse, 0xff, sizeof(fse));
 	fse.which = which;
 	fse.pad = pad;
+	fse.stream = 0;
 	fse.code = code;
 	fse.index = 0;
 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_ENUM_FRAME_SIZE, &fse));
@@ -195,7 +200,7 @@ static int testSubDevEnumFrameSize(struct node *node, unsigned which,
 	return 0;
 }
 
-int testSubDevEnum(struct node *node, unsigned which, unsigned pad)
+int testSubDevEnum(struct node *node, unsigned which, unsigned pad, unsigned stream)
 {
 	struct v4l2_subdev_mbus_code_enum mbus_core_enum;
 	unsigned num_codes;
@@ -204,6 +209,7 @@ int testSubDevEnum(struct node *node, unsigned which, unsigned pad)
 	memset(&mbus_core_enum, 0, sizeof(mbus_core_enum));
 	mbus_core_enum.which = which;
 	mbus_core_enum.pad = pad;
+	mbus_core_enum.stream = stream;
 	ret = doioctl(node, VIDIOC_SUBDEV_ENUM_MBUS_CODE, &mbus_core_enum);
 	node->has_subdev_enum_code |= (ret != ENOTTY) << which;
 	if (ret == ENOTTY) {
@@ -214,8 +220,10 @@ int testSubDevEnum(struct node *node, unsigned which, unsigned pad)
 		memset(&fie, 0, sizeof(fie));
 		fse.which = which;
 		fse.pad = pad;
+		fse.stream = stream;
 		fie.which = which;
 		fie.pad = pad;
+		fie.stream = stream;
 		fail_on_test(doioctl(node, VIDIOC_SUBDEV_ENUM_FRAME_SIZE, &fse) != ENOTTY);
 		fail_on_test(doioctl(node, VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL, &fie) != ENOTTY);
 		return ret;
@@ -226,16 +234,19 @@ int testSubDevEnum(struct node *node, unsigned which, unsigned pad)
 	mbus_core_enum.index = ~0;
 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_ENUM_MBUS_CODE, &mbus_core_enum) != EINVAL);
 	mbus_core_enum.pad = node->entity.pads;
+	mbus_core_enum.stream = stream;
 	mbus_core_enum.index = 0;
 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_ENUM_MBUS_CODE, &mbus_core_enum) != EINVAL);
 	memset(&mbus_core_enum, 0xff, sizeof(mbus_core_enum));
 	mbus_core_enum.which = which;
 	mbus_core_enum.pad = pad;
+	mbus_core_enum.stream = stream;
 	mbus_core_enum.index = 0;
 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_ENUM_MBUS_CODE, &mbus_core_enum));
 	fail_on_test(check_0(mbus_core_enum.reserved, sizeof(mbus_core_enum.reserved)));
 	fail_on_test(mbus_core_enum.code == ~0U);
 	fail_on_test(mbus_core_enum.pad != pad);
+	fail_on_test(mbus_core_enum.stream != stream);
 	fail_on_test(mbus_core_enum.index);
 	fail_on_test(mbus_core_enum.which != which);
 	do {
@@ -252,6 +263,7 @@ int testSubDevEnum(struct node *node, unsigned which, unsigned pad)
 		fail_on_test(!mbus_core_enum.code);
 		fail_on_test(mbus_core_enum.which != which);
 		fail_on_test(mbus_core_enum.pad != pad);
+		fail_on_test(mbus_core_enum.stream != stream);
 		fail_on_test(mbus_core_enum.index != i);
 
 		ret = testSubDevEnumFrameSize(node, which, pad, mbus_core_enum.code);
@@ -260,7 +272,7 @@ int testSubDevEnum(struct node *node, unsigned which, unsigned pad)
 	return 0;
 }
 
-int testSubDevFrameInterval(struct node *node, unsigned pad)
+int testSubDevFrameInterval(struct node *node, unsigned pad, unsigned stream)
 {
 	struct v4l2_subdev_frame_interval fival;
 	struct v4l2_fract ival;
@@ -268,6 +280,7 @@ int testSubDevFrameInterval(struct node *node, unsigned pad)
 
 	memset(&fival, 0xff, sizeof(fival));
 	fival.pad = pad;
+	fival.stream = stream;
 	ret = doioctl(node, VIDIOC_SUBDEV_G_FRAME_INTERVAL, &fival);
 	if (ret == ENOTTY) {
 		fail_on_test(node->enum_frame_interval_pad >= 0);
@@ -279,6 +292,7 @@ int testSubDevFrameInterval(struct node *node, unsigned pad)
 	node->frame_interval_pad = pad;
 	fail_on_test(check_0(fival.reserved, sizeof(fival.reserved)));
 	fail_on_test(fival.pad != pad);
+	fail_on_test(fival.stream != stream);
 	fail_on_test(!fival.interval.numerator);
 	fail_on_test(!fival.interval.denominator);
 	fail_on_test(fival.interval.numerator == ~0U || fival.interval.denominator == ~0U);
@@ -290,20 +304,25 @@ int testSubDevFrameInterval(struct node *node, unsigned pad)
 	}
 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_S_FRAME_INTERVAL, &fival));
 	fail_on_test(fival.pad != pad);
+	fail_on_test(fival.stream != stream);
 	fail_on_test(ival.numerator != fival.interval.numerator);
 	fail_on_test(ival.denominator != fival.interval.denominator);
 	fail_on_test(check_0(fival.reserved, sizeof(fival.reserved)));
 	memset(&fival, 0, sizeof(fival));
 	fival.pad = pad;
+	fival.stream = stream;
 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_G_FRAME_INTERVAL, &fival));
 	fail_on_test(fival.pad != pad);
+	fail_on_test(fival.stream != stream);
 	fail_on_test(ival.numerator != fival.interval.numerator);
 	fail_on_test(ival.denominator != fival.interval.denominator);
 
 	fival.pad = node->entity.pads;
+	fival.stream = stream;
 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_G_FRAME_INTERVAL, &fival) != EINVAL);
 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_S_FRAME_INTERVAL, &fival) != EINVAL);
 	fival.pad = pad;
+	fival.stream = stream;
 	fival.interval = ival;
 	fival.interval.numerator = 0;
 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_S_FRAME_INTERVAL, &fival));
@@ -340,7 +359,7 @@ static int checkMBusFrameFmt(struct node *node, struct v4l2_mbus_framefmt &fmt)
 	return 0;
 }
 
-int testSubDevFormat(struct node *node, unsigned which, unsigned pad)
+int testSubDevFormat(struct node *node, unsigned which, unsigned pad, unsigned stream)
 {
 	struct v4l2_subdev_format fmt;
 	struct v4l2_subdev_format s_fmt;
@@ -349,6 +368,7 @@ int testSubDevFormat(struct node *node, unsigned which, unsigned pad)
 	memset(&fmt, 0, sizeof(fmt));
 	fmt.which = which;
 	fmt.pad = pad;
+	fmt.stream = stream;
 	ret = doioctl(node, VIDIOC_SUBDEV_G_FMT, &fmt);
 	node->has_subdev_fmt |= (ret != ENOTTY) << which;
 	if (ret == ENOTTY) {
@@ -359,14 +379,17 @@ int testSubDevFormat(struct node *node, unsigned which, unsigned pad)
 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_G_FMT, &fmt) != EINVAL);
 	fmt.which = 0;
 	fmt.pad = node->entity.pads;
+	fmt.stream = stream;
 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_G_FMT, &fmt) != EINVAL);
 	memset(&fmt, 0xff, sizeof(fmt));
 	fmt.which = which;
 	fmt.pad = pad;
+	fmt.stream = stream;
 	fail_on_test(doioctl(node, VIDIOC_SUBDEV_G_FMT, &fmt));
 	fail_on_test(check_0(fmt.reserved, sizeof(fmt.reserved)));
 	fail_on_test(fmt.which != which);
 	fail_on_test(fmt.pad != pad);
+	fail_on_test(fmt.stream != stream);
 	fail_on_test(checkMBusFrameFmt(node, fmt.format));
 	s_fmt = fmt;
 	memset(s_fmt.reserved, 0xff, sizeof(s_fmt.reserved));
@@ -379,6 +402,7 @@ int testSubDevFormat(struct node *node, unsigned which, unsigned pad)
 	fail_on_test(ret && ret != ENOTTY);
 	fail_on_test(s_fmt.which != which);
 	fail_on_test(s_fmt.pad != pad);
+	fail_on_test(s_fmt.stream != stream);
 	if (ret) {
 		warn("VIDIOC_SUBDEV_G_FMT is supported but not VIDIOC_SUBDEV_S_FMT\n");
 		return 0;
@@ -423,7 +447,7 @@ static target_info targets[] = {
 	{ ~0U },
 };
 
-int testSubDevSelection(struct node *node, unsigned which, unsigned pad)
+int testSubDevSelection(struct node *node, unsigned which, unsigned pad, unsigned stream)
 {
 	struct v4l2_subdev_selection sel;
 	struct v4l2_subdev_selection s_sel;
@@ -435,10 +459,12 @@ int testSubDevSelection(struct node *node, unsigned which, unsigned pad)
 	targets[V4L2_SEL_TGT_NATIVE_SIZE].readonly = is_sink;
 	memset(&crop, 0, sizeof(crop));
 	crop.pad = pad;
+	crop.stream = stream;
 	crop.which = which;
 	memset(&sel, 0, sizeof(sel));
 	sel.which = which;
 	sel.pad = pad;
+	sel.stream = stream;
 	sel.target = V4L2_SEL_TGT_CROP;
 	ret = doioctl(node, VIDIOC_SUBDEV_G_SELECTION, &sel);
 	node->has_subdev_selection |= (ret != ENOTTY) << which;
@@ -451,6 +477,7 @@ int testSubDevSelection(struct node *node, unsigned which, unsigned pad)
 	fail_on_test(check_0(crop.reserved, sizeof(crop.reserved)));
 	fail_on_test(crop.which != which);
 	fail_on_test(crop.pad != pad);
+	fail_on_test(crop.stream != stream);
 	fail_on_test(memcmp(&crop.rect, &sel.r, sizeof(sel.r)));
 
 	for (unsigned tgt = 0; targets[tgt].target != ~0U; tgt++) {
@@ -458,6 +485,7 @@ int testSubDevSelection(struct node *node, unsigned which, unsigned pad)
 		memset(&sel, 0xff, sizeof(sel));
 		sel.which = which;
 		sel.pad = pad;
+		sel.stream = stream;
 		sel.target = tgt;
 		ret = doioctl(node, VIDIOC_SUBDEV_G_SELECTION, &sel);
 		targets[tgt].found = !ret;
@@ -469,6 +497,7 @@ int testSubDevSelection(struct node *node, unsigned which, unsigned pad)
 		fail_on_test(check_0(sel.reserved, sizeof(sel.reserved)));
 		fail_on_test(sel.which != which);
 		fail_on_test(sel.pad != pad);
+		fail_on_test(sel.stream != stream);
 		fail_on_test(sel.target != tgt);
 		fail_on_test(!sel.r.width);
 		fail_on_test(sel.r.width == ~0U);
@@ -480,9 +509,11 @@ int testSubDevSelection(struct node *node, unsigned which, unsigned pad)
 		fail_on_test(doioctl(node, VIDIOC_SUBDEV_G_SELECTION, &sel) != EINVAL);
 		sel.which = 0;
 		sel.pad = node->entity.pads;
+		sel.stream = stream;
 		fail_on_test(doioctl(node, VIDIOC_SUBDEV_G_SELECTION, &sel) != EINVAL);
 		sel.which = which;
 		sel.pad = pad;
+		sel.stream = stream;
 		s_sel = sel;
 		memset(s_sel.reserved, 0xff, sizeof(s_sel.reserved));
 		ret = doioctl(node, VIDIOC_SUBDEV_S_SELECTION, &s_sel);
@@ -496,6 +527,7 @@ int testSubDevSelection(struct node *node, unsigned which, unsigned pad)
 				fail_on_test(check_0(crop.reserved, sizeof(crop.reserved)));
 				fail_on_test(crop.which != which);
 				fail_on_test(crop.pad != pad);
+				fail_on_test(crop.stream != stream);
 				fail_on_test(memcmp(&crop.rect, &sel.r, sizeof(sel.r)));
 			}
 		}
@@ -504,6 +536,7 @@ int testSubDevSelection(struct node *node, unsigned which, unsigned pad)
 		fail_on_test(!ret && targets[tgt].readonly);
 		fail_on_test(s_sel.which != which);
 		fail_on_test(s_sel.pad != pad);
+		fail_on_test(s_sel.stream != stream);
 		if (ret && !targets[tgt].readonly && tgt != V4L2_SEL_TGT_NATIVE_SIZE)
 			warn("VIDIOC_SUBDEV_G_SELECTION is supported for target %u but not VIDIOC_SUBDEV_S_SELECTION\n", tgt);
 		if (ret)
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v5 4/7] v4l2-ctl/compliance: Add simple routing test
  2023-05-29  8:49 [PATCH v5 0/7] v4l-utils: Support multiplexed streams Tomi Valkeinen
                   ` (2 preceding siblings ...)
  2023-05-29  8:49 ` [PATCH v5 3/7] v4l2-ctl/compliance: Add routing and streams multiplexed streams Tomi Valkeinen
@ 2023-05-29  8:50 ` Tomi Valkeinen
  2023-06-07 11:35   ` Hans Verkuil
  2023-05-29  8:50 ` [PATCH v5 5/7] media-ctl: Check for Streams API support Tomi Valkeinen
                   ` (2 subsequent siblings)
  6 siblings, 1 reply; 15+ messages in thread
From: Tomi Valkeinen @ 2023-05-29  8:50 UTC (permalink / raw)
  To: linux-media, sakari.ailus, Jacopo Mondi, Laurent Pinchart,
	niklas.soderlund+renesas, Mauro Carvalho Chehab, Hans Verkuil,
	satish.nagireddy
  Cc: Tomi Valkeinen, Laurent Pinchart

Add a very simple test for
VIDIOC_SUBDEV_G_ROUTING/VIDIOC_SUBDEV_S_ROUTING.

We can't (at least at the moment) really know here what kind of routings
the driver would accept, but we can test a VIDIOC_SUBDEV_G_ROUTING call,
followed by a VIDIOC_SUBDEV_S_ROUTING call with the routing we
received.

Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
---
 utils/v4l2-compliance/v4l2-compliance.cpp   | 12 ++++++++++++
 utils/v4l2-compliance/v4l2-compliance.h     |  1 +
 utils/v4l2-compliance/v4l2-test-subdevs.cpp | 16 ++++++++++++++++
 3 files changed, 29 insertions(+)

diff --git a/utils/v4l2-compliance/v4l2-compliance.cpp b/utils/v4l2-compliance/v4l2-compliance.cpp
index d7c10482..f082f569 100644
--- a/utils/v4l2-compliance/v4l2-compliance.cpp
+++ b/utils/v4l2-compliance/v4l2-compliance.cpp
@@ -1249,6 +1249,18 @@ void testNode(struct node &node, struct node &node_m2m_cap, struct node &expbuf_
 		node.is_passthrough_subdev = has_source && has_sink;
 
 		if (has_routes) {
+			printf("Sub-Device routing ioctls:\n");
+
+			for (unsigned which = V4L2_SUBDEV_FORMAT_TRY;
+				which <= V4L2_SUBDEV_FORMAT_ACTIVE; which++) {
+
+				printf("\ttest %s VIDIOC_SUBDEV_G_ROUTING/VIDIOC_SUBDEV_S_ROUTING: %s\n",
+				       which ? "Active" : "Try",
+				       ok(testSubDevRouting(&node, which)));
+			}
+
+			printf("\n");
+
 			for (unsigned which = V4L2_SUBDEV_FORMAT_TRY;
 				which <= V4L2_SUBDEV_FORMAT_ACTIVE; which++) {
 
diff --git a/utils/v4l2-compliance/v4l2-compliance.h b/utils/v4l2-compliance/v4l2-compliance.h
index 0cd43980..35b2274b 100644
--- a/utils/v4l2-compliance/v4l2-compliance.h
+++ b/utils/v4l2-compliance/v4l2-compliance.h
@@ -375,6 +375,7 @@ int testSubDevEnum(struct node *node, unsigned which, unsigned pad, unsigned str
 int testSubDevFormat(struct node *node, unsigned which, unsigned pad, unsigned stream);
 int testSubDevSelection(struct node *node, unsigned which, unsigned pad, unsigned stream);
 int testSubDevFrameInterval(struct node *node, unsigned pad, unsigned stream);
+int testSubDevRouting(struct node *node, unsigned which);
 
 // Buffer ioctl tests
 int testReqBufs(struct node *node);
diff --git a/utils/v4l2-compliance/v4l2-test-subdevs.cpp b/utils/v4l2-compliance/v4l2-test-subdevs.cpp
index 07192bda..962d9244 100644
--- a/utils/v4l2-compliance/v4l2-test-subdevs.cpp
+++ b/utils/v4l2-compliance/v4l2-test-subdevs.cpp
@@ -551,3 +551,19 @@ int testSubDevSelection(struct node *node, unsigned which, unsigned pad, unsigne
 
 	return have_sel ? 0 : ENOTTY;
 }
+
+int testSubDevRouting(struct node *node, unsigned which)
+{
+	struct v4l2_subdev_routing routing = {};
+	struct v4l2_subdev_route routes[256] = {};
+
+	routing.which = which;
+	routing.routes = (__u64)&routes;
+	routing.num_routes = 256;
+
+	fail_on_test(doioctl(node, VIDIOC_SUBDEV_G_ROUTING, &routing));
+
+	fail_on_test(doioctl(node, VIDIOC_SUBDEV_S_ROUTING, &routing));
+
+	return 0;
+}
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v5 5/7] media-ctl: Check for Streams API support
  2023-05-29  8:49 [PATCH v5 0/7] v4l-utils: Support multiplexed streams Tomi Valkeinen
                   ` (3 preceding siblings ...)
  2023-05-29  8:50 ` [PATCH v5 4/7] v4l2-ctl/compliance: Add simple routing test Tomi Valkeinen
@ 2023-05-29  8:50 ` Tomi Valkeinen
  2023-05-29  8:50 ` [PATCH v5 6/7] utils/common: Set V4L2_SUBDEV_CLIENT_CAP_STREAMS for subdevs Tomi Valkeinen
  2023-05-29  8:50 ` [PATCH v5 7/7] v4l2-ctl: Check for Streams API support Tomi Valkeinen
  6 siblings, 0 replies; 15+ messages in thread
From: Tomi Valkeinen @ 2023-05-29  8:50 UTC (permalink / raw)
  To: linux-media, sakari.ailus, Jacopo Mondi, Laurent Pinchart,
	niklas.soderlund+renesas, Mauro Carvalho Chehab, Hans Verkuil,
	satish.nagireddy
  Cc: Tomi Valkeinen, Laurent Pinchart

Use the new VIDIOC_SUBDEV_S_CLIENT_CAP ioctl to inform the kernel that
we support streams, and use the return values of
VIDIOC_SUBDEV_S_CLIENT_CAP and VIDIOC_SUBDEV_QUERYCAP to decide if the
entity supports streams. If not, return an error in case the user tries
to use streams.

Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
---
 utils/media-ctl/libv4l2subdev.c | 56 +++++++++++++++++++++++++++++++++
 utils/media-ctl/mediactl-priv.h |  1 +
 2 files changed, 57 insertions(+)

diff --git a/utils/media-ctl/libv4l2subdev.c b/utils/media-ctl/libv4l2subdev.c
index 51d30e61..750796cc 100644
--- a/utils/media-ctl/libv4l2subdev.c
+++ b/utils/media-ctl/libv4l2subdev.c
@@ -42,6 +42,12 @@
 
 int v4l2_subdev_open(struct media_entity *entity)
 {
+	struct v4l2_subdev_client_capability clientcap = {};
+	struct v4l2_subdev_capability subdevcap = {};
+	bool subdev_streams;
+	bool client_streams;
+	int ret;
+
 	if (entity->fd != -1)
 		return 0;
 
@@ -54,6 +60,16 @@ int v4l2_subdev_open(struct media_entity *entity)
 		return ret;
 	}
 
+	ret = ioctl(entity->fd, VIDIOC_SUBDEV_QUERYCAP, &subdevcap);
+	subdev_streams = !ret && (subdevcap.capabilities & V4L2_SUBDEV_CAP_STREAMS);
+
+	clientcap.capabilities = V4L2_SUBDEV_CLIENT_CAP_STREAMS;
+
+	ret = ioctl(entity->fd, VIDIOC_SUBDEV_S_CLIENT_CAP, &clientcap);
+	client_streams = !ret && (clientcap.capabilities & V4L2_SUBDEV_CLIENT_CAP_STREAMS);
+
+	entity->supports_streams = subdev_streams && client_streams;
+
 	return 0;
 }
 
@@ -74,6 +90,11 @@ int v4l2_subdev_get_format(struct media_entity *entity,
 	if (ret < 0)
 		return ret;
 
+	if (!entity->supports_streams && stream) {
+		media_dbg(entity->media, "Streams API not supported\n");
+		return -ENOTSUP;
+	}
+
 	memset(&fmt, 0, sizeof(fmt));
 	fmt.pad = pad;
 	fmt.stream = stream;
@@ -99,6 +120,11 @@ int v4l2_subdev_set_format(struct media_entity *entity,
 	if (ret < 0)
 		return ret;
 
+	if (!entity->supports_streams && stream) {
+		media_dbg(entity->media, "Streams API not supported\n");
+		return -ENOTSUP;
+	}
+
 	memset(&fmt, 0, sizeof(fmt));
 	fmt.pad = pad;
 	fmt.stream = stream;
@@ -127,6 +153,11 @@ int v4l2_subdev_get_selection(struct media_entity *entity,
 	if (ret < 0)
 		return ret;
 
+	if (!entity->supports_streams && stream) {
+		media_dbg(entity->media, "Streams API not supported\n");
+		return -ENOTSUP;
+	}
+
 	memset(&u.sel, 0, sizeof(u.sel));
 	u.sel.pad = pad;
 	u.sel.target = target;
@@ -166,6 +197,11 @@ int v4l2_subdev_set_selection(struct media_entity *entity,
 	if (ret < 0)
 		return ret;
 
+	if (!entity->supports_streams && stream) {
+		media_dbg(entity->media, "Streams API not supported\n");
+		return -ENOTSUP;
+	}
+
 	memset(&u.sel, 0, sizeof(u.sel));
 	u.sel.pad = pad;
 	u.sel.stream = stream;
@@ -209,6 +245,11 @@ int v4l2_subdev_get_routing(struct media_entity *entity,
 	if (ret < 0)
 		return ret;
 
+	if (!entity->supports_streams) {
+		media_dbg(entity->media, "Streams API not supported\n");
+		return -ENOTSUP;
+	}
+
 	ret = ioctl(entity->fd, VIDIOC_SUBDEV_G_ROUTING, &routing);
 	if (ret == -1 && errno != ENOSPC)
 		return -errno;
@@ -251,6 +292,11 @@ int v4l2_subdev_set_routing(struct media_entity *entity,
 	if (ret < 0)
 		return ret;
 
+	if (!entity->supports_streams) {
+		media_dbg(entity->media, "Streams API not supported\n");
+		return -ENOTSUP;
+	}
+
 	ret = ioctl(entity->fd, VIDIOC_SUBDEV_S_ROUTING, &routing);
 	if (ret == -1)
 		return -errno;
@@ -341,6 +387,11 @@ int v4l2_subdev_get_frame_interval(struct media_entity *entity,
 	if (ret < 0)
 		return ret;
 
+	if (!entity->supports_streams && stream) {
+		media_dbg(entity->media, "Streams API not supported\n");
+		return -ENOTSUP;
+	}
+
 	memset(&ival, 0, sizeof(ival));
 	ival.pad = pad;
 	ival.stream = stream;
@@ -364,6 +415,11 @@ int v4l2_subdev_set_frame_interval(struct media_entity *entity,
 	if (ret < 0)
 		return ret;
 
+	if (!entity->supports_streams && stream) {
+		media_dbg(entity->media, "Streams API not supported\n");
+		return -ENOTSUP;
+	}
+
 	memset(&ival, 0, sizeof(ival));
 	ival.pad = pad;
 	ival.stream = stream;
diff --git a/utils/media-ctl/mediactl-priv.h b/utils/media-ctl/mediactl-priv.h
index a0d3a55a..eb55e07e 100644
--- a/utils/media-ctl/mediactl-priv.h
+++ b/utils/media-ctl/mediactl-priv.h
@@ -33,6 +33,7 @@ struct media_entity {
 	struct media_link *links;
 	unsigned int max_links;
 	unsigned int num_links;
+	bool supports_streams;
 
 	char devname[32];
 	int fd;
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v5 6/7] utils/common: Set V4L2_SUBDEV_CLIENT_CAP_STREAMS for subdevs
  2023-05-29  8:49 [PATCH v5 0/7] v4l-utils: Support multiplexed streams Tomi Valkeinen
                   ` (4 preceding siblings ...)
  2023-05-29  8:50 ` [PATCH v5 5/7] media-ctl: Check for Streams API support Tomi Valkeinen
@ 2023-05-29  8:50 ` Tomi Valkeinen
  2023-05-29  8:50 ` [PATCH v5 7/7] v4l2-ctl: Check for Streams API support Tomi Valkeinen
  6 siblings, 0 replies; 15+ messages in thread
From: Tomi Valkeinen @ 2023-05-29  8:50 UTC (permalink / raw)
  To: linux-media, sakari.ailus, Jacopo Mondi, Laurent Pinchart,
	niklas.soderlund+renesas, Mauro Carvalho Chehab, Hans Verkuil,
	satish.nagireddy
  Cc: Tomi Valkeinen, Laurent Pinchart

Use the new VIDIOC_SUBDEV_S_CLIENT_CAP ioctl to inform the kernel that
we support streams, and use the return values of
VIDIOC_SUBDEV_S_CLIENT_CAP and VIDIOC_SUBDEV_QUERYCAP to decide if the
entity supports streams.

Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
---
 utils/common/cv4l-helpers.h |  1 +
 utils/common/v4l-helpers.h  | 18 ++++++++++++++++++
 2 files changed, 19 insertions(+)

diff --git a/utils/common/cv4l-helpers.h b/utils/common/cv4l-helpers.h
index 3cee372b..91a04146 100644
--- a/utils/common/cv4l-helpers.h
+++ b/utils/common/cv4l-helpers.h
@@ -82,6 +82,7 @@ public:
 	bool has_rw() const { return v4l_has_rw(this); }
 	bool has_streaming() const { return v4l_has_streaming(this); }
 	bool has_ext_pix_format() const { return v4l_has_ext_pix_format(this); }
+	bool has_streams() const { return have_streams; }
 
 	int querycap(v4l2_capability &cap, bool force = false)
 	{
diff --git a/utils/common/v4l-helpers.h b/utils/common/v4l-helpers.h
index c09cd987..f8e96d58 100644
--- a/utils/common/v4l-helpers.h
+++ b/utils/common/v4l-helpers.h
@@ -9,6 +9,7 @@
 #ifndef _V4L_HELPERS_H_
 #define _V4L_HELPERS_H_
 
+#include <linux/v4l2-subdev.h>
 #include <linux/videodev2.h>
 #include <string.h>
 #include <stdlib.h>
@@ -39,6 +40,7 @@ struct v4l_fd {
 	bool have_selection;
 	bool is_subdev;
 	bool is_media;
+	bool have_streams;
 
 	int (*open)(struct v4l_fd *f, const char *file, int oflag, ...);
 	int (*close)(struct v4l_fd *f);
@@ -507,6 +509,12 @@ static inline int v4l_open(struct v4l_fd *f, const char *devname, bool non_block
 
 static inline int v4l_subdev_s_fd(struct v4l_fd *f, int fd, const char *devname)
 {
+	struct v4l2_subdev_client_capability clientcap = {};
+	struct v4l2_subdev_capability subdevcap = {};
+	bool subdev_streams;
+	bool client_streams;
+	int ret;
+
 	if (f->fd >= 0)
 		f->close(f);
 
@@ -528,6 +536,16 @@ static inline int v4l_subdev_s_fd(struct v4l_fd *f, int fd, const char *devname)
 	f->have_next_ctrl = false;
 	f->have_selection = false;
 
+	ret = ioctl(f->fd, VIDIOC_SUBDEV_QUERYCAP, &subdevcap);
+	subdev_streams = !ret && (subdevcap.capabilities & V4L2_SUBDEV_CAP_STREAMS);
+
+	clientcap.capabilities = V4L2_SUBDEV_CLIENT_CAP_STREAMS;
+
+	ret = ioctl(f->fd, VIDIOC_SUBDEV_S_CLIENT_CAP, &clientcap);
+	client_streams = !ret && (clientcap.capabilities & V4L2_SUBDEV_CLIENT_CAP_STREAMS);
+
+	f->have_streams = subdev_streams && client_streams;
+
 	return f->fd;
 }
 
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v5 7/7] v4l2-ctl: Check for Streams API support
  2023-05-29  8:49 [PATCH v5 0/7] v4l-utils: Support multiplexed streams Tomi Valkeinen
                   ` (5 preceding siblings ...)
  2023-05-29  8:50 ` [PATCH v5 6/7] utils/common: Set V4L2_SUBDEV_CLIENT_CAP_STREAMS for subdevs Tomi Valkeinen
@ 2023-05-29  8:50 ` Tomi Valkeinen
  6 siblings, 0 replies; 15+ messages in thread
From: Tomi Valkeinen @ 2023-05-29  8:50 UTC (permalink / raw)
  To: linux-media, sakari.ailus, Jacopo Mondi, Laurent Pinchart,
	niklas.soderlund+renesas, Mauro Carvalho Chehab, Hans Verkuil,
	satish.nagireddy
  Cc: Tomi Valkeinen, Laurent Pinchart

Return an error if the user tries to use streams related features, but
streams are not supported.

Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
---
 utils/v4l2-ctl/v4l2-ctl-subdev.cpp | 55 ++++++++++++++++++++++++++++++
 1 file changed, 55 insertions(+)

diff --git a/utils/v4l2-ctl/v4l2-ctl-subdev.cpp b/utils/v4l2-ctl/v4l2-ctl-subdev.cpp
index fafb7d92..8539c416 100644
--- a/utils/v4l2-ctl/v4l2-ctl-subdev.cpp
+++ b/utils/v4l2-ctl/v4l2-ctl-subdev.cpp
@@ -569,6 +569,11 @@ void subdev_set(cv4l_fd &_fd)
 	if (options[OptSetSubDevFormat] || options[OptTrySubDevFormat]) {
 		struct v4l2_subdev_format fmt;
 
+		if (!_fd.has_streams() && set_fmt_stream) {
+			printf("Streams API not supported.\n");
+			return;
+		}
+
 		memset(&fmt, 0, sizeof(fmt));
 		fmt.pad = set_fmt_pad;
 		fmt.stream = set_fmt_stream;
@@ -617,6 +622,11 @@ void subdev_set(cv4l_fd &_fd)
 	if (options[OptSetSubDevSelection] || options[OptTrySubDevSelection]) {
 		struct v4l2_subdev_selection sel;
 
+		if (!_fd.has_streams() && vsel.stream) {
+			printf("Streams API not supported.\n");
+			return;
+		}
+
 		memset(&sel, 0, sizeof(sel));
 		sel.pad = vsel.pad;
 		sel.stream = vsel.stream;
@@ -649,6 +659,11 @@ void subdev_set(cv4l_fd &_fd)
 	if (options[OptSetSubDevFPS]) {
 		struct v4l2_subdev_frame_interval fival;
 
+		if (!_fd.has_streams() && set_fps_stream) {
+			printf("Streams API not supported.\n");
+			return;
+		}
+
 		memset(&fival, 0, sizeof(fival));
 		fival.pad = set_fps_pad;
 		fival.stream = set_fps_stream;
@@ -674,6 +689,11 @@ void subdev_set(cv4l_fd &_fd)
 		}
 	}
 	if (options[OptSetRouting]) {
+		if (!_fd.has_streams()) {
+			printf("Streams API not supported.\n");
+			return;
+		}
+
 		if (doioctl(fd, VIDIOC_SUBDEV_S_ROUTING, &routing) == 0)
 			printf("Routing set\n");
 	}
@@ -731,6 +751,11 @@ void subdev_get(cv4l_fd &_fd)
 	if (options[OptGetSubDevFormat]) {
 		struct v4l2_subdev_format fmt;
 
+		if (!_fd.has_streams() && get_fmt_stream) {
+			printf("Streams API not supported.\n");
+			return;
+		}
+
 		memset(&fmt, 0, sizeof(fmt));
 		fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
 		fmt.pad = get_fmt_pad;
@@ -745,6 +770,11 @@ void subdev_get(cv4l_fd &_fd)
 		struct v4l2_subdev_selection sel;
 		unsigned idx = 0;
 
+		if (!_fd.has_streams() && get_sel_stream) {
+			printf("Streams API not supported.\n");
+			return;
+		}
+
 		memset(&sel, 0, sizeof(sel));
 		sel.which = V4L2_SUBDEV_FORMAT_ACTIVE;
 		sel.pad = get_sel_pad;
@@ -767,6 +797,11 @@ void subdev_get(cv4l_fd &_fd)
 	if (options[OptGetSubDevFPS]) {
 		struct v4l2_subdev_frame_interval fival;
 
+		if (!_fd.has_streams() && get_fps_stream) {
+			printf("Streams API not supported.\n");
+			return;
+		}
+
 		memset(&fival, 0, sizeof(fival));
 		fival.pad = get_fps_pad;
 		fival.stream = get_fps_stream;
@@ -784,6 +819,11 @@ void subdev_get(cv4l_fd &_fd)
 	}
 
 	if (options[OptGetRouting]) {
+		if (!_fd.has_streams()) {
+			printf("Streams API not supported.\n");
+			return;
+		}
+
 		memset(&routing, 0, sizeof(routing));
 		memset(routes, 0, sizeof(routes[0]) * NUM_ROUTES_MAX);
 		routing.which = V4L2_SUBDEV_FORMAT_ACTIVE;
@@ -867,11 +907,21 @@ void subdev_list(cv4l_fd &_fd)
 	int fd = _fd.g_fd();
 
 	if (options[OptListSubDevMBusCodes]) {
+		if (!_fd.has_streams() && list_mbus_codes_stream) {
+			printf("Streams API not supported.\n");
+			return;
+		}
+
 		printf("ioctl: VIDIOC_SUBDEV_ENUM_MBUS_CODE (pad=%u,stream=%u)\n",
 		       list_mbus_codes_pad, list_mbus_codes_stream);
 		print_mbus_codes(fd, list_mbus_codes_pad, list_mbus_codes_stream);
 	}
 	if (options[OptListSubDevFrameSizes]) {
+		if (!_fd.has_streams() && frmsize.stream) {
+			printf("Streams API not supported.\n");
+			return;
+		}
+
 		printf("ioctl: VIDIOC_SUBDEV_ENUM_FRAME_SIZE (pad=%u,stream=%u)\n",
 		       frmsize.pad, frmsize.stream);
 		frmsize.index = 0;
@@ -882,6 +932,11 @@ void subdev_list(cv4l_fd &_fd)
 		}
 	}
 	if (options[OptListSubDevFrameIntervals]) {
+		if (!_fd.has_streams() && frmival.stream) {
+			printf("Streams API not supported.\n");
+			return;
+		}
+
 		printf("ioctl: VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL (pad=%u,stream=%u)\n",
 		       frmival.pad, frmival.stream);
 		frmival.index = 0;
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* Re: [PATCH v5 1/7] v4l2-ctl: Add routing and streams support
  2023-05-29  8:49 ` [PATCH v5 1/7] v4l2-ctl: Add routing and streams support Tomi Valkeinen
@ 2023-06-07 11:28   ` Hans Verkuil
  2023-07-18 13:51     ` Tomi Valkeinen
  0 siblings, 1 reply; 15+ messages in thread
From: Hans Verkuil @ 2023-06-07 11:28 UTC (permalink / raw)
  To: Tomi Valkeinen, linux-media, sakari.ailus, Jacopo Mondi,
	Laurent Pinchart, niklas.soderlund+renesas,
	Mauro Carvalho Chehab, satish.nagireddy

On 29/05/2023 10:49, Tomi Valkeinen wrote:
> Add support to get and set subdev routes and to get and set
> configurations per stream.
> 
> Based on work from Jacopo Mondi <jacopo@jmondi.org> and
> Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>.
> 
> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> ---
>  utils/v4l2-ctl/v4l2-ctl-subdev.cpp | 310 ++++++++++++++++++++++++++---
>  utils/v4l2-ctl/v4l2-ctl.cpp        |   2 +
>  utils/v4l2-ctl/v4l2-ctl.h          |   2 +
>  3 files changed, 281 insertions(+), 33 deletions(-)
> 
> diff --git a/utils/v4l2-ctl/v4l2-ctl-subdev.cpp b/utils/v4l2-ctl/v4l2-ctl-subdev.cpp
> index 33cc1342..fafb7d92 100644
> --- a/utils/v4l2-ctl/v4l2-ctl-subdev.cpp
> +++ b/utils/v4l2-ctl/v4l2-ctl-subdev.cpp
> @@ -1,5 +1,13 @@
>  #include "v4l2-ctl.h"
>  
> +#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
> +
> +/*
> + * The max value comes from a check in the kernel source code
> + * drivers/media/v4l2-core/v4l2-ioctl.c check_array_args()
> + */
> +#define NUM_ROUTES_MAX 256
> +
>  struct mbus_name {
>  	const char *name;
>  	__u32 code;
> @@ -19,45 +27,57 @@ static const struct mbus_name mbus_names[] = {
>  #define SelectionFlags 		(1L<<4)
>  
>  static __u32 list_mbus_codes_pad;
> +static __u32 list_mbus_codes_stream = 0;
>  static __u32 get_fmt_pad;
> +static __u32 get_fmt_stream = 0;
>  static __u32 get_sel_pad;
> +static __u32 get_sel_stream = 0;
>  static __u32 get_fps_pad;
> +static __u32 get_fps_stream = 0;
>  static int get_sel_target = -1;
>  static unsigned int set_selection;
>  static struct v4l2_subdev_selection vsel;
>  static unsigned int set_fmt;
>  static __u32 set_fmt_pad;
> +static __u32 set_fmt_stream = 0;
>  static struct v4l2_mbus_framefmt ffmt;
>  static struct v4l2_subdev_frame_size_enum frmsize;
>  static struct v4l2_subdev_frame_interval_enum frmival;
>  static __u32 set_fps_pad;
> +static __u32 set_fps_stream = 0;
>  static double set_fps;
> +static struct v4l2_subdev_routing routing;
> +static struct v4l2_subdev_route routes[NUM_ROUTES_MAX];
>  
>  void subdev_usage()
>  {
>  	printf("\nSub-Device options:\n"
> -	       "  --list-subdev-mbus-codes <pad>\n"
> +	       "  --list-subdev-mbus-codes pad=<pad>,stream=<stream>\n"
>  	       "                      display supported mediabus codes for this pad (0 is default)\n"
>  	       "                      [VIDIOC_SUBDEV_ENUM_MBUS_CODE]\n"
> -	       "  --list-subdev-framesizes pad=<pad>,code=<code>\n"
> +	       "  --list-subdev-framesizes pad=<pad>,stream=<stream>,code=<code>\n"
>  	       "                     list supported framesizes for this pad and code\n"
>  	       "                     [VIDIOC_SUBDEV_ENUM_FRAME_SIZE]\n"
>  	       "                     <code> is the value of the mediabus code\n"
> -	       "  --list-subdev-frameintervals pad=<pad>,width=<w>,height=<h>,code=<code>\n"
> +	       "  --list-subdev-frameintervals pad=<pad>,stream=<stream>,width=<w>,height=<h>,code=<code>\n"
>  	       "                     list supported frame intervals for this pad and code and\n"
>  	       "                     the given width and height [VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL]\n"
>  	       "                     <code> is the value of the mediabus code\n"
> -	       "  --get-subdev-fmt [<pad>]\n"
> -	       "     		     query the frame format for the given pad [VIDIOC_SUBDEV_G_FMT]\n"
> -	       "  --get-subdev-selection pad=<pad>,target=<target>\n"
> +	       "  --get-subdev-fmt pad=<pad>,stream=<stream>\n"
> +	       "     		     query the frame format for the given pad and optional stream [VIDIOC_SUBDEV_G_FMT]\n"
> +	       "		     <pad> the pad to get the format from\n"
> +	       "		     <stream> the stream to get the format from (0 if not specified)\n"

Here you explicitly state that <stream> is optional, but you did not do
that for --list-subdev-mbus-codes, --list-subdev-framesizes and --list-subdev-frameintervals
and other options below (just go through the list).

I also see that the 'pad' suboption is only sometimes documented.
In particular it is not clear when 'pad' is optional (defaulting
to 0) or required.

In some places I used [<pad>] to indicate that it is optional,
but I think it is better to explicitly state that <pad> defaults
to 0, or something along those lines.

If it isn't already the case, my preference is that both pad and
stream are optional and both default to 0.

> +	       "  --get-subdev-selection pad=<pad>,stream=<stream>,target=<target>\n"
>  	       "                     query the frame selection rectangle [VIDIOC_SUBDEV_G_SELECTION]\n"
>  	       "                     See --set-subdev-selection command for the valid <target> values.\n"
> -	       "  --get-subdev-fps [<pad>]\n"
> +	       "  --get-subdev-fps pad=<pad>,stream=<stream>\n"
>  	       "                     query the frame rate [VIDIOC_SUBDEV_G_FRAME_INTERVAL]\n"
>  	       "  --set-subdev-fmt   (for testing only, otherwise use media-ctl)\n"
> -	       "  --try-subdev-fmt pad=<pad>,width=<w>,height=<h>,code=<code>,field=<f>,colorspace=<c>,\n"
> +	       "  --try-subdev-fmt pad=<pad>,stream=<stream>,width=<w>,height=<h>,code=<code>,field=<f>,colorspace=<c>,\n"
>  	       "                   xfer=<xf>,ycbcr=<y>,hsv=<hsv>,quantization=<q>\n"
> -	       "                     set the frame format [VIDIOC_SUBDEV_S_FMT]\n"
> +	       "                     set the frame format for the given pad and optional stream [VIDIOC_SUBDEV_S_FMT]\n"
> +	       "                     <pad> the pad to get the format from\n"
> +	       "                     <stream> the stream to get the format from (0 if not specified)\n"
>  	       "                     <code> is the value of the mediabus code\n"
>  	       "                     <f> can be one of the following field layouts:\n"
>  	       "                       any, none, top, bottom, interlaced, seq_tb, seq_bt,\n"
> @@ -74,31 +94,74 @@ void subdev_usage()
>  	       "                     <q> can be one of the following quantization methods:\n"
>  	       "                       default, full-range, lim-range\n"
>  	       "  --set-subdev-selection (for testing only, otherwise use media-ctl)\n"
> -	       "  --try-subdev-selection pad=<pad>,target=<target>,flags=<flags>,\n"
> +	       "  --try-subdev-selection pad=<pad>,stream=<stream>,target=<target>,flags=<flags>,\n"
>  	       "                         top=<x>,left=<y>,width=<w>,height=<h>\n"
>  	       "                     set the video capture selection rectangle [VIDIOC_SUBDEV_S_SELECTION]\n"
>  	       "                     target=crop|crop_bounds|crop_default|compose|compose_bounds|\n"
>  	       "                            compose_default|compose_padded|native_size\n"
>  	       "                     flags=le|ge|keep-config\n"
> -	       "  --set-subdev-fps pad=<pad>,fps=<fps> (for testing only, otherwise use media-ctl)\n"
> +	       "  --set-subdev-fps pad=<pad>,stream=<stream>,fps=<fps> (for testing only, otherwise use media-ctl)\n"
>  	       "                     set the frame rate [VIDIOC_SUBDEV_S_FRAME_INTERVAL]\n"
> +	       "  --get-routing      Print the route topology\n"
> +	       "  --set-routing <routes>\n"
> +	       "                     Comma-separated list of route descriptors to setup\n"
> +	       "\n"
> +	       "Routes are defined as\n"
> +	       "	routes		= route { ',' route } ;\n"
> +	       "	route		= sink '->' source '[' flags ']' ;\n"
> +	       "	sink		= sink-pad '/' sink-stream ;\n"
> +	       "	source		= source-pad '/' source-stream ;\n"
> +	       "\n"
> +	       "where\n"
> +	       "	sink-pad	= Pad numeric identifier for sink\n"
> +	       "	sink-stream	= Stream numeric identifier for sink\n"
> +	       "	source-pad	= Pad numeric identifier for source\n"
> +	       "	source-stream	= Stream numeric identifier for source\n"
> +	       "	flags		= Route flags (0: inactive, 1: active)\n"
>  	       );
>  }

Regards,

	Hans

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v5 3/7] v4l2-ctl/compliance: Add routing and streams multiplexed streams
  2023-05-29  8:49 ` [PATCH v5 3/7] v4l2-ctl/compliance: Add routing and streams multiplexed streams Tomi Valkeinen
@ 2023-06-07 11:34   ` Hans Verkuil
  0 siblings, 0 replies; 15+ messages in thread
From: Hans Verkuil @ 2023-06-07 11:34 UTC (permalink / raw)
  To: Tomi Valkeinen, linux-media, sakari.ailus, Jacopo Mondi,
	Laurent Pinchart, niklas.soderlund+renesas,
	Mauro Carvalho Chehab, satish.nagireddy

On 29/05/2023 10:49, Tomi Valkeinen wrote:
> Add basic support for routing and streams.
> 
> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> ---
>  utils/v4l2-compliance/v4l2-compliance.cpp   | 120 ++++++++++++++++----
>  utils/v4l2-compliance/v4l2-compliance.h     |   8 +-
>  utils/v4l2-compliance/v4l2-test-subdevs.cpp |  43 ++++++-
>  3 files changed, 137 insertions(+), 34 deletions(-)
> 
> diff --git a/utils/v4l2-compliance/v4l2-compliance.cpp b/utils/v4l2-compliance/v4l2-compliance.cpp
> index e3556b1f..d7c10482 100644
> --- a/utils/v4l2-compliance/v4l2-compliance.cpp
> +++ b/utils/v4l2-compliance/v4l2-compliance.cpp
> @@ -1233,6 +1233,10 @@ void testNode(struct node &node, struct node &node_m2m_cap, struct node &expbuf_
>  	if (node.is_subdev()) {
>  		bool has_source = false;
>  		bool has_sink = false;
> +		struct v4l2_subdev_routing sd_routing[2] = {};
> +		struct v4l2_subdev_route sd_routes[2][256] = {};

256 comes from the same kernel check, right? Can you use the same
NUM_ROUTES_MAX define that you used in v4l2-ctl? Perhaps this define
should be added to utils/common/v4l2-info.h?

> +		bool has_routes = !!(subdevcap.capabilities & V4L2_SUBDEV_CAP_STREAMS);
> +		int ret;
>  
>  		node.frame_interval_pad = -1;
>  		node.enum_frame_interval_pad = -1;
> @@ -1244,6 +1248,22 @@ void testNode(struct node &node, struct node &node_m2m_cap, struct node &expbuf_
>  		}
>  		node.is_passthrough_subdev = has_source && has_sink;
>  
> +		if (has_routes) {
> +			for (unsigned which = V4L2_SUBDEV_FORMAT_TRY;
> +				which <= V4L2_SUBDEV_FORMAT_ACTIVE; which++) {
> +
> +				sd_routing[which].which = which;
> +				sd_routing[which].routes = (__u64)sd_routes[which];
> +				sd_routing[which].num_routes = 256;

Also NUM_ROUTES_MAX?

> +
> +				ret = doioctl(&node, VIDIOC_SUBDEV_G_ROUTING, &sd_routing[which]);
> +				if (ret) {
> +					fail("VIDIOC_SUBDEV_G_ROUTING: failed to get routing\n");
> +					sd_routing[which].num_routes = 0;
> +				}
> +			}
> +		}
> +

Regards,

	Hans

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v5 4/7] v4l2-ctl/compliance: Add simple routing test
  2023-05-29  8:50 ` [PATCH v5 4/7] v4l2-ctl/compliance: Add simple routing test Tomi Valkeinen
@ 2023-06-07 11:35   ` Hans Verkuil
  2023-06-07 11:48     ` Hans Verkuil
  0 siblings, 1 reply; 15+ messages in thread
From: Hans Verkuil @ 2023-06-07 11:35 UTC (permalink / raw)
  To: Tomi Valkeinen, linux-media, sakari.ailus, Jacopo Mondi,
	Laurent Pinchart, niklas.soderlund+renesas,
	Mauro Carvalho Chehab, satish.nagireddy
  Cc: Laurent Pinchart

On 29/05/2023 10:50, Tomi Valkeinen wrote:
> Add a very simple test for
> VIDIOC_SUBDEV_G_ROUTING/VIDIOC_SUBDEV_S_ROUTING.
> 
> We can't (at least at the moment) really know here what kind of routings
> the driver would accept, but we can test a VIDIOC_SUBDEV_G_ROUTING call,
> followed by a VIDIOC_SUBDEV_S_ROUTING call with the routing we
> received.
> 
> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> Reviewed-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
> ---
>  utils/v4l2-compliance/v4l2-compliance.cpp   | 12 ++++++++++++
>  utils/v4l2-compliance/v4l2-compliance.h     |  1 +
>  utils/v4l2-compliance/v4l2-test-subdevs.cpp | 16 ++++++++++++++++
>  3 files changed, 29 insertions(+)
> 
> diff --git a/utils/v4l2-compliance/v4l2-compliance.cpp b/utils/v4l2-compliance/v4l2-compliance.cpp
> index d7c10482..f082f569 100644
> --- a/utils/v4l2-compliance/v4l2-compliance.cpp
> +++ b/utils/v4l2-compliance/v4l2-compliance.cpp
> @@ -1249,6 +1249,18 @@ void testNode(struct node &node, struct node &node_m2m_cap, struct node &expbuf_
>  		node.is_passthrough_subdev = has_source && has_sink;
>  
>  		if (has_routes) {
> +			printf("Sub-Device routing ioctls:\n");
> +
> +			for (unsigned which = V4L2_SUBDEV_FORMAT_TRY;
> +				which <= V4L2_SUBDEV_FORMAT_ACTIVE; which++) {
> +
> +				printf("\ttest %s VIDIOC_SUBDEV_G_ROUTING/VIDIOC_SUBDEV_S_ROUTING: %s\n",
> +				       which ? "Active" : "Try",
> +				       ok(testSubDevRouting(&node, which)));
> +			}
> +
> +			printf("\n");
> +
>  			for (unsigned which = V4L2_SUBDEV_FORMAT_TRY;
>  				which <= V4L2_SUBDEV_FORMAT_ACTIVE; which++) {
>  
> diff --git a/utils/v4l2-compliance/v4l2-compliance.h b/utils/v4l2-compliance/v4l2-compliance.h
> index 0cd43980..35b2274b 100644
> --- a/utils/v4l2-compliance/v4l2-compliance.h
> +++ b/utils/v4l2-compliance/v4l2-compliance.h
> @@ -375,6 +375,7 @@ int testSubDevEnum(struct node *node, unsigned which, unsigned pad, unsigned str
>  int testSubDevFormat(struct node *node, unsigned which, unsigned pad, unsigned stream);
>  int testSubDevSelection(struct node *node, unsigned which, unsigned pad, unsigned stream);
>  int testSubDevFrameInterval(struct node *node, unsigned pad, unsigned stream);
> +int testSubDevRouting(struct node *node, unsigned which);
>  
>  // Buffer ioctl tests
>  int testReqBufs(struct node *node);
> diff --git a/utils/v4l2-compliance/v4l2-test-subdevs.cpp b/utils/v4l2-compliance/v4l2-test-subdevs.cpp
> index 07192bda..962d9244 100644
> --- a/utils/v4l2-compliance/v4l2-test-subdevs.cpp
> +++ b/utils/v4l2-compliance/v4l2-test-subdevs.cpp
> @@ -551,3 +551,19 @@ int testSubDevSelection(struct node *node, unsigned which, unsigned pad, unsigne
>  
>  	return have_sel ? 0 : ENOTTY;
>  }
> +
> +int testSubDevRouting(struct node *node, unsigned which)
> +{
> +	struct v4l2_subdev_routing routing = {};
> +	struct v4l2_subdev_route routes[256] = {};

NUM_ROUTES_MAX

> +
> +	routing.which = which;
> +	routing.routes = (__u64)&routes;
> +	routing.num_routes = 256;

NUM_ROUTES_MAX

Regards,

	Hans

> +
> +	fail_on_test(doioctl(node, VIDIOC_SUBDEV_G_ROUTING, &routing));
> +
> +	fail_on_test(doioctl(node, VIDIOC_SUBDEV_S_ROUTING, &routing));
> +
> +	return 0;
> +}


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v5 4/7] v4l2-ctl/compliance: Add simple routing test
  2023-06-07 11:35   ` Hans Verkuil
@ 2023-06-07 11:48     ` Hans Verkuil
  2023-06-07 11:57       ` Hans Verkuil
  0 siblings, 1 reply; 15+ messages in thread
From: Hans Verkuil @ 2023-06-07 11:48 UTC (permalink / raw)
  To: Tomi Valkeinen, linux-media, sakari.ailus, Jacopo Mondi,
	Laurent Pinchart, niklas.soderlund+renesas,
	Mauro Carvalho Chehab, satish.nagireddy
  Cc: Laurent Pinchart

On 07/06/2023 13:35, Hans Verkuil wrote:
> On 29/05/2023 10:50, Tomi Valkeinen wrote:
>> Add a very simple test for
>> VIDIOC_SUBDEV_G_ROUTING/VIDIOC_SUBDEV_S_ROUTING.
>>
>> We can't (at least at the moment) really know here what kind of routings
>> the driver would accept, but we can test a VIDIOC_SUBDEV_G_ROUTING call,
>> followed by a VIDIOC_SUBDEV_S_ROUTING call with the routing we
>> received.
>>
>> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
>> Reviewed-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
>> ---
>>  utils/v4l2-compliance/v4l2-compliance.cpp   | 12 ++++++++++++
>>  utils/v4l2-compliance/v4l2-compliance.h     |  1 +
>>  utils/v4l2-compliance/v4l2-test-subdevs.cpp | 16 ++++++++++++++++
>>  3 files changed, 29 insertions(+)
>>
>> diff --git a/utils/v4l2-compliance/v4l2-compliance.cpp b/utils/v4l2-compliance/v4l2-compliance.cpp
>> index d7c10482..f082f569 100644
>> --- a/utils/v4l2-compliance/v4l2-compliance.cpp
>> +++ b/utils/v4l2-compliance/v4l2-compliance.cpp
>> @@ -1249,6 +1249,18 @@ void testNode(struct node &node, struct node &node_m2m_cap, struct node &expbuf_
>>  		node.is_passthrough_subdev = has_source && has_sink;
>>  
>>  		if (has_routes) {
>> +			printf("Sub-Device routing ioctls:\n");
>> +
>> +			for (unsigned which = V4L2_SUBDEV_FORMAT_TRY;
>> +				which <= V4L2_SUBDEV_FORMAT_ACTIVE; which++) {
>> +
>> +				printf("\ttest %s VIDIOC_SUBDEV_G_ROUTING/VIDIOC_SUBDEV_S_ROUTING: %s\n",
>> +				       which ? "Active" : "Try",
>> +				       ok(testSubDevRouting(&node, which)));
>> +			}
>> +
>> +			printf("\n");
>> +
>>  			for (unsigned which = V4L2_SUBDEV_FORMAT_TRY;
>>  				which <= V4L2_SUBDEV_FORMAT_ACTIVE; which++) {
>>  
>> diff --git a/utils/v4l2-compliance/v4l2-compliance.h b/utils/v4l2-compliance/v4l2-compliance.h
>> index 0cd43980..35b2274b 100644
>> --- a/utils/v4l2-compliance/v4l2-compliance.h
>> +++ b/utils/v4l2-compliance/v4l2-compliance.h
>> @@ -375,6 +375,7 @@ int testSubDevEnum(struct node *node, unsigned which, unsigned pad, unsigned str
>>  int testSubDevFormat(struct node *node, unsigned which, unsigned pad, unsigned stream);
>>  int testSubDevSelection(struct node *node, unsigned which, unsigned pad, unsigned stream);
>>  int testSubDevFrameInterval(struct node *node, unsigned pad, unsigned stream);
>> +int testSubDevRouting(struct node *node, unsigned which);
>>  
>>  // Buffer ioctl tests
>>  int testReqBufs(struct node *node);
>> diff --git a/utils/v4l2-compliance/v4l2-test-subdevs.cpp b/utils/v4l2-compliance/v4l2-test-subdevs.cpp
>> index 07192bda..962d9244 100644
>> --- a/utils/v4l2-compliance/v4l2-test-subdevs.cpp
>> +++ b/utils/v4l2-compliance/v4l2-test-subdevs.cpp
>> @@ -551,3 +551,19 @@ int testSubDevSelection(struct node *node, unsigned which, unsigned pad, unsigne
>>  
>>  	return have_sel ? 0 : ENOTTY;
>>  }
>> +
>> +int testSubDevRouting(struct node *node, unsigned which)
>> +{
>> +	struct v4l2_subdev_routing routing = {};
>> +	struct v4l2_subdev_route routes[256] = {};
> 
> NUM_ROUTES_MAX
> 
>> +
>> +	routing.which = which;
>> +	routing.routes = (__u64)&routes;
>> +	routing.num_routes = 256;
> 
> NUM_ROUTES_MAX

Actually, you should also test the corner cases of NUM_ROUTES_MAX + 1
(that should fail, right?) and setting num_routes to 0 and check that
ENOSPC is returned and num_routes is updated.

Also verify that 'reserved' is zeroed (i.e. set it to 0xff here, then
check for 0 after the ioctl).

Regards,

	Hans

> 
> Regards,
> 
> 	Hans
> 
>> +
>> +	fail_on_test(doioctl(node, VIDIOC_SUBDEV_G_ROUTING, &routing));
>> +
>> +	fail_on_test(doioctl(node, VIDIOC_SUBDEV_S_ROUTING, &routing));
>> +
>> +	return 0;
>> +}
> 


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v5 4/7] v4l2-ctl/compliance: Add simple routing test
  2023-06-07 11:48     ` Hans Verkuil
@ 2023-06-07 11:57       ` Hans Verkuil
  2023-07-19  8:22         ` Tomi Valkeinen
  0 siblings, 1 reply; 15+ messages in thread
From: Hans Verkuil @ 2023-06-07 11:57 UTC (permalink / raw)
  To: Tomi Valkeinen, linux-media, sakari.ailus, Jacopo Mondi,
	Laurent Pinchart, niklas.soderlund+renesas,
	Mauro Carvalho Chehab, satish.nagireddy
  Cc: Laurent Pinchart

On 07/06/2023 13:48, Hans Verkuil wrote:
> On 07/06/2023 13:35, Hans Verkuil wrote:
>> On 29/05/2023 10:50, Tomi Valkeinen wrote:
>>> Add a very simple test for
>>> VIDIOC_SUBDEV_G_ROUTING/VIDIOC_SUBDEV_S_ROUTING.
>>>
>>> We can't (at least at the moment) really know here what kind of routings
>>> the driver would accept, but we can test a VIDIOC_SUBDEV_G_ROUTING call,
>>> followed by a VIDIOC_SUBDEV_S_ROUTING call with the routing we
>>> received.
>>>
>>> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
>>> Reviewed-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
>>> ---
>>>  utils/v4l2-compliance/v4l2-compliance.cpp   | 12 ++++++++++++
>>>  utils/v4l2-compliance/v4l2-compliance.h     |  1 +
>>>  utils/v4l2-compliance/v4l2-test-subdevs.cpp | 16 ++++++++++++++++
>>>  3 files changed, 29 insertions(+)
>>>
>>> diff --git a/utils/v4l2-compliance/v4l2-compliance.cpp b/utils/v4l2-compliance/v4l2-compliance.cpp
>>> index d7c10482..f082f569 100644
>>> --- a/utils/v4l2-compliance/v4l2-compliance.cpp
>>> +++ b/utils/v4l2-compliance/v4l2-compliance.cpp
>>> @@ -1249,6 +1249,18 @@ void testNode(struct node &node, struct node &node_m2m_cap, struct node &expbuf_
>>>  		node.is_passthrough_subdev = has_source && has_sink;
>>>  
>>>  		if (has_routes) {
>>> +			printf("Sub-Device routing ioctls:\n");
>>> +
>>> +			for (unsigned which = V4L2_SUBDEV_FORMAT_TRY;
>>> +				which <= V4L2_SUBDEV_FORMAT_ACTIVE; which++) {
>>> +
>>> +				printf("\ttest %s VIDIOC_SUBDEV_G_ROUTING/VIDIOC_SUBDEV_S_ROUTING: %s\n",
>>> +				       which ? "Active" : "Try",
>>> +				       ok(testSubDevRouting(&node, which)));
>>> +			}
>>> +
>>> +			printf("\n");
>>> +
>>>  			for (unsigned which = V4L2_SUBDEV_FORMAT_TRY;
>>>  				which <= V4L2_SUBDEV_FORMAT_ACTIVE; which++) {
>>>  
>>> diff --git a/utils/v4l2-compliance/v4l2-compliance.h b/utils/v4l2-compliance/v4l2-compliance.h
>>> index 0cd43980..35b2274b 100644
>>> --- a/utils/v4l2-compliance/v4l2-compliance.h
>>> +++ b/utils/v4l2-compliance/v4l2-compliance.h
>>> @@ -375,6 +375,7 @@ int testSubDevEnum(struct node *node, unsigned which, unsigned pad, unsigned str
>>>  int testSubDevFormat(struct node *node, unsigned which, unsigned pad, unsigned stream);
>>>  int testSubDevSelection(struct node *node, unsigned which, unsigned pad, unsigned stream);
>>>  int testSubDevFrameInterval(struct node *node, unsigned pad, unsigned stream);
>>> +int testSubDevRouting(struct node *node, unsigned which);
>>>  
>>>  // Buffer ioctl tests
>>>  int testReqBufs(struct node *node);
>>> diff --git a/utils/v4l2-compliance/v4l2-test-subdevs.cpp b/utils/v4l2-compliance/v4l2-test-subdevs.cpp
>>> index 07192bda..962d9244 100644
>>> --- a/utils/v4l2-compliance/v4l2-test-subdevs.cpp
>>> +++ b/utils/v4l2-compliance/v4l2-test-subdevs.cpp
>>> @@ -551,3 +551,19 @@ int testSubDevSelection(struct node *node, unsigned which, unsigned pad, unsigne
>>>  
>>>  	return have_sel ? 0 : ENOTTY;
>>>  }
>>> +
>>> +int testSubDevRouting(struct node *node, unsigned which)
>>> +{
>>> +	struct v4l2_subdev_routing routing = {};
>>> +	struct v4l2_subdev_route routes[256] = {};
>>
>> NUM_ROUTES_MAX
>>
>>> +
>>> +	routing.which = which;
>>> +	routing.routes = (__u64)&routes;
>>> +	routing.num_routes = 256;
>>
>> NUM_ROUTES_MAX
> 
> Actually, you should also test the corner cases of NUM_ROUTES_MAX + 1
> (that should fail, right?) and setting num_routes to 0 and check that
> ENOSPC is returned and num_routes is updated.
> 
> Also verify that 'reserved' is zeroed (i.e. set it to 0xff here, then
> check for 0 after the ioctl).

I assume also that if num_routes is set to 256, then G_ROUTING is called,
num_routes is updated to the actual number of routes? The spec does not
actually state that.

And what should happen when num_routes is set to 0 and S_ROUTING is called?
Would that clear all routes? Or is that an error? And can G_ROUTING actually
return num_routes == 0 as well if there are no routes defined?

Additional checks you can do is to verify that all sink/source pads are valid
and that 'flags' is valid.

Another test is setting a pad or stream to an invalid value and verify that EINVAL
is returned.

Note that the spec says that E2BIG is returned for S_ROUTING, but it is returned
for G_ROUTING as well, that should be updated.

Regards,

	Hans

> 
> Regards,
> 
> 	Hans
> 
>>
>> Regards,
>>
>> 	Hans
>>
>>> +
>>> +	fail_on_test(doioctl(node, VIDIOC_SUBDEV_G_ROUTING, &routing));
>>> +
>>> +	fail_on_test(doioctl(node, VIDIOC_SUBDEV_S_ROUTING, &routing));
>>> +
>>> +	return 0;
>>> +}
>>
> 


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v5 1/7] v4l2-ctl: Add routing and streams support
  2023-06-07 11:28   ` Hans Verkuil
@ 2023-07-18 13:51     ` Tomi Valkeinen
  0 siblings, 0 replies; 15+ messages in thread
From: Tomi Valkeinen @ 2023-07-18 13:51 UTC (permalink / raw)
  To: Hans Verkuil, linux-media, sakari.ailus, Jacopo Mondi,
	Laurent Pinchart, niklas.soderlund+renesas,
	Mauro Carvalho Chehab, satish.nagireddy

On 07/06/2023 14:28, Hans Verkuil wrote:
> On 29/05/2023 10:49, Tomi Valkeinen wrote:
>> Add support to get and set subdev routes and to get and set
>> configurations per stream.
>>
>> Based on work from Jacopo Mondi <jacopo@jmondi.org> and
>> Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>.
>>
>> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
>> ---
>>   utils/v4l2-ctl/v4l2-ctl-subdev.cpp | 310 ++++++++++++++++++++++++++---
>>   utils/v4l2-ctl/v4l2-ctl.cpp        |   2 +
>>   utils/v4l2-ctl/v4l2-ctl.h          |   2 +
>>   3 files changed, 281 insertions(+), 33 deletions(-)
>>
>> diff --git a/utils/v4l2-ctl/v4l2-ctl-subdev.cpp b/utils/v4l2-ctl/v4l2-ctl-subdev.cpp
>> index 33cc1342..fafb7d92 100644
>> --- a/utils/v4l2-ctl/v4l2-ctl-subdev.cpp
>> +++ b/utils/v4l2-ctl/v4l2-ctl-subdev.cpp
>> @@ -1,5 +1,13 @@
>>   #include "v4l2-ctl.h"
>>   
>> +#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
>> +
>> +/*
>> + * The max value comes from a check in the kernel source code
>> + * drivers/media/v4l2-core/v4l2-ioctl.c check_array_args()
>> + */
>> +#define NUM_ROUTES_MAX 256
>> +
>>   struct mbus_name {
>>   	const char *name;
>>   	__u32 code;
>> @@ -19,45 +27,57 @@ static const struct mbus_name mbus_names[] = {
>>   #define SelectionFlags 		(1L<<4)
>>   
>>   static __u32 list_mbus_codes_pad;
>> +static __u32 list_mbus_codes_stream = 0;
>>   static __u32 get_fmt_pad;
>> +static __u32 get_fmt_stream = 0;
>>   static __u32 get_sel_pad;
>> +static __u32 get_sel_stream = 0;
>>   static __u32 get_fps_pad;
>> +static __u32 get_fps_stream = 0;
>>   static int get_sel_target = -1;
>>   static unsigned int set_selection;
>>   static struct v4l2_subdev_selection vsel;
>>   static unsigned int set_fmt;
>>   static __u32 set_fmt_pad;
>> +static __u32 set_fmt_stream = 0;
>>   static struct v4l2_mbus_framefmt ffmt;
>>   static struct v4l2_subdev_frame_size_enum frmsize;
>>   static struct v4l2_subdev_frame_interval_enum frmival;
>>   static __u32 set_fps_pad;
>> +static __u32 set_fps_stream = 0;
>>   static double set_fps;
>> +static struct v4l2_subdev_routing routing;
>> +static struct v4l2_subdev_route routes[NUM_ROUTES_MAX];
>>   
>>   void subdev_usage()
>>   {
>>   	printf("\nSub-Device options:\n"
>> -	       "  --list-subdev-mbus-codes <pad>\n"
>> +	       "  --list-subdev-mbus-codes pad=<pad>,stream=<stream>\n"
>>   	       "                      display supported mediabus codes for this pad (0 is default)\n"
>>   	       "                      [VIDIOC_SUBDEV_ENUM_MBUS_CODE]\n"
>> -	       "  --list-subdev-framesizes pad=<pad>,code=<code>\n"
>> +	       "  --list-subdev-framesizes pad=<pad>,stream=<stream>,code=<code>\n"
>>   	       "                     list supported framesizes for this pad and code\n"
>>   	       "                     [VIDIOC_SUBDEV_ENUM_FRAME_SIZE]\n"
>>   	       "                     <code> is the value of the mediabus code\n"
>> -	       "  --list-subdev-frameintervals pad=<pad>,width=<w>,height=<h>,code=<code>\n"
>> +	       "  --list-subdev-frameintervals pad=<pad>,stream=<stream>,width=<w>,height=<h>,code=<code>\n"
>>   	       "                     list supported frame intervals for this pad and code and\n"
>>   	       "                     the given width and height [VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL]\n"
>>   	       "                     <code> is the value of the mediabus code\n"
>> -	       "  --get-subdev-fmt [<pad>]\n"
>> -	       "     		     query the frame format for the given pad [VIDIOC_SUBDEV_G_FMT]\n"
>> -	       "  --get-subdev-selection pad=<pad>,target=<target>\n"
>> +	       "  --get-subdev-fmt pad=<pad>,stream=<stream>\n"
>> +	       "     		     query the frame format for the given pad and optional stream [VIDIOC_SUBDEV_G_FMT]\n"
>> +	       "		     <pad> the pad to get the format from\n"
>> +	       "		     <stream> the stream to get the format from (0 if not specified)\n"
> 
> Here you explicitly state that <stream> is optional, but you did not do
> that for --list-subdev-mbus-codes, --list-subdev-framesizes and --list-subdev-frameintervals
> and other options below (just go through the list).
> 
> I also see that the 'pad' suboption is only sometimes documented.
> In particular it is not clear when 'pad' is optional (defaulting
> to 0) or required.
> 
> In some places I used [<pad>] to indicate that it is optional,
> but I think it is better to explicitly state that <pad> defaults
> to 0, or something along those lines.
> 
> If it isn't already the case, my preference is that both pad and
> stream are optional and both default to 0.

I think for most of these (pad, stream, code, width, height, etc...) the 
parameters are optional and default to 0. These parameters are used to 
fill in fields in various static structs (initialized to 0 implicitly) 
passed to the ioctls, and no checks are done. With a quick look, I 
didn't find any parameter that would be required.

Maybe it's easier to just mention that all parameters are optional and 
default to 0 unless otherwise noted?

>> +	       "  --get-subdev-selection pad=<pad>,stream=<stream>,target=<target>\n"
>>   	       "                     query the frame selection rectangle [VIDIOC_SUBDEV_G_SELECTION]\n"
>>   	       "                     See --set-subdev-selection command for the valid <target> values.\n"
>> -	       "  --get-subdev-fps [<pad>]\n"
>> +	       "  --get-subdev-fps pad=<pad>,stream=<stream>\n"
>>   	       "                     query the frame rate [VIDIOC_SUBDEV_G_FRAME_INTERVAL]\n"
>>   	       "  --set-subdev-fmt   (for testing only, otherwise use media-ctl)\n"
>> -	       "  --try-subdev-fmt pad=<pad>,width=<w>,height=<h>,code=<code>,field=<f>,colorspace=<c>,\n"
>> +	       "  --try-subdev-fmt pad=<pad>,stream=<stream>,width=<w>,height=<h>,code=<code>,field=<f>,colorspace=<c>,\n"
>>   	       "                   xfer=<xf>,ycbcr=<y>,hsv=<hsv>,quantization=<q>\n"
>> -	       "                     set the frame format [VIDIOC_SUBDEV_S_FMT]\n"
>> +	       "                     set the frame format for the given pad and optional stream [VIDIOC_SUBDEV_S_FMT]\n"
>> +	       "                     <pad> the pad to get the format from\n"
>> +	       "                     <stream> the stream to get the format from (0 if not specified)\n"
>>   	       "                     <code> is the value of the mediabus code\n"
>>   	       "                     <f> can be one of the following field layouts:\n"
>>   	       "                       any, none, top, bottom, interlaced, seq_tb, seq_bt,\n"
>> @@ -74,31 +94,74 @@ void subdev_usage()
>>   	       "                     <q> can be one of the following quantization methods:\n"
>>   	       "                       default, full-range, lim-range\n"
>>   	       "  --set-subdev-selection (for testing only, otherwise use media-ctl)\n"
>> -	       "  --try-subdev-selection pad=<pad>,target=<target>,flags=<flags>,\n"
>> +	       "  --try-subdev-selection pad=<pad>,stream=<stream>,target=<target>,flags=<flags>,\n"
>>   	       "                         top=<x>,left=<y>,width=<w>,height=<h>\n"
>>   	       "                     set the video capture selection rectangle [VIDIOC_SUBDEV_S_SELECTION]\n"
>>   	       "                     target=crop|crop_bounds|crop_default|compose|compose_bounds|\n"
>>   	       "                            compose_default|compose_padded|native_size\n"
>>   	       "                     flags=le|ge|keep-config\n"
>> -	       "  --set-subdev-fps pad=<pad>,fps=<fps> (for testing only, otherwise use media-ctl)\n"
>> +	       "  --set-subdev-fps pad=<pad>,stream=<stream>,fps=<fps> (for testing only, otherwise use media-ctl)\n"
>>   	       "                     set the frame rate [VIDIOC_SUBDEV_S_FRAME_INTERVAL]\n"
>> +	       "  --get-routing      Print the route topology\n"
>> +	       "  --set-routing <routes>\n"
>> +	       "                     Comma-separated list of route descriptors to setup\n"
>> +	       "\n"
>> +	       "Routes are defined as\n"
>> +	       "	routes		= route { ',' route } ;\n"
>> +	       "	route		= sink '->' source '[' flags ']' ;\n"
>> +	       "	sink		= sink-pad '/' sink-stream ;\n"
>> +	       "	source		= source-pad '/' source-stream ;\n"
>> +	       "\n"
>> +	       "where\n"
>> +	       "	sink-pad	= Pad numeric identifier for sink\n"
>> +	       "	sink-stream	= Stream numeric identifier for sink\n"
>> +	       "	source-pad	= Pad numeric identifier for source\n"
>> +	       "	source-stream	= Stream numeric identifier for source\n"
>> +	       "	flags		= Route flags (0: inactive, 1: active)\n"
>>   	       );
>>   }
> 
> Regards,
> 
> 	Hans


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v5 4/7] v4l2-ctl/compliance: Add simple routing test
  2023-06-07 11:57       ` Hans Verkuil
@ 2023-07-19  8:22         ` Tomi Valkeinen
  0 siblings, 0 replies; 15+ messages in thread
From: Tomi Valkeinen @ 2023-07-19  8:22 UTC (permalink / raw)
  To: Hans Verkuil, linux-media, sakari.ailus, Jacopo Mondi,
	Laurent Pinchart, niklas.soderlund+renesas,
	Mauro Carvalho Chehab, satish.nagireddy
  Cc: Laurent Pinchart

On 07/06/2023 14:57, Hans Verkuil wrote:
> On 07/06/2023 13:48, Hans Verkuil wrote:
>> On 07/06/2023 13:35, Hans Verkuil wrote:
>>> On 29/05/2023 10:50, Tomi Valkeinen wrote:
>>>> Add a very simple test for
>>>> VIDIOC_SUBDEV_G_ROUTING/VIDIOC_SUBDEV_S_ROUTING.
>>>>
>>>> We can't (at least at the moment) really know here what kind of routings
>>>> the driver would accept, but we can test a VIDIOC_SUBDEV_G_ROUTING call,
>>>> followed by a VIDIOC_SUBDEV_S_ROUTING call with the routing we
>>>> received.
>>>>
>>>> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
>>>> Reviewed-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
>>>> ---
>>>>   utils/v4l2-compliance/v4l2-compliance.cpp   | 12 ++++++++++++
>>>>   utils/v4l2-compliance/v4l2-compliance.h     |  1 +
>>>>   utils/v4l2-compliance/v4l2-test-subdevs.cpp | 16 ++++++++++++++++
>>>>   3 files changed, 29 insertions(+)
>>>>
>>>> diff --git a/utils/v4l2-compliance/v4l2-compliance.cpp b/utils/v4l2-compliance/v4l2-compliance.cpp
>>>> index d7c10482..f082f569 100644
>>>> --- a/utils/v4l2-compliance/v4l2-compliance.cpp
>>>> +++ b/utils/v4l2-compliance/v4l2-compliance.cpp
>>>> @@ -1249,6 +1249,18 @@ void testNode(struct node &node, struct node &node_m2m_cap, struct node &expbuf_
>>>>   		node.is_passthrough_subdev = has_source && has_sink;
>>>>   
>>>>   		if (has_routes) {
>>>> +			printf("Sub-Device routing ioctls:\n");
>>>> +
>>>> +			for (unsigned which = V4L2_SUBDEV_FORMAT_TRY;
>>>> +				which <= V4L2_SUBDEV_FORMAT_ACTIVE; which++) {
>>>> +
>>>> +				printf("\ttest %s VIDIOC_SUBDEV_G_ROUTING/VIDIOC_SUBDEV_S_ROUTING: %s\n",
>>>> +				       which ? "Active" : "Try",
>>>> +				       ok(testSubDevRouting(&node, which)));
>>>> +			}
>>>> +
>>>> +			printf("\n");
>>>> +
>>>>   			for (unsigned which = V4L2_SUBDEV_FORMAT_TRY;
>>>>   				which <= V4L2_SUBDEV_FORMAT_ACTIVE; which++) {
>>>>   
>>>> diff --git a/utils/v4l2-compliance/v4l2-compliance.h b/utils/v4l2-compliance/v4l2-compliance.h
>>>> index 0cd43980..35b2274b 100644
>>>> --- a/utils/v4l2-compliance/v4l2-compliance.h
>>>> +++ b/utils/v4l2-compliance/v4l2-compliance.h
>>>> @@ -375,6 +375,7 @@ int testSubDevEnum(struct node *node, unsigned which, unsigned pad, unsigned str
>>>>   int testSubDevFormat(struct node *node, unsigned which, unsigned pad, unsigned stream);
>>>>   int testSubDevSelection(struct node *node, unsigned which, unsigned pad, unsigned stream);
>>>>   int testSubDevFrameInterval(struct node *node, unsigned pad, unsigned stream);
>>>> +int testSubDevRouting(struct node *node, unsigned which);
>>>>   
>>>>   // Buffer ioctl tests
>>>>   int testReqBufs(struct node *node);
>>>> diff --git a/utils/v4l2-compliance/v4l2-test-subdevs.cpp b/utils/v4l2-compliance/v4l2-test-subdevs.cpp
>>>> index 07192bda..962d9244 100644
>>>> --- a/utils/v4l2-compliance/v4l2-test-subdevs.cpp
>>>> +++ b/utils/v4l2-compliance/v4l2-test-subdevs.cpp
>>>> @@ -551,3 +551,19 @@ int testSubDevSelection(struct node *node, unsigned which, unsigned pad, unsigne
>>>>   
>>>>   	return have_sel ? 0 : ENOTTY;
>>>>   }
>>>> +
>>>> +int testSubDevRouting(struct node *node, unsigned which)
>>>> +{
>>>> +	struct v4l2_subdev_routing routing = {};
>>>> +	struct v4l2_subdev_route routes[256] = {};
>>>
>>> NUM_ROUTES_MAX
>>>
>>>> +
>>>> +	routing.which = which;
>>>> +	routing.routes = (__u64)&routes;
>>>> +	routing.num_routes = 256;
>>>
>>> NUM_ROUTES_MAX

Yes, a common NUM_ROUTES_MAX in utils/common/v4l2-info.h makes sense.

>> Actually, you should also test the corner cases of NUM_ROUTES_MAX + 1

Yes, that fails, but I don't think it's a good test as the maximum 
number of routes is not part of the uAPI, it's just an arbitrary sanity 
limit.

>> (that should fail, right?) and setting num_routes to 0 and check that
>> ENOSPC is returned and num_routes is updated.

Yes, I'll add that. However, if the subdev has 0 routes, the ioctl can 
succeed and num_routes is 0. I'll check that too.

>> Also verify that 'reserved' is zeroed (i.e. set it to 0xff here, then
>> check for 0 after the ioctl).

Ok.

> I assume also that if num_routes is set to 256, then G_ROUTING is called,
> num_routes is updated to the actual number of routes? The spec does not
> actually state that.

Hmm, indeed, the doc doesn't say that num_routes is updated on success. 
I need to add that.

> And what should happen when num_routes is set to 0 and S_ROUTING is called?
> Would that clear all routes? Or is that an error? And can G_ROUTING actually
> return num_routes == 0 as well if there are no routes defined?

Yes, it would clear the routes, and G_ROUTING would return num_routes == 
0. However, this is really up to the driver, and it can return an error 
for S_ROUTING with num_routes == 0.

> Additional checks you can do is to verify that all sink/source pads are valid
> and that 'flags' is valid.

Ok. But if I understand the code right, I can only do that when the user 
is testing a media device, not if the user is testing a single 
subdevice. I can do this part of the test when node->pads is non-NULL.

> Another test is setting a pad or stream to an invalid value and verify that EINVAL
> is returned.

There are no invalid stream values, but I can test for invalid pads. 
However, here I again need a media device as above.

> Note that the spec says that E2BIG is returned for S_ROUTING, but it is returned
> for G_ROUTING as well, that should be updated.

For the 256+ case? As I mentioned above, it's an arbitrary "hidden" 
limit, which can be increased in the kernel when needed. So, in theory, 
G_ROUTING should accept any num_routes value, but to avoid large 
allocations in the kernel side, it's at the moment limited to 256.

Maybe a better kernel side implementation would be to silently limit the 
kernel side arrays to 256, even if the userspace provides a larger array.

  Tomi


^ permalink raw reply	[flat|nested] 15+ messages in thread

end of thread, other threads:[~2023-07-19  8:22 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-05-29  8:49 [PATCH v5 0/7] v4l-utils: Support multiplexed streams Tomi Valkeinen
2023-05-29  8:49 ` [PATCH v5 1/7] v4l2-ctl: Add routing and streams support Tomi Valkeinen
2023-06-07 11:28   ` Hans Verkuil
2023-07-18 13:51     ` Tomi Valkeinen
2023-05-29  8:49 ` [PATCH v5 2/7] media-ctl: Add support for routes and streams Tomi Valkeinen
2023-05-29  8:49 ` [PATCH v5 3/7] v4l2-ctl/compliance: Add routing and streams multiplexed streams Tomi Valkeinen
2023-06-07 11:34   ` Hans Verkuil
2023-05-29  8:50 ` [PATCH v5 4/7] v4l2-ctl/compliance: Add simple routing test Tomi Valkeinen
2023-06-07 11:35   ` Hans Verkuil
2023-06-07 11:48     ` Hans Verkuil
2023-06-07 11:57       ` Hans Verkuil
2023-07-19  8:22         ` Tomi Valkeinen
2023-05-29  8:50 ` [PATCH v5 5/7] media-ctl: Check for Streams API support Tomi Valkeinen
2023-05-29  8:50 ` [PATCH v5 6/7] utils/common: Set V4L2_SUBDEV_CLIENT_CAP_STREAMS for subdevs Tomi Valkeinen
2023-05-29  8:50 ` [PATCH v5 7/7] v4l2-ctl: Check for Streams API support Tomi Valkeinen

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).