• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Media controller Next Generation test app
3  *
4  * Copyright (C) 2015 Mauro Carvalho Chehab <mchehab@kernel.org>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation version 2
9  * of the License.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  * Or, point your browser to http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20  * */
21 
22 #include <linux/media.h>
23 
24 #include <argp.h>
25 
26 #include <syslog.h>
27 #include <stdio.h>
28 #include <sys/types.h>
29 #include <sys/sysmacros.h>
30 #include <sys/stat.h>
31 #include <sys/ioctl.h>
32 #include <fcntl.h>
33 #include <stdlib.h>
34 #include <stdint.h>
35 #include <unistd.h>
36 #include <stdarg.h>
37 #include <errno.h>
38 #include <string.h>
39 
40 #define PROGRAM_NAME	"mc_nextgen_test"
41 
42 const char *argp_program_version = PROGRAM_NAME " version " V4L_UTILS_VERSION;
43 const char *argp_program_bug_address = "Mauro Carvalho Chehab <mchehab@kernel.org>";
44 
45 static const char doc[] = "\nA testing tool for the MC next geneneration API\n";
46 
47 static const struct argp_option options[] = {
48 	{"entities",		'e',	0,		0,	"show entities", 0},
49 	{"interfaces",		'i',	0,		0,	"show pads", 0},
50 	{"data-links",		'l',	0,		0,	"show data links", 0},
51 	{"intf-links",		'I',	0,		0,	"show interface links", 0},
52 	{"ancillary-links",	'a',	0,		0,	"show ancillary links", 0},
53 	{"dot",			'D',	0,		0,	"show in Graphviz format", 0},
54 	{"max_tsout",		't',	"NUM_TSOUT",	0,	"max number of DTV TS out entities/interfaces in graphviz output (default: 5)", 0},
55 	{"device",		'd',	"DEVICE",	0,	"media controller device (default: /dev/media0", 0},
56 
57 	{"help",		'?',	0,		0,	"Give this help list", -1},
58 	{"usage",		-3,	0,		0,	"Give a short usage message"},
59 	{"version",		'V',	0,		0,	"Print program version", -1},
60 	{ 0, 0, 0, 0, 0, 0 }
61 };
62 
63 static int show_entities = 0;
64 static int show_interfaces = 0;
65 static int show_data_links = 0;
66 static int show_intf_links = 0;
67 static int show_ancillary_links = 0;
68 static int show_dot = 0;
69 static int max_tsout = 5;
70 static char media_device[256] = "/dev/media0";
71 
72 
media_get_uptr(__u64 arg)73 static inline void *media_get_uptr(__u64 arg)
74 {
75 	return (void *)(uintptr_t)arg;
76 }
77 
parse_opt(int k,char * arg,struct argp_state * state)78 static error_t parse_opt(int k, char *arg, struct argp_state *state)
79 {
80 	switch (k) {
81 	case 'e':
82 		show_entities++;
83 		break;
84 	case 'i':
85 		show_interfaces++;
86 		break;
87 	case 'l':
88 		show_data_links++;
89 		break;
90 	case 'I':
91 		show_intf_links++;
92 		break;
93 	case 'a':
94 		show_ancillary_links++;
95 		break;
96 	case 'D':
97 		show_dot++;
98 		break;
99 
100 	case 'd':
101 		strncpy(media_device, arg, sizeof(media_device) - 1);
102 		media_device[sizeof(media_device)-1] = '\0';
103 		break;
104 
105 	case 't':
106 		max_tsout = atoi(arg);
107 		break;
108 
109 	case '?':
110 		argp_state_help(state, state->out_stream,
111 				ARGP_HELP_SHORT_USAGE | ARGP_HELP_LONG
112 				| ARGP_HELP_DOC);
113 		fprintf(state->out_stream, "\nReport bugs to %s.\n",
114 			argp_program_bug_address);
115 		exit(0);
116 	case 'V':
117 		fprintf (state->out_stream, "%s\n", argp_program_version);
118 		exit(0);
119 	case -3:
120 		argp_state_help(state, state->out_stream, ARGP_HELP_USAGE);
121 		exit(0);
122 	default:
123 		return ARGP_ERR_UNKNOWN;
124 	}
125 	return 0;
126 }
127 
128 static struct argp argp = {
129 	.options = options,
130 	.parser = parse_opt,
131 	.doc = doc,
132 };
133 
134 
135 /*
136  * Those came from media-entity.h and bitops.h
137  * They should actually be added at the media.h UAPI if we decide to keep
138  * both ID and TYPE fields together - as current proposal
139  */
140 
141 enum media_gobj_type {
142 	MEDIA_GRAPH_ENTITY,
143 	MEDIA_GRAPH_PAD,
144 	MEDIA_GRAPH_LINK,
145 	MEDIA_GRAPH_INTF_DEVNODE,
146 };
147 
media_type(uint32_t id)148 static uint32_t media_type(uint32_t id)
149 {
150 	return id >> 24;
151 }
152 
media_localid(uint32_t id)153 static inline uint32_t media_localid(uint32_t id)
154 {
155 	return id & 0xffffff;
156 }
157 
gobj_type(uint32_t id)158 static inline const char *gobj_type(uint32_t id)
159 {
160 	switch (media_type(id)) {
161 	case MEDIA_GRAPH_ENTITY:
162 		return "entity";
163 	case MEDIA_GRAPH_PAD:
164 		return "pad";
165 	case MEDIA_GRAPH_LINK:
166 		return "link";
167 	case MEDIA_GRAPH_INTF_DEVNODE:
168 		return "intf_devnode";
169 	default:
170 		return "unknown intf type";
171 	}
172 }
173 
intf_type(uint32_t intf_type)174 static inline const char *intf_type(uint32_t intf_type)
175 {
176 	switch (intf_type) {
177 	case MEDIA_INTF_T_DVB_FE:
178 		return "frontend";
179 	case MEDIA_INTF_T_DVB_DEMUX:
180 		return "demux";
181 	case MEDIA_INTF_T_DVB_DVR:
182 		return "DVR";
183 	case MEDIA_INTF_T_DVB_CA:
184 		return  "CA";
185 	case MEDIA_INTF_T_DVB_NET:
186 		return "dvbnet";
187 
188 	case MEDIA_INTF_T_V4L_VIDEO:
189 		return "video";
190 	case MEDIA_INTF_T_V4L_VBI:
191 		return "vbi";
192 	case MEDIA_INTF_T_V4L_RADIO:
193 		return "radio";
194 	case MEDIA_INTF_T_V4L_SUBDEV:
195 		return "v4l2-subdev";
196 	case MEDIA_INTF_T_V4L_SWRADIO:
197 		return "swradio";
198 
199 	case MEDIA_INTF_T_ALSA_PCM_CAPTURE:
200 		return "pcm-capture";
201 	case MEDIA_INTF_T_ALSA_PCM_PLAYBACK:
202 		return "pcm-playback";
203 	case MEDIA_INTF_T_ALSA_CONTROL:
204 		return "alsa-control";
205 	case MEDIA_INTF_T_ALSA_COMPRESS:
206 		return "compress";
207 	case MEDIA_INTF_T_ALSA_RAWMIDI:
208 		return "rawmidi";
209 	case MEDIA_INTF_T_ALSA_HWDEP:
210 		return "hwdep";
211 	case MEDIA_INTF_T_ALSA_SEQUENCER:
212 		return "sequencer";
213 	case MEDIA_INTF_T_ALSA_TIMER:
214 		return "ALSA timer";
215 	default:
216 		return "unknown_intf";
217 	}
218 };
219 
ent_function(uint32_t function)220 static inline const char *ent_function(uint32_t function)
221 {
222 	switch (function) {
223 
224 	/* DVB entities */
225 	case MEDIA_ENT_F_DTV_DEMOD:
226 		return "DTV demod";
227 	case MEDIA_ENT_F_TS_DEMUX:
228 		return "MPEG-TS demux";
229 	case MEDIA_ENT_F_DTV_CA:
230 		return "DTV CA";
231 	case MEDIA_ENT_F_DTV_NET_DECAP:
232 		return "DTV Network decap";
233 
234 	/* I/O entities */
235 	case MEDIA_ENT_F_IO_DTV:
236 		return "DTV I/O";
237 	case MEDIA_ENT_F_IO_VBI:
238 		return "VBI I/O";
239 	case MEDIA_ENT_F_IO_SWRADIO:
240 		return "SDR I/O";
241 
242 	/*Analog TV IF-PLL decoders */
243 	case MEDIA_ENT_F_IF_VID_DECODER:
244 		return "IF video decoder";
245 	case MEDIA_ENT_F_IF_AUD_DECODER:
246 		return "IF sound decoder";
247 
248 	/* Audio Entity Functions */
249 	case MEDIA_ENT_F_AUDIO_CAPTURE:
250 		return "Audio Capture";
251 	case MEDIA_ENT_F_AUDIO_PLAYBACK:
252 		return "Audio Playback";
253 	case MEDIA_ENT_F_AUDIO_MIXER:
254 		return "Audio Mixer";
255 
256 #if 0
257 	/* Connectors */
258 	case MEDIA_ENT_F_CONN_RF:
259 		return "RF connector";
260 	case MEDIA_ENT_F_CONN_SVIDEO:
261 		return "S-Video connector";
262 	case MEDIA_ENT_F_CONN_COMPOSITE:
263 		return "Composite connector";
264 #endif
265 
266 	/* Entities based on MEDIA_ENT_F_OLD_BASE */
267 	case MEDIA_ENT_F_IO_V4L:
268 		return "V4L I/O";
269 	case MEDIA_ENT_F_CAM_SENSOR:
270 		return "Camera Sensor";
271 	case MEDIA_ENT_F_FLASH:
272 		return "Flash LED/light";
273 	case MEDIA_ENT_F_LENS:
274 		return "Lens";
275 	case MEDIA_ENT_F_ATV_DECODER:
276 		return "ATV decoder";
277 	case MEDIA_ENT_F_TUNER:
278 		return "tuner";
279 
280 	/* Anything else */
281 	case MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN:
282 	default:
283 		return "unknown entity type";
284 	}
285 }
286 
287 /* Ancilary function to produce an human readable ID for an object */
288 
objname(uint32_t id,char delimiter)289 static char *objname(uint32_t id, char delimiter)
290 {
291 	char *name;
292 	int ret;
293 
294 	ret = asprintf(&name, "%s%c%d", gobj_type(id), delimiter, media_localid(id));
295 	if (ret < 0)
296 		return NULL;
297 
298 	return name;
299 }
300 
301 enum ansi_colors {
302 	BLACK = 30,
303 	RED,
304 	GREEN,
305 	YELLOW,
306 	BLUE,
307 	MAGENTA,
308 	CYAN,
309 	WHITE
310 };
311 
312 #define NORMAL_COLOR "\033[0;%dm"
313 #define BRIGHT_COLOR "\033[1;%dm"
314 
show(int color,int bright,const char * fmt,...)315 void show(int color, int bright, const char *fmt, ...)
316 {
317 	va_list ap;
318 
319 	va_start(ap, fmt);
320 
321 	if (isatty(STDOUT_FILENO)) {
322 		if (bright)
323 			printf(BRIGHT_COLOR, color);
324 		else
325 			printf(NORMAL_COLOR, color);
326 	}
327 
328 	vprintf(fmt, ap);
329 
330 	va_end(ap);
331 }
332 
333 #define logperror(msg) do {\
334        show(RED, 0, "%s: %s", msg, strerror(errno)); \
335        printf("\n"); \
336 } while (0)
337 
338 /*
339  * Code to convert devnode major, minor into a name
340  *
341  * This code was imported from the Media controller interface library (libmediactl.c) under LGPL v2.1
342  *      Copyright (C) 2010-2014 Ideas on board SPRL
343  *      Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
344  */
345 #ifdef HAVE_LIBUDEV
346 
347 #include <libudev.h>
348 
media_open_ifname(void ** priv)349 static int media_open_ifname(void **priv)
350 {
351 	struct udev **udev = (struct udev **)priv;
352 
353 	*udev = udev_new();
354 	if (*udev == NULL)
355 		return -ENOMEM;
356 	return 0;
357 }
358 
media_close_ifname(void * priv)359 static void media_close_ifname(void *priv)
360 {
361 	struct udev *udev = (struct udev *)priv;
362 
363 	if (udev != NULL)
364 		udev_unref(udev);
365 }
366 
media_get_ifname_udev(struct media_v2_intf_devnode * devnode,struct udev * udev)367 static char *media_get_ifname_udev(struct media_v2_intf_devnode *devnode, struct udev *udev)
368 {
369 	struct udev_device *device;
370 	dev_t devnum;
371 	const char *p;
372 	char *name = NULL;
373 	int ret;
374 
375 	if (udev == NULL)
376 		return NULL;
377 
378 	devnum = makedev(devnode->major, devnode->minor);
379 	device = udev_device_new_from_devnum(udev, 'c', devnum);
380 	if (device) {
381 		p = udev_device_get_devnode(device);
382 		if (p) {
383 			ret = asprintf(&name, "%s", p);
384 			if (ret < 0)
385 				return NULL;
386 		}
387 	}
388 
389 	udev_device_unref(device);
390 
391 	return name;
392 }
393 #else
media_open_ifname(void ** priv)394 static inline char *media_open_ifname(void **priv) { return NULL; } ;
media_close_ifname(void * priv)395 static void media_close_ifname(void *priv) {};
396 #endif	/* HAVE_LIBUDEV */
397 
media_get_ifname(struct media_v2_interface * intf,void * priv)398 char *media_get_ifname(struct media_v2_interface *intf, void *priv)
399 {
400 	struct media_v2_intf_devnode *devnode;
401 	struct stat devstat;
402 	char devname[32];
403 	char sysname[32];
404 	char target[1024];
405 	char *p, *name = NULL;
406 	int ret;
407 
408 	/* Only handles Devnode interfaces */
409 	if (media_type(intf->id) != MEDIA_GRAPH_INTF_DEVNODE)
410 		return NULL;
411 
412 	devnode = &intf->devnode;
413 
414 #ifdef HAVE_LIBUDEV
415 	/* Try first to convert using udev */
416 	name = media_get_ifname_udev(devnode, priv);
417 	if (name)
418 		return name;
419 #endif
420 
421 	/* Failed. Let's fallback to get it via sysfs */
422 
423 	sprintf(sysname, "/sys/dev/char/%u:%u",
424 		devnode->major, devnode->minor);
425 
426 	ret = readlink(sysname, target, sizeof(target) - 1);
427 	if (ret < 0)
428 		return NULL;
429 
430 	target[ret] = '\0';
431 	p = strrchr(target, '/');
432 	if (p == NULL)
433 		return NULL;
434 
435 	sprintf(devname, "/dev/%s", p + 1);
436 	if (strstr(p + 1, "dvb")) {
437 		char *s = p + 1;
438 
439 		if (strncmp(s, "dvb", 3))
440 			return NULL;
441 		s += 3;
442 		p = strchr(s, '.');
443 		if (!p)
444 			return NULL;
445 		*p = '/';
446 		sprintf(devname, "/dev/dvb/adapter%s", s);
447 	} else {
448 		sprintf(devname, "/dev/%s", p + 1);
449 	}
450 	ret = stat(devname, &devstat);
451 	if (ret < 0)
452 		return NULL;
453 
454 	/* Sanity check: udev might have reordered the device nodes.
455 	 * Make sure the major/minor match. We should really use
456 	 * libudev.
457 	 */
458 	if (major(devstat.st_rdev) == intf->devnode.major &&
459 	    minor(devstat.st_rdev) == intf->devnode.minor) {
460 		ret = asprintf(&name, "%s", devname);
461 		if (ret < 0)
462 			return NULL;
463 	}
464 	return name;
465 }
466 
467 /*
468  * The real code starts here
469  */
470 
471 struct graph_obj {
472 	uint32_t id;
473 	union {
474 		struct media_v2_entity *entity;
475 		struct media_v2_pad *pad;
476 		struct media_v2_interface *intf;
477 		struct media_v2_link *link;
478 	};
479 	/* Currently, used only for pads->entity */
480 	struct graph_obj *parent;
481 	/* Used only for entities */
482 	int num_pads, num_pad_sinks, num_pad_sources;
483 };
484 
485 struct media_controller {
486 	int fd;
487 	struct media_v2_topology topo;
488 	struct media_device_info info;
489 
490 	int num_gobj;
491 	struct graph_obj *gobj;
492 };
493 
494 static inline
find_gobj(struct media_controller * mc,uint32_t id)495 struct graph_obj *find_gobj(struct media_controller *mc, uint32_t id)
496 {
497 	int i;
498 
499 	/*
500 	 * If we were concerned about performance, we could use bsearch
501 	 */
502 	for (i = 0; i < mc->num_gobj; i++) {
503 		if (mc->gobj[i].id == id)
504 			return &mc->gobj[i];
505 	}
506 	return NULL;
507 }
508 
media_init_graph_obj(struct media_controller * mc)509 static int media_init_graph_obj(struct media_controller *mc)
510 {
511 	struct media_v2_topology *topo = &mc->topo;
512 	int i, j, num_gobj;
513 	int idx = 0;
514 	struct media_v2_entity *entities = media_get_uptr(topo->ptr_entities);
515 	struct media_v2_interface *interfaces = media_get_uptr(topo->ptr_interfaces);
516 	struct media_v2_pad *pads = media_get_uptr(topo->ptr_pads);
517 	struct media_v2_link *links = media_get_uptr(topo->ptr_links);
518 
519 	num_gobj = topo->num_entities + topo->num_interfaces
520 		   + topo->num_pads + topo->num_links;
521 
522 	mc->gobj = calloc(num_gobj, sizeof(*mc->gobj));
523 	if (!mc->gobj) {
524 		logperror("couldn't allocate space for graph_obj");
525 		return -ENOMEM;
526 	}
527 
528 	for (i = 0; i < topo->num_pads; i++) {
529 		mc->gobj[idx].id = pads[i].id;
530 		mc->gobj[idx].pad = &pads[i];
531 		idx++;
532 	}
533 
534 	mc->num_gobj = num_gobj;
535 
536 	for (i = 0; i < topo->num_entities; i++) {
537 		struct graph_obj *gobj;
538 
539 		mc->gobj[idx].id = entities[i].id;
540 		mc->gobj[idx].entity = &entities[i];
541 
542 		/* Set the parent object for the pads */
543 		for (j = 0; j < topo->num_pads; j++) {
544 			if (pads[j].entity_id != entities[i].id)
545 				continue;
546 
547 			/* The data below is useful for Graphviz generation */
548 			mc->gobj[idx].num_pads++;
549 			if (pads[j].flags & MEDIA_PAD_FL_SINK)
550 				mc->gobj[idx].num_pad_sinks++;
551 			if (pads[j].flags & MEDIA_PAD_FL_SOURCE)
552 				mc->gobj[idx].num_pad_sources++;
553 
554 			gobj = find_gobj(mc, pads[j].id);
555 			if (gobj)
556 				gobj->parent = &mc->gobj[idx];
557 		}
558 		idx++;
559 	}
560 	for (i = 0; i < topo->num_interfaces; i++) {
561 		mc->gobj[idx].id = interfaces[i].id;
562 		mc->gobj[idx].intf = &interfaces[i];
563 		idx++;
564 	}
565 	for (i = 0; i < topo->num_links; i++) {
566 		mc->gobj[idx].id = links[i].id;
567 		mc->gobj[idx].link = &links[i];
568 		idx++;
569 	}
570 
571 	mc->num_gobj = num_gobj;
572 
573 	/*
574 	 * If we were concerned about performance, we could now sort
575 	 * the objects and use binary search at the find routine
576 	 *
577 	 * We could also add some logic here to create per-interfaces
578 	 * and per-entities linked lists with pads and links.
579 	 *
580 	 * However, this is just a test program, so let's keep it simple
581 	 */
582 	return 0;
583 }
584 
media_show_entities(struct media_controller * mc)585 static void media_show_entities(struct media_controller *mc)
586 {
587 	struct media_v2_topology *topo = &mc->topo;
588 	struct media_v2_entity *entities = media_get_uptr(topo->ptr_entities);
589 	struct media_v2_pad *pads = media_get_uptr(topo->ptr_pads);
590 	int i, j;
591 
592 	for (i = 0; i < topo->num_entities; i++) {
593 		struct media_v2_entity *entity = &entities[i];
594 		char *obj;
595 		int num_pads = 0;
596 		int num_sinks = 0;
597 		int num_sources = 0;
598 
599 		/*
600 		 * Count the number of patches - If this would be a
601 		 * real application/library, we would likely be creating
602 		 * either a list of an array to associate entities/pads/links
603 		 *
604 		 * However, we just want to test the API, so we don't care
605 		 * about performance.
606 		 */
607 		for (j = 0; j < topo->num_pads; j++) {
608 			if (pads[j].entity_id != entity->id)
609 				continue;
610 
611 			num_pads++;
612 			if (pads[j].flags & MEDIA_PAD_FL_SINK)
613 				num_sinks++;
614 			if (pads[j].flags & MEDIA_PAD_FL_SOURCE)
615 				num_sources++;
616 		}
617 
618 		obj = objname(entity->id, '#');
619 		show(YELLOW, 0, "entity %s: '%s' %s, %d pad(s)",
620 		     obj, ent_function(entity->function),
621 		     entity->name, num_pads);
622 		if (num_sinks)
623 			show(YELLOW, 0,", %d sink(s)", num_sinks);
624 		if (num_sources)
625 			show(YELLOW, 0,", %d source(s)", num_sources);
626 		printf("\n");
627 
628 		free(obj);
629 	}
630 }
631 
media_show_interfaces(struct media_controller * mc)632 static void media_show_interfaces(struct media_controller *mc)
633 {
634 	struct media_v2_topology *topo = &mc->topo;
635 	struct media_v2_interface *interfaces = media_get_uptr(topo->ptr_interfaces);
636 	void *priv = NULL;
637 	int i;
638 
639 	media_open_ifname(&priv);
640 	for (i = 0; i < topo->num_interfaces; i++) {
641 		char *obj, *devname;
642 		struct media_v2_interface *intf = &interfaces[i];
643 
644 		obj = objname(intf->id, '#');
645 		devname = media_get_ifname(intf, priv);
646 		show(GREEN, 0, "interface %s: %s %s\n",
647 		     obj, intf_type(intf->intf_type),
648 		     devname);
649 		free(obj);
650 		free(devname);
651 	}
652 	media_close_ifname(priv);
653 }
654 
media_show_links(struct media_controller * mc)655 static void media_show_links(struct media_controller *mc)
656 {
657 	struct media_v2_topology *topo = &mc->topo;
658 	struct media_v2_link *links = media_get_uptr(topo->ptr_links);
659 	int i, color;
660 
661 	for (i = 0; i < topo->num_links; i++) {
662 		struct media_v2_link *link = &links[i];
663 		char *obj, *source_obj, *sink_obj;
664 
665 		__u32 type = link->flags & MEDIA_LNK_FL_LINK_TYPE;
666 
667 		color = CYAN;
668 		switch (type) {
669 		case MEDIA_LNK_FL_DATA_LINK:
670 			if (!show_data_links)
671 				continue;
672 
673 			show(color, 0, "data");
674 
675 			break;
676 		case MEDIA_LNK_FL_INTERFACE_LINK:
677 			if (!show_intf_links)
678 				continue;
679 
680 			color = BLUE;
681 			show(color, 0, "interface");
682 
683 			break;
684 		case MEDIA_LNK_FL_ANCILLARY_LINK:
685 			if (!show_ancillary_links)
686 				continue;
687 
688 			color = MAGENTA;
689 			show(color, 0, "ancillary");
690 			break;
691 		}
692 
693 		obj = objname(link->id, '#');
694 		source_obj = objname(link->source_id, '#');
695 		sink_obj = objname(link->sink_id, '#');
696 
697 		show(color, 0, "link %s: %s %s %s",
698 		     obj, source_obj,
699 		     ((type) == MEDIA_LNK_FL_DATA_LINK) ? "=>" : "<=>",
700 		     sink_obj);
701 		if (link->flags & MEDIA_LNK_FL_IMMUTABLE)
702 			show(color, 0, " [IMMUTABLE]");
703 		if (link->flags & MEDIA_LNK_FL_DYNAMIC)
704 			show(color, 0, " [DYNAMIC]");
705 		if (link->flags & MEDIA_LNK_FL_ENABLED)
706 			show(color, 1, " [ENABLED]");
707 
708 		printf("\n");
709 
710 		free(obj);
711 		free(source_obj);
712 		free(sink_obj);
713 	}
714 }
715 
media_get_device_info(struct media_controller * mc)716 static void media_get_device_info(struct media_controller *mc)
717 {
718 	struct media_device_info *info = &mc->info;
719 	int ret = 0;
720 
721 	ret = ioctl(mc->fd, MEDIA_IOC_DEVICE_INFO, info);
722 	if (ret < 0) {
723 		logperror("MEDIA_IOC_DEVICE_INFO failed");
724 		return;
725 	}
726 	if (show_dot)
727 		return;
728 
729 	show(WHITE, 0, "Device: %s (driver %s)\n",
730 	     info->model, info->driver);
731 	if (info->serial[0])
732 		show(WHITE, 0, "Serial: %s\n", info->serial);
733 	show(WHITE, 0, "Bus: %s\n", info->bus_info);
734 }
735 
media_get_topology(struct media_controller * mc)736 static int media_get_topology(struct media_controller *mc)
737 {
738 	struct media_v2_topology *topo = &mc->topo;
739 	int ret = 0, topology_version;
740 
741 	media_get_device_info(mc);
742 
743 	/* First call: get the amount of elements */
744 	memset(topo, 0, sizeof(*topo));
745 	ret = ioctl(mc->fd, MEDIA_IOC_G_TOPOLOGY, topo);
746 	if (ret < 0) {
747 		logperror("MEDIA_IOC_G_TOPOLOGY faled to get numbers");
748 		goto error;
749 	}
750 
751 	topology_version = topo->topology_version;
752 
753 	if (!show_dot) {
754 		show(WHITE, 0, "version: %d\n", topology_version);
755 		show(WHITE, 0, "number of entities: %d\n", topo->num_entities);
756 		show(WHITE, 0, "number of interfaces: %d\n", topo->num_interfaces);
757 		show(WHITE, 0, "number of pads: %d\n", topo->num_pads);
758 		show(WHITE, 0, "number of links: %d\n", topo->num_links);
759 	}
760 
761 	do {
762 		topo->ptr_entities = (uintptr_t)calloc(topo->num_entities,
763 					sizeof(struct media_v2_entity));
764 		if (topo->num_entities && !topo->ptr_entities)
765 			goto error;
766 
767 		topo->ptr_interfaces = (uintptr_t)calloc(topo->num_interfaces,
768 					  sizeof(struct media_v2_interface));
769 		if (topo->num_interfaces && !topo->ptr_interfaces)
770 			goto error;
771 
772 		topo->ptr_pads = (uintptr_t)calloc(topo->num_pads,
773 				   sizeof(struct media_v2_pad));
774 		if (topo->num_pads && !topo->ptr_pads)
775 			goto error;
776 
777 		topo->ptr_links = (uintptr_t)calloc(topo->num_links,
778 				     sizeof(struct media_v2_link));
779 		if (topo->num_links && !topo->ptr_links)
780 			goto error;
781 
782 		ret = ioctl(mc->fd, MEDIA_IOC_G_TOPOLOGY, topo);
783 		if (ret < 0) {
784 			if (topo->topology_version != topology_version) {
785 				show(WHITE, 0, "Topology changed from version %d to %d. trying again.\n",
786 					  topology_version,
787 					  topo->topology_version);
788 				/*
789 				 * The logic here could be smarter, but, as
790 				 * topology changes should be rare, this
791 				 * should do the work
792 				 */
793 				free(media_get_uptr(topo->ptr_entities));
794 				free(media_get_uptr(topo->ptr_interfaces));
795 				free(media_get_uptr(topo->ptr_pads));
796 				free(media_get_uptr(topo->ptr_links));
797 				topology_version = topo->topology_version;
798 				continue;
799 			}
800 			logperror("MEDIA_IOC_G_TOPOLOGY faled");
801 			goto error;
802 		}
803 	} while (ret < 0);
804 
805 	media_init_graph_obj(mc);
806 
807 	return 0;
808 
809 error:
810 	if (topo->ptr_entities)
811 		free(media_get_uptr(topo->ptr_entities));
812 	if (topo->ptr_interfaces)
813 		free(media_get_uptr(topo->ptr_interfaces));
814 	if (topo->ptr_pads)
815 		free(media_get_uptr(topo->ptr_pads));
816 	if (topo->ptr_links)
817 		free(media_get_uptr(topo->ptr_links));
818 
819 	topo->ptr_entities = 0;
820 	topo->ptr_interfaces = 0;
821 	topo->ptr_pads = 0;
822 	topo->ptr_links = 0;
823 
824 	return ret;
825 }
826 
mc_open(char * devname)827 static struct media_controller *mc_open(char *devname)
828 {
829 	struct media_controller *mc;
830 
831 	mc = calloc(1, sizeof(*mc));
832 
833 	mc->fd = open(devname, O_RDWR);
834 	if (mc->fd < 0) {
835 		logperror("Can't open media device");
836 		return NULL;
837 	}
838 
839 	return mc;
840 }
841 
mc_close(struct media_controller * mc)842 static int mc_close(struct media_controller *mc)
843 {
844 	int ret;
845 
846 	ret = close(mc->fd);
847 
848 	if (mc->gobj)
849 		free(mc->gobj);
850 	if (mc->topo.ptr_entities)
851 		free(media_get_uptr(mc->topo.ptr_entities));
852 	if (mc->topo.ptr_interfaces)
853 		free(media_get_uptr(mc->topo.ptr_interfaces));
854 	if (mc->topo.ptr_pads)
855 		free(media_get_uptr(mc->topo.ptr_pads));
856 	if (mc->topo.ptr_links)
857 		free(media_get_uptr(mc->topo.ptr_links));
858 	free(mc);
859 
860 	return ret;
861 }
862 
863 /* Graphviz styles */
864 #define DOT_HEADER	"digraph board {\n\trankdir=TB\n\tcolorscheme=x11\n"
865 #define STYLE_INTF	"shape=box, style=filled, fillcolor=yellow"
866 #define STYLE_ENTITY	"shape=Mrecord, style=filled, fillcolor=lightblue"
867 #define STYLE_ENT_SRC	"shape=Mrecord, style=filled, fillcolor=cadetblue"
868 #define STYLE_ENT_SINK	"shape=Mrecord, style=filled, fillcolor=aquamarine"
869 #define STYLE_DATA_LINK	"color=blue"
870 #define STYLE_INTF_LINK	"dir=\"none\" color=\"orange\""
871 
media_show_graphviz(struct media_controller * mc)872 static void media_show_graphviz(struct media_controller *mc)
873 {
874 	struct media_v2_topology *topo = &mc->topo;
875 	struct media_v2_entity *entities = media_get_uptr(topo->ptr_entities);
876 	struct media_v2_interface *interfaces = media_get_uptr(topo->ptr_interfaces);
877 	struct media_v2_pad *pads = media_get_uptr(topo->ptr_pads);
878 	struct media_v2_link *links = media_get_uptr(topo->ptr_links);
879 
880 	int i, j;
881 	char *obj;
882 	void *priv = NULL;
883 
884 	printf("%s", DOT_HEADER);
885 
886 	if (mc->info.model[0])
887 		printf("\tlabelloc=\"t\"\n\tlabel=\"%s\n driver:%s, bus: %s\n\"\n",
888 		       mc->info.model, mc->info.driver, mc->info.bus_info);
889 
890 	media_open_ifname(&priv);
891 	for (i = 0; i < topo->num_interfaces; i++) {
892 		struct media_v2_interface *intf = &interfaces[i];
893 		char *devname;
894 
895 		obj = objname(intf->id, '_');
896 		devname = media_get_ifname(intf, priv);
897 		printf("\t%s [label=\"%s\\n%s\\n%s\", " STYLE_INTF"]\n",
898 		       obj, obj, intf_type(intf->intf_type),
899 		     devname);
900 		free(obj);
901 		free(devname);
902 	}
903 	media_close_ifname(priv);
904 
905 #define DEMUX_TSOUT	"demux-tsout #"
906 #define DVR_TSOUT	"dvr-tsout #"
907 
908 	for (i = 0; i < topo->num_entities; i++) {
909 		struct media_v2_entity *entity = &entities[i];
910 		struct graph_obj *gobj;
911 		int first, idx;
912 
913 		if (max_tsout) {
914 			int i = 0;
915 
916 			if (!strncmp(entity->name, DEMUX_TSOUT, strlen(DEMUX_TSOUT)))
917 				i = atoi(&entity->name[strlen(DEMUX_TSOUT)]);
918 			else if (!strncmp(entity->name, DVR_TSOUT, strlen(DVR_TSOUT)))
919 				i = atoi(&entity->name[strlen(DVR_TSOUT)]);
920 
921 			if (i >= max_tsout)
922 				continue;
923 		}
924 
925 		gobj = find_gobj(mc, entity->id);
926 		obj = objname(entity->id, '_');
927 		printf("\t%s [label=\"{", obj);
928 		free(obj);
929 
930 		/* Print the sink pads */
931 		if (!gobj || gobj->num_pad_sinks) {
932 			first = 1;
933 			idx = 0;
934 			printf("{");
935 			for (j = 0; j < topo->num_pads; j++) {
936 				if (pads[j].entity_id != entity->id)
937 					continue;
938 
939 				if (pads[j].flags & MEDIA_PAD_FL_SINK) {
940 					if (first)
941 						first = 0;
942 					else
943 						printf (" | ");
944 
945 					obj = objname(pads[j].id, '_');
946 					printf("<%s> %d", obj, idx);
947 					free(obj);
948 				}
949 				idx++;
950 			}
951 			printf("} | ");
952 		}
953 		obj = objname(entity->id, '_');
954 		printf("%s\\n%s\\n%s", obj, ent_function(entity->function), entity->name);
955 		free(obj);
956 
957 		/* Print the source pads */
958 		if (!gobj || gobj->num_pad_sources) {
959 			int pad_count = 0;
960 			first = 1;
961 			idx = 0;
962 			printf(" | {");
963 
964 			for (j = 0; j < topo->num_pads; j++) {
965 				if (pads[j].entity_id != entity->id)
966 					continue;
967 
968 				if (entity->function == MEDIA_ENT_F_TS_DEMUX && pad_count > max_tsout)
969 					continue;
970 				pad_count++;
971 
972 				if (pads[j].flags & MEDIA_PAD_FL_SOURCE) {
973 					if (first)
974 						first = 0;
975 					else
976 						printf (" | ");
977 
978 					obj = objname(pads[j].id, '_');
979 					printf("<%s> %d", obj, idx);
980 					free(obj);
981 				}
982 				idx++;
983 			}
984 			printf("}");
985 		}
986 		printf("}\", ");
987 		if (!gobj || (gobj->num_pad_sources && gobj->num_pad_sinks))
988 			printf(STYLE_ENTITY"]\n");
989 		else if (gobj->num_pad_sinks)
990 			printf(STYLE_ENT_SINK"]\n");
991 		else
992 			printf(STYLE_ENT_SRC"]\n");
993 	}
994 
995 	for (i = 0; i < topo->num_links; i++) {
996 		struct media_v2_link *link = &links[i];
997 		char *source_pad_obj, *sink_pad_obj;
998 		char *source_ent_obj, *sink_ent_obj;
999 
1000 		if (media_type(link->source_id) == MEDIA_GRAPH_PAD) {
1001 			struct media_v2_entity *source, *sink;
1002 			struct graph_obj *gobj, *parent;
1003 
1004 			source_pad_obj = objname(link->source_id, '_');
1005 			gobj = find_gobj(mc, link->source_id);
1006 			if (!gobj) {
1007 				show(RED, 0, "Graph object for %s not found\n",
1008 				     source_pad_obj);
1009 				free(source_pad_obj);
1010 				continue;
1011 			}
1012 			parent = gobj->parent;
1013 			if (!parent) {
1014 				show(RED, 0, "Sink entity for %s not found\n",
1015 				     source_pad_obj);
1016 				free(source_pad_obj);
1017 				continue;
1018 			}
1019 			source = parent->entity;
1020 
1021 			sink_pad_obj = objname(link->sink_id, '_');
1022 
1023 			gobj = find_gobj(mc, link->sink_id);
1024 			if (!gobj) {
1025 				show(RED, 0, "Graph object for %s not found\n",
1026 				     sink_pad_obj);
1027 				free(source_pad_obj);
1028 				free(sink_pad_obj);
1029 				continue;
1030 			}
1031 			parent = gobj->parent;
1032 			if (!parent) {
1033 				show(RED, 0, "Sink entity for %s not found\n",
1034 				     sink_pad_obj);
1035 				free(source_pad_obj);
1036 				free(sink_pad_obj);
1037 				continue;
1038 			}
1039 			sink = parent->entity;
1040 
1041 			if (max_tsout) {
1042 				int i = 0;
1043 
1044 				if (!strncmp(sink->name, DEMUX_TSOUT, strlen(DEMUX_TSOUT)))
1045 					i = atoi(&sink->name[strlen(DEMUX_TSOUT)]);
1046 				else if (!strncmp(sink->name, DVR_TSOUT, strlen(DVR_TSOUT)))
1047 					i = atoi(&sink->name[strlen(DVR_TSOUT)]);
1048 				if (i >= max_tsout)
1049 					continue;
1050 			}
1051 
1052 			source_ent_obj = objname(source->id, '_');
1053 			sink_ent_obj = objname(sink->id, '_');
1054 
1055 			printf("\t%s:%s -> %s:%s [" STYLE_DATA_LINK,
1056 			       source_ent_obj, source_pad_obj,
1057 			       sink_ent_obj, sink_pad_obj);
1058 			if (!(link->flags & MEDIA_LNK_FL_ENABLED))
1059 				printf(" style=\"dashed\"");
1060 			printf("]\n");
1061 
1062 			free(source_pad_obj);
1063 			free(sink_pad_obj);
1064 			free(source_ent_obj);
1065 			free(sink_ent_obj);
1066 		}
1067 
1068 		if (media_type(link->source_id) == MEDIA_GRAPH_INTF_DEVNODE) {
1069 			struct media_v2_entity *sink;
1070 			struct graph_obj *gobj;
1071 
1072 			sink_ent_obj = objname(link->sink_id, '_');
1073 			gobj = find_gobj(mc, link->sink_id);
1074 			if (!gobj) {
1075 				show(RED, 0, "Graph object for %s not found\n",
1076 				     sink_ent_obj);
1077 				free(sink_ent_obj);
1078 				continue;
1079 			}
1080 			sink = gobj->entity;
1081 
1082 			if (max_tsout) {
1083 				int i = 0;
1084 
1085 				if (!strncmp(sink->name, DEMUX_TSOUT, strlen(DEMUX_TSOUT)))
1086 					i = atoi(&sink->name[strlen(DEMUX_TSOUT)]);
1087 				else if (!strncmp(sink->name, DVR_TSOUT, strlen(DVR_TSOUT)))
1088 					i = atoi(&sink->name[strlen(DVR_TSOUT)]);
1089 				if (i >= max_tsout)
1090 					continue;
1091 			}
1092 
1093 			source_ent_obj = objname(link->source_id, '_');
1094 
1095 			printf("\t%s -> %s [" STYLE_INTF_LINK,
1096 			       source_ent_obj, sink_ent_obj);
1097 			if (!(link->flags & MEDIA_LNK_FL_ENABLED))
1098 				printf(" style=\"dashed\"");
1099 			printf("]\n");
1100 
1101 			free(source_ent_obj);
1102 			free(sink_ent_obj);
1103 		}
1104 	}
1105 
1106 	printf ("}\n");
1107 }
1108 
main(int argc,char * argv[])1109 int main(int argc, char *argv[])
1110 {
1111 	struct media_controller *mc;
1112 	int rc;
1113 
1114 	argp_parse(&argp, argc, argv, ARGP_NO_HELP | ARGP_NO_EXIT, 0, 0);
1115 
1116 	mc = mc_open(media_device);
1117 	if (!mc)
1118 	return -1;
1119 
1120 	rc = media_get_topology(mc);
1121 	if (rc) {
1122 		mc_close(mc);
1123 		return -2;
1124 	}
1125 
1126 	if (show_dot) {
1127 		media_show_graphviz(mc);
1128 	} else {
1129 		if (show_entities)
1130 			media_show_entities(mc);
1131 
1132 		if (show_interfaces)
1133 			media_show_interfaces(mc);
1134 
1135 		if (show_data_links || show_intf_links || show_ancillary_links)
1136 			media_show_links(mc);
1137 	}
1138 
1139 	mc_close(mc);
1140 
1141 	return 0;
1142 }
1143