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