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