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