linux-media.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v4 0/8] v4l-utils: Support multiplexed streams
@ 2023-04-21 12:44 Tomi Valkeinen
  2023-04-21 12:44 ` [PATCH v4 1/8] v4l2-ctl: Add routing and streams support Tomi Valkeinen
                   ` (7 more replies)
  0 siblings, 8 replies; 23+ messages in thread
From: Tomi Valkeinen @ 2023-04-21 12:44 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,

You can find the diff to v3 below. Main changes in v4:

- Add support for the client capabilities ioctl. Note: there is a HACK
  patch that adds the relevant changes to the kernel headers. The HACK
  patch should be dropped before merging, when the client capabilities is
  in upstream kernel.

- Simple v4l2-compliance test for routing, which just gets and sets the
  routing.

- Add legacy style command line parameter parsing for v4l2-ctl

 Tomi

Tomi Valkeinen (8):
  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
  HACK: include/linux: Add client capabilities
  media-ctl: Check for Streams API support
  utils/common: Set V4L2_SUBDEV_CLIENT_CAP_STREAMS for subdevs
  v4l2-ctl: Check for Streams API support

 include/linux/v4l2-subdev.h                 |  21 ++
 utils/common/cv4l-helpers.h                 |   1 +
 utils/common/v4l-helpers.h                  |  17 +
 utils/media-ctl/libmediactl.c               |  41 +++
 utils/media-ctl/libv4l2subdev.c             | 335 ++++++++++++++++--
 utils/media-ctl/media-ctl.c                 | 121 ++++++-
 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                |  58 +++-
 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 +
 17 files changed, 1072 insertions(+), 124 deletions(-)

Interdiff against v3:
diff --git a/include/linux/v4l2-subdev.h b/include/linux/v4l2-subdev.h
index 654d659d..4a195b68 100644
--- a/include/linux/v4l2-subdev.h
+++ b/include/linux/v4l2-subdev.h
@@ -233,6 +233,24 @@ struct v4l2_subdev_routing {
 	__u32 reserved[6];
 };
 
+/*
+ * The client is aware of streams. Setting this flag enables the use of 'stream'
+ * fields (referring to the stream number) with various ioctls. If this is not
+ * set (which is the default), the 'stream' fields will be forced to 0 by the
+ * kernel.
+ */
+ #define V4L2_SUBDEV_CLIENT_CAP_STREAMS		(1U << 0)
+
+/**
+ * struct v4l2_subdev_client_capability - Capabilities of the client accessing
+ *					  the subdev
+ *
+ * @capabilities: A bitmask of V4L2_SUBDEV_CLIENT_CAP_* flags.
+ */
+struct v4l2_subdev_client_capability {
+	__u64 capabilities;
+};
+
 /* Backwards compatibility define --- to be removed */
 #define v4l2_subdev_edid v4l2_edid
 
@@ -250,6 +268,9 @@ struct v4l2_subdev_routing {
 #define VIDIOC_SUBDEV_S_SELECTION		_IOWR('V', 62, struct v4l2_subdev_selection)
 #define VIDIOC_SUBDEV_G_ROUTING			_IOWR('V', 38, struct v4l2_subdev_routing)
 #define VIDIOC_SUBDEV_S_ROUTING			_IOWR('V', 39, struct v4l2_subdev_routing)
+#define VIDIOC_SUBDEV_G_CLIENT_CAP		_IOR('V',  101, struct v4l2_subdev_client_capability)
+#define VIDIOC_SUBDEV_S_CLIENT_CAP		_IOWR('V',  102, struct v4l2_subdev_client_capability)
+
 /* The following ioctls are identical to the ioctls in videodev2.h */
 #define VIDIOC_SUBDEV_G_STD			_IOR('V', 23, v4l2_std_id)
 #define VIDIOC_SUBDEV_S_STD			_IOW('V', 24, v4l2_std_id)
diff --git a/utils/common/cv4l-helpers.h b/utils/common/cv4l-helpers.h
index 3cee372b..502df6ac 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_support() const { return subdev_supports_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..2dd7f061 100644
--- a/utils/common/v4l-helpers.h
+++ b/utils/common/v4l-helpers.h
@@ -39,6 +39,7 @@ struct v4l_fd {
 	bool have_selection;
 	bool is_subdev;
 	bool is_media;
+	bool subdev_supports_streams;
 
 	int (*open)(struct v4l_fd *f, const char *file, int oflag, ...);
 	int (*close)(struct v4l_fd *f);
@@ -507,6 +508,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 +535,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->subdev_supports_streams = subdev_streams && client_streams;
+
 	return f->fd;
 }
 
diff --git a/utils/media-ctl/libmediactl.c b/utils/media-ctl/libmediactl.c
index 0b591d69..c32fe56a 100644
--- a/utils/media-ctl/libmediactl.c
+++ b/utils/media-ctl/libmediactl.c
@@ -896,7 +896,7 @@ struct media_pad *media_parse_pad_stream(struct media_device *media,
 		s = strtoul(p, &ep, 10);
 
 		if (ep == p) {
-			printf("Unable to parse stream: '%s'\n", orig_p);
+			media_dbg(media, "Unable to parse stream: '%s'\n", orig_p);
 			if (endp)
 				*endp = (char*)p;
 			return NULL;
diff --git a/utils/media-ctl/libv4l2subdev.c b/utils/media-ctl/libv4l2subdev.c
index d203e5b4..186708ff 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;
@@ -210,6 +246,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;
@@ -221,7 +262,9 @@ int v4l2_subdev_get_routing(struct media_entity *entity,
 			    struct v4l2_subdev_route **routes,
 			    unsigned int *num_routes)
 {
-	struct v4l2_subdev_routing routing = { 0 };
+	struct v4l2_subdev_routing routing = {
+		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
+	};
 	struct v4l2_subdev_route *r;
 	int ret;
 
@@ -229,7 +272,8 @@ int v4l2_subdev_get_routing(struct media_entity *entity,
 	if (ret < 0)
 		return ret;
 
-	routing.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+	if (!entity->supports_streams)
+		return -ENOTSUP;
 
 	ret = ioctl(entity->fd, VIDIOC_SUBDEV_G_ROUTING, &routing);
 	if (ret == -1 && errno != ENOSPC)
@@ -341,6 +385,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 +413,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;
@@ -386,7 +440,6 @@ static int v4l2_subdev_parse_setup_route(struct media_device *media,
 	/* sink pad/stream */
 
 	r->sink_pad = strtoul(p, &end, 10);
-
 	if (*end != '/') {
 		media_dbg(media, "Expected '/'\n");
 		return -EINVAL;
@@ -407,7 +460,6 @@ static int v4l2_subdev_parse_setup_route(struct media_device *media,
 	/* source pad/stream */
 
 	r->source_pad = strtoul(p, &end, 10);
-
 	if (*end != '/') {
 		media_dbg(media, "Expected '/'\n");
 		return -EINVAL;
@@ -431,7 +483,6 @@ static int v4l2_subdev_parse_setup_route(struct media_device *media,
 	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;
@@ -516,7 +567,8 @@ int v4l2_subdev_parse_setup_routes(struct media_device *media, const char *p)
 
 	ret = v4l2_subdev_set_routing(entity, routes, num_routes);
 	if (ret) {
-		printf("VIDIOC_SUBDEV_S_ROUTING failed: %d\n", ret);
+		media_dbg(entity->media, "VIDIOC_SUBDEV_S_ROUTING failed: %d\n",
+			  ret);
 		goto out;
 	}
 
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;
diff --git a/utils/media-ctl/options.c b/utils/media-ctl/options.c
index 58ddec3c..3c408a1b 100644
--- a/utils/media-ctl/options.c
+++ b/utils/media-ctl/options.c
@@ -105,7 +105,7 @@ static void usage(const char *argv0)
 	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, immutable - 0x2, source - 0x4)\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");
diff --git a/utils/v4l2-compliance/v4l2-compliance.cpp b/utils/v4l2-compliance/v4l2-compliance.cpp
index a147604c..7c3a688b 100644
--- a/utils/v4l2-compliance/v4l2-compliance.cpp
+++ b/utils/v4l2-compliance/v4l2-compliance.cpp
@@ -1241,6 +1241,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;
+}
diff --git a/utils/v4l2-ctl/v4l2-ctl-subdev.cpp b/utils/v4l2-ctl/v4l2-ctl-subdev.cpp
index 81236451..ec70b52b 100644
--- a/utils/v4l2-ctl/v4l2-ctl-subdev.cpp
+++ b/utils/v4l2-ctl/v4l2-ctl-subdev.cpp
@@ -124,9 +124,17 @@ void subdev_usage()
 void subdev_cmd(int ch, char *optarg)
 {
 	char *value, *subs;
+	char *endp;
 
 	switch (ch) {
 	case OptListSubDevMBusCodes:
+		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[] = {
@@ -209,6 +217,13 @@ void subdev_cmd(int ch, char *optarg)
 		}
 		break;
 	case OptGetSubDevFormat:
+		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[] = {
@@ -263,6 +278,13 @@ void subdev_cmd(int ch, char *optarg)
 		}
 		break;
 	case OptGetSubDevFPS:
+		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[] = {
@@ -547,6 +569,11 @@ void subdev_set(cv4l_fd &_fd)
 	if (options[OptSetSubDevFormat] || options[OptTrySubDevFormat]) {
 		struct v4l2_subdev_format fmt;
 
+		if (!_fd.has_streams_support() && 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;
@@ -595,6 +622,11 @@ void subdev_set(cv4l_fd &_fd)
 	if (options[OptSetSubDevSelection] || options[OptTrySubDevSelection]) {
 		struct v4l2_subdev_selection sel;
 
+		if (!_fd.has_streams_support() && vsel.stream) {
+			printf("Streams API not supported.\n");
+			return;
+		}
+
 		memset(&sel, 0, sizeof(sel));
 		sel.pad = vsel.pad;
 		sel.stream = vsel.stream;
@@ -627,6 +659,11 @@ void subdev_set(cv4l_fd &_fd)
 	if (options[OptSetSubDevFPS]) {
 		struct v4l2_subdev_frame_interval fival;
 
+		if (!_fd.has_streams_support() && 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;
@@ -652,6 +689,11 @@ void subdev_set(cv4l_fd &_fd)
 		}
 	}
 	if (options[OptSetRouting]) {
+		if (!_fd.has_streams_support()) {
+			printf("Streams API not supported.\n");
+			return;
+		}
+
 		if (doioctl(fd, VIDIOC_SUBDEV_S_ROUTING, &routing) == 0)
 			printf("Routing set\n");
 	}
@@ -709,6 +751,11 @@ void subdev_get(cv4l_fd &_fd)
 	if (options[OptGetSubDevFormat]) {
 		struct v4l2_subdev_format fmt;
 
+		if (!_fd.has_streams_support() && 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;
@@ -723,6 +770,11 @@ void subdev_get(cv4l_fd &_fd)
 		struct v4l2_subdev_selection sel;
 		unsigned idx = 0;
 
+		if (!_fd.has_streams_support() && 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;
@@ -745,6 +797,11 @@ void subdev_get(cv4l_fd &_fd)
 	if (options[OptGetSubDevFPS]) {
 		struct v4l2_subdev_frame_interval fival;
 
+		if (!_fd.has_streams_support() && 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;
@@ -762,6 +819,11 @@ void subdev_get(cv4l_fd &_fd)
 	}
 
 	if (options[OptGetRouting]) {
+		if (!_fd.has_streams_support()) {
+			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;
@@ -845,11 +907,21 @@ void subdev_list(cv4l_fd &_fd)
 	int fd = _fd.g_fd();
 
 	if (options[OptListSubDevMBusCodes]) {
+		if (!_fd.has_streams_support() && 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_support() && 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;
@@ -860,6 +932,11 @@ void subdev_list(cv4l_fd &_fd)
 		}
 	}
 	if (options[OptListSubDevFrameIntervals]) {
+		if (!_fd.has_streams_support() && 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] 23+ messages in thread

* [PATCH v4 1/8] v4l2-ctl: Add routing and streams support
  2023-04-21 12:44 [PATCH v4 0/8] v4l-utils: Support multiplexed streams Tomi Valkeinen
@ 2023-04-21 12:44 ` Tomi Valkeinen
  2023-04-24  7:04   ` Laurent Pinchart
  2023-04-21 12:44 ` [PATCH v4 2/8] media-ctl: Add support for routes and streams Tomi Valkeinen
                   ` (6 subsequent siblings)
  7 siblings, 1 reply; 23+ messages in thread
From: Tomi Valkeinen @ 2023-04-21 12:44 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..7ab64646 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("%d/%d -> %d/%d [",
+		       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..1cfb50f7 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},
+	{"set-routing", required_argument, 0, OptSetRouting},
+	{"get-routing", no_argument, 0, OptGetRouting},
 	{"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..9396c974 100644
--- a/utils/v4l2-ctl/v4l2-ctl.h
+++ b/utils/v4l2-ctl/v4l2-ctl.h
@@ -191,6 +191,8 @@ enum Option {
 	OptInfoEdid,
 	OptShowEdid,
 	OptFixEdidChecksums,
+	OptSetRouting,
+	OptGetRouting,
 	OptFreqSeek,
 	OptEncoderCmd,
 	OptTryEncoderCmd,
-- 
2.34.1


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

* [PATCH v4 2/8] media-ctl: Add support for routes and streams
  2023-04-21 12:44 [PATCH v4 0/8] v4l-utils: Support multiplexed streams Tomi Valkeinen
  2023-04-21 12:44 ` [PATCH v4 1/8] v4l2-ctl: Add routing and streams support Tomi Valkeinen
@ 2023-04-21 12:44 ` Tomi Valkeinen
  2023-04-24  7:29   ` Laurent Pinchart
  2023-04-21 12:44 ` [PATCH v4 3/8] v4l2-ctl/compliance: Add routing and streams multiplexed streams Tomi Valkeinen
                   ` (5 subsequent siblings)
  7 siblings, 1 reply; 23+ messages in thread
From: Tomi Valkeinen @ 2023-04-21 12:44 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   |  41 +++++
 utils/media-ctl/libv4l2subdev.c | 281 ++++++++++++++++++++++++++++----
 utils/media-ctl/media-ctl.c     | 121 ++++++++++++--
 utils/media-ctl/mediactl.h      |  16 ++
 utils/media-ctl/options.c       |  15 +-
 utils/media-ctl/options.h       |   1 +
 utils/media-ctl/v4l2subdev.h    |  58 ++++++-
 7 files changed, 476 insertions(+), 57 deletions(-)

diff --git a/utils/media-ctl/libmediactl.c b/utils/media-ctl/libmediactl.c
index a18b063e..c32fe56a 100644
--- a/utils/media-ctl/libmediactl.c
+++ b/utils/media-ctl/libmediactl.c
@@ -874,6 +874,47 @@ 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;
+	}
+
+	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..9205cfa4 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_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_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_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,153 @@ 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++;
+
+	*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;
+	char *end;
+	int ret;
+	int i;
+
+	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, flags 0x%8.8x\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 +659,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 +671,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 +893,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 +902,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 +924,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 +933,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 +953,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 +962,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 +990,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 +1005,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 +1042,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..831136a0 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;
+
+	for (i = 0; i < num_routes; i++) {
+		const struct v4l2_subdev_route *r = &routes[i];
+
+		if (i == 0)
+			printf("\troutes:\n");
+
+		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");
+	}
+}
+
 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,16 +476,58 @@ 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)
 {
+	unsigned int i;
+	uint64_t printed_streams_mask;
+
 	if (media_entity_type(entity) != MEDIA_ENT_T_V4L2_SUBDEV)
 		return;
 
-	v4l2_subdev_print_format(entity, pad->index, V4L2_SUBDEV_FORMAT_ACTIVE);
-	v4l2_subdev_print_pad_dv(entity, pad->index, V4L2_SUBDEV_FORMAT_ACTIVE);
+	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);
+
+		return;
+	}
+
+	printed_streams_mask = 0;
+
+	for (i = 0; i < num_routes; ++i) {
+		const struct v4l2_subdev_route *r = &routes[i];
+		unsigned int stream;
 
-	if (pad->flags & MEDIA_PAD_FL_SOURCE)
-		v4l2_subdev_print_subdev_dv(entity);
+		if (!(r->flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE))
+			continue;
+
+		if (pad->flags & MEDIA_PAD_FL_SINK) {
+			if (r->sink_pad != pad->index)
+				continue;
+
+			stream = r->sink_stream;
+		} else {
+			if (r->source_pad != pad->index)
+				continue;
+
+			stream = r->source_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);
+
+		printed_streams_mask |= (1 << stream);
+	}
 }
 
 static void media_print_topology_text_entity(struct media_device *media,
@@ -480,11 +543,17 @@ static void media_print_topology_text_entity(struct media_device *media,
 	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;
+
+	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, %u route%s)\n", info->name,
 	       info->pads, info->pads > 1 ? "s" : "",
-	       num_links, num_links > 1 ? "s" : "");
+	       num_links, num_links > 1 ? "s" : "",
+	       num_routes, num_routes > 1 ? "s" : "");
 	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..a8a6e7ad 100644
--- a/utils/media-ctl/v4l2subdev.h
+++ b/utils/media-ctl/v4l2subdev.h
@@ -64,7 +64,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);
 
 /**
@@ -86,6 +86,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);
 
 /**
@@ -107,8 +108,8 @@ 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.
@@ -129,8 +130,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.
@@ -200,7 +233,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);
+	struct v4l2_fract *interval, unsigned int pad, unsigned int stream);
 
 /**
  * @brief Set the frame interval on a sub-device.
@@ -217,7 +250,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 +268,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] 23+ messages in thread

* [PATCH v4 3/8] v4l2-ctl/compliance: Add routing and streams multiplexed streams
  2023-04-21 12:44 [PATCH v4 0/8] v4l-utils: Support multiplexed streams Tomi Valkeinen
  2023-04-21 12:44 ` [PATCH v4 1/8] v4l2-ctl: Add routing and streams support Tomi Valkeinen
  2023-04-21 12:44 ` [PATCH v4 2/8] media-ctl: Add support for routes and streams Tomi Valkeinen
@ 2023-04-21 12:44 ` Tomi Valkeinen
  2023-04-21 12:44 ` [PATCH v4 4/8] v4l2-ctl/compliance: Add simple routing test Tomi Valkeinen
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 23+ messages in thread
From: Tomi Valkeinen @ 2023-04-21 12:44 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 830c2578..a147604c 100644
--- a/utils/v4l2-compliance/v4l2-compliance.cpp
+++ b/utils/v4l2-compliance/v4l2-compliance.cpp
@@ -1225,6 +1225,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;
@@ -1236,6 +1240,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) ?
@@ -1245,32 +1265,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] 23+ messages in thread

* [PATCH v4 4/8] v4l2-ctl/compliance: Add simple routing test
  2023-04-21 12:44 [PATCH v4 0/8] v4l-utils: Support multiplexed streams Tomi Valkeinen
                   ` (2 preceding siblings ...)
  2023-04-21 12:44 ` [PATCH v4 3/8] v4l2-ctl/compliance: Add routing and streams multiplexed streams Tomi Valkeinen
@ 2023-04-21 12:44 ` Tomi Valkeinen
  2023-04-24  8:04   ` Laurent Pinchart
  2023-04-21 12:44 ` [PATCH v4 5/8] HACK: include/linux: Add client capabilities Tomi Valkeinen
                   ` (3 subsequent siblings)
  7 siblings, 1 reply; 23+ messages in thread
From: Tomi Valkeinen @ 2023-04-21 12:44 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 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>
---
 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 a147604c..7c3a688b 100644
--- a/utils/v4l2-compliance/v4l2-compliance.cpp
+++ b/utils/v4l2-compliance/v4l2-compliance.cpp
@@ -1241,6 +1241,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] 23+ messages in thread

* [PATCH v4 5/8] HACK: include/linux: Add client capabilities
  2023-04-21 12:44 [PATCH v4 0/8] v4l-utils: Support multiplexed streams Tomi Valkeinen
                   ` (3 preceding siblings ...)
  2023-04-21 12:44 ` [PATCH v4 4/8] v4l2-ctl/compliance: Add simple routing test Tomi Valkeinen
@ 2023-04-21 12:44 ` Tomi Valkeinen
  2023-04-24  7:32   ` Laurent Pinchart
  2023-04-21 12:44 ` [PATCH v4 6/8] media-ctl: Check for Streams API support Tomi Valkeinen
                   ` (2 subsequent siblings)
  7 siblings, 1 reply; 23+ messages in thread
From: Tomi Valkeinen @ 2023-04-21 12:44 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 client capabilities related hanges to include/linux/v4l2-subdev.h.
This should be dropped when the v4l-utils kernel headers are updated to
the version which contains client capabilities.

Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
---
 include/linux/v4l2-subdev.h | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/include/linux/v4l2-subdev.h b/include/linux/v4l2-subdev.h
index 654d659d..4a195b68 100644
--- a/include/linux/v4l2-subdev.h
+++ b/include/linux/v4l2-subdev.h
@@ -233,6 +233,24 @@ struct v4l2_subdev_routing {
 	__u32 reserved[6];
 };
 
+/*
+ * The client is aware of streams. Setting this flag enables the use of 'stream'
+ * fields (referring to the stream number) with various ioctls. If this is not
+ * set (which is the default), the 'stream' fields will be forced to 0 by the
+ * kernel.
+ */
+ #define V4L2_SUBDEV_CLIENT_CAP_STREAMS		(1U << 0)
+
+/**
+ * struct v4l2_subdev_client_capability - Capabilities of the client accessing
+ *					  the subdev
+ *
+ * @capabilities: A bitmask of V4L2_SUBDEV_CLIENT_CAP_* flags.
+ */
+struct v4l2_subdev_client_capability {
+	__u64 capabilities;
+};
+
 /* Backwards compatibility define --- to be removed */
 #define v4l2_subdev_edid v4l2_edid
 
@@ -250,6 +268,9 @@ struct v4l2_subdev_routing {
 #define VIDIOC_SUBDEV_S_SELECTION		_IOWR('V', 62, struct v4l2_subdev_selection)
 #define VIDIOC_SUBDEV_G_ROUTING			_IOWR('V', 38, struct v4l2_subdev_routing)
 #define VIDIOC_SUBDEV_S_ROUTING			_IOWR('V', 39, struct v4l2_subdev_routing)
+#define VIDIOC_SUBDEV_G_CLIENT_CAP		_IOR('V',  101, struct v4l2_subdev_client_capability)
+#define VIDIOC_SUBDEV_S_CLIENT_CAP		_IOWR('V',  102, struct v4l2_subdev_client_capability)
+
 /* The following ioctls are identical to the ioctls in videodev2.h */
 #define VIDIOC_SUBDEV_G_STD			_IOR('V', 23, v4l2_std_id)
 #define VIDIOC_SUBDEV_S_STD			_IOW('V', 24, v4l2_std_id)
-- 
2.34.1


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

* [PATCH v4 6/8] media-ctl: Check for Streams API support
  2023-04-21 12:44 [PATCH v4 0/8] v4l-utils: Support multiplexed streams Tomi Valkeinen
                   ` (4 preceding siblings ...)
  2023-04-21 12:44 ` [PATCH v4 5/8] HACK: include/linux: Add client capabilities Tomi Valkeinen
@ 2023-04-21 12:44 ` Tomi Valkeinen
  2023-04-24  7:54   ` Laurent Pinchart
  2023-04-21 12:44 ` [PATCH v4 7/8] utils/common: Set V4L2_SUBDEV_CLIENT_CAP_STREAMS for subdevs Tomi Valkeinen
  2023-04-21 12:44 ` [PATCH v4 8/8] v4l2-ctl: Check for Streams API support Tomi Valkeinen
  7 siblings, 1 reply; 23+ messages in thread
From: Tomi Valkeinen @ 2023-04-21 12:44 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

Do two things:

- Inform the kernel that we support streams with a call to
  VIDIOC_SUBDEV_S_CLIENT_CAP

- Use the returns from VIDIOC_SUBDEV_S_CLIENT_CAP and
  VIDIOC_SUBDEV_QUERYCAP to decide if streams are supported. If not,
  fail in case the user tries to use streams.

Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
---
 utils/media-ctl/libv4l2subdev.c | 54 +++++++++++++++++++++++++++++++++
 utils/media-ctl/mediactl-priv.h |  1 +
 2 files changed, 55 insertions(+)

diff --git a/utils/media-ctl/libv4l2subdev.c b/utils/media-ctl/libv4l2subdev.c
index 9205cfa4..186708ff 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;
@@ -210,6 +246,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;
@@ -231,6 +272,9 @@ int v4l2_subdev_get_routing(struct media_entity *entity,
 	if (ret < 0)
 		return ret;
 
+	if (!entity->supports_streams)
+		return -ENOTSUP;
+
 	ret = ioctl(entity->fd, VIDIOC_SUBDEV_G_ROUTING, &routing);
 	if (ret == -1 && errno != ENOSPC)
 		return -errno;
@@ -341,6 +385,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 +413,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] 23+ messages in thread

* [PATCH v4 7/8] utils/common: Set V4L2_SUBDEV_CLIENT_CAP_STREAMS for subdevs
  2023-04-21 12:44 [PATCH v4 0/8] v4l-utils: Support multiplexed streams Tomi Valkeinen
                   ` (5 preceding siblings ...)
  2023-04-21 12:44 ` [PATCH v4 6/8] media-ctl: Check for Streams API support Tomi Valkeinen
@ 2023-04-21 12:44 ` Tomi Valkeinen
  2023-04-24  8:01   ` Laurent Pinchart
  2023-04-21 12:44 ` [PATCH v4 8/8] v4l2-ctl: Check for Streams API support Tomi Valkeinen
  7 siblings, 1 reply; 23+ messages in thread
From: Tomi Valkeinen @ 2023-04-21 12:44 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

Do two things:

- Inform the kernel that we support streams with a call to
  VIDIOC_SUBDEV_S_CLIENT_CAP

- Use the returns from VIDIOC_SUBDEV_S_CLIENT_CAP and
  VIDIOC_SUBDEV_QUERYCAP to decide if streams are supported, and
  return that via has_streams_support() method.

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

diff --git a/utils/common/cv4l-helpers.h b/utils/common/cv4l-helpers.h
index 3cee372b..502df6ac 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_support() const { return subdev_supports_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..2dd7f061 100644
--- a/utils/common/v4l-helpers.h
+++ b/utils/common/v4l-helpers.h
@@ -39,6 +39,7 @@ struct v4l_fd {
 	bool have_selection;
 	bool is_subdev;
 	bool is_media;
+	bool subdev_supports_streams;
 
 	int (*open)(struct v4l_fd *f, const char *file, int oflag, ...);
 	int (*close)(struct v4l_fd *f);
@@ -507,6 +508,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 +535,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->subdev_supports_streams = subdev_streams && client_streams;
+
 	return f->fd;
 }
 
-- 
2.34.1


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

* [PATCH v4 8/8] v4l2-ctl: Check for Streams API support
  2023-04-21 12:44 [PATCH v4 0/8] v4l-utils: Support multiplexed streams Tomi Valkeinen
                   ` (6 preceding siblings ...)
  2023-04-21 12:44 ` [PATCH v4 7/8] utils/common: Set V4L2_SUBDEV_CLIENT_CAP_STREAMS for subdevs Tomi Valkeinen
@ 2023-04-21 12:44 ` Tomi Valkeinen
  2023-04-24  8:03   ` Laurent Pinchart
  7 siblings, 1 reply; 23+ messages in thread
From: Tomi Valkeinen @ 2023-04-21 12:44 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

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>
---
 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 7ab64646..ec70b52b 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_support() && 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_support() && 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_support() && 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_support()) {
+			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_support() && 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_support() && 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_support() && 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_support()) {
+			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_support() && 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_support() && 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_support() && 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] 23+ messages in thread

* Re: [PATCH v4 1/8] v4l2-ctl: Add routing and streams support
  2023-04-21 12:44 ` [PATCH v4 1/8] v4l2-ctl: Add routing and streams support Tomi Valkeinen
@ 2023-04-24  7:04   ` Laurent Pinchart
  2023-05-29  7:02     ` Tomi Valkeinen
  0 siblings, 1 reply; 23+ messages in thread
From: Laurent Pinchart @ 2023-04-24  7:04 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: linux-media, sakari.ailus, Jacopo Mondi,
	niklas.soderlund+renesas, Mauro Carvalho Chehab, Hans Verkuil,
	satish.nagireddy

Hi Tomi,

Thank you for the patch.

On Fri, Apr 21, 2023 at 03:44:21PM +0300, 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..7ab64646 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]))

It would be nice to put the 14 implementations of this macro to a common
header. Out of scope for this patch.

> +
> +/*
> + * 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) {

Requiring a space around '->' isn't nice, especially as it's not present
in the help text. MC link parsing makes spaces optional, please do the
same here.

> +				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);
> +	}
> +}

This could also be a helper shared by multiple source files.

> +
> +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("%d/%d -> %d/%d [",

The values are unsigned, %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);

In some places you have a space after the comma, in some places you
don't. I prefer the space personally but I'm fine with either as long as
it's consistent.

>  		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..1cfb50f7 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},
> +	{"set-routing", required_argument, 0, OptSetRouting},
> +	{"get-routing", no_argument, 0, OptGetRouting},

Maybe get before set as for other options ?

>  	{"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..9396c974 100644
> --- a/utils/v4l2-ctl/v4l2-ctl.h
> +++ b/utils/v4l2-ctl/v4l2-ctl.h
> @@ -191,6 +191,8 @@ enum Option {
>  	OptInfoEdid,
>  	OptShowEdid,
>  	OptFixEdidChecksums,
> +	OptSetRouting,
> +	OptGetRouting,
>  	OptFreqSeek,
>  	OptEncoderCmd,
>  	OptTryEncoderCmd,

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v4 2/8] media-ctl: Add support for routes and streams
  2023-04-21 12:44 ` [PATCH v4 2/8] media-ctl: Add support for routes and streams Tomi Valkeinen
@ 2023-04-24  7:29   ` Laurent Pinchart
  2023-05-29  7:34     ` Tomi Valkeinen
  0 siblings, 1 reply; 23+ messages in thread
From: Laurent Pinchart @ 2023-04-24  7:29 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: linux-media, sakari.ailus, Jacopo Mondi,
	niklas.soderlund+renesas, Mauro Carvalho Chehab, Hans Verkuil,
	satish.nagireddy

Hi Tomi,

Thank you for the patch.

On Fri, Apr 21, 2023 at 03:44:22PM +0300, Tomi Valkeinen wrote:
> 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   |  41 +++++
>  utils/media-ctl/libv4l2subdev.c | 281 ++++++++++++++++++++++++++++----
>  utils/media-ctl/media-ctl.c     | 121 ++++++++++++--
>  utils/media-ctl/mediactl.h      |  16 ++
>  utils/media-ctl/options.c       |  15 +-
>  utils/media-ctl/options.h       |   1 +
>  utils/media-ctl/v4l2subdev.h    |  58 ++++++-
>  7 files changed, 476 insertions(+), 57 deletions(-)
> 
> diff --git a/utils/media-ctl/libmediactl.c b/utils/media-ctl/libmediactl.c
> index a18b063e..c32fe56a 100644
> --- a/utils/media-ctl/libmediactl.c
> +++ b/utils/media-ctl/libmediactl.c
> @@ -874,6 +874,47 @@ 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++;

Skip spaces at the end.

> +	} else {
> +		*stream = 0;
> +	}
> +
> +	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..9205cfa4 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_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_routing(struct media_entity *entity,
> +			    struct v4l2_subdev_route **routes,
> +			    unsigned int *num_routes)

Move get before set.

> +{
> +	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) {

		ret = -errno;

> +		free(r);
> +		return ret;
> +	}
> +
> +	*num_routes = routing.num_routes;
> +	*routes = r;
> +
> +	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,153 @@ 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)

At some point we should probably use a real parser...

> +{
> +	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++;

Skip spaces at the 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;
> +	char *end;
> +	int ret;
> +	int i;

unsigned int

> +
> +	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, flags 0x%8.8x\n",

Flags are usually printed in "[0x%08x]" format.

> +			  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 +659,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 +671,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 +893,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 +902,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 +924,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 +933,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 +953,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 +962,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 +990,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 +1005,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 +1042,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..831136a0 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;
> +
> +	for (i = 0; i < num_routes; i++) {
> +		const struct v4l2_subdev_route *r = &routes[i];

Naming the variable 'route' would be more explicit.

> +
> +		if (i == 0)
> +			printf("\troutes:\n");

You could move this before the loop with

	if (num_routes)
		printf("\troutes:\n");

> +
> +		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");
> +	}
> +}
> +
>  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,16 +476,58 @@ 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)
>  {
> +	unsigned int i;
> +	uint64_t printed_streams_mask;

I'd swap those two variables. printed_streams_mask could be initialized
to 0 here.

> +
>  	if (media_entity_type(entity) != MEDIA_ENT_T_V4L2_SUBDEV)
>  		return;
>  
> -	v4l2_subdev_print_format(entity, pad->index, V4L2_SUBDEV_FORMAT_ACTIVE);
> -	v4l2_subdev_print_pad_dv(entity, pad->index, V4L2_SUBDEV_FORMAT_ACTIVE);
> +	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);
> +
> +		return;
> +	}
> +
> +	printed_streams_mask = 0;
> +
> +	for (i = 0; i < num_routes; ++i) {
> +		const struct v4l2_subdev_route *r = &routes[i];

Naming the variable 'route' would be more explicit.

> +		unsigned int stream;
>  
> -	if (pad->flags & MEDIA_PAD_FL_SOURCE)
> -		v4l2_subdev_print_subdev_dv(entity);
> +		if (!(r->flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE))
> +			continue;
> +
> +		if (pad->flags & MEDIA_PAD_FL_SINK) {
> +			if (r->sink_pad != pad->index)
> +				continue;
> +
> +			stream = r->sink_stream;
> +		} else {
> +			if (r->source_pad != pad->index)
> +				continue;
> +
> +			stream = r->source_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() and v4l2_subdev_print_subdev_dv() don't
depend on routes or streams, should they be printed outside of the loop
?

> +
> +		printed_streams_mask |= (1 << stream);
> +	}
>  }
>  
>  static void media_print_topology_text_entity(struct media_device *media,
> @@ -480,11 +543,17 @@ static void media_print_topology_text_entity(struct media_device *media,
>  	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;

Move these two before 'j, k'.

> +
> +	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, %u route%s)\n", info->name,

Should we skip printing the number of routes when the entity isn't a
subdev ?

>  	       info->pads, info->pads > 1 ? "s" : "",
> -	       num_links, num_links > 1 ? "s" : "");
> +	       num_links, num_links > 1 ? "s" : "",
> +	       num_routes, num_routes > 1 ? "s" : "");
>  	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..a8a6e7ad 100644
> --- a/utils/media-ctl/v4l2subdev.h
> +++ b/utils/media-ctl/v4l2subdev.h
> @@ -64,7 +64,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,

Missing documentation for the new parameter. Same below.

>  	enum v4l2_subdev_format_whence which);
>  
>  /**
> @@ -86,6 +86,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);
>  
>  /**
> @@ -107,8 +108,8 @@ 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.
> @@ -129,8 +130,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.
> @@ -200,7 +233,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);
> +	struct v4l2_fract *interval, unsigned int pad, unsigned int stream);
>  
>  /**
>   * @brief Set the frame interval on a sub-device.
> @@ -217,7 +250,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 +268,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

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v4 5/8] HACK: include/linux: Add client capabilities
  2023-04-21 12:44 ` [PATCH v4 5/8] HACK: include/linux: Add client capabilities Tomi Valkeinen
@ 2023-04-24  7:32   ` Laurent Pinchart
  2023-05-25 14:05     ` Hans Verkuil
  0 siblings, 1 reply; 23+ messages in thread
From: Laurent Pinchart @ 2023-04-24  7:32 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: linux-media, sakari.ailus, Jacopo Mondi,
	niklas.soderlund+renesas, Mauro Carvalho Chehab, Hans Verkuil,
	satish.nagireddy

Hi Tomi,

Thank you for the patch.

On Fri, Apr 21, 2023 at 03:44:25PM +0300, Tomi Valkeinen wrote:
> Add client capabilities related hanges to include/linux/v4l2-subdev.h.
> This should be dropped when the v4l-utils kernel headers are updated to
> the version which contains client capabilities.
> 
> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>

The subdev client capabilities patch is now in the media tree, maybe you
can sync the headers already ? The media tree master branch should get
merged in v6.4-rc1 within two weeks.

> ---
>  include/linux/v4l2-subdev.h | 21 +++++++++++++++++++++
>  1 file changed, 21 insertions(+)
> 
> diff --git a/include/linux/v4l2-subdev.h b/include/linux/v4l2-subdev.h
> index 654d659d..4a195b68 100644
> --- a/include/linux/v4l2-subdev.h
> +++ b/include/linux/v4l2-subdev.h
> @@ -233,6 +233,24 @@ struct v4l2_subdev_routing {
>  	__u32 reserved[6];
>  };
>  
> +/*
> + * The client is aware of streams. Setting this flag enables the use of 'stream'
> + * fields (referring to the stream number) with various ioctls. If this is not
> + * set (which is the default), the 'stream' fields will be forced to 0 by the
> + * kernel.
> + */
> + #define V4L2_SUBDEV_CLIENT_CAP_STREAMS		(1U << 0)
> +
> +/**
> + * struct v4l2_subdev_client_capability - Capabilities of the client accessing
> + *					  the subdev
> + *
> + * @capabilities: A bitmask of V4L2_SUBDEV_CLIENT_CAP_* flags.
> + */
> +struct v4l2_subdev_client_capability {
> +	__u64 capabilities;
> +};
> +
>  /* Backwards compatibility define --- to be removed */
>  #define v4l2_subdev_edid v4l2_edid
>  
> @@ -250,6 +268,9 @@ struct v4l2_subdev_routing {
>  #define VIDIOC_SUBDEV_S_SELECTION		_IOWR('V', 62, struct v4l2_subdev_selection)
>  #define VIDIOC_SUBDEV_G_ROUTING			_IOWR('V', 38, struct v4l2_subdev_routing)
>  #define VIDIOC_SUBDEV_S_ROUTING			_IOWR('V', 39, struct v4l2_subdev_routing)
> +#define VIDIOC_SUBDEV_G_CLIENT_CAP		_IOR('V',  101, struct v4l2_subdev_client_capability)
> +#define VIDIOC_SUBDEV_S_CLIENT_CAP		_IOWR('V',  102, struct v4l2_subdev_client_capability)
> +
>  /* The following ioctls are identical to the ioctls in videodev2.h */
>  #define VIDIOC_SUBDEV_G_STD			_IOR('V', 23, v4l2_std_id)
>  #define VIDIOC_SUBDEV_S_STD			_IOW('V', 24, v4l2_std_id)

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v4 6/8] media-ctl: Check for Streams API support
  2023-04-21 12:44 ` [PATCH v4 6/8] media-ctl: Check for Streams API support Tomi Valkeinen
@ 2023-04-24  7:54   ` Laurent Pinchart
  0 siblings, 0 replies; 23+ messages in thread
From: Laurent Pinchart @ 2023-04-24  7:54 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: linux-media, sakari.ailus, Jacopo Mondi,
	niklas.soderlund+renesas, Mauro Carvalho Chehab, Hans Verkuil,
	satish.nagireddy

Hi Tomi,

Thank you for the patch.

On Fri, Apr 21, 2023 at 03:44:26PM +0300, Tomi Valkeinen wrote:
> Do two things:

That usually calls for two patches ;-) Or an explanation in the commit
message about why the two are combined.

> - Inform the kernel that we support streams with a call to
>   VIDIOC_SUBDEV_S_CLIENT_CAP
> 
> - Use the returns from VIDIOC_SUBDEV_S_CLIENT_CAP and
>   VIDIOC_SUBDEV_QUERYCAP to decide if streams are supported. If not,
>   fail in case the user tries to use streams.
> 
> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> ---
>  utils/media-ctl/libv4l2subdev.c | 54 +++++++++++++++++++++++++++++++++
>  utils/media-ctl/mediactl-priv.h |  1 +
>  2 files changed, 55 insertions(+)
> 
> diff --git a/utils/media-ctl/libv4l2subdev.c b/utils/media-ctl/libv4l2subdev.c
> index 9205cfa4..186708ff 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;
> @@ -210,6 +246,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;
> @@ -231,6 +272,9 @@ int v4l2_subdev_get_routing(struct media_entity *entity,
>  	if (ret < 0)
>  		return ret;
>  
> +	if (!entity->supports_streams)

No need for a debug message here ?

Reviewed-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>

> +		return -ENOTSUP;
> +
>  	ret = ioctl(entity->fd, VIDIOC_SUBDEV_G_ROUTING, &routing);
>  	if (ret == -1 && errno != ENOSPC)
>  		return -errno;
> @@ -341,6 +385,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 +413,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;

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v4 7/8] utils/common: Set V4L2_SUBDEV_CLIENT_CAP_STREAMS for subdevs
  2023-04-21 12:44 ` [PATCH v4 7/8] utils/common: Set V4L2_SUBDEV_CLIENT_CAP_STREAMS for subdevs Tomi Valkeinen
@ 2023-04-24  8:01   ` Laurent Pinchart
  0 siblings, 0 replies; 23+ messages in thread
From: Laurent Pinchart @ 2023-04-24  8:01 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: linux-media, sakari.ailus, Jacopo Mondi,
	niklas.soderlund+renesas, Mauro Carvalho Chehab, Hans Verkuil,
	satish.nagireddy

Hi Tomi,

Thank you for the patch.

On Fri, Apr 21, 2023 at 03:44:27PM +0300, Tomi Valkeinen wrote:
> Do two things:
> 
> - Inform the kernel that we support streams with a call to
>   VIDIOC_SUBDEV_S_CLIENT_CAP
> 
> - Use the returns from VIDIOC_SUBDEV_S_CLIENT_CAP and
>   VIDIOC_SUBDEV_QUERYCAP to decide if streams are supported, and
>   return that via has_streams_support() method.
> 
> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> ---
>  utils/common/cv4l-helpers.h |  1 +
>  utils/common/v4l-helpers.h  | 17 +++++++++++++++++
>  2 files changed, 18 insertions(+)
> 
> diff --git a/utils/common/cv4l-helpers.h b/utils/common/cv4l-helpers.h
> index 3cee372b..502df6ac 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_support() const { return subdev_supports_streams; }

The other functions don't have a "_support" suffix, I'd write
"has_stream()".

>  
>  	int querycap(v4l2_capability &cap, bool force = false)
>  	{
> diff --git a/utils/common/v4l-helpers.h b/utils/common/v4l-helpers.h
> index c09cd987..2dd7f061 100644
> --- a/utils/common/v4l-helpers.h
> +++ b/utils/common/v4l-helpers.h
> @@ -39,6 +39,7 @@ struct v4l_fd {
>  	bool have_selection;
>  	bool is_subdev;
>  	bool is_media;
> +	bool subdev_supports_streams;

Same here, and I would probably also drop the subdev_ prefix.

Reviewed-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>

>  
>  	int (*open)(struct v4l_fd *f, const char *file, int oflag, ...);
>  	int (*close)(struct v4l_fd *f);
> @@ -507,6 +508,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 +535,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->subdev_supports_streams = subdev_streams && client_streams;
> +
>  	return f->fd;
>  }
>  

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v4 8/8] v4l2-ctl: Check for Streams API support
  2023-04-21 12:44 ` [PATCH v4 8/8] v4l2-ctl: Check for Streams API support Tomi Valkeinen
@ 2023-04-24  8:03   ` Laurent Pinchart
  0 siblings, 0 replies; 23+ messages in thread
From: Laurent Pinchart @ 2023-04-24  8:03 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: linux-media, sakari.ailus, Jacopo Mondi,
	niklas.soderlund+renesas, Mauro Carvalho Chehab, Hans Verkuil,
	satish.nagireddy

Hi Tomi,

Thank you for the patch.

On Fri, Apr 21, 2023 at 03:44:28PM +0300, Tomi Valkeinen wrote:
> 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 7ab64646..ec70b52b 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_support() && 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_support() && 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_support() && 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_support()) {
> +			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_support() && 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_support() && 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_support() && 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_support()) {
> +			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_support() && 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_support() && 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_support() && 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;

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v4 4/8] v4l2-ctl/compliance: Add simple routing test
  2023-04-21 12:44 ` [PATCH v4 4/8] v4l2-ctl/compliance: Add simple routing test Tomi Valkeinen
@ 2023-04-24  8:04   ` Laurent Pinchart
  0 siblings, 0 replies; 23+ messages in thread
From: Laurent Pinchart @ 2023-04-24  8:04 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: linux-media, sakari.ailus, Jacopo Mondi,
	niklas.soderlund+renesas, Mauro Carvalho Chehab, Hans Verkuil,
	satish.nagireddy

Hi Tomi,

Thank you for the patch.

On Fri, Apr 21, 2023 at 03:44:24PM +0300, 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>
> ---
>  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 a147604c..7c3a688b 100644
> --- a/utils/v4l2-compliance/v4l2-compliance.cpp
> +++ b/utils/v4l2-compliance/v4l2-compliance.cpp
> @@ -1241,6 +1241,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)));

Weird indentation.

Reviewed-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>

> +			}
> +
> +			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;
> +}

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v4 5/8] HACK: include/linux: Add client capabilities
  2023-04-24  7:32   ` Laurent Pinchart
@ 2023-05-25 14:05     ` Hans Verkuil
  2023-05-26  8:19       ` Tomi Valkeinen
  0 siblings, 1 reply; 23+ messages in thread
From: Hans Verkuil @ 2023-05-25 14:05 UTC (permalink / raw)
  To: Laurent Pinchart, Tomi Valkeinen
  Cc: linux-media, sakari.ailus, Jacopo Mondi,
	niklas.soderlund+renesas, Mauro Carvalho Chehab,
	satish.nagireddy

Hi Tomi,

On 24/04/2023 09:32, Laurent Pinchart wrote:
> Hi Tomi,
> 
> Thank you for the patch.
> 
> On Fri, Apr 21, 2023 at 03:44:25PM +0300, Tomi Valkeinen wrote:
>> Add client capabilities related hanges to include/linux/v4l2-subdev.h.
>> This should be dropped when the v4l-utils kernel headers are updated to
>> the version which contains client capabilities.
>>
>> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> 
> The subdev client capabilities patch is now in the media tree, maybe you
> can sync the headers already ? The media tree master branch should get
> merged in v6.4-rc1 within two weeks.

I've just synced the headers for v4l-utils.

I think it is easiest if you post a v5, assuming everything that this series
needs is now merged in the kernel. I'll pick it up.

Regards,

	Hans

> 
>> ---
>>  include/linux/v4l2-subdev.h | 21 +++++++++++++++++++++
>>  1 file changed, 21 insertions(+)
>>
>> diff --git a/include/linux/v4l2-subdev.h b/include/linux/v4l2-subdev.h
>> index 654d659d..4a195b68 100644
>> --- a/include/linux/v4l2-subdev.h
>> +++ b/include/linux/v4l2-subdev.h
>> @@ -233,6 +233,24 @@ struct v4l2_subdev_routing {
>>  	__u32 reserved[6];
>>  };
>>  
>> +/*
>> + * The client is aware of streams. Setting this flag enables the use of 'stream'
>> + * fields (referring to the stream number) with various ioctls. If this is not
>> + * set (which is the default), the 'stream' fields will be forced to 0 by the
>> + * kernel.
>> + */
>> + #define V4L2_SUBDEV_CLIENT_CAP_STREAMS		(1U << 0)
>> +
>> +/**
>> + * struct v4l2_subdev_client_capability - Capabilities of the client accessing
>> + *					  the subdev
>> + *
>> + * @capabilities: A bitmask of V4L2_SUBDEV_CLIENT_CAP_* flags.
>> + */
>> +struct v4l2_subdev_client_capability {
>> +	__u64 capabilities;
>> +};
>> +
>>  /* Backwards compatibility define --- to be removed */
>>  #define v4l2_subdev_edid v4l2_edid
>>  
>> @@ -250,6 +268,9 @@ struct v4l2_subdev_routing {
>>  #define VIDIOC_SUBDEV_S_SELECTION		_IOWR('V', 62, struct v4l2_subdev_selection)
>>  #define VIDIOC_SUBDEV_G_ROUTING			_IOWR('V', 38, struct v4l2_subdev_routing)
>>  #define VIDIOC_SUBDEV_S_ROUTING			_IOWR('V', 39, struct v4l2_subdev_routing)
>> +#define VIDIOC_SUBDEV_G_CLIENT_CAP		_IOR('V',  101, struct v4l2_subdev_client_capability)
>> +#define VIDIOC_SUBDEV_S_CLIENT_CAP		_IOWR('V',  102, struct v4l2_subdev_client_capability)
>> +
>>  /* The following ioctls are identical to the ioctls in videodev2.h */
>>  #define VIDIOC_SUBDEV_G_STD			_IOR('V', 23, v4l2_std_id)
>>  #define VIDIOC_SUBDEV_S_STD			_IOW('V', 24, v4l2_std_id)
> 


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

* Re: [PATCH v4 5/8] HACK: include/linux: Add client capabilities
  2023-05-25 14:05     ` Hans Verkuil
@ 2023-05-26  8:19       ` Tomi Valkeinen
  0 siblings, 0 replies; 23+ messages in thread
From: Tomi Valkeinen @ 2023-05-26  8:19 UTC (permalink / raw)
  To: Hans Verkuil, Laurent Pinchart
  Cc: linux-media, sakari.ailus, Jacopo Mondi,
	niklas.soderlund+renesas, Mauro Carvalho Chehab,
	satish.nagireddy

On 25/05/2023 17:05, Hans Verkuil wrote:
> Hi Tomi,
> 
> On 24/04/2023 09:32, Laurent Pinchart wrote:
>> Hi Tomi,
>>
>> Thank you for the patch.
>>
>> On Fri, Apr 21, 2023 at 03:44:25PM +0300, Tomi Valkeinen wrote:
>>> Add client capabilities related hanges to include/linux/v4l2-subdev.h.
>>> This should be dropped when the v4l-utils kernel headers are updated to
>>> the version which contains client capabilities.
>>>
>>> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
>>
>> The subdev client capabilities patch is now in the media tree, maybe you
>> can sync the headers already ? The media tree master branch should get
>> merged in v6.4-rc1 within two weeks.
> 
> I've just synced the headers for v4l-utils.
> 
> I think it is easiest if you post a v5, assuming everything that this series
> needs is now merged in the kernel. I'll pick it up.

Ok. There are comments in this thread which I haven't addressed yet. So 
I'll rebase on top of latest v4l-utils, address the comments and post v5.

  Tomi


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

* Re: [PATCH v4 1/8] v4l2-ctl: Add routing and streams support
  2023-04-24  7:04   ` Laurent Pinchart
@ 2023-05-29  7:02     ` Tomi Valkeinen
  2023-05-29  7:47       ` Laurent Pinchart
  0 siblings, 1 reply; 23+ messages in thread
From: Tomi Valkeinen @ 2023-05-29  7:02 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: linux-media, sakari.ailus, Jacopo Mondi,
	niklas.soderlund+renesas, Mauro Carvalho Chehab, Hans Verkuil,
	satish.nagireddy

On 24/04/2023 10:04, Laurent Pinchart wrote:

>> +		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) {
> 
> Requiring a space around '->' isn't nice, especially as it's not present
> in the help text. MC link parsing makes spaces optional, please do the
> same here.

The space are not required. sscanf skips white-space, so this parses 
fine "1/2->3/4[1]".

  Tomi


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

* Re: [PATCH v4 2/8] media-ctl: Add support for routes and streams
  2023-04-24  7:29   ` Laurent Pinchart
@ 2023-05-29  7:34     ` Tomi Valkeinen
  2023-05-29  7:49       ` Laurent Pinchart
  0 siblings, 1 reply; 23+ messages in thread
From: Tomi Valkeinen @ 2023-05-29  7:34 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: linux-media, sakari.ailus, Jacopo Mondi,
	niklas.soderlund+renesas, Mauro Carvalho Chehab, Hans Verkuil,
	satish.nagireddy

On 24/04/2023 10:29, Laurent Pinchart wrote:

>> +
>>   	if (media_entity_type(entity) != MEDIA_ENT_T_V4L2_SUBDEV)
>>   		return;
>>   
>> -	v4l2_subdev_print_format(entity, pad->index, V4L2_SUBDEV_FORMAT_ACTIVE);
>> -	v4l2_subdev_print_pad_dv(entity, pad->index, V4L2_SUBDEV_FORMAT_ACTIVE);
>> +	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);
>> +
>> +		return;
>> +	}
>> +
>> +	printed_streams_mask = 0;
>> +
>> +	for (i = 0; i < num_routes; ++i) {
>> +		const struct v4l2_subdev_route *r = &routes[i];
> 
> Naming the variable 'route' would be more explicit.
> 
>> +		unsigned int stream;
>>   
>> -	if (pad->flags & MEDIA_PAD_FL_SOURCE)
>> -		v4l2_subdev_print_subdev_dv(entity);
>> +		if (!(r->flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE))
>> +			continue;
>> +
>> +		if (pad->flags & MEDIA_PAD_FL_SINK) {
>> +			if (r->sink_pad != pad->index)
>> +				continue;
>> +
>> +			stream = r->sink_stream;
>> +		} else {
>> +			if (r->source_pad != pad->index)
>> +				continue;
>> +
>> +			stream = r->source_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() and v4l2_subdev_print_subdev_dv() don't
> depend on routes or streams, should they be printed outside of the loop
> ?

There's an if-block above the for loop which handles the no-routes case.

  Tomi


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

* Re: [PATCH v4 1/8] v4l2-ctl: Add routing and streams support
  2023-05-29  7:02     ` Tomi Valkeinen
@ 2023-05-29  7:47       ` Laurent Pinchart
  0 siblings, 0 replies; 23+ messages in thread
From: Laurent Pinchart @ 2023-05-29  7:47 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: linux-media, sakari.ailus, Jacopo Mondi,
	niklas.soderlund+renesas, Mauro Carvalho Chehab, Hans Verkuil,
	satish.nagireddy

On Mon, May 29, 2023 at 10:02:47AM +0300, Tomi Valkeinen wrote:
> On 24/04/2023 10:04, Laurent Pinchart wrote:
> 
> >> +		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) {
> > 
> > Requiring a space around '->' isn't nice, especially as it's not present
> > in the help text. MC link parsing makes spaces optional, please do the
> > same here.
> 
> The space are not required. sscanf skips white-space, so this parses 
> fine "1/2->3/4[1]".

I've learned something today :-)

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v4 2/8] media-ctl: Add support for routes and streams
  2023-05-29  7:34     ` Tomi Valkeinen
@ 2023-05-29  7:49       ` Laurent Pinchart
  2023-05-29  8:14         ` Tomi Valkeinen
  0 siblings, 1 reply; 23+ messages in thread
From: Laurent Pinchart @ 2023-05-29  7:49 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: linux-media, sakari.ailus, Jacopo Mondi,
	niklas.soderlund+renesas, Mauro Carvalho Chehab, Hans Verkuil,
	satish.nagireddy

On Mon, May 29, 2023 at 10:34:16AM +0300, Tomi Valkeinen wrote:
> On 24/04/2023 10:29, Laurent Pinchart wrote:
> 
> >> +
> >>   	if (media_entity_type(entity) != MEDIA_ENT_T_V4L2_SUBDEV)
> >>   		return;
> >>   
> >> -	v4l2_subdev_print_format(entity, pad->index, V4L2_SUBDEV_FORMAT_ACTIVE);
> >> -	v4l2_subdev_print_pad_dv(entity, pad->index, V4L2_SUBDEV_FORMAT_ACTIVE);
> >> +	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);
> >> +
> >> +		return;
> >> +	}
> >> +
> >> +	printed_streams_mask = 0;
> >> +
> >> +	for (i = 0; i < num_routes; ++i) {
> >> +		const struct v4l2_subdev_route *r = &routes[i];
> > 
> > Naming the variable 'route' would be more explicit.
> > 
> >> +		unsigned int stream;
> >>   
> >> -	if (pad->flags & MEDIA_PAD_FL_SOURCE)
> >> -		v4l2_subdev_print_subdev_dv(entity);
> >> +		if (!(r->flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE))
> >> +			continue;
> >> +
> >> +		if (pad->flags & MEDIA_PAD_FL_SINK) {
> >> +			if (r->sink_pad != pad->index)
> >> +				continue;
> >> +
> >> +			stream = r->sink_stream;
> >> +		} else {
> >> +			if (r->source_pad != pad->index)
> >> +				continue;
> >> +
> >> +			stream = r->source_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() and v4l2_subdev_print_subdev_dv() don't
> > depend on routes or streams, should they be printed outside of the loop
> > ?
> 
> There's an if-block above the for loop which handles the no-routes case.

What I meant is that the pad and entity variables are constant through
the whole loop, so why should the pad dv and subdev dv information be
printed for each route?

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v4 2/8] media-ctl: Add support for routes and streams
  2023-05-29  7:49       ` Laurent Pinchart
@ 2023-05-29  8:14         ` Tomi Valkeinen
  0 siblings, 0 replies; 23+ messages in thread
From: Tomi Valkeinen @ 2023-05-29  8:14 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: linux-media, sakari.ailus, Jacopo Mondi,
	niklas.soderlund+renesas, Mauro Carvalho Chehab, Hans Verkuil,
	satish.nagireddy

On 29/05/2023 10:49, Laurent Pinchart wrote:
> On Mon, May 29, 2023 at 10:34:16AM +0300, Tomi Valkeinen wrote:
>> On 24/04/2023 10:29, Laurent Pinchart wrote:
>>
>>>> +
>>>>    	if (media_entity_type(entity) != MEDIA_ENT_T_V4L2_SUBDEV)
>>>>    		return;
>>>>    
>>>> -	v4l2_subdev_print_format(entity, pad->index, V4L2_SUBDEV_FORMAT_ACTIVE);
>>>> -	v4l2_subdev_print_pad_dv(entity, pad->index, V4L2_SUBDEV_FORMAT_ACTIVE);
>>>> +	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);
>>>> +
>>>> +		return;
>>>> +	}
>>>> +
>>>> +	printed_streams_mask = 0;
>>>> +
>>>> +	for (i = 0; i < num_routes; ++i) {
>>>> +		const struct v4l2_subdev_route *r = &routes[i];
>>>
>>> Naming the variable 'route' would be more explicit.
>>>
>>>> +		unsigned int stream;
>>>>    
>>>> -	if (pad->flags & MEDIA_PAD_FL_SOURCE)
>>>> -		v4l2_subdev_print_subdev_dv(entity);
>>>> +		if (!(r->flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE))
>>>> +			continue;
>>>> +
>>>> +		if (pad->flags & MEDIA_PAD_FL_SINK) {
>>>> +			if (r->sink_pad != pad->index)
>>>> +				continue;
>>>> +
>>>> +			stream = r->sink_stream;
>>>> +		} else {
>>>> +			if (r->source_pad != pad->index)
>>>> +				continue;
>>>> +
>>>> +			stream = r->source_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() and v4l2_subdev_print_subdev_dv() don't
>>> depend on routes or streams, should they be printed outside of the loop
>>> ?
>>
>> There's an if-block above the for loop which handles the no-routes case.
> 
> What I meant is that the pad and entity variables are constant through
> the whole loop, so why should the pad dv and subdev dv information be
> printed for each route?

I see. Yes, you're right. Previously the DV prints happened after the 
pad format print. So I could move them after the for loop which prints 
all the streams. I don't have any DV devices (and they wouldn't have 
streams anyway), but after hacking the code so that it always prints 
some DV prints, it doesn't look too bad.

  Tomi


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

end of thread, other threads:[~2023-05-29  8:14 UTC | newest]

Thread overview: 23+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-04-21 12:44 [PATCH v4 0/8] v4l-utils: Support multiplexed streams Tomi Valkeinen
2023-04-21 12:44 ` [PATCH v4 1/8] v4l2-ctl: Add routing and streams support Tomi Valkeinen
2023-04-24  7:04   ` Laurent Pinchart
2023-05-29  7:02     ` Tomi Valkeinen
2023-05-29  7:47       ` Laurent Pinchart
2023-04-21 12:44 ` [PATCH v4 2/8] media-ctl: Add support for routes and streams Tomi Valkeinen
2023-04-24  7:29   ` Laurent Pinchart
2023-05-29  7:34     ` Tomi Valkeinen
2023-05-29  7:49       ` Laurent Pinchart
2023-05-29  8:14         ` Tomi Valkeinen
2023-04-21 12:44 ` [PATCH v4 3/8] v4l2-ctl/compliance: Add routing and streams multiplexed streams Tomi Valkeinen
2023-04-21 12:44 ` [PATCH v4 4/8] v4l2-ctl/compliance: Add simple routing test Tomi Valkeinen
2023-04-24  8:04   ` Laurent Pinchart
2023-04-21 12:44 ` [PATCH v4 5/8] HACK: include/linux: Add client capabilities Tomi Valkeinen
2023-04-24  7:32   ` Laurent Pinchart
2023-05-25 14:05     ` Hans Verkuil
2023-05-26  8:19       ` Tomi Valkeinen
2023-04-21 12:44 ` [PATCH v4 6/8] media-ctl: Check for Streams API support Tomi Valkeinen
2023-04-24  7:54   ` Laurent Pinchart
2023-04-21 12:44 ` [PATCH v4 7/8] utils/common: Set V4L2_SUBDEV_CLIENT_CAP_STREAMS for subdevs Tomi Valkeinen
2023-04-24  8:01   ` Laurent Pinchart
2023-04-21 12:44 ` [PATCH v4 8/8] v4l2-ctl: Check for Streams API support Tomi Valkeinen
2023-04-24  8:03   ` Laurent Pinchart

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).