1 /*
2 * Media controller test application
3 *
4 * Copyright (C) 2010-2014 Ideas on board SPRL
5 *
6 * Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU Lesser General Public License as published
10 * by the Free Software Foundation; either version 2.1 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 #include <sys/ioctl.h>
23 #include <sys/mman.h>
24 #include <sys/stat.h>
25 #include <sys/time.h>
26
27 #include <ctype.h>
28 #include <errno.h>
29 #include <fcntl.h>
30 #include <stdbool.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <unistd.h>
35
36 #include <linux/media.h>
37 #include <linux/types.h>
38 #include <linux/v4l2-mediabus.h>
39 #include <linux/v4l2-subdev.h>
40 #include <linux/videodev2.h>
41
42 #include "mediactl.h"
43 #include "options.h"
44 #include "tools.h"
45 #include "v4l2subdev.h"
46
47 /* -----------------------------------------------------------------------------
48 * Printing
49 */
50
51 struct flag_name {
52 uint32_t flag;
53 char *name;
54 };
55
print_flags(const struct flag_name * flag_names,unsigned int num_entries,uint32_t flags)56 static void print_flags(const struct flag_name *flag_names, unsigned int num_entries, uint32_t flags)
57 {
58 bool first = true;
59 unsigned int i;
60
61 for (i = 0; i < num_entries; i++) {
62 if (!(flags & flag_names[i].flag))
63 continue;
64 if (!first)
65 printf(",");
66 printf("%s", flag_names[i].name);
67 flags &= ~flag_names[i].flag;
68 first = false;
69 }
70
71 if (flags) {
72 if (!first)
73 printf(",");
74 printf("0x%x", flags);
75 }
76 }
77
v4l2_subdev_print_format(struct media_entity * entity,unsigned int pad,enum v4l2_subdev_format_whence which)78 static void v4l2_subdev_print_format(struct media_entity *entity,
79 unsigned int pad, enum v4l2_subdev_format_whence which)
80 {
81 struct v4l2_mbus_framefmt format;
82 struct v4l2_fract interval = { 0, 0 };
83 struct v4l2_rect rect;
84 int ret;
85
86 ret = v4l2_subdev_get_format(entity, &format, pad, which);
87 if (ret != 0)
88 return;
89
90 ret = v4l2_subdev_get_frame_interval(entity, &interval, pad);
91 if (ret != 0 && ret != -ENOTTY && ret != -EINVAL)
92 return;
93
94 printf("\t\t[fmt:%s/%ux%u",
95 v4l2_subdev_pixelcode_to_string(format.code),
96 format.width, format.height);
97
98 if (interval.numerator || interval.denominator)
99 printf("@%u/%u", interval.numerator, interval.denominator);
100
101 if (format.field)
102 printf(" field:%s", v4l2_subdev_field_to_string(format.field));
103
104 if (format.colorspace) {
105 printf(" colorspace:%s",
106 v4l2_subdev_colorspace_to_string(format.colorspace));
107
108 if (format.xfer_func)
109 printf(" xfer:%s",
110 v4l2_subdev_xfer_func_to_string(format.xfer_func));
111
112 if (format.ycbcr_enc)
113 printf(" ycbcr:%s",
114 v4l2_subdev_ycbcr_encoding_to_string(format.ycbcr_enc));
115
116 if (format.quantization)
117 printf(" quantization:%s",
118 v4l2_subdev_quantization_to_string(format.quantization));
119 }
120
121 ret = v4l2_subdev_get_selection(entity, &rect, pad,
122 V4L2_SEL_TGT_CROP_BOUNDS,
123 which);
124 if (ret == 0)
125 printf("\n\t\t crop.bounds:(%u,%u)/%ux%u", rect.left, rect.top,
126 rect.width, rect.height);
127
128 ret = v4l2_subdev_get_selection(entity, &rect, pad,
129 V4L2_SEL_TGT_CROP,
130 which);
131 if (ret == 0)
132 printf("\n\t\t crop:(%u,%u)/%ux%u", rect.left, rect.top,
133 rect.width, rect.height);
134
135 ret = v4l2_subdev_get_selection(entity, &rect, pad,
136 V4L2_SEL_TGT_COMPOSE_BOUNDS,
137 which);
138 if (ret == 0)
139 printf("\n\t\t compose.bounds:(%u,%u)/%ux%u",
140 rect.left, rect.top, rect.width, rect.height);
141
142 ret = v4l2_subdev_get_selection(entity, &rect, pad,
143 V4L2_SEL_TGT_COMPOSE,
144 which);
145 if (ret == 0)
146 printf("\n\t\t compose:(%u,%u)/%ux%u",
147 rect.left, rect.top, rect.width, rect.height);
148
149 printf("]\n");
150 }
151
v4l2_dv_type_to_string(unsigned int type)152 static const char *v4l2_dv_type_to_string(unsigned int type)
153 {
154 static const struct {
155 uint32_t type;
156 const char *name;
157 } types[] = {
158 { V4L2_DV_BT_656_1120, "BT.656/1120" },
159 };
160
161 static char unknown[20];
162 unsigned int i;
163
164 for (i = 0; i < ARRAY_SIZE(types); i++) {
165 if (types[i].type == type)
166 return types[i].name;
167 }
168
169 sprintf(unknown, "Unknown (%u)", type);
170 return unknown;
171 }
172
173 static const struct flag_name bt_standards[] = {
174 { V4L2_DV_BT_STD_CEA861, "CEA-861" },
175 { V4L2_DV_BT_STD_DMT, "DMT" },
176 { V4L2_DV_BT_STD_CVT, "CVT" },
177 { V4L2_DV_BT_STD_GTF, "GTF" },
178 { V4L2_DV_BT_STD_SDI, "SDI" },
179 };
180
181 static const struct flag_name bt_capabilities[] = {
182 { V4L2_DV_BT_CAP_INTERLACED, "interlaced" },
183 { V4L2_DV_BT_CAP_PROGRESSIVE, "progressive" },
184 { V4L2_DV_BT_CAP_REDUCED_BLANKING, "reduced-blanking" },
185 { V4L2_DV_BT_CAP_CUSTOM, "custom" },
186 };
187
188 static const struct flag_name bt_flags[] = {
189 { V4L2_DV_FL_REDUCED_BLANKING, "reduced-blanking" },
190 { V4L2_DV_FL_CAN_REDUCE_FPS, "can-reduce-fps" },
191 { V4L2_DV_FL_REDUCED_FPS, "reduced-fps" },
192 { V4L2_DV_FL_HALF_LINE, "half-line" },
193 { V4L2_DV_FL_IS_CE_VIDEO, "CE-video" },
194 { V4L2_DV_FL_FIRST_FIELD_EXTRA_LINE, "first-field-extra-line" },
195 { V4L2_DV_FL_HAS_PICTURE_ASPECT, "has-picture-aspect" },
196 { V4L2_DV_FL_HAS_CEA861_VIC, "has-cea861-vic" },
197 { V4L2_DV_FL_HAS_HDMI_VIC, "has-hdmi-vic" },
198 { V4L2_DV_FL_CAN_DETECT_REDUCED_FPS, "can-detect-reduced-fps" },
199 };
200
v4l2_subdev_print_dv_timings(const struct v4l2_dv_timings * timings,const char * name)201 static void v4l2_subdev_print_dv_timings(const struct v4l2_dv_timings *timings,
202 const char *name)
203 {
204 printf("\t\t[dv.%s:%s", name, v4l2_dv_type_to_string(timings->type));
205
206 switch (timings->type) {
207 case V4L2_DV_BT_656_1120: {
208 const struct v4l2_bt_timings *bt = &timings->bt;
209 unsigned int htotal, vtotal;
210
211 htotal = V4L2_DV_BT_FRAME_WIDTH(bt);
212 vtotal = V4L2_DV_BT_FRAME_HEIGHT(bt);
213
214 printf(" %ux%u%s%llu (%ux%u)",
215 bt->width, bt->height, bt->interlaced ? "i" : "p",
216 (htotal * vtotal) > 0 ? (bt->pixelclock / (htotal * vtotal)) : 0,
217 htotal, vtotal);
218
219 printf(" stds:");
220 print_flags(bt_standards, ARRAY_SIZE(bt_standards),
221 bt->standards);
222 printf(" flags:");
223 print_flags(bt_flags, ARRAY_SIZE(bt_flags),
224 bt->flags);
225
226 break;
227 }
228 }
229
230 printf("]\n");
231 }
232
v4l2_subdev_print_pad_dv(struct media_entity * entity,unsigned int pad,enum v4l2_subdev_format_whence which)233 static void v4l2_subdev_print_pad_dv(struct media_entity *entity,
234 unsigned int pad, enum v4l2_subdev_format_whence which)
235 {
236 struct v4l2_dv_timings_cap caps;
237 int ret;
238
239 caps.pad = pad;
240 ret = v4l2_subdev_get_dv_timings_caps(entity, &caps);
241 if (ret != 0)
242 return;
243
244 printf("\t\t[dv.caps:%s", v4l2_dv_type_to_string(caps.type));
245
246 switch (caps.type) {
247 case V4L2_DV_BT_656_1120:
248 printf(" min:%ux%u@%llu max:%ux%u@%llu",
249 caps.bt.min_width, caps.bt.min_height, caps.bt.min_pixelclock,
250 caps.bt.max_width, caps.bt.max_height, caps.bt.max_pixelclock);
251
252 printf(" stds:");
253 print_flags(bt_standards, ARRAY_SIZE(bt_standards),
254 caps.bt.standards);
255 printf(" caps:");
256 print_flags(bt_capabilities, ARRAY_SIZE(bt_capabilities),
257 caps.bt.capabilities);
258
259 break;
260 }
261
262 printf("]\n");
263 }
264
v4l2_subdev_print_subdev_dv(struct media_entity * entity)265 static void v4l2_subdev_print_subdev_dv(struct media_entity *entity)
266 {
267 struct v4l2_dv_timings timings;
268 int ret;
269
270 ret = v4l2_subdev_query_dv_timings(entity, &timings);
271 switch (ret) {
272 case -ENOLINK:
273 printf("\t\t[dv.query:no-link]\n");
274 break;
275 case -ENOLCK:
276 printf("\t\t[dv.query:no-lock]\n");
277 break;
278 case -ERANGE:
279 printf("\t\t[dv.query:out-of-range]\n");
280 break;
281 case 0:
282 v4l2_subdev_print_dv_timings(&timings, "detect");
283 break;
284 default:
285 return;
286 }
287
288 ret = v4l2_subdev_get_dv_timings(entity, &timings);
289 if (ret == 0)
290 v4l2_subdev_print_dv_timings(&timings, "current");
291 }
292
media_entity_type_to_string(unsigned type)293 static const char *media_entity_type_to_string(unsigned type)
294 {
295 static const struct {
296 uint32_t type;
297 const char *name;
298 } types[] = {
299 { MEDIA_ENT_T_DEVNODE, "Node" },
300 { MEDIA_ENT_T_V4L2_SUBDEV, "V4L2 subdev" },
301 };
302
303 unsigned int i;
304
305 type &= MEDIA_ENT_TYPE_MASK;
306
307 for (i = 0; i < ARRAY_SIZE(types); i++) {
308 if (types[i].type == type)
309 return types[i].name;
310 }
311
312 return "Unknown";
313 }
314
media_entity_subtype_to_string(unsigned type)315 static const char *media_entity_subtype_to_string(unsigned type)
316 {
317 static const char *node_types[] = {
318 "Unknown",
319 "V4L",
320 "FB",
321 "ALSA",
322 "DVB",
323 };
324 static const char *subdev_types[] = {
325 "Unknown",
326 "Sensor",
327 "Flash",
328 "Lens",
329 "Decoder",
330 "Tuner",
331 };
332
333 unsigned int subtype = type & MEDIA_ENT_SUBTYPE_MASK;
334
335 switch (type & MEDIA_ENT_TYPE_MASK) {
336 case MEDIA_ENT_T_DEVNODE:
337 if (subtype >= ARRAY_SIZE(node_types))
338 subtype = 0;
339 return node_types[subtype];
340
341 case MEDIA_ENT_T_V4L2_SUBDEV:
342 if (subtype >= ARRAY_SIZE(subdev_types))
343 subtype = 0;
344 return subdev_types[subtype];
345 default:
346 return node_types[0];
347 }
348 }
349
media_pad_type_to_string(unsigned flag)350 static const char *media_pad_type_to_string(unsigned flag)
351 {
352 static const struct {
353 uint32_t flag;
354 const char *name;
355 } flags[] = {
356 { MEDIA_PAD_FL_SINK, "Sink" },
357 { MEDIA_PAD_FL_SOURCE, "Source" },
358 };
359
360 unsigned int i;
361
362 for (i = 0; i < ARRAY_SIZE(flags); i++) {
363 if (flags[i].flag & flag)
364 return flags[i].name;
365 }
366
367 return "Unknown";
368 }
369
media_print_topology_dot(struct media_device * media)370 static void media_print_topology_dot(struct media_device *media)
371 {
372 unsigned int nents = media_get_entities_count(media);
373 unsigned int i, j;
374
375 printf("digraph board {\n");
376 printf("\trankdir=TB\n");
377
378 for (i = 0; i < nents; ++i) {
379 struct media_entity *entity = media_get_entity(media, i);
380 const struct media_entity_desc *info = media_entity_get_info(entity);
381 const char *devname = media_entity_get_devname(entity);
382 unsigned int num_links = media_entity_get_links_count(entity);
383 unsigned int npads;
384
385 if (!devname)
386 devname = "";
387
388 switch (media_entity_type(entity)) {
389 case MEDIA_ENT_T_DEVNODE:
390 printf("\tn%08x [label=\"%s\\n%s\", shape=box, style=filled, "
391 "fillcolor=yellow]\n",
392 info->id, info->name, devname);
393 break;
394
395 case MEDIA_ENT_T_V4L2_SUBDEV:
396 printf("\tn%08x [label=\"{{", info->id);
397
398 for (j = 0, npads = 0; j < info->pads; ++j) {
399 const struct media_pad *pad = media_entity_get_pad(entity, j);
400
401 if (!(pad->flags & MEDIA_PAD_FL_SINK))
402 continue;
403
404 printf("%s<port%u> %u", npads ? " | " : "", j, j);
405 npads++;
406 }
407
408 printf("} | %s", info->name);
409 if (devname)
410 printf("\\n%s", devname);
411 printf(" | {");
412
413 for (j = 0, npads = 0; j < info->pads; ++j) {
414 const struct media_pad *pad = media_entity_get_pad(entity, j);
415
416 if (!(pad->flags & MEDIA_PAD_FL_SOURCE))
417 continue;
418
419 printf("%s<port%u> %u", npads ? " | " : "", j, j);
420 npads++;
421 }
422
423 printf("}}\", shape=Mrecord, style=filled, fillcolor=green]\n");
424 break;
425
426 default:
427 continue;
428 }
429
430 for (j = 0; j < num_links; j++) {
431 const struct media_link *link = media_entity_get_link(entity, j);
432 const struct media_pad *source = link->source;
433 const struct media_pad *sink = link->sink;
434
435 if (source->entity != entity)
436 continue;
437
438 printf("\tn%08x", media_entity_get_info(source->entity)->id);
439 if (media_entity_type(source->entity) == MEDIA_ENT_T_V4L2_SUBDEV)
440 printf(":port%u", source->index);
441 printf(" -> ");
442 printf("n%08x", media_entity_get_info(sink->entity)->id);
443 if (media_entity_type(sink->entity) == MEDIA_ENT_T_V4L2_SUBDEV)
444 printf(":port%u", sink->index);
445
446 if (link->flags & MEDIA_LNK_FL_IMMUTABLE)
447 printf(" [style=bold]");
448 else if (!(link->flags & MEDIA_LNK_FL_ENABLED))
449 printf(" [style=dashed]");
450 printf("\n");
451 }
452 }
453
454 printf("}\n");
455 }
456
media_print_pad_text(struct media_entity * entity,const struct media_pad * pad)457 static void media_print_pad_text(struct media_entity *entity,
458 const struct media_pad *pad)
459 {
460 if (media_entity_type(entity) != MEDIA_ENT_T_V4L2_SUBDEV)
461 return;
462
463 v4l2_subdev_print_format(entity, pad->index, V4L2_SUBDEV_FORMAT_ACTIVE);
464 v4l2_subdev_print_pad_dv(entity, pad->index, V4L2_SUBDEV_FORMAT_ACTIVE);
465
466 if (pad->flags & MEDIA_PAD_FL_SOURCE)
467 v4l2_subdev_print_subdev_dv(entity);
468 }
469
media_print_topology_text_entity(struct media_device * media,struct media_entity * entity)470 static void media_print_topology_text_entity(struct media_device *media,
471 struct media_entity *entity)
472 {
473 static const struct flag_name link_flags[] = {
474 { MEDIA_LNK_FL_ENABLED, "ENABLED" },
475 { MEDIA_LNK_FL_IMMUTABLE, "IMMUTABLE" },
476 { MEDIA_LNK_FL_DYNAMIC, "DYNAMIC" },
477 };
478 const struct media_entity_desc *info = media_entity_get_info(entity);
479 const char *devname = media_entity_get_devname(entity);
480 unsigned int num_links = media_entity_get_links_count(entity);
481 unsigned int j, k;
482 unsigned int padding;
483
484 padding = printf("- entity %u: ", info->id);
485 printf("%s (%u pad%s, %u link%s)\n", info->name,
486 info->pads, info->pads > 1 ? "s" : "",
487 num_links, num_links > 1 ? "s" : "");
488 printf("%*ctype %s subtype %s flags %x\n", padding, ' ',
489 media_entity_type_to_string(info->type),
490 media_entity_subtype_to_string(info->type),
491 info->flags);
492 if (devname)
493 printf("%*cdevice node name %s\n", padding, ' ', devname);
494
495 for (j = 0; j < info->pads; j++) {
496 const struct media_pad *pad = media_entity_get_pad(entity, j);
497
498 printf("\tpad%u: %s\n", j, media_pad_type_to_string(pad->flags));
499
500 media_print_pad_text(entity, pad);
501
502 for (k = 0; k < num_links; k++) {
503 const struct media_link *link = media_entity_get_link(entity, k);
504 const struct media_pad *source = link->source;
505 const struct media_pad *sink = link->sink;
506
507 if (source->entity == entity && source->index == j)
508 printf("\t\t-> \"%s\":%u [",
509 media_entity_get_info(sink->entity)->name,
510 sink->index);
511 else if (sink->entity == entity && sink->index == j)
512 printf("\t\t<- \"%s\":%u [",
513 media_entity_get_info(source->entity)->name,
514 source->index);
515 else
516 continue;
517
518 print_flags(link_flags, ARRAY_SIZE(link_flags), link->flags);
519
520 printf("]\n");
521 }
522 }
523 printf("\n");
524 }
525
media_print_topology_text(struct media_device * media)526 static void media_print_topology_text(struct media_device *media)
527 {
528 unsigned int nents = media_get_entities_count(media);
529 unsigned int i;
530
531 printf("Device topology\n");
532
533 for (i = 0; i < nents; ++i)
534 media_print_topology_text_entity(
535 media, media_get_entity(media, i));
536 }
537
main(int argc,char ** argv)538 int main(int argc, char **argv)
539 {
540 struct media_device *media;
541 struct media_entity *entity = NULL;
542 int ret = -1;
543
544 if (parse_cmdline(argc, argv))
545 return EXIT_FAILURE;
546
547 media = media_device_new(media_opts.devname);
548 if (media == NULL) {
549 printf("Failed to create media device\n");
550 goto out;
551 }
552
553 if (media_opts.verbose)
554 media_debug_set_handler(media,
555 (void (*)(void *, ...))fprintf, stdout);
556
557 /* Enumerate entities, pads and links. */
558 ret = media_device_enumerate(media);
559 if (ret < 0) {
560 printf("Failed to enumerate %s (%d)\n", media_opts.devname, ret);
561 goto out;
562 }
563
564 if (media_opts.print) {
565 const struct media_device_info *info = media_get_info(media);
566
567 printf("Media controller API version %u.%u.%u\n\n",
568 (info->media_version >> 16) & 0xff,
569 (info->media_version >> 8) & 0xff,
570 (info->media_version >> 0) & 0xff);
571 printf("Media device information\n"
572 "------------------------\n"
573 "driver %s\n"
574 "model %s\n"
575 "serial %s\n"
576 "bus info %s\n"
577 "hw revision 0x%x\n"
578 "driver version %u.%u.%u\n\n",
579 info->driver, info->model,
580 info->serial, info->bus_info,
581 info->hw_revision,
582 (info->driver_version >> 16) & 0xff,
583 (info->driver_version >> 8) & 0xff,
584 (info->driver_version >> 0) & 0xff);
585 }
586
587 if (media_opts.entity) {
588 entity = media_get_entity_by_name(media, media_opts.entity);
589 if (entity == NULL) {
590 printf("Entity '%s' not found\n", media_opts.entity);
591 goto out;
592 }
593 }
594
595 if (media_opts.fmt_pad) {
596 struct media_pad *pad;
597
598 pad = media_parse_pad(media, media_opts.fmt_pad, NULL);
599 if (pad == NULL) {
600 printf("Pad '%s' not found\n", media_opts.fmt_pad);
601 goto out;
602 }
603
604 v4l2_subdev_print_format(pad->entity, pad->index,
605 V4L2_SUBDEV_FORMAT_ACTIVE);
606 }
607
608 if (media_opts.get_dv_pad) {
609 struct media_pad *pad;
610
611 pad = media_parse_pad(media, media_opts.get_dv_pad, NULL);
612 if (pad == NULL) {
613 printf("Pad '%s' not found\n", media_opts.get_dv_pad);
614 goto out;
615 }
616
617 v4l2_subdev_print_subdev_dv(pad->entity);
618 }
619
620 if (media_opts.dv_pad) {
621 struct v4l2_dv_timings timings;
622 struct media_pad *pad;
623
624 pad = media_parse_pad(media, media_opts.dv_pad, NULL);
625 if (pad == NULL) {
626 printf("Pad '%s' not found\n", media_opts.dv_pad);
627 goto out;
628 }
629
630 ret = v4l2_subdev_query_dv_timings(pad->entity, &timings);
631 if (ret < 0) {
632 printf("Failed to query DV timings: %s\n", strerror(-ret));
633 goto out;
634 }
635
636 ret = v4l2_subdev_set_dv_timings(pad->entity, &timings);
637 if (ret < 0) {
638 printf("Failed to set DV timings: %s\n", strerror(-ret));
639 goto out;
640 }
641 }
642
643 if (media_opts.print_dot) {
644 media_print_topology_dot(media);
645 } else if (media_opts.print) {
646 if (entity)
647 media_print_topology_text_entity(media, entity);
648 else
649 media_print_topology_text(media);
650 } else if (entity) {
651 const char *devname;
652
653 devname = media_entity_get_devname(entity);
654 if (devname)
655 printf("%s\n", devname);
656 }
657
658 if (media_opts.reset) {
659 if (media_opts.verbose)
660 printf("Resetting all links to inactive\n");
661 ret = media_reset_links(media);
662 if (ret) {
663 printf("Unable to reset links: %s (%d)\n",
664 strerror(-ret), -ret);
665 goto out;
666 }
667 }
668
669 if (media_opts.links) {
670 ret = media_parse_setup_links(media, media_opts.links);
671 if (ret) {
672 printf("Unable to parse link: %s (%d)\n",
673 strerror(-ret), -ret);
674 goto out;
675 }
676 }
677
678 if (media_opts.formats) {
679 ret = v4l2_subdev_parse_setup_formats(media,
680 media_opts.formats);
681 if (ret) {
682 printf("Unable to setup formats: %s (%d)\n",
683 strerror(-ret), -ret);
684 goto out;
685 }
686 }
687
688 if (media_opts.interactive) {
689 while (1) {
690 char buffer[32];
691 char *end;
692
693 printf("Enter a link to modify or enter to stop\n");
694 if (fgets(buffer, sizeof buffer, stdin) == NULL)
695 break;
696
697 if (buffer[0] == '\n')
698 break;
699
700 ret = media_parse_setup_link(media, buffer, &end);
701 if (ret)
702 printf("Unable to parse link: %s (%d)\n",
703 strerror(-ret), -ret);
704 }
705 }
706
707 ret = 0;
708
709 out:
710 if (media)
711 media_device_unref(media);
712
713 return ret ? EXIT_FAILURE : EXIT_SUCCESS;
714 }
715
716