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