diff mbox series

[v3,2/3] media-ctl: add support for routes and streams

Message ID 20230210115546.199809-3-tomi.valkeinen@ideasonboard.com (mailing list archive)
State New, archived
Headers show
Series v4l-utils: support multiplexed streams | expand

Commit Message

Tomi Valkeinen Feb. 10, 2023, 11:55 a.m. UTC
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 | 283 ++++++++++++++++++++++++++++----
 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, 478 insertions(+), 57 deletions(-)

Comments

Sakari Ailus Feb. 10, 2023, 1:10 p.m. UTC | #1
Hi Tomi,

Thanks for the patch.

On Fri, Feb 10, 2023 at 01:55:45PM +0200, 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 | 283 ++++++++++++++++++++++++++++----
>  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, 478 insertions(+), 57 deletions(-)
> 
> diff --git a/utils/media-ctl/libmediactl.c b/utils/media-ctl/libmediactl.c
> index 1fd6525b..537365d0 100644
> --- a/utils/media-ctl/libmediactl.c
> +++ b/utils/media-ctl/libmediactl.c
> @@ -876,6 +876,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) {
> +			printf("Unable to parse stream: '%s'\n", orig_p);

media_dbg()?

> +			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..d203e5b4 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 = { 0 };
> +	struct v4l2_subdev_route *r;
> +	int ret;
> +
> +	ret = v4l2_subdev_open(entity);
> +	if (ret < 0)
> +		return ret;
> +
> +	routing.which = V4L2_SUBDEV_FORMAT_ACTIVE;

You can assing this in variable declaration.

> +
> +	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,155 @@ int v4l2_subdev_set_frame_interval(struct media_entity *entity,
>  	return 0;
>  }
>  
> +static int v4l2_subdev_parse_setup_route(struct media_device *media,
> +					 struct v4l2_subdev_route *r,
> +					 const char *p, char **endp)
> +{
> +	char *end;
> +
> +	/* sink pad/stream */

You could remove the emply lines in places like this. Up to you.

> +
> +	r->sink_pad = strtoul(p, &end, 10);
> +

And this, too.

> +	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) {
> +		printf("VIDIOC_SUBDEV_S_ROUTING failed: %d\n", ret);
> +		goto out;
> +	}
> +
> +out:
> +	free(routes);
> +
> +	return ret;
> +}
> +
>  static int v4l2_subdev_parse_format(struct media_device *media,
>  				    struct v4l2_mbus_framefmt *format,
>  				    const char *p, char **endp)
> @@ -442,7 +661,8 @@ static bool strhazit(const char *str, const char **p)
>  }
>  
>  static struct media_pad *v4l2_subdev_parse_pad_format(
> -	struct media_device *media, struct v4l2_mbus_framefmt *format,
> +	struct media_device *media, unsigned int *stream,
> +	struct v4l2_mbus_framefmt *format,
>  	struct v4l2_rect *crop, struct v4l2_rect *compose,
>  	struct v4l2_fract *interval, const char *p, char **endp)
>  {
> @@ -453,7 +673,7 @@ static struct media_pad *v4l2_subdev_parse_pad_format(
>  
>  	for (; isspace(*p); ++p);
>  
> -	pad = media_parse_pad(media, p, &end);
> +	pad = media_parse_pad_stream(media, p, stream, &end);
>  	if (pad == NULL) {
>  		*endp = end;
>  		return NULL;
> @@ -675,6 +895,7 @@ static struct media_pad *v4l2_subdev_parse_pad_format(
>  }
>  
>  static int set_format(struct media_pad *pad,
> +		      unsigned int stream,
>  		      struct v4l2_mbus_framefmt *format)
>  {
>  	int ret;
> @@ -683,12 +904,12 @@ static int set_format(struct media_pad *pad,
>  		return 0;
>  
>  	media_dbg(pad->entity->media,
> -		  "Setting up format %s %ux%u on pad %s/%u\n",
> +		  "Setting up format %s %ux%u on pad %s/%u/%u\n",
>  		  v4l2_subdev_pixelcode_to_string(format->code),
>  		  format->width, format->height,
> -		  pad->entity->info.name, pad->index);
> +		  pad->entity->info.name, pad->index, stream);
>  
> -	ret = v4l2_subdev_set_format(pad->entity, format, pad->index,
> +	ret = v4l2_subdev_set_format(pad->entity, format, pad->index, stream,
>  				     V4L2_SUBDEV_FORMAT_ACTIVE);
>  	if (ret < 0) {
>  		media_dbg(pad->entity->media,
> @@ -705,8 +926,8 @@ static int set_format(struct media_pad *pad,
>  	return 0;
>  }
>  
> -static int set_selection(struct media_pad *pad, unsigned int target,
> -			 struct v4l2_rect *rect)
> +static int set_selection(struct media_pad *pad, unsigned int stream,
> +			 unsigned int target, struct v4l2_rect *rect)
>  {
>  	int ret;
>  
> @@ -714,11 +935,11 @@ static int set_selection(struct media_pad *pad, unsigned int target,
>  		return 0;
>  
>  	media_dbg(pad->entity->media,
> -		  "Setting up selection target %u rectangle (%u,%u)/%ux%u on pad %s/%u\n",
> +		  "Setting up selection target %u rectangle (%u,%u)/%ux%u on pad %s/%u/%u\n",
>  		  target, rect->left, rect->top, rect->width, rect->height,
> -		  pad->entity->info.name, pad->index);
> +		  pad->entity->info.name, pad->index, stream);
>  
> -	ret = v4l2_subdev_set_selection(pad->entity, rect, pad->index,
> +	ret = v4l2_subdev_set_selection(pad->entity, rect, pad->index, stream,
>  					target, V4L2_SUBDEV_FORMAT_ACTIVE);
>  	if (ret < 0) {
>  		media_dbg(pad->entity->media,
> @@ -734,7 +955,7 @@ static int set_selection(struct media_pad *pad, unsigned int target,
>  	return 0;
>  }
>  
> -static int set_frame_interval(struct media_pad *pad,
> +static int set_frame_interval(struct media_pad *pad, unsigned int stream,
>  			      struct v4l2_fract *interval)
>  {
>  	int ret;
> @@ -743,11 +964,12 @@ static int set_frame_interval(struct media_pad *pad,
>  		return 0;
>  
>  	media_dbg(pad->entity->media,
> -		  "Setting up frame interval %u/%u on pad %s/%u\n",
> +		  "Setting up frame interval %u/%u on pad %s/%u/%u\n",
>  		  interval->numerator, interval->denominator,
> -		  pad->entity->info.name, pad->index);
> +		  pad->entity->info.name, pad->index, stream);
>  
> -	ret = v4l2_subdev_set_frame_interval(pad->entity, interval, pad->index);
> +	ret = v4l2_subdev_set_frame_interval(pad->entity, interval, pad->index,
> +					     stream);
>  	if (ret < 0) {
>  		media_dbg(pad->entity->media,
>  			  "Unable to set frame interval: %s (%d)",
> @@ -770,11 +992,13 @@ static int v4l2_subdev_parse_setup_format(struct media_device *media,
>  	struct v4l2_rect crop = { -1, -1, -1, -1 };
>  	struct v4l2_rect compose = crop;
>  	struct v4l2_fract interval = { 0, 0 };
> +	unsigned int stream;
>  	unsigned int i;
>  	char *end;
>  	int ret;
>  
> -	pad = v4l2_subdev_parse_pad_format(media, &format, &crop, &compose,
> +	pad = v4l2_subdev_parse_pad_format(media, &stream,
> +					   &format, &crop, &compose,
>  					   &interval, p, &end);
>  	if (pad == NULL) {
>  		media_print_streampos(media, p, end);
> @@ -783,30 +1007,29 @@ static int v4l2_subdev_parse_setup_format(struct media_device *media,
>  	}
>  
>  	if (pad->flags & MEDIA_PAD_FL_SINK) {
> -		ret = set_format(pad, &format);
> +		ret = set_format(pad, stream, &format);
>  		if (ret < 0)
>  			return ret;
>  	}
>  
> -	ret = set_selection(pad, V4L2_SEL_TGT_CROP, &crop);
> +	ret = set_selection(pad, stream, V4L2_SEL_TGT_CROP, &crop);
>  	if (ret < 0)
>  		return ret;
>  
> -	ret = set_selection(pad, V4L2_SEL_TGT_COMPOSE, &compose);
> +	ret = set_selection(pad, stream, V4L2_SEL_TGT_COMPOSE, &compose);
>  	if (ret < 0)
>  		return ret;
>  
>  	if (pad->flags & MEDIA_PAD_FL_SOURCE) {
> -		ret = set_format(pad, &format);
> +		ret = set_format(pad, stream, &format);
>  		if (ret < 0)
>  			return ret;
>  	}
>  
> -	ret = set_frame_interval(pad, &interval);
> +	ret = set_frame_interval(pad, stream, &interval);
>  	if (ret < 0)
>  		return ret;
>  
> -
>  	/* If the pad is an output pad, automatically set the same format and
>  	 * frame interval on the remote subdev input pads, if any.
>  	 */
> @@ -821,9 +1044,9 @@ static int v4l2_subdev_parse_setup_format(struct media_device *media,
>  			if (link->source == pad &&
>  			    link->sink->entity->info.type == MEDIA_ENT_T_V4L2_SUBDEV) {
>  				remote_format = format;
> -				set_format(link->sink, &remote_format);
> +				set_format(link->sink, stream, &remote_format);
>  
> -				ret = set_frame_interval(link->sink, &interval);
> +				ret = set_frame_interval(link->sink, stream, &interval);
>  				if (ret < 0 && ret != -EINVAL && ret != -ENOTTY)
>  					return ret;
>  			}
> diff --git a/utils/media-ctl/media-ctl.c b/utils/media-ctl/media-ctl.c
> index 84ee7a83..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..58ddec3c 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, immutable - 0x2, source - 0x4)\n");

Which of these flags we have defined in the kernel right now?

>  	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 b1751f56..8796f1b6 100644
> --- a/utils/media-ctl/options.h
> +++ b/utils/media-ctl/options.h
> @@ -38,6 +38,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
Tomi Valkeinen Feb. 10, 2023, 2:15 p.m. UTC | #2
Hi Sakari,

On 10/02/2023 15:10, Sakari Ailus wrote:
> Hi Tomi,
> 
> Thanks for the patch.
> 
> On Fri, Feb 10, 2023 at 01:55:45PM +0200, 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 | 283 ++++++++++++++++++++++++++++----
>>   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, 478 insertions(+), 57 deletions(-)
>>
>> diff --git a/utils/media-ctl/libmediactl.c b/utils/media-ctl/libmediactl.c
>> index 1fd6525b..537365d0 100644
>> --- a/utils/media-ctl/libmediactl.c
>> +++ b/utils/media-ctl/libmediactl.c
>> @@ -876,6 +876,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) {
>> +			printf("Unable to parse stream: '%s'\n", orig_p);
> 
> media_dbg()?

Right.

>> +			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..d203e5b4 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 = { 0 };
>> +	struct v4l2_subdev_route *r;
>> +	int ret;
>> +
>> +	ret = v4l2_subdev_open(entity);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	routing.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> 
> You can assing this in variable declaration.

Ok.

>> +
>> +	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,155 @@ int v4l2_subdev_set_frame_interval(struct media_entity *entity,
>>   	return 0;
>>   }
>>   
>> +static int v4l2_subdev_parse_setup_route(struct media_device *media,
>> +					 struct v4l2_subdev_route *r,
>> +					 const char *p, char **endp)
>> +{
>> +	char *end;
>> +
>> +	/* sink pad/stream */
> 
> You could remove the emply lines in places like this. Up to you.

It refers to multiple lines below. I usually leave a blank line in cases 
like this.

>> +
>> +	r->sink_pad = strtoul(p, &end, 10);
>> +
> 
> And this, too.

Ok.

>> +	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) {
>> +		printf("VIDIOC_SUBDEV_S_ROUTING failed: %d\n", ret);
>> +		goto out;
>> +	}
>> +
>> +out:
>> +	free(routes);
>> +
>> +	return ret;
>> +}
>> +
>>   static int v4l2_subdev_parse_format(struct media_device *media,
>>   				    struct v4l2_mbus_framefmt *format,
>>   				    const char *p, char **endp)
>> @@ -442,7 +661,8 @@ static bool strhazit(const char *str, const char **p)
>>   }
>>   
>>   static struct media_pad *v4l2_subdev_parse_pad_format(
>> -	struct media_device *media, struct v4l2_mbus_framefmt *format,
>> +	struct media_device *media, unsigned int *stream,
>> +	struct v4l2_mbus_framefmt *format,
>>   	struct v4l2_rect *crop, struct v4l2_rect *compose,
>>   	struct v4l2_fract *interval, const char *p, char **endp)
>>   {
>> @@ -453,7 +673,7 @@ static struct media_pad *v4l2_subdev_parse_pad_format(
>>   
>>   	for (; isspace(*p); ++p);
>>   
>> -	pad = media_parse_pad(media, p, &end);
>> +	pad = media_parse_pad_stream(media, p, stream, &end);
>>   	if (pad == NULL) {
>>   		*endp = end;
>>   		return NULL;
>> @@ -675,6 +895,7 @@ static struct media_pad *v4l2_subdev_parse_pad_format(
>>   }
>>   
>>   static int set_format(struct media_pad *pad,
>> +		      unsigned int stream,
>>   		      struct v4l2_mbus_framefmt *format)
>>   {
>>   	int ret;
>> @@ -683,12 +904,12 @@ static int set_format(struct media_pad *pad,
>>   		return 0;
>>   
>>   	media_dbg(pad->entity->media,
>> -		  "Setting up format %s %ux%u on pad %s/%u\n",
>> +		  "Setting up format %s %ux%u on pad %s/%u/%u\n",
>>   		  v4l2_subdev_pixelcode_to_string(format->code),
>>   		  format->width, format->height,
>> -		  pad->entity->info.name, pad->index);
>> +		  pad->entity->info.name, pad->index, stream);
>>   
>> -	ret = v4l2_subdev_set_format(pad->entity, format, pad->index,
>> +	ret = v4l2_subdev_set_format(pad->entity, format, pad->index, stream,
>>   				     V4L2_SUBDEV_FORMAT_ACTIVE);
>>   	if (ret < 0) {
>>   		media_dbg(pad->entity->media,
>> @@ -705,8 +926,8 @@ static int set_format(struct media_pad *pad,
>>   	return 0;
>>   }
>>   
>> -static int set_selection(struct media_pad *pad, unsigned int target,
>> -			 struct v4l2_rect *rect)
>> +static int set_selection(struct media_pad *pad, unsigned int stream,
>> +			 unsigned int target, struct v4l2_rect *rect)
>>   {
>>   	int ret;
>>   
>> @@ -714,11 +935,11 @@ static int set_selection(struct media_pad *pad, unsigned int target,
>>   		return 0;
>>   
>>   	media_dbg(pad->entity->media,
>> -		  "Setting up selection target %u rectangle (%u,%u)/%ux%u on pad %s/%u\n",
>> +		  "Setting up selection target %u rectangle (%u,%u)/%ux%u on pad %s/%u/%u\n",
>>   		  target, rect->left, rect->top, rect->width, rect->height,
>> -		  pad->entity->info.name, pad->index);
>> +		  pad->entity->info.name, pad->index, stream);
>>   
>> -	ret = v4l2_subdev_set_selection(pad->entity, rect, pad->index,
>> +	ret = v4l2_subdev_set_selection(pad->entity, rect, pad->index, stream,
>>   					target, V4L2_SUBDEV_FORMAT_ACTIVE);
>>   	if (ret < 0) {
>>   		media_dbg(pad->entity->media,
>> @@ -734,7 +955,7 @@ static int set_selection(struct media_pad *pad, unsigned int target,
>>   	return 0;
>>   }
>>   
>> -static int set_frame_interval(struct media_pad *pad,
>> +static int set_frame_interval(struct media_pad *pad, unsigned int stream,
>>   			      struct v4l2_fract *interval)
>>   {
>>   	int ret;
>> @@ -743,11 +964,12 @@ static int set_frame_interval(struct media_pad *pad,
>>   		return 0;
>>   
>>   	media_dbg(pad->entity->media,
>> -		  "Setting up frame interval %u/%u on pad %s/%u\n",
>> +		  "Setting up frame interval %u/%u on pad %s/%u/%u\n",
>>   		  interval->numerator, interval->denominator,
>> -		  pad->entity->info.name, pad->index);
>> +		  pad->entity->info.name, pad->index, stream);
>>   
>> -	ret = v4l2_subdev_set_frame_interval(pad->entity, interval, pad->index);
>> +	ret = v4l2_subdev_set_frame_interval(pad->entity, interval, pad->index,
>> +					     stream);
>>   	if (ret < 0) {
>>   		media_dbg(pad->entity->media,
>>   			  "Unable to set frame interval: %s (%d)",
>> @@ -770,11 +992,13 @@ static int v4l2_subdev_parse_setup_format(struct media_device *media,
>>   	struct v4l2_rect crop = { -1, -1, -1, -1 };
>>   	struct v4l2_rect compose = crop;
>>   	struct v4l2_fract interval = { 0, 0 };
>> +	unsigned int stream;
>>   	unsigned int i;
>>   	char *end;
>>   	int ret;
>>   
>> -	pad = v4l2_subdev_parse_pad_format(media, &format, &crop, &compose,
>> +	pad = v4l2_subdev_parse_pad_format(media, &stream,
>> +					   &format, &crop, &compose,
>>   					   &interval, p, &end);
>>   	if (pad == NULL) {
>>   		media_print_streampos(media, p, end);
>> @@ -783,30 +1007,29 @@ static int v4l2_subdev_parse_setup_format(struct media_device *media,
>>   	}
>>   
>>   	if (pad->flags & MEDIA_PAD_FL_SINK) {
>> -		ret = set_format(pad, &format);
>> +		ret = set_format(pad, stream, &format);
>>   		if (ret < 0)
>>   			return ret;
>>   	}
>>   
>> -	ret = set_selection(pad, V4L2_SEL_TGT_CROP, &crop);
>> +	ret = set_selection(pad, stream, V4L2_SEL_TGT_CROP, &crop);
>>   	if (ret < 0)
>>   		return ret;
>>   
>> -	ret = set_selection(pad, V4L2_SEL_TGT_COMPOSE, &compose);
>> +	ret = set_selection(pad, stream, V4L2_SEL_TGT_COMPOSE, &compose);
>>   	if (ret < 0)
>>   		return ret;
>>   
>>   	if (pad->flags & MEDIA_PAD_FL_SOURCE) {
>> -		ret = set_format(pad, &format);
>> +		ret = set_format(pad, stream, &format);
>>   		if (ret < 0)
>>   			return ret;
>>   	}
>>   
>> -	ret = set_frame_interval(pad, &interval);
>> +	ret = set_frame_interval(pad, stream, &interval);
>>   	if (ret < 0)
>>   		return ret;
>>   
>> -
>>   	/* If the pad is an output pad, automatically set the same format and
>>   	 * frame interval on the remote subdev input pads, if any.
>>   	 */
>> @@ -821,9 +1044,9 @@ static int v4l2_subdev_parse_setup_format(struct media_device *media,
>>   			if (link->source == pad &&
>>   			    link->sink->entity->info.type == MEDIA_ENT_T_V4L2_SUBDEV) {
>>   				remote_format = format;
>> -				set_format(link->sink, &remote_format);
>> +				set_format(link->sink, stream, &remote_format);
>>   
>> -				ret = set_frame_interval(link->sink, &interval);
>> +				ret = set_frame_interval(link->sink, stream, &interval);
>>   				if (ret < 0 && ret != -EINVAL && ret != -ENOTTY)
>>   					return ret;
>>   			}
>> diff --git a/utils/media-ctl/media-ctl.c b/utils/media-ctl/media-ctl.c
>> index 84ee7a83..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..58ddec3c 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, immutable - 0x2, source - 0x4)\n");
> 
> Which of these flags we have defined in the kernel right now?

Oops. I removed to references in actual code, but forgot to remove the 
flags here. Only active exists at the moment.

  Tomi
diff mbox series

Patch

diff --git a/utils/media-ctl/libmediactl.c b/utils/media-ctl/libmediactl.c
index 1fd6525b..537365d0 100644
--- a/utils/media-ctl/libmediactl.c
+++ b/utils/media-ctl/libmediactl.c
@@ -876,6 +876,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) {
+			printf("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..d203e5b4 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 = { 0 };
+	struct v4l2_subdev_route *r;
+	int ret;
+
+	ret = v4l2_subdev_open(entity);
+	if (ret < 0)
+		return ret;
+
+	routing.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+
+	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,155 @@  int v4l2_subdev_set_frame_interval(struct media_entity *entity,
 	return 0;
 }
 
+static int v4l2_subdev_parse_setup_route(struct media_device *media,
+					 struct v4l2_subdev_route *r,
+					 const char *p, char **endp)
+{
+	char *end;
+
+	/* sink pad/stream */
+
+	r->sink_pad = strtoul(p, &end, 10);
+
+	if (*end != '/') {
+		media_dbg(media, "Expected '/'\n");
+		return -EINVAL;
+	}
+
+	p = end + 1;
+
+	r->sink_stream = strtoul(p, &end, 10);
+
+	for (; isspace(*end); ++end);
+
+	if (end[0] != '-' || end[1] != '>') {
+		media_dbg(media, "Expected '->'\n");
+		return -EINVAL;
+	}
+	p = end + 2;
+
+	/* source pad/stream */
+
+	r->source_pad = strtoul(p, &end, 10);
+
+	if (*end != '/') {
+		media_dbg(media, "Expected '/'\n");
+		return -EINVAL;
+	}
+
+	p = end + 1;
+
+	r->source_stream = strtoul(p, &end, 10);
+
+	/* flags */
+
+	for (; isspace(*end); ++end);
+
+	if (*end != '[') {
+		media_dbg(media, "Expected '['\n");
+		return -EINVAL;
+	}
+
+	for (end++; isspace(*end); ++end);
+
+	p = end;
+
+	r->flags = strtoul(p, &end, 0);
+
+	if (r->flags & ~(V4L2_SUBDEV_ROUTE_FL_ACTIVE)) {
+		media_dbg(media, "Bad route flags %#x\n", r->flags);
+		return -EINVAL;
+	}
+
+	for (; isspace(*end); ++end);
+
+	if (*end != ']') {
+		media_dbg(media, "Expected ']'\n");
+		return -EINVAL;
+	}
+	end++;
+
+	*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) {
+		printf("VIDIOC_SUBDEV_S_ROUTING failed: %d\n", ret);
+		goto out;
+	}
+
+out:
+	free(routes);
+
+	return ret;
+}
+
 static int v4l2_subdev_parse_format(struct media_device *media,
 				    struct v4l2_mbus_framefmt *format,
 				    const char *p, char **endp)
@@ -442,7 +661,8 @@  static bool strhazit(const char *str, const char **p)
 }
 
 static struct media_pad *v4l2_subdev_parse_pad_format(
-	struct media_device *media, struct v4l2_mbus_framefmt *format,
+	struct media_device *media, unsigned int *stream,
+	struct v4l2_mbus_framefmt *format,
 	struct v4l2_rect *crop, struct v4l2_rect *compose,
 	struct v4l2_fract *interval, const char *p, char **endp)
 {
@@ -453,7 +673,7 @@  static struct media_pad *v4l2_subdev_parse_pad_format(
 
 	for (; isspace(*p); ++p);
 
-	pad = media_parse_pad(media, p, &end);
+	pad = media_parse_pad_stream(media, p, stream, &end);
 	if (pad == NULL) {
 		*endp = end;
 		return NULL;
@@ -675,6 +895,7 @@  static struct media_pad *v4l2_subdev_parse_pad_format(
 }
 
 static int set_format(struct media_pad *pad,
+		      unsigned int stream,
 		      struct v4l2_mbus_framefmt *format)
 {
 	int ret;
@@ -683,12 +904,12 @@  static int set_format(struct media_pad *pad,
 		return 0;
 
 	media_dbg(pad->entity->media,
-		  "Setting up format %s %ux%u on pad %s/%u\n",
+		  "Setting up format %s %ux%u on pad %s/%u/%u\n",
 		  v4l2_subdev_pixelcode_to_string(format->code),
 		  format->width, format->height,
-		  pad->entity->info.name, pad->index);
+		  pad->entity->info.name, pad->index, stream);
 
-	ret = v4l2_subdev_set_format(pad->entity, format, pad->index,
+	ret = v4l2_subdev_set_format(pad->entity, format, pad->index, stream,
 				     V4L2_SUBDEV_FORMAT_ACTIVE);
 	if (ret < 0) {
 		media_dbg(pad->entity->media,
@@ -705,8 +926,8 @@  static int set_format(struct media_pad *pad,
 	return 0;
 }
 
-static int set_selection(struct media_pad *pad, unsigned int target,
-			 struct v4l2_rect *rect)
+static int set_selection(struct media_pad *pad, unsigned int stream,
+			 unsigned int target, struct v4l2_rect *rect)
 {
 	int ret;
 
@@ -714,11 +935,11 @@  static int set_selection(struct media_pad *pad, unsigned int target,
 		return 0;
 
 	media_dbg(pad->entity->media,
-		  "Setting up selection target %u rectangle (%u,%u)/%ux%u on pad %s/%u\n",
+		  "Setting up selection target %u rectangle (%u,%u)/%ux%u on pad %s/%u/%u\n",
 		  target, rect->left, rect->top, rect->width, rect->height,
-		  pad->entity->info.name, pad->index);
+		  pad->entity->info.name, pad->index, stream);
 
-	ret = v4l2_subdev_set_selection(pad->entity, rect, pad->index,
+	ret = v4l2_subdev_set_selection(pad->entity, rect, pad->index, stream,
 					target, V4L2_SUBDEV_FORMAT_ACTIVE);
 	if (ret < 0) {
 		media_dbg(pad->entity->media,
@@ -734,7 +955,7 @@  static int set_selection(struct media_pad *pad, unsigned int target,
 	return 0;
 }
 
-static int set_frame_interval(struct media_pad *pad,
+static int set_frame_interval(struct media_pad *pad, unsigned int stream,
 			      struct v4l2_fract *interval)
 {
 	int ret;
@@ -743,11 +964,12 @@  static int set_frame_interval(struct media_pad *pad,
 		return 0;
 
 	media_dbg(pad->entity->media,
-		  "Setting up frame interval %u/%u on pad %s/%u\n",
+		  "Setting up frame interval %u/%u on pad %s/%u/%u\n",
 		  interval->numerator, interval->denominator,
-		  pad->entity->info.name, pad->index);
+		  pad->entity->info.name, pad->index, stream);
 
-	ret = v4l2_subdev_set_frame_interval(pad->entity, interval, pad->index);
+	ret = v4l2_subdev_set_frame_interval(pad->entity, interval, pad->index,
+					     stream);
 	if (ret < 0) {
 		media_dbg(pad->entity->media,
 			  "Unable to set frame interval: %s (%d)",
@@ -770,11 +992,13 @@  static int v4l2_subdev_parse_setup_format(struct media_device *media,
 	struct v4l2_rect crop = { -1, -1, -1, -1 };
 	struct v4l2_rect compose = crop;
 	struct v4l2_fract interval = { 0, 0 };
+	unsigned int stream;
 	unsigned int i;
 	char *end;
 	int ret;
 
-	pad = v4l2_subdev_parse_pad_format(media, &format, &crop, &compose,
+	pad = v4l2_subdev_parse_pad_format(media, &stream,
+					   &format, &crop, &compose,
 					   &interval, p, &end);
 	if (pad == NULL) {
 		media_print_streampos(media, p, end);
@@ -783,30 +1007,29 @@  static int v4l2_subdev_parse_setup_format(struct media_device *media,
 	}
 
 	if (pad->flags & MEDIA_PAD_FL_SINK) {
-		ret = set_format(pad, &format);
+		ret = set_format(pad, stream, &format);
 		if (ret < 0)
 			return ret;
 	}
 
-	ret = set_selection(pad, V4L2_SEL_TGT_CROP, &crop);
+	ret = set_selection(pad, stream, V4L2_SEL_TGT_CROP, &crop);
 	if (ret < 0)
 		return ret;
 
-	ret = set_selection(pad, V4L2_SEL_TGT_COMPOSE, &compose);
+	ret = set_selection(pad, stream, V4L2_SEL_TGT_COMPOSE, &compose);
 	if (ret < 0)
 		return ret;
 
 	if (pad->flags & MEDIA_PAD_FL_SOURCE) {
-		ret = set_format(pad, &format);
+		ret = set_format(pad, stream, &format);
 		if (ret < 0)
 			return ret;
 	}
 
-	ret = set_frame_interval(pad, &interval);
+	ret = set_frame_interval(pad, stream, &interval);
 	if (ret < 0)
 		return ret;
 
-
 	/* If the pad is an output pad, automatically set the same format and
 	 * frame interval on the remote subdev input pads, if any.
 	 */
@@ -821,9 +1044,9 @@  static int v4l2_subdev_parse_setup_format(struct media_device *media,
 			if (link->source == pad &&
 			    link->sink->entity->info.type == MEDIA_ENT_T_V4L2_SUBDEV) {
 				remote_format = format;
-				set_format(link->sink, &remote_format);
+				set_format(link->sink, stream, &remote_format);
 
-				ret = set_frame_interval(link->sink, &interval);
+				ret = set_frame_interval(link->sink, stream, &interval);
 				if (ret < 0 && ret != -EINVAL && ret != -ENOTTY)
 					return ret;
 			}
diff --git a/utils/media-ctl/media-ctl.c b/utils/media-ctl/media-ctl.c
index 84ee7a83..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..58ddec3c 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, immutable - 0x2, source - 0x4)\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 b1751f56..8796f1b6 100644
--- a/utils/media-ctl/options.h
+++ b/utils/media-ctl/options.h
@@ -38,6 +38,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