• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // SPDX-License-Identifier: LGPL-2.1-only
2 /*
3  * Copyright 2018 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
4  */
5 
6 #include <cstdint>
7 #include <cstring>
8 #include <fstream>
9 #include <iostream>
10 
11 #include <dirent.h>
12 #include <fcntl.h>
13 #include <sys/ioctl.h>
14 #include <sys/stat.h>
15 #include <sys/sysmacros.h>
16 #include <unistd.h>
17 
18 #include <linux/media.h>
19 
20 #include <media-info.h>
21 #ifndef __linux__
22 #include <linux/videodev2.h>
23 #include <fcntl.h>
24 #endif
25 
num2s(unsigned num,bool is_hex=true)26 static std::string num2s(unsigned num, bool is_hex = true)
27 {
28 	char buf[16];
29 
30 	if (is_hex)
31 		sprintf(buf, "%08x", num);
32 	else
33 		sprintf(buf, "%u", num);
34 	return buf;
35 }
36 
37 static constexpr struct {
38 	const char *devname;
39 	enum media_type type;
40 } media_types[] = {
41 	{ "video", MEDIA_TYPE_VIDEO },
42 	{ "vbi", MEDIA_TYPE_VBI },
43 	{ "radio", MEDIA_TYPE_RADIO },
44 	{ "swradio", MEDIA_TYPE_SDR },
45 	{ "v4l-subdev", MEDIA_TYPE_SUBDEV },
46 	{ "v4l-touch", MEDIA_TYPE_TOUCH },
47 	{ "media", MEDIA_TYPE_MEDIA },
48 	{ "frontend", MEDIA_TYPE_DVB_FRONTEND },
49 	{ "demux", MEDIA_TYPE_DVB_DEMUX },
50 	{ "dvr", MEDIA_TYPE_DVB_DVR },
51 	{ "net", MEDIA_TYPE_DVB_NET },
52 	{ "ca", MEDIA_TYPE_DTV_CA },
53 	{ nullptr, MEDIA_TYPE_UNKNOWN }
54 };
55 
mi_media_detect_type(const char * device)56 media_type mi_media_detect_type(const char *device)
57 {
58 	struct stat sb;
59 
60 	if (stat(device, &sb) == -1)
61 		return MEDIA_TYPE_CANT_STAT;
62 #ifdef __linux__
63 	std::string uevent_path("/sys/dev/char/");
64 
65 	uevent_path += num2s(major(sb.st_rdev), false) + ":" +
66 		num2s(minor(sb.st_rdev), false) + "/uevent";
67 
68 	std::ifstream uevent_file(uevent_path.c_str());
69 	if (uevent_file.fail())
70 		return MEDIA_TYPE_UNKNOWN;
71 
72 	std::string line;
73 
74 	while (std::getline(uevent_file, line)) {
75 		if (line.compare(0, 8, "DEVNAME="))
76 			continue;
77 
78 		line.erase(0, 8);
79 		if (!line.compare(0, 11, "dvb/adapter")) {
80 			line.erase(0, 11);
81 			unsigned len = 0;
82 			while (line[len] && line[len] != '/')
83 				len++;
84 			line.erase(0, len + 1);
85 		}
86 		for (size_t i = 0; media_types[i].devname; i++) {
87 			const char *devname = media_types[i].devname;
88 			size_t len = strlen(devname);
89 
90 			if (!line.compare(0, len, devname) && isdigit(line[0+len])) {
91 				uevent_file.close();
92 				return media_types[i].type;
93 			}
94 		}
95 	}
96 
97 	uevent_file.close();
98 #else // Not Linux
99 	int fd = open(device, O_RDONLY);
100 	if (fd >= 0) {
101 		struct v4l2_capability caps;
102 		int error = ioctl(fd, VIDIOC_QUERYCAP, &caps);
103 		close(fd);
104 		if (error == 0) {
105 			if (caps.device_caps & V4L2_CAP_VIDEO_CAPTURE) {
106 				return MEDIA_TYPE_VIDEO;
107 			} else if (caps.device_caps & V4L2_CAP_VBI_CAPTURE) {
108 				return MEDIA_TYPE_VBI;
109 			} else if (caps.device_caps & V4L2_CAP_RADIO) {
110 				return MEDIA_TYPE_RADIO;
111 			}
112 		}
113 	}
114 #endif
115 	return MEDIA_TYPE_UNKNOWN;
116 }
117 
mi_media_get_device(__u32 major,__u32 minor)118 std::string mi_media_get_device(__u32 major, __u32 minor)
119 {
120 	char fmt[32];
121 	std::string uevent_path("/sys/dev/char/");
122 
123 	sprintf(fmt, "%d:%d", major, minor);
124 	uevent_path += std::string(fmt) + "/uevent";
125 
126 	std::ifstream uevent_file(uevent_path.c_str());
127 	if (uevent_file.fail())
128 		return "";
129 
130 	std::string line;
131 
132 	while (std::getline(uevent_file, line)) {
133 		if (line.compare(0, 8, "DEVNAME="))
134 			continue;
135 		uevent_file.close();
136 		return std::string("/dev/") + &line[8];
137 	}
138 
139 	uevent_file.close();
140 	return "";
141 }
142 
143 struct flag_def {
144 	unsigned flag;
145 	const char *str;
146 };
147 
flags2s(unsigned val,const flag_def * def)148 static std::string flags2s(unsigned val, const flag_def *def)
149 {
150 	std::string s;
151 
152 	while (def->flag) {
153 		if (val & def->flag) {
154 			if (s.length()) s += ", ";
155 			s += def->str;
156 			val &= ~def->flag;
157 		}
158 		def++;
159 	}
160 	if (val) {
161 		if (s.length()) s += ", ";
162 		s += num2s(val);
163 	}
164 	return s;
165 }
166 
mi_get_dev_t_from_fd(int fd,dev_t * dev)167 int mi_get_dev_t_from_fd(int fd, dev_t *dev)
168 {
169 	struct stat sb;
170 
171 	if (fstat(fd, &sb) == -1) {
172 		fprintf(stderr, "failed to stat file\n");
173 		return -1;
174 	}
175 	*dev = sb.st_rdev;
176 	return 0;
177 }
178 
mi_get_devpath_from_dev_t(dev_t dev)179 std::string mi_get_devpath_from_dev_t(dev_t dev)
180 {
181 	if (!dev)
182 		return "";
183 
184 	std::string media_uevent_path("/sys/dev/char/");
185 
186 	media_uevent_path += num2s(major(dev), false) + ":" +
187 		num2s(minor(dev), false) + "/uevent";
188 
189 	FILE *uevent_fd = fopen(media_uevent_path.c_str(), "r");
190 
191 	if (uevent_fd == nullptr) {
192 		fprintf(stderr, "failed to open %s\n", media_uevent_path.c_str());
193 		return "";
194 	}
195 
196 	char *line = nullptr;
197 	size_t size = 0;
198 	std::string devpath;
199 
200 	for (;;) {
201 		ssize_t bytes = getline(&line, &size, uevent_fd);
202 
203 		if (bytes <= 0)
204 			break;
205 		line[bytes - 1] = 0;
206 		if (memcmp(line, "DEVNAME=", 8) || !line[8])
207 			continue;
208 		devpath = "/dev/";
209 		devpath += line + 8;
210 		break;
211 	}
212 	free(line);
213 
214 	if (devpath.empty())
215 		fprintf(stderr, "failed to find DEVNAME in %s\n", media_uevent_path.c_str());
216 	return devpath;
217 }
218 
mi_get_media_fd(int fd,const char * bus_info)219 int mi_get_media_fd(int fd, const char *bus_info)
220 {
221 	int media_fd = -1;
222 	dev_t dev;
223 
224 	if (mi_get_dev_t_from_fd(fd, &dev) < 0)
225 		return -1;
226 
227 	std::string media_path("/sys/dev/char/");
228 
229 	media_path += num2s(major(dev), false) + ":" +
230 		num2s(minor(dev), false) + "/device";
231 
232 	DIR *dp;
233 	struct dirent *ep;
234 	dp = opendir(media_path.c_str());
235 	if (dp == nullptr)
236 		return -1;
237 	media_path[0] = 0;
238 	while ((ep = readdir(dp))) {
239 		if (!memcmp(ep->d_name, "media", 5) && isdigit(ep->d_name[5])) {
240 			struct media_device_info mdinfo;
241 			std::string devname("/dev/");
242 
243 			devname += ep->d_name;
244 			media_fd = open(devname.c_str(), O_RDWR);
245 
246 			if (bus_info &&
247 			    (ioctl(media_fd, MEDIA_IOC_DEVICE_INFO, &mdinfo) ||
248 			     strcmp(mdinfo.bus_info, bus_info))) {
249 				close(media_fd);
250 				continue;
251 			}
252 			break;
253 		}
254 	}
255 	closedir(dp);
256 	return media_fd;
257 }
258 
259 static constexpr flag_def entity_flags_def[] = {
260 	{ MEDIA_ENT_FL_DEFAULT, "default" },
261 	{ MEDIA_ENT_FL_CONNECTOR, "connector" },
262 	{ 0, nullptr }
263 };
264 
mi_entflags2s(__u32 flags)265 std::string mi_entflags2s(__u32 flags)
266 {
267 	return flags2s(flags, entity_flags_def);
268 }
269 
270 static constexpr flag_def interface_types_def[] = {
271 	{ MEDIA_INTF_T_DVB_FE, "DVB Front End" },
272 	{ MEDIA_INTF_T_DVB_DEMUX, "DVB Demuxer" },
273 	{ MEDIA_INTF_T_DVB_DVR, "DVB DVR" },
274 	{ MEDIA_INTF_T_DVB_CA, "DVB Conditional Access" },
275 	{ MEDIA_INTF_T_DVB_NET, "DVB Net" },
276 
277 	{ MEDIA_INTF_T_V4L_VIDEO, "V4L Video" },
278 	{ MEDIA_INTF_T_V4L_VBI, "V4L VBI" },
279 	{ MEDIA_INTF_T_V4L_RADIO, "V4L Radio" },
280 	{ MEDIA_INTF_T_V4L_SUBDEV, "V4L Sub-Device" },
281 	{ MEDIA_INTF_T_V4L_SWRADIO, "V4L Software Defined Radio" },
282 	{ MEDIA_INTF_T_V4L_TOUCH, "V4L Touch" },
283 
284 	{ MEDIA_INTF_T_ALSA_PCM_CAPTURE, "ALSA PCM Capture" },
285 	{ MEDIA_INTF_T_ALSA_PCM_PLAYBACK, "ALSA PCM Playback" },
286 	{ MEDIA_INTF_T_ALSA_CONTROL, "ALSA Control" },
287 	{ MEDIA_INTF_T_ALSA_COMPRESS, "ALSA Compress" },
288 	{ MEDIA_INTF_T_ALSA_RAWMIDI, "ALSA Raw MIDI" },
289 	{ MEDIA_INTF_T_ALSA_HWDEP, "ALSA HWDEP" },
290 	{ MEDIA_INTF_T_ALSA_SEQUENCER, "ALSA Sequencer" },
291 	{ MEDIA_INTF_T_ALSA_TIMER, "ALSA Timer" },
292 	{ 0, nullptr }
293 };
294 
mi_ifacetype2s(__u32 type)295 std::string mi_ifacetype2s(__u32 type)
296 {
297 	for (unsigned i = 0; interface_types_def[i].str; i++)
298 		if (type == interface_types_def[i].flag)
299 			return interface_types_def[i].str;
300 	return "FAIL: Unknown (" + num2s(type) + ")";
301 }
302 
303 static constexpr flag_def entity_functions_def[] = {
304 	{ MEDIA_ENT_F_UNKNOWN, "FAIL: Uninitialized Function" },
305 	{ MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN, "FAIL: Unknown V4L2 Sub-Device" },
306 	{ MEDIA_ENT_T_DEVNODE_UNKNOWN, "FAIL: Unknown Device Node" },
307 
308 	{ MEDIA_ENT_F_DTV_DEMOD, "Digital TV Demodulator" },
309 	{ MEDIA_ENT_F_TS_DEMUX, "Transport Stream Demuxer" },
310 	{ MEDIA_ENT_F_DTV_CA, "Digital TV Conditional Access" },
311 	{ MEDIA_ENT_F_DTV_NET_DECAP, "Digital TV Network ULE/MLE Desencapsulation" },
312 	{ MEDIA_ENT_F_IO_DTV, "Digital TV I/O" },
313 	{ MEDIA_ENT_F_IO_V4L, "V4L2 I/O" },
314 	{ MEDIA_ENT_F_IO_VBI, "VBI I/O" },
315 	{ MEDIA_ENT_F_IO_SWRADIO, "Software Radio I/O" },
316 
317 	{ MEDIA_ENT_F_CAM_SENSOR, "Camera Sensor" },
318 	{ MEDIA_ENT_F_FLASH, "Flash Controller" },
319 	{ MEDIA_ENT_F_LENS, "Lens Controller" },
320 	{ MEDIA_ENT_F_ATV_DECODER, "Analog Video Decoder" },
321 	{ MEDIA_ENT_F_DV_DECODER, "Digital Video Decoder" },
322 	{ MEDIA_ENT_F_DV_ENCODER, "Digital Video Encoder" },
323 	{ MEDIA_ENT_F_TUNER, "Tuner" },
324 	{ MEDIA_ENT_F_IF_VID_DECODER, "IF-PLL Video Decoder" },
325 	{ MEDIA_ENT_F_IF_AUD_DECODER, "IF-PLL Audio Decoder" },
326 	{ MEDIA_ENT_F_AUDIO_CAPTURE, "Audio Capture" },
327 	{ MEDIA_ENT_F_AUDIO_PLAYBACK, "Audio Playback" },
328 	{ MEDIA_ENT_F_AUDIO_MIXER, "Audio Mixer" },
329 	{ MEDIA_ENT_F_PROC_VIDEO_COMPOSER, "Video Composer" },
330 	{ MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER, "Video Pixel Formatter" },
331 	{ MEDIA_ENT_F_PROC_VIDEO_PIXEL_ENC_CONV, "Video Pixel Encoding Converter" },
332 	{ MEDIA_ENT_F_PROC_VIDEO_LUT, "Video Look-Up Table" },
333 	{ MEDIA_ENT_F_PROC_VIDEO_SCALER, "Video Scaler" },
334 	{ MEDIA_ENT_F_PROC_VIDEO_STATISTICS, "Video Statistics" },
335 	{ MEDIA_ENT_F_PROC_VIDEO_DECODER, "Video Decoder" },
336 	{ MEDIA_ENT_F_PROC_VIDEO_ENCODER, "Video Encoder" },
337 	{ MEDIA_ENT_F_PROC_VIDEO_ISP, "Image Signal Processor" },
338 	{ MEDIA_ENT_F_VID_MUX, "Video Muxer" },
339 	{ MEDIA_ENT_F_VID_IF_BRIDGE, "Video Interface Bridge" },
340 	{ 0, nullptr }
341 };
342 
mi_entfunction2s(__u32 function,bool * is_invalid)343 std::string mi_entfunction2s(__u32 function, bool *is_invalid)
344 {
345 	std::string s;
346 
347 	if (function != MEDIA_ENT_T_DEVNODE_UNKNOWN &&
348 	    (function & MEDIA_ENT_TYPE_MASK) == MEDIA_ENT_F_OLD_BASE &&
349 	    function > MEDIA_ENT_T_DEVNODE_DVB) {
350 		s = "Unknown device node (" + num2s(function) + ")";
351 		if (!is_invalid)
352 			return s;
353 		*is_invalid = true;
354 		return "FAIL: " + s;
355 	}
356 	if (function != MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN &&
357 	    (function & MEDIA_ENT_TYPE_MASK) == MEDIA_ENT_F_OLD_SUBDEV_BASE &&
358 	    function > MEDIA_ENT_F_TUNER) {
359 		s = "Unknown sub-device (" + num2s(function) + ")";
360 		if (!is_invalid)
361 			return s;
362 		*is_invalid = true;
363 		return "FAIL: " + s;
364 	}
365 
366 	for (unsigned i = 0; entity_functions_def[i].str; i++) {
367 		if (function == entity_functions_def[i].flag) {
368 			bool fail = !memcmp(entity_functions_def[i].str, "FAIL: ", 6);
369 
370 			if (is_invalid && fail) {
371 				*is_invalid = fail;
372 				return entity_functions_def[i].str;
373 			}
374 			return fail ? entity_functions_def[i].str + 6 : entity_functions_def[i].str;
375 		}
376 	}
377 	if (is_invalid) {
378 		*is_invalid = true;
379 		return "FAIL: Unknown Function (" + num2s(function) + "), is v4l2-compliance out-of-date?";
380 	}
381 	return "Unknown Function (" + num2s(function) + ")";
382 }
383 
mi_func_requires_intf(__u32 function)384 bool mi_func_requires_intf(__u32 function)
385 {
386 	switch (function) {
387 	case MEDIA_ENT_F_DTV_DEMOD:
388 	case MEDIA_ENT_F_TS_DEMUX:
389 	case MEDIA_ENT_F_DTV_CA:
390 	case MEDIA_ENT_F_IO_V4L:
391 	case MEDIA_ENT_F_IO_VBI:
392 	case MEDIA_ENT_F_IO_SWRADIO:
393 		return true;
394 	default:
395 		return false;
396 	}
397 }
398 
399 static constexpr flag_def pad_flags_def[] = {
400 	{ MEDIA_PAD_FL_SINK, "Sink" },
401 	{ MEDIA_PAD_FL_SOURCE, "Source" },
402 	{ MEDIA_PAD_FL_MUST_CONNECT, "Must Connect" },
403 	{ 0, nullptr }
404 };
405 
mi_padflags2s(__u32 flags)406 std::string mi_padflags2s(__u32 flags)
407 {
408 	return flags2s(flags, pad_flags_def);
409 }
410 
411 static constexpr flag_def link_flags_def[] = {
412 	{ MEDIA_LNK_FL_ENABLED, "Enabled" },
413 	{ MEDIA_LNK_FL_IMMUTABLE, "Immutable" },
414 	{ MEDIA_LNK_FL_DYNAMIC, "Dynamic" },
415 	{ 0, nullptr }
416 };
417 
mi_linkflags2s(__u32 flags)418 std::string mi_linkflags2s(__u32 flags)
419 {
420 	std::string s = flags2s(flags & ~MEDIA_LNK_FL_LINK_TYPE, link_flags_def);
421 
422 	if (!s.empty())
423 		s = ", " + s;
424 	switch (flags & MEDIA_LNK_FL_LINK_TYPE) {
425 	case MEDIA_LNK_FL_DATA_LINK:
426 		return "Data" + s;
427 	case MEDIA_LNK_FL_INTERFACE_LINK:
428 		return "Interface" + s;
429 	case MEDIA_LNK_FL_ANCILLARY_LINK:
430 		return "Ancillary" + s;
431 	default:
432 		return "Unknown (" + num2s(flags) + ")" + s;
433 	}
434 }
435 
read_topology(int media_fd,__u32 major,__u32 minor,__u32 media_version,bool * is_invalid,__u32 * function)436 static __u32 read_topology(int media_fd, __u32 major, __u32 minor,
437 			   __u32 media_version, bool *is_invalid,
438 			   __u32 *function)
439 {
440 	media_v2_topology topology;
441 	unsigned i, j;
442 
443 	memset(&topology, 0, sizeof(topology));
444 	if (ioctl(media_fd, MEDIA_IOC_G_TOPOLOGY, &topology))
445 		return 0;
446 
447 	media_v2_entity v2_ents[topology.num_entities];
448 	media_v2_interface v2_ifaces[topology.num_interfaces];
449 	media_v2_pad v2_pads[topology.num_pads];
450 	media_v2_link v2_links[topology.num_links];
451 
452 	topology.ptr_entities = (uintptr_t)v2_ents;
453 	topology.ptr_interfaces = (uintptr_t)v2_ifaces;
454 	topology.ptr_pads = (uintptr_t)v2_pads;
455 	topology.ptr_links = (uintptr_t)v2_links;
456 	if (ioctl(media_fd, MEDIA_IOC_G_TOPOLOGY, &topology))
457 		return 0;
458 	for (i = 0; i < topology.num_interfaces; i++)
459 		if (v2_ifaces[i].devnode.major == major &&
460 		    v2_ifaces[i].devnode.minor == minor)
461 			break;
462 	if (i == topology.num_interfaces) {
463 		fprintf(stderr, "FAIL: could not find device %d:%d in topology\n",
464 			major, minor);
465 		if (is_invalid)
466 			*is_invalid = true;
467 		if (function)
468 			*function = MEDIA_ENT_F_UNKNOWN;
469 		return MEDIA_ENT_F_UNKNOWN;
470 	}
471 	const media_v2_interface &iface = v2_ifaces[i];
472 	for (i = 0; i < topology.num_links; i++) {
473 		__u32 type = v2_links[i].flags & MEDIA_LNK_FL_LINK_TYPE;
474 
475 		if (type != MEDIA_LNK_FL_INTERFACE_LINK)
476 			continue;
477 		if (v2_links[i].source_id == iface.id)
478 			break;
479 	}
480 	bool is_radio = iface.intf_type == MEDIA_INTF_T_V4L_RADIO;
481 
482 	if (is_radio && i < topology.num_links) {
483 		if (is_invalid)
484 			*is_invalid = true;
485 		fprintf(stderr, "FAIL: unexpectedly found link for radio interface 0x%08x in topology\n",
486 			iface.id);
487 		return MEDIA_ENT_F_UNKNOWN;
488 	}
489 	if (!is_radio) {
490 		if (i == topology.num_links) {
491 			if (is_invalid)
492 				*is_invalid = true;
493 			fprintf(stderr, "FAIL: could not find link for interface 0x%08x in topology\n",
494 				iface.id);
495 			return MEDIA_ENT_F_UNKNOWN;
496 		}
497 		__u32 ent_id = v2_links[i].sink_id;
498 		for (i = 0; i < topology.num_entities; i++) {
499 			if (v2_ents[i].id == ent_id)
500 				break;
501 		}
502 		if (i == topology.num_entities) {
503 			if (is_invalid)
504 				*is_invalid = true;
505 			fprintf(stderr, "FAIL: could not find entity 0x%08x in topology\n",
506 				ent_id);
507 			return MEDIA_ENT_F_UNKNOWN;
508 		}
509 	}
510 
511 	printf("Interface Info:\n");
512 	printf("\tID               : 0x%08x\n", iface.id);
513 	printf("\tType             : %s\n", mi_ifacetype2s(iface.intf_type).c_str());
514 
515 	if (is_radio)
516 		return MEDIA_ENT_F_UNKNOWN;
517 
518 	const media_v2_entity &ent = v2_ents[i];
519 	printf("Entity Info:\n");
520 	printf("\tID               : 0x%08x (%u)\n", ent.id, ent.id);
521 	printf("\tName             : %s\n", ent.name);
522 	printf("\tFunction         : %s\n", mi_entfunction2s(ent.function, is_invalid).c_str());
523 	if (MEDIA_V2_ENTITY_HAS_FLAGS(media_version) && ent.flags)
524 		printf("\tFlags            : %s\n", mi_entflags2s(ent.flags).c_str());
525 
526 	// Yes, I know, lots of nested for-loops. If we get really complex
527 	// devices with such large topologies that this becomes too inefficient
528 	// then this needs to be changed and we'd need some maps to quickly
529 	// look up values.
530 	for (i = 0; i < topology.num_pads; i++) {
531 		const media_v2_pad &pad = v2_pads[i];
532 
533 		if (pad.entity_id != ent.id)
534 			continue;
535 		printf("\tPad 0x%08x   : ", pad.id);
536 		if (MEDIA_V2_PAD_HAS_INDEX(media_version))
537 			printf("%u: ", pad.index);
538 		printf("%s\n", mi_padflags2s(pad.flags).c_str());
539 		for (j = 0; j < topology.num_links; j++) {
540 			const media_v2_link &link = v2_links[j];
541 			__u32 type = link.flags & MEDIA_LNK_FL_LINK_TYPE;
542 			__u32 remote_pad;
543 			__u32 remote_ent_id = 0;
544 			const media_v2_entity *remote_ent = nullptr;
545 
546 			if (type != MEDIA_LNK_FL_DATA_LINK ||
547 			    (link.source_id != pad.id && link.sink_id != pad.id))
548 				continue;
549 			bool is_sink = link.sink_id == pad.id;
550 			remote_pad = is_sink ? link.source_id : link.sink_id;
551 			for (unsigned k = 0; k < topology.num_pads; k++)
552 				if (v2_pads[k].id == remote_pad) {
553 					remote_ent_id = v2_pads[k].entity_id;
554 					break;
555 				}
556 			if (!remote_ent_id) {
557 				if (is_invalid)
558 					*is_invalid = true;
559 				fprintf(stderr, "FAIL: could not find remote pad 0x%08x in topology\n",
560 					remote_pad);
561 				return ent.id;
562 			}
563 			for (unsigned k = 0; k < topology.num_entities; k++)
564 				if (v2_ents[k].id == remote_ent_id) {
565 					remote_ent = &v2_ents[k];
566 					break;
567 				}
568 			if (!remote_ent) {
569 				if (is_invalid)
570 					*is_invalid = true;
571 				fprintf(stderr, "FAIL: could not find remote entity 0x%08x in topology\n",
572 					remote_ent_id);
573 				return ent.id;
574 			}
575 			printf("\t  Link 0x%08x: %s remote pad 0x%x of entity '%s' (%s): %s\n",
576 			       link.id, is_sink ? "from" : "to", remote_pad,
577 			       remote_ent->name, mi_entfunction2s(remote_ent->function, is_invalid).c_str(),
578 			       mi_linkflags2s(link.flags).c_str());
579 			if (function && !*function)
580 				*function = remote_ent->function;
581 		}
582 	}
583 	return ent.id;
584 }
585 
mi_media_info_for_fd(int media_fd,int fd,bool * is_invalid,__u32 * function)586 __u32 mi_media_info_for_fd(int media_fd, int fd, bool *is_invalid, __u32 *function)
587 {
588 	struct media_device_info mdinfo;
589 	struct stat sb;
590 	__u32 ent_id = 0;
591 
592 	if (is_invalid)
593 		*is_invalid = false;
594 
595 	if (function)
596 		*function = MEDIA_ENT_F_UNKNOWN;
597 
598 	if (ioctl(media_fd, MEDIA_IOC_DEVICE_INFO, &mdinfo))
599 		return 0;
600 
601 	struct media_entity_desc ent;
602 	bool found = false;
603 
604 	printf("Media Driver Info:\n");
605 	printf("\tDriver name      : %s\n", mdinfo.driver);
606 	printf("\tModel            : %s\n", mdinfo.model);
607 	printf("\tSerial           : %s\n", mdinfo.serial);
608 	printf("\tBus info         : %s\n", mdinfo.bus_info);
609 	printf("\tMedia version    : %d.%d.%d\n",
610 	       mdinfo.media_version >> 16,
611 	       (mdinfo.media_version >> 8) & 0xff,
612 	       mdinfo.media_version & 0xff);
613 	printf("\tHardware revision: 0x%08x (%u)\n",
614 	       mdinfo.hw_revision, mdinfo.hw_revision);
615 	printf("\tDriver version   : %d.%d.%d\n",
616 	       mdinfo.driver_version >> 16,
617 	       (mdinfo.driver_version >> 8) & 0xff,
618 	       mdinfo.driver_version & 0xff);
619 
620 	if (fd < 0)
621 		return 0;
622 
623 	if (fstat(fd, &sb) == -1) {
624 		fprintf(stderr, "failed to stat file\n");
625 		std::exit(EXIT_FAILURE);
626 	}
627 
628 	ent_id = read_topology(media_fd, major(sb.st_rdev), minor(sb.st_rdev),
629 			       mdinfo.media_version, is_invalid, function);
630 	if (ent_id)
631 		return ent_id;
632 
633 	memset(&ent, 0, sizeof(ent));
634 	ent.id = MEDIA_ENT_ID_FLAG_NEXT;
635 	while (!ioctl(media_fd, MEDIA_IOC_ENUM_ENTITIES, &ent)) {
636 		if (ent.dev.major == major(sb.st_rdev) &&
637 		    ent.dev.minor == minor(sb.st_rdev)) {
638 			found = true;
639 			break;
640 		}
641 		ent.id |= MEDIA_ENT_ID_FLAG_NEXT;
642 	}
643 	if (!found)
644 		return 0;
645 
646 	printf("Entity Info:\n");
647 	printf("\tID               : %u\n", ent.id);
648 	printf("\tName             : %s\n", ent.name);
649 	printf("\tType             : %s\n", mi_entfunction2s(ent.type).c_str());
650 	if (ent.flags)
651 		printf("\tFlags            : %s\n", mi_entflags2s(ent.flags).c_str());
652 	if (ent.flags & MEDIA_ENT_FL_DEFAULT) {
653 		printf("\tMajor            : %u\n", ent.dev.major);
654 		printf("\tMinor            : %u\n", ent.dev.minor);
655 	}
656 
657 	struct media_links_enum links_enum;
658 	struct media_pad_desc pads[ent.pads];
659 	struct media_link_desc links[ent.links];
660 
661 	memset(&links_enum, 0, sizeof(links_enum));
662 	links_enum.entity = ent.id;
663 	links_enum.pads = pads;
664 	links_enum.links = links;
665 	if (ioctl(media_fd, MEDIA_IOC_ENUM_LINKS, &links_enum))
666 		return ent.id;
667 
668 	for (unsigned i = 0; i < ent.pads; i++)
669 		printf("\tPad              : %u: %s\n", pads[i].index,
670 		       mi_padflags2s(pads[i].flags).c_str());
671 	for (unsigned i = 0; i < ent.links; i++)
672 		printf("\tLink             : %u->%u: %s\n",
673 		       links[i].source.entity,
674 		       links[i].sink.entity,
675 		       mi_linkflags2s(links[i].flags).c_str());
676 	return ent.id;
677 }
678