• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* SPDX-License-Identifier: GPL-2.0-only */
2 /*
3  * Copyright 2022 Collabora Ltd.
4  */
5 
6 #include "v4l2-tracer-common.h"
7 #include <iomanip>
8 #include <iostream>
9 #include <sstream>
10 
is_debug(void)11 bool is_debug(void)
12 {
13 	return (getenv("V4L2_TRACER_OPTION_DEBUG") != nullptr);
14 }
15 
is_verbose(void)16 bool is_verbose(void)
17 {
18 	return (getenv("V4L2_TRACER_OPTION_VERBOSE") != nullptr);
19 }
20 
print_v4l2_tracer_info(void)21 void print_v4l2_tracer_info(void)
22 {
23 	fprintf(stderr, "v4l2-tracer %s%s\n", PACKAGE_VERSION, STRING(GIT_COMMIT_CNT));
24 	if (strlen(STRING(GIT_SHA)) != 0U)
25 		fprintf(stderr, "v4l2-tracer SHA: '%s' %s\n", STRING(GIT_SHA), STRING(GIT_COMMIT_DATE));
26 }
27 
print_usage(void)28 void print_usage(void)
29 {
30 	print_v4l2_tracer_info();
31 	fprintf(stderr, "Usage:\n\tv4l2-tracer [options] trace <tracee>\n"
32 	        "\tv4l2-tracer [options] retrace <trace_file>.json\n"
33 	        "\tv4l2-tracer clean <trace_file>.json\n\n"
34 
35 	        "\tCommon options:\n"
36 	        "\t\t-c, --compact     Write minimal whitespace in JSON file.\n"
37 	        "\t\t-g, --debug       Turn on verbose reporting plus additional debug info.\n"
38 	        "\t\t-h, --help        Display this message.\n"
39 	        "\t\t-r  --raw         Write decoded video frame data to JSON file.\n"
40 	        "\t\t-u  --userspace   Trace userspace arguments.\n"
41 	        "\t\t-v, --verbose     Turn on verbose reporting.\n"
42 	        "\t\t-y, --yuv         Write decoded video frame data to yuv file.\n\n"
43 
44 	        "\tRetrace options:\n"
45 	        "\t\t-d, --video_device <dev>   Retrace with a specific video device.\n"
46 	        "\t\t                           <dev> must be a digit corresponding to\n"
47 	        "\t\t                           /dev/video<dev> \n\n"
48 	        "\t\t-m, --media_device <dev>   Retrace with a specific media device.\n"
49 	        "\t\t                           <dev> must be a digit corresponding to\n"
50 	        "\t\t                           /dev/media<dev> \n\n");
51 }
52 
add_separator(std::string & str)53 void add_separator(std::string &str)
54 {
55 	if (!str.empty())
56 		str += "|";
57 }
58 
clean_string(size_t idx,std::string substring_to_erase,std::string & str)59 void clean_string(size_t idx, std::string substring_to_erase, std::string &str)
60 {
61 	std::string temp = substring_to_erase + '|';
62 	if (str.find(temp) != std::string::npos)
63 		str.erase(idx, temp.length());
64 	else
65 		str.erase(idx, substring_to_erase.length());
66 }
67 
ver2s(unsigned int version)68 std::string ver2s(unsigned int version)
69 {
70 	const int mask = 0xff;
71 	const int BUF_SIZE = 16;
72 	char buf[BUF_SIZE];
73 	sprintf(buf, "%d.%d.%d", version >> BUF_SIZE, (version >> (BUF_SIZE / 2)) & mask, version & mask);
74 	return buf;
75 }
76 
77 /* Convert a number to an octal string. If num is 0, return an empty string. */
number2s_oct(long num)78 std::string number2s_oct(long num)
79 {
80 	const int min_width = 5;
81 	std::stringstream stream;
82 	stream << std::setfill ('0') << std::setw(min_width) << std::oct << num;
83 	return stream.str();
84 }
85 
86 /* Convert a number to a hex string. If num is 0, return an empty string. */
number2s(long num)87 std::string number2s(long num)
88 {
89 	if (num == 0)
90 		return "";
91 	std::stringstream stream;
92 	stream << std::hex << num;
93 	return "0x" + stream.str();
94 }
95 
val2s(long val,const val_def * def)96 std::string val2s(long val, const val_def *def)
97 {
98 	if (def == nullptr)
99 		return number2s(val);
100 
101 	while ((def->val != -1) && (def->val != val))
102 		def++;
103 
104 	if (def->val == val)
105 		return def->str;
106 
107 	return number2s(val);
108 }
109 
fl2s(unsigned val,const flag_def * def)110 std::string fl2s(unsigned val, const flag_def *def)
111 {
112 	std::string str;
113 
114 	if (def == nullptr)
115 		return number2s(val);
116 
117 	while ((def->flag) != 0U) {
118 		if ((val & def->flag) != 0U) {
119 			add_separator(str);
120 			str += def->str;
121 			val &= ~def->flag;
122 		}
123 		def++;
124 	}
125 	if (val != 0U) {
126 		add_separator(str);
127 		str += number2s(val);
128 	}
129 
130 	return str;
131 }
132 
fl2s_buffer(__u32 flags)133 std::string fl2s_buffer(__u32 flags)
134 {
135 	std::string str;
136 
137 	switch (flags & V4L2_BUF_FLAG_TIMESTAMP_MASK) {
138 	case V4L2_BUF_FLAG_TIMESTAMP_UNKNOWN:
139 		str += "V4L2_BUF_FLAG_TIMESTAMP_UNKNOWN";
140 		flags &= ~V4L2_BUF_FLAG_TIMESTAMP_UNKNOWN;
141 		break;
142 	case V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC:
143 		str += "V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC";
144 		flags &= ~V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
145 		break;
146 	case V4L2_BUF_FLAG_TIMESTAMP_COPY:
147 		str += "V4L2_BUF_FLAG_TIMESTAMP_COPY";
148 		flags &= ~V4L2_BUF_FLAG_TIMESTAMP_COPY;
149 		break;
150 	default:
151 		break;
152 	}
153 
154 	/* Since V4L2_BUF_FLAG_TSTAMP_SRC_EOF == 0, at least this flag will always be added. */
155 	add_separator(str);
156 	switch (flags & V4L2_BUF_FLAG_TSTAMP_SRC_MASK) {
157 	case V4L2_BUF_FLAG_TSTAMP_SRC_EOF:
158 		str += "V4L2_BUF_FLAG_TSTAMP_SRC_EOF";
159 		flags &= ~V4L2_BUF_FLAG_TSTAMP_SRC_EOF;
160 		break;
161 	case V4L2_BUF_FLAG_TSTAMP_SRC_SOE:
162 		str += "V4L2_BUF_FLAG_TSTAMP_SRC_SOE";
163 		flags &= ~V4L2_BUF_FLAG_TSTAMP_SRC_SOE;
164 		break;
165 	default:
166 		break;
167 	}
168 
169 	if (flags != 0U) {
170 		add_separator(str);
171 		const unsigned ts_mask = V4L2_BUF_FLAG_TIMESTAMP_MASK | V4L2_BUF_FLAG_TSTAMP_SRC_MASK;
172 		str += fl2s(flags & ~ts_mask, v4l2_buf_flag_def);
173 	}
174 
175 	return str;
176 }
177 
fl2s_fwht(__u32 flags)178 std::string fl2s_fwht(__u32 flags)
179 {
180 	std::string str;
181 	switch (flags & V4L2_FWHT_FL_PIXENC_MSK) {
182 	case V4L2_FWHT_FL_PIXENC_YUV:
183 		str += "V4L2_FWHT_FL_PIXENC_YUV";
184 		flags &= ~V4L2_FWHT_FL_PIXENC_YUV;
185 		break;
186 	case V4L2_FWHT_FL_PIXENC_RGB:
187 		str += "V4L2_FWHT_FL_PIXENC_RGB";
188 		flags &= ~V4L2_FWHT_FL_PIXENC_RGB;
189 		break;
190 	case V4L2_FWHT_FL_PIXENC_HSV:
191 		str += "V4L2_FWHT_FL_PIXENC_HSV";
192 		flags &= ~V4L2_FWHT_FL_PIXENC_HSV;
193 		break;
194 	default:
195 		break;
196 	}
197 	add_separator(str);
198 	str += fl2s(flags, v4l2_ctrl_fwht_params_flag_def);
199 	return str;
200 }
201 
s2number(const char * char_str)202 long s2number(const char *char_str)
203 {
204 	if (char_str == nullptr)
205 		return 0;
206 
207 	std::string str = char_str;
208 
209 	long num = 0;
210 	if (str.empty())
211 		return 0;
212 	try {
213 		num = std::strtol(str.c_str(), nullptr, 0); /* base is auto-detected */
214 	} catch (std::invalid_argument& ia) {
215 		line_info("\n\tString \'%s\' is invalid.", str.c_str());
216 	} catch (std::out_of_range& oor) {
217 		line_info("\n\tString \'%s\' is out of range.", str.c_str());
218 	}
219 	return num;
220 }
221 
s2val(const char * char_str,const val_def * def)222 long s2val(const char *char_str, const val_def *def)
223 {
224 	if (char_str == nullptr)
225 		return 0;
226 	std::string str = char_str;
227 
228 	if (str.empty())
229 		return 0;
230 
231 	if (def == nullptr)
232 		return s2number(char_str);
233 
234 	while ((def->val != -1) && (def->str != str))
235 		def++;
236 
237 	if (def->str == str)
238 		return def->val;
239 
240 	return s2number(char_str);
241 }
242 
s2flags(const char * char_str,const flag_def * def)243 unsigned long s2flags(const char *char_str, const flag_def *def)
244 {
245 	if (char_str == nullptr)
246 		return 0;
247 	std::string str = char_str;
248 
249 	size_t idx = 0;
250 	unsigned long flags = 0;
251 
252 	if (def == nullptr)
253 		return s2number(char_str);
254 
255 	while ((def->flag) != 0U) {
256 		idx = str.find(def->str);
257 		if (idx == std::string::npos) {
258 			def++;
259 			continue;
260 		}
261 		/* Stop false substring matches e.g. in V4L2_BUF_CAP_SUPPORTS_MMAP_CACHE_HINTS */
262 		std::string check = def->str;
263 		if (check.length() != str.length()) {
264 			idx = str.find(check + '|');
265 			if (idx == std::string::npos) {
266 				def++;
267 				continue;
268 			}
269 		}
270 		flags += def->flag;
271 		clean_string(idx, def->str, str);
272 		def++;
273 	}
274 	if (!str.empty())
275 		flags += s2number(str.c_str());
276 
277 	return flags;
278 }
279 
s2flags_buffer(const char * char_str)280 unsigned long s2flags_buffer(const char *char_str)
281 {
282 	if (char_str == nullptr)
283 		return 0;
284 	std::string str = char_str;
285 
286 	size_t idx = 0;
287 	unsigned long flags = 0;
288 
289 	idx = str.find("V4L2_BUF_FLAG_TIMESTAMP_COPY");
290 	if (idx != std::string::npos) {
291 		flags += V4L2_BUF_FLAG_TIMESTAMP_COPY;
292 		clean_string(idx, "V4L2_BUF_FLAG_TIMESTAMP_COPY", str);
293 	}
294 	idx = str.find("V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC");
295 	if (idx != std::string::npos) {
296 		flags += V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
297 		clean_string(idx, "V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC", str);
298 	}
299 	idx = str.find("V4L2_BUF_FLAG_TIMESTAMP_UNKNOWN");
300 	if (idx != std::string::npos) {
301 		flags += V4L2_BUF_FLAG_TIMESTAMP_UNKNOWN;
302 		clean_string(idx, "V4L2_BUF_FLAG_TIMESTAMP_UNKNOWN", str);
303 	}
304 	idx = str.find("V4L2_BUF_FLAG_TSTAMP_SRC_SOE");
305 	if (idx != std::string::npos) {
306 		flags += V4L2_BUF_FLAG_TSTAMP_SRC_SOE;
307 		clean_string(idx, "V4L2_BUF_FLAG_TSTAMP_SRC_SOE", str);
308 	}
309 	idx = str.find("V4L2_BUF_FLAG_TSTAMP_SRC_EOF");
310 	if (idx != std::string::npos) {
311 		flags += V4L2_BUF_FLAG_TSTAMP_SRC_EOF;
312 		clean_string(idx, "V4L2_BUF_FLAG_TSTAMP_SRC_EOF", str);
313 	}
314 	if (!str.empty())
315 		flags += s2flags(str.c_str(), v4l2_buf_flag_def);
316 	return flags;
317 }
318 
s2flags_fwht(const char * char_str)319 unsigned long s2flags_fwht(const char *char_str)
320 {
321 	if (char_str == nullptr)
322 		return 0;
323 	std::string str = char_str;
324 
325 	size_t idx = 0;
326 	unsigned long flags = 0;
327 	idx = str.find("V4L2_FWHT_FL_PIXENC_YUV");
328 	if (idx != std::string::npos) {
329 		flags += V4L2_FWHT_FL_PIXENC_YUV;
330 		clean_string(idx, "V4L2_FWHT_FL_PIXENC_YUV", str);
331 	}
332 	idx = str.find("V4L2_FWHT_FL_PIXENC_RGB");
333 	if (idx != std::string::npos) {
334 		flags += V4L2_FWHT_FL_PIXENC_RGB;
335 		clean_string(idx, "V4L2_FWHT_FL_PIXENC_RGB", str);
336 	}
337 	idx = str.find("V4L2_FWHT_FL_PIXENC_HSV");
338 	if (idx != std::string::npos) {
339 		flags += V4L2_FWHT_FL_PIXENC_HSV;
340 		clean_string(idx, "V4L2_FWHT_FL_PIXENC_HSV", str);
341 	}
342 	if (!str.empty())
343 		flags += s2flags(str.c_str(), v4l2_ctrl_fwht_params_flag_def);
344 	return flags;
345 }
346 
get_path_media(std::string driver)347 std::string get_path_media(std::string driver)
348 {
349 	struct dirent *entry_pointer = nullptr;
350 	std::string path_media;
351 	DIR *directory_pointer = opendir("/dev");
352 	if (directory_pointer == nullptr)
353 		return path_media;
354 
355 	while ((entry_pointer = readdir(directory_pointer)) != nullptr) {
356 		std::string media = "media";
357 		const char *name = entry_pointer->d_name;
358 		if ((memcmp(name, media.c_str(), media.length()) != 0) || (isdigit(name[media.length()]) == 0))
359 			continue;
360 
361 		std::string media_devname = std::string("/dev/") + name;
362 		setenv("V4L2_TRACER_PAUSE_TRACE", "true", 0);
363 		int media_fd = open(media_devname.c_str(), O_RDONLY);
364 		unsetenv("V4L2_TRACER_PAUSE_TRACE");
365 		if (media_fd < 0)
366 			continue;
367 
368 		struct media_device_info info = {};
369 		if (ioctl(media_fd, MEDIA_IOC_DEVICE_INFO, &info) || info.driver != driver) {
370 			setenv("V4L2_TRACER_PAUSE_TRACE", "true", 0);
371 			close(media_fd);
372 			unsetenv("V4L2_TRACER_PAUSE_TRACE");
373 			continue;
374 		}
375 		path_media = media_devname;
376 		setenv("V4L2_TRACER_PAUSE_TRACE", "true", 0);
377 		close(media_fd);
378 		unsetenv("V4L2_TRACER_PAUSE_TRACE");
379 	}
380 	closedir(directory_pointer);
381 	return path_media;
382 }
383 
get_path_video(int media_fd,std::list<std::string> linked_entities)384 std::string get_path_video(int media_fd, std::list<std::string> linked_entities)
385 {
386 	int err = 0;
387 	std::string path_video;
388 	struct media_v2_topology topology = {};
389 
390 	err = ioctl(media_fd, MEDIA_IOC_G_TOPOLOGY, &topology);
391 	if (err < 0)
392 		return path_video;
393 
394 	std::vector<media_v2_interface> ifaces(topology.num_interfaces);
395 	topology.ptr_interfaces = (uintptr_t) ifaces.data();
396 
397 	std::vector<media_v2_link> links(topology.num_links);
398 	topology.ptr_links = (uintptr_t) links.data();
399 
400 	std::vector<media_v2_entity> ents(topology.num_entities);
401 	topology.ptr_entities = (uintptr_t) ents.data();
402 
403 	err = ioctl(media_fd, MEDIA_IOC_G_TOPOLOGY, &topology);
404 	if (err < 0)
405 		return path_video;
406 
407 	for (auto &name : linked_entities) {
408 		/* Find an entity listed in the video device's linked_entities. */
409 		for (__u32 i = 0; i < topology.num_entities; i++) {
410 			if (ents[i].name != name)
411 				continue;
412 			/* Find the first link connected to that entity. */
413 			for (__u32 j = 0; j < topology.num_links; j++) {
414 				if (links[j].sink_id != ents[i].id)
415 					continue;
416 				/* Find the interface connected to that link. */
417 				for (__u32 k = 0; k < topology.num_interfaces; k++) {
418 					if (ifaces[k].id != links[j].source_id)
419 						continue;
420 					std::string video_devname = mi_media_get_device(ifaces[k].devnode.major,
421 					                                                ifaces[k].devnode.minor);
422 					if (!video_devname.empty()) {
423 						path_video = video_devname;
424 						break;
425 					}
426 				}
427 			}
428 		}
429 	}
430 	return path_video;
431 }
432 
get_linked_entities(int media_fd,std::string path_video)433 std::list<std::string> get_linked_entities(int media_fd, std::string path_video)
434 {
435 	int err = 0;
436 	std::list<std::string> linked_entities;
437 	struct media_v2_topology topology = {};
438 
439 	err = ioctl(media_fd, MEDIA_IOC_G_TOPOLOGY, &topology);
440 	if (err < 0)
441 		return linked_entities;
442 
443 	std::vector<media_v2_interface> ifaces(topology.num_interfaces);
444 	topology.ptr_interfaces = (uintptr_t) ifaces.data();
445 
446 	std::vector<media_v2_link> links(topology.num_links);
447 	topology.ptr_links = (uintptr_t) links.data();
448 
449 	std::vector<media_v2_entity> ents(topology.num_entities);
450 	topology.ptr_entities = (uintptr_t) ents.data();
451 
452 	err = ioctl(media_fd, MEDIA_IOC_G_TOPOLOGY, &topology);
453 	if (err < 0)
454 		return linked_entities;
455 
456 	/* find the interface corresponding to the path_video */
457 	for (__u32 i = 0; i < topology.num_interfaces; i++) {
458 		if (path_video != mi_media_get_device(ifaces[i].devnode.major, ifaces[i].devnode.minor))
459 			continue;
460 		/* find the links from that interface */
461 		for (__u32 j = 0; j < topology.num_links; j++) {
462 			if (links[j].source_id != ifaces[i].id)
463 				continue;
464 			/* find the entities connected by that link to the interface */
465 			for (__u32 k = 0; k < topology.num_entities; k++) {
466 				if (ents[k].id != links[j].sink_id)
467 					continue;
468 				linked_entities.push_back(ents[k].name);
469 			}
470 		}
471 		if (linked_entities.size())
472 			break;
473 	}
474 	return linked_entities;
475 }
476