Message ID | 20221004185703.13393-1-deborah.brouwer@collabora.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | [v2] utils: add v4l2-tracer utility | expand |
Hi Deb, On 10/4/22 20:57, Deborah Brouwer wrote: > The v4l2-tracer utility traces, records and replays userspace applications > that implement the v4l2 memory-to-memory stateless video decoder > interface. The trace function intercepts and copies system calls, > stateless codec controls and buffers to a json-formatted trace file. > The retrace function independently reads and replays the json trace file. > The json trace file can be retraced independently from its original > userspace environment and can be edited to inject errors to test a > driver's error handling abilities. > > Signed-off-by: Deborah Brouwer <deborah.brouwer@collabora.com> > --- > Currently supports MPEG2, VP8, H264, FWHT > > Changes since v1: > - auto-generate code from uAPI headers > - generated code adds some ability to trace > VP9/HEVC which compiles but still needs work > - generated code adds full support for MPEG2 > - fix multiplanar decoding for vicodec > - change library name to libv4l2tracer > - update README > - expand help to show options > - remove extraneous files from gitignore > > Examples: > > Trace VP8 decoding: > > v4l2-tracer trace -- gst-launch-1.0 -- filesrc > location=test-25fps.vp8 ! parsebin ! v4l2slvp8dec ! > videocodectestsink > > Trace H264 decoding: > > v4l2-tracer trace -y -- gst-launch-1.0 -- filesrc > location=test-25fps.h264 ! parsebin ! v4l2slh264dec ! > videocodectestsink > > Trace FWHT decoding: > > v4l2-tracer trace -- v4l2-ctl -d9 --stream-mmap > --stream-out-mmap --stream-from-hdr test-25fps.fwht > --stream-to out.yuv > > Note: if tracing another v4l2 utility like > v4l2-ctl, either used the installed versions > or configure with: > --disable-dyn-libv4l --disable-v4l2-ctl-libv4l > > Retrace: > v4l2-tracer retrace 79568_trace.json > > Trace a Retrace: > v4l2-tracer trace -- v4l2-tracer retrace > 79568_trace.json > > README | 8 + > configure.ac | 12 + > utils/Makefile.am | 5 + > utils/v4l2-tracer/.gitignore | 8 + > utils/v4l2-tracer/Makefile.am | 33 + > utils/v4l2-tracer/libv4l2tracer.cpp | 222 ++++ > utils/v4l2-tracer/retrace-helper.cpp | 196 ++++ > utils/v4l2-tracer/retracer.cpp | 1319 ++++++++++++++++++++++++ > utils/v4l2-tracer/trace-helper.cpp | 816 +++++++++++++++ > utils/v4l2-tracer/trace-helper.h | 51 + > utils/v4l2-tracer/trace.cpp | 669 ++++++++++++ > utils/v4l2-tracer/v4l2-tracer-common.h | 40 + > utils/v4l2-tracer/v4l2-tracer-gen.pl | 359 +++++++ > utils/v4l2-tracer/v4l2-tracer.1.in | 104 ++ > utils/v4l2-tracer/v4l2-tracer.cpp | 187 ++++ > 15 files changed, 4029 insertions(+) > create mode 100644 utils/v4l2-tracer/.gitignore > create mode 100644 utils/v4l2-tracer/Makefile.am > create mode 100644 utils/v4l2-tracer/libv4l2tracer.cpp > create mode 100644 utils/v4l2-tracer/retrace-helper.cpp > create mode 100644 utils/v4l2-tracer/retracer.cpp > create mode 100644 utils/v4l2-tracer/trace-helper.cpp > create mode 100644 utils/v4l2-tracer/trace-helper.h > create mode 100644 utils/v4l2-tracer/trace.cpp > create mode 100644 utils/v4l2-tracer/v4l2-tracer-common.h > create mode 100755 utils/v4l2-tracer/v4l2-tracer-gen.pl > create mode 100644 utils/v4l2-tracer/v4l2-tracer.1.in > create mode 100644 utils/v4l2-tracer/v4l2-tracer.cpp > > diff --git a/README b/README > index a7759171..c5b6df16 100644 > --- a/README > +++ b/README > @@ -45,6 +45,8 @@ you'll need also: > alsa-lib-devel doxygen libjpeg-turbo-devel qt5-qtbase-devel > libudev-devel mesa-libGLU-devel > > +The v4l2-tracer also needs the json-c library. > +On Debian: libjson-c-dev; on Fedora: json-c-devel. > > After downloading and installing the needed packages, you should run: > > @@ -227,6 +229,12 @@ v4l2-sysfs-path: > FIXME add description. > Installed by make install under <prefix>/bin. > > +v4l2-tracer: > +Tool to trace, record and replay userspace applications > +that implement the v4l2 memory-to-memory stateless video > +decoder interface. > +Installed by make install under <prefix>/bin. make install -> 'make install' > + > xc3028-firmware: > Xceive XC2028/3028 tuner module firmware manipulation tool. > xc3028-firmware does not get installed by make install. <snip> > diff --git a/utils/v4l2-tracer/libv4l2tracer.cpp b/utils/v4l2-tracer/libv4l2tracer.cpp > new file mode 100644 > index 00000000..c344d8a1 > --- /dev/null > +++ b/utils/v4l2-tracer/libv4l2tracer.cpp > @@ -0,0 +1,222 @@ > +/* SPDX-License-Identifier: GPL-2.0-only */ > +/* > + * Copyright 2022 Collabora Ltd. > + */ > + > +#include <dlfcn.h> > +#include <stdarg.h> > +#include <config.h> /* For PROMOTED_MODE_T */ > +#include "v4l2-tracer-common.h" > +#include "trace-helper.h" > + > +/* from trace.cpp */ > +void trace_open(int fd, const char *path, int oflag, mode_t mode, bool is_open64); > +void trace_mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off, > + unsigned long buf_address, bool is_mmap64); > +void trace_mem(int fd, __u32 offset, __u32 type, int index, __u32 bytesused, unsigned long start); > +void trace_mem_encoded(int fd, __u32 offset); > +json_object *trace_ioctl_args(int fd, unsigned long request, void *arg, > + bool from_userspace = true); > + > +int open(const char *path, int oflag, ...) > +{ > + errno = 0; > + mode_t mode = 0; > + > + if (oflag & O_CREAT) { > + va_list ap; > + va_start(ap, oflag); > + mode = va_arg(ap, PROMOTED_MODE_T); > + va_end(ap); > + } > + > + int (*original_open)(const char *path, int oflag, ...); > + original_open = (int (*)(const char*, int, ...)) dlsym(RTLD_NEXT, "open"); > + int fd = (*original_open)(path, oflag, mode); > + > + if (is_video_or_media_device(path)) { > + if (!options_are_set()) > + set_options(); > + add_device(fd, path); > + trace_open(fd, path, oflag, mode, false); > + } > + > + return fd; > +} > + > +int open64(const char *path, int oflag, ...) > +{ > + errno = 0; > + mode_t mode = 0; > + if (oflag & O_CREAT) { > + va_list ap; > + va_start(ap, oflag); > + mode = va_arg(ap, PROMOTED_MODE_T); > + va_end(ap); > + } > + > + int (*original_open64)(const char *path, int oflag, ...); > + original_open64 = (int (*)(const char*, int, ...)) dlsym(RTLD_NEXT, "open64"); > + int fd = (*original_open64)(path, oflag, mode); > + > + if (is_video_or_media_device(path)) { > + if (!options_are_set()) > + set_options(); > + add_device(fd, path); > + trace_open(fd, path, oflag, mode, true); > + } > + > + return fd; > +} > + > +int close(int fd) > +{ > + errno = 0; > + std::string path = get_device(fd); > + > + /* Only trace the close if a corresponding open was also traced. */ > + if (!path.empty()) { > + json_object *close_obj = json_object_new_object(); > + json_object_object_add(close_obj, "syscall_str", json_object_new_string("close")); > + json_object_object_add(close_obj, "syscall", json_object_new_int(LIBTRACER_SYSCALL_CLOSE)); Do you need to provide both a syscall_str and syscall value? Wouldn't the string be sufficient? The replayer can just map the string to the syscall number. > + json_object_object_add(close_obj, "fd", json_object_new_int(fd)); > + json_object_object_add(close_obj, "path", json_object_new_string(path.c_str())); > + write_json_object_to_json_file(close_obj); > + json_object_put(close_obj); > + remove_device(fd); > + > + /* If we removed the last device, close the json trace file. */ > + if (!count_devices()) > + close_json_file(); > + } > + > + int (*original_close)(int fd); > + original_close = (int (*)(int)) dlsym(RTLD_NEXT, "close"); > + > + return (*original_close)(fd); > +} > + > +void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off) > +{ > + errno = 0; > + void *(*original_mmap)(void *addr, size_t len, int prot, int flags, int fildes, off_t off); > + original_mmap = (void*(*)(void*, size_t, int, int, int, off_t)) dlsym(RTLD_NEXT, "mmap"); > + void *buf_address_pointer = (*original_mmap)(addr, len, prot, flags, fildes, off); > + > + set_buffer_address_trace(fildes, off, (unsigned long) buf_address_pointer); > + > + if (buffer_in_trace_context(fildes, off)) > + trace_mmap(addr, len, prot, flags, fildes, off, (unsigned long) buf_address_pointer, false); > + > + return buf_address_pointer; > +} > + > +void *mmap64(void *addr, size_t len, int prot, int flags, int fildes, off_t off) > +{ > + errno = 0; > + void *(*original_mmap64)(void *addr, size_t len, int prot, int flags, int fildes, off_t off); > + original_mmap64 = (void*(*)(void*, size_t, int, int, int, off_t)) dlsym(RTLD_NEXT, "mmap64"); > + void *buf_address_pointer = (*original_mmap64)(addr, len, prot, flags, fildes, off); > + > + set_buffer_address_trace(fildes, off, (unsigned long) buf_address_pointer); > + > + if (buffer_in_trace_context(fildes, off)) > + trace_mmap(addr, len, prot, flags, fildes, off, (unsigned long) buf_address_pointer, true); > + > + return buf_address_pointer; > +} > + > +int munmap(void *start, size_t length) > +{ > + errno = 0; > + int(*original_munmap)(void *start, size_t length); > + original_munmap = (int(*)(void *, size_t)) dlsym(RTLD_NEXT, "munmap"); > + int ret = (*original_munmap)(start, length); > + > + /* Only trace the unmapping if the original mapping was traced. */ > + if (!buffer_is_mapped((unsigned long) start)) > + return ret; > + > + json_object *munmap_obj = json_object_new_object(); > + json_object_object_add(munmap_obj, "syscall_str", json_object_new_string("munmap")); > + json_object_object_add(munmap_obj, "syscall", json_object_new_int(LIBTRACER_SYSCALL_MUNMAP)); > + > + if (errno) { > + json_object_object_add(munmap_obj, "errno", json_object_new_int(errno)); > + json_object_object_add(munmap_obj, "errno_str", json_object_new_string(strerror(errno))); > + } > + > + json_object *munmap_args = json_object_new_object(); > + json_object_object_add(munmap_args, "start", json_object_new_int64((int64_t)start)); > + json_object_object_add(munmap_args, "length", json_object_new_uint64(length)); > + json_object_object_add(munmap_obj, "munmap_args", munmap_args); > + > + write_json_object_to_json_file(munmap_obj); > + json_object_put(munmap_obj); > + > + return ret; > +} > + > +int ioctl(int fd, unsigned long request, ...) I think 'request' is a confusing name, esp. since codecs use the Request API. I see it comes from the man page, but in this case I would rename it to 'cmd', which is what it is called in e.g. v4l2-ioctl.c and fs/ioctl.c. > +{ > + errno = 0; > + va_list ap; > + va_start(ap, request); > + void *arg = va_arg(ap, void *); > + va_end(ap); > + > + int (*original_ioctl)(int fd, unsigned long request, ...); > + original_ioctl = (int (*)(int, long unsigned int, ...)) dlsym(RTLD_NEXT, "ioctl"); > + > + if ((request == VIDIOC_S_EXT_CTRLS) && (_IOC_TYPE(request) == 'V')) Why check _IOC_TYPE? That's already encoded in VIDIOC_S_EXT_CTRLS, so testing for that should be sufficient. Ditto for the same checks below. > + s_ext_ctrls_setup(static_cast<struct v4l2_ext_controls*>(arg)); > + > + if ((request == VIDIOC_QBUF) && (_IOC_TYPE(request) == 'V')) > + qbuf_setup(static_cast<struct v4l2_buffer*>(arg)); > + > + if ((request == VIDIOC_STREAMOFF) && (_IOC_TYPE(request) == 'V')) > + streamoff_cleanup(*(static_cast<v4l2_buf_type*>(arg))); > + > + json_object *ioctl_obj = json_object_new_object(); > + json_object_object_add(ioctl_obj, "syscall_str", json_object_new_string("ioctl")); > + json_object_object_add(ioctl_obj, "syscall", json_object_new_int(LIBTRACER_SYSCALL_IOCTL)); > + json_object_object_add(ioctl_obj, "fd", json_object_new_int(fd)); > + json_object_object_add(ioctl_obj, "request", json_object_new_uint64(request)); > + json_object_object_add(ioctl_obj, "request_str", > + json_object_new_string(get_ioctl_request_str(request).c_str())); So this would be renamed to "cmd" and "cmd_str". But, do you really need both? I think you could just store the string: "syscall_str":"ioctl","syscall":0,"fd":5,"cmd":"VIDIOC_QUERYCAP" When you replay it, it is easy enough to map the cmd string to the actual value. If for some reason a non-V4L2 cmd value is used, then you can write "0xHHHHHHHH" as the value, and the replay would take that as the cmd hex value. > + > + /* Trace the ioctl arguments provided by userspace. */ > + json_object *ioctl_args_userspace = trace_ioctl_args(fd, request, arg); > + if (json_object_object_length(ioctl_args_userspace)) > + json_object_object_add(ioctl_obj, "ioctl_args_from_userspace", ioctl_args_userspace); > + > + /* Make the original ioctl call. */ > + int ret = (*original_ioctl)(fd, request, arg); > + > + if (errno) { > + json_object_object_add(ioctl_obj, "errno", json_object_new_int(errno));> + json_object_object_add(ioctl_obj, "errno_str", json_object_new_string(strerror(errno))); Why not just store the error define: e.g. "EINVAL". And if the errno code isn't known, then just store it as a number "12345". It's much shorter than using the strerror() result, and even easier to read. It's a general theme throughout: whenever you write "foo":F,"foo_str":"Foo bar" you can also write: "foo":"FOO" where FOO is the V4L2 define, or a decimal or hex value if the value didn't map to a define. So this: { "syscall_str":"ioctl", "syscall":0, "fd":5, "request":3234878981, "request_str":"VIDIOC_S_FMT", "ioctl_args_from_userspace":{ "v4l2_format":{ "type":10, "type_str":"Video Output Multiplanar", "v4l2_pix_format_mplane":{ "width":320, "height":240, "pixelformat":875967059, "pixelformat_str":"H.264 Parsed Slice Data", "fcc_str":"S264", "field":0, "field_str":"Any", "colorspace":0, "colorspace_str":"Default", "num_planes":0, "flags":0, "flags_str":"", "ycbcr_enc":0, "ycbcr_enc_str":"Default", "quantization":0, "quantization_str":"Default", "xfer_func":0, "xfer_func_str":"Default" } } }, could become: { "syscall":"ioctl", "fd":5, "cmd":"VIDIOC_S_FMT", "ioctl_args_from_userspace":{ "v4l2_format":{ "type":"V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE", "v4l2_pix_format_mplane":{ "width":320, "height":240, "pixelformat":"V4L2_PIX_FMT_H264_SLICE", "fcc_str":"S264", // I'm not sure if this is useful or not "field":"V4L2_FIELD_ANY", "colorspace":"V4L2_COLORSPACE_DEFAULT", "num_planes":0, "flags":0, "flags_str":"", // I'm not sure if this is useful "ycbcr_enc":"V4L2_YCBCR_ENC_DEFAULT", "quantization":"V4L2_QUANTIZATION_DEFAULT", "xfer_func":"V4L2_XFER_FUNC_DEFAULT", } } }, Here is a more representative example of "flags": "flags":8404993, "flags_str":"mapped, request-fd, ts-copy, ts-src-eof", I'm wondering if flags_str is really useful. One question I have is whether it is possible to direct json to write "flags" as a hex value instead of decimal. It's much easier to decode a hex value to the corresponding bits than doing that with a decimal value. The other issue is that it makes the json file larger. I'm actually wondering if it wouldn't be better to keep the json file as compact as possible, since it is not human readable regardless of how you massage it :-). Instead I wonder if it would be possible to add an option that reads the json file and then outputs a really human readable version. I'm not sure what is easiest/best here. > + } > + > + /* Also trace the ioctl arguments as modified by the driver. */ > + json_object *ioctl_args_driver = trace_ioctl_args(fd, request, arg, false); > + if (json_object_object_length(ioctl_args_driver)) > + json_object_object_add(ioctl_obj, "ioctl_args_from_driver", ioctl_args_driver); > + > + write_json_object_to_json_file(ioctl_obj); > + json_object_put(ioctl_obj); > + > + if ((request == VIDIOC_G_FMT) && (_IOC_TYPE(request) == 'V')) > + g_fmt_setup_trace(static_cast<struct v4l2_format*>(arg)); > + > + if ((request == VIDIOC_S_FMT) && (_IOC_TYPE(request) == 'V')) > + s_fmt_setup(static_cast<struct v4l2_format*>(arg)); > + > + if ((request == VIDIOC_EXPBUF) && (_IOC_TYPE(request) == 'V')) > + expbuf_setup(static_cast<struct v4l2_exportbuffer*>(arg)); > + > + if ((request == VIDIOC_QUERYBUF) && (_IOC_TYPE(request) == 'V')) > + querybuf_setup(fd, static_cast<struct v4l2_buffer*>(arg)); > + > + return ret; > +} > diff --git a/utils/v4l2-tracer/retrace-helper.cpp b/utils/v4l2-tracer/retrace-helper.cpp > new file mode 100644 > index 00000000..37fad2c3 > --- /dev/null > +++ b/utils/v4l2-tracer/retrace-helper.cpp > @@ -0,0 +1,196 @@ > +/* SPDX-License-Identifier: GPL-2.0-only */ > +/* > + * Copyright 2022 Collabora Ltd. > + */ > + > +#include "v4l2-tracer-common.h" > + > +struct buffer_retrace { > + int fd; > + __u32 type; /* enum v4l2_buf_type */ > + __u32 index; > + __u32 offset; > + long address_trace; > + long address_retrace; > +}; > + > +struct retrace_context { > + __u32 width; > + __u32 height; > + __u32 pixelformat; > + pthread_mutex_t lock; > + /* Key is a file descriptor from the trace, value is the corresponding fd in the retrace. */ > + std::unordered_map<int, int> retrace_fds; > + /* List of output and capture buffers being retraced. */ > + std::list<struct buffer_retrace> buffers; > +}; > + > +static struct retrace_context ctx_retrace = { > + .lock = PTHREAD_MUTEX_INITIALIZER > +}; > + > +bool buffer_in_retrace_context(int fd, __u32 offset) > +{ > + bool buffer_in_retrace_context = false; > + pthread_mutex_lock(&ctx_retrace.lock); > + for (auto &b : ctx_retrace.buffers) { > + if ((b.fd == fd) && (b.offset == offset)) { > + buffer_in_retrace_context = true; > + break; > + } > + } > + pthread_mutex_unlock(&ctx_retrace.lock); > + return buffer_in_retrace_context; > +} > + > +int get_buffer_fd_retrace(__u32 type, __u32 index) > +{ > + int fd = 0; Hmm, strictly speaking 0 is a valid file descriptor number: typically it maps to stdin. Usually -1 is used to denote an invalid fd. > + pthread_mutex_lock(&ctx_retrace.lock); > + for (auto &b : ctx_retrace.buffers) { > + if ((b.type == type) && (b.index == index)) { > + fd = b.fd; > + break; > + } > + } > + pthread_mutex_unlock(&ctx_retrace.lock); > + return fd; > +} > + > +void add_buffer_retrace(int fd, __u32 type, __u32 index, __u32 offset) > +{ > + pthread_mutex_lock(&ctx_retrace.lock); > + struct buffer_retrace buf = {}; > + buf.fd = fd; > + buf.type = type; > + buf.index = index; > + buf.offset = offset; > + ctx_retrace.buffers.push_front(buf); > + pthread_mutex_unlock(&ctx_retrace.lock); > +} > + > +void remove_buffer_retrace(int fd) > +{ > + pthread_mutex_lock(&ctx_retrace.lock); > + for (auto it = ctx_retrace.buffers.begin(); it != ctx_retrace.buffers.end(); ++it) { > + if (it->fd == fd) { > + ctx_retrace.buffers.erase(it); > + break; > + } > + } > + pthread_mutex_unlock(&ctx_retrace.lock); > +} > + > +void set_buffer_address_retrace(int fd, __u32 offset, long address_trace, long address_retrace) > +{ > + pthread_mutex_lock(&ctx_retrace.lock); > + for (auto &b : ctx_retrace.buffers) { > + if ((b.fd == fd) && (b.offset == offset)) { > + b.address_trace = address_trace; > + b.address_retrace = address_retrace; > + break; > + } > + } > + pthread_mutex_unlock(&ctx_retrace.lock); > +} > + > +long get_retrace_address_from_trace_address(long address_trace) > +{ > + long address_retrace = 0; > + pthread_mutex_lock(&ctx_retrace.lock); > + for (auto &b : ctx_retrace.buffers) { > + if (b.address_trace == address_trace) { > + address_retrace = b.address_retrace; > + break; > + } > + } > + pthread_mutex_unlock(&ctx_retrace.lock); > + return address_retrace; > +} > + > +void print_buffers_retrace(void) > +{ > + pthread_mutex_lock(&ctx_retrace.lock); > + for (auto &b : ctx_retrace.buffers) { > + fprintf(stderr, "fd: %d, offset: %d, address_trace:%ld, address_retrace:%ld\n", > + b.fd, b.offset, b.address_trace, b.address_retrace); > + } > + pthread_mutex_unlock(&ctx_retrace.lock); > +} > + > +/* > + * Create a new file descriptor entry in retrace context. > + * Add both the fd from the trace context and the corresponding fd from the retrace context. > + */ > +void add_fd(int fd_trace, int fd_retrace) > +{ > + std::pair<int, int> new_pair; > + new_pair = std::make_pair(fd_trace, fd_retrace); > + pthread_mutex_lock(&ctx_retrace.lock); > + ctx_retrace.retrace_fds.insert(new_pair); > + pthread_mutex_unlock(&ctx_retrace.lock); > +} > + > +int get_fd_retrace_from_fd_trace(int fd_trace) > +{ > + int fd_retrace = 0; > + std::unordered_map<int, int>::const_iterator it; > + > + pthread_mutex_lock(&ctx_retrace.lock); > + it = ctx_retrace.retrace_fds.find(fd_trace); > + if (it != ctx_retrace.retrace_fds.end()) > + fd_retrace = it->second; > + pthread_mutex_unlock(&ctx_retrace.lock); > + > + return fd_retrace; > +} > + > +void remove_fd(int fd_trace) > +{ > + pthread_mutex_lock(&ctx_retrace.lock); > + ctx_retrace.retrace_fds.erase(fd_trace); > + pthread_mutex_unlock(&ctx_retrace.lock); > +} > + > +void print_fds(void) > +{ > + pthread_mutex_lock(&ctx_retrace.lock); > + if (ctx_retrace.retrace_fds.empty()) > + fprintf(stderr, "all devices closed\n"); > + for (auto it = ctx_retrace.retrace_fds.cbegin(); it != ctx_retrace.retrace_fds.cend(); ++it) > + fprintf(stderr, "fd_trace: %d, fd_retrace: %d\n", it->first, it->second); > + pthread_mutex_unlock(&ctx_retrace.lock); > +} > + > +void set_pixelformat_retrace(__u32 width, __u32 height, __u32 pixelformat) > +{ > + pthread_mutex_lock(&ctx_retrace.lock); > + ctx_retrace.width = width; > + ctx_retrace.height = height; > + ctx_retrace.pixelformat = pixelformat; > + pthread_mutex_unlock(&ctx_retrace.lock); > +} > + > +unsigned get_expected_length_retrace(void) > +{ > + pthread_mutex_lock(&ctx_retrace.lock); > + unsigned width = ctx_retrace.width; > + unsigned height = ctx_retrace.height; > + unsigned pixelformat = ctx_retrace.pixelformat; > + pthread_mutex_unlock(&ctx_retrace.lock); > + > + unsigned expected_length = width * height; > + if (pixelformat == V4L2_PIX_FMT_NV12 || pixelformat == V4L2_PIX_FMT_YUV420) { > + expected_length *= 3; > + expected_length /= 2; > + expected_length += (expected_length % 2); > + } > + return expected_length; > +} > + > +void print_context(void) > +{ > + print_fds(); > + print_buffers_retrace(); > + fprintf(stderr, "\n"); > +} <snip> > diff --git a/utils/v4l2-tracer/trace-helper.cpp b/utils/v4l2-tracer/trace-helper.cpp > new file mode 100644 > index 00000000..6b4bf567 > --- /dev/null > +++ b/utils/v4l2-tracer/trace-helper.cpp > @@ -0,0 +1,816 @@ > +/* SPDX-License-Identifier: GPL-2.0-only */ > +/* > + * Copyright 2022 Collabora Ltd. > + */ > + > +#include <math.h> /* for pow */ > +#include "v4l2-tracer-common.h" > +#include "trace-helper.h" > +#include "trace-info-gen.h" > + > +void trace_mem(int fd, __u32 offset, __u32 type, int index, __u32 bytesused, unsigned long start); > +void trace_mem_encoded(int fd, __u32 offset); > + > +struct buffer_trace { > + int fd; > + __u32 type; /* enum v4l2_buf_type */ > + __u32 index; > + __u32 offset; > + __u32 bytesused; > + long display_order; > + unsigned long address; > +}; > + > +struct trace_context { > + __u32 width; > + __u32 height; > + FILE *trace_file; > + __u32 pixelformat; > + int pic_order_cnt_lsb; > + int max_pic_order_cnt_lsb; > + pthread_mutex_t lock; > + __u32 compression_format; > + std::string trace_filename; > + int compressed_frame_count; > + std::list<long> decode_order; > + std::list<struct buffer_trace> buffers; > + std::unordered_map<int, std::string> devices; /* key:fd, value: path of the device */ > +}; > + > +struct trace_options { > + bool options_are_set; > + bool verbose; > + bool pretty_print_all; > + bool pretty_print_mem; > + bool write_decoded_data_to_yuv_file; > + bool write_decoded_data_to_json_file; > +}; > + > +std::string ver2s(unsigned int version) > +{ > + char buf[16]; > + sprintf(buf, "%d.%d.%d", version >> 16, (version >> 8) & 0xff, version & 0xff); > + return buf; > +} > + > +std::string which2s(unsigned long which) > +{ > + std::string s = "unknown"; > + > + switch (which) { > + case V4L2_CTRL_WHICH_CUR_VAL: > + s = "V4L2_CTRL_WHICH_CUR_VAL"; > + break; > + case V4L2_CTRL_WHICH_DEF_VAL: > + s= "V4L2_CTRL_WHICH_DEF_VAL"; > + break; > + case V4L2_CTRL_WHICH_REQUEST_VAL: > + s = "V4L2_CTRL_WHICH_REQUEST_VAL"; > + break; > + default: > + break; > + } > + > + return s; > +} > + > +std::string val2s(unsigned long val, const definition *def) > +{ > + std::string s; > + > + if (val == 0) > + return s; > + > + while ((def->val) && (def->val != val)) > + def++; > + > + if (def->val == val) > + s = def->str; > + > + return s; > +} > + > +struct trace_options options; > + > +struct trace_context ctx_trace = { > + .lock = PTHREAD_MUTEX_INITIALIZER > +}; > + > +bool options_are_set(void) > +{ > + return options.options_are_set; > +} > + > +void set_options(void) > +{ > + options.verbose = getenv("TRACE_OPTION_VERBOSE") ? true : false; > + options.pretty_print_mem = getenv("TRACE_OPTION_PRETTY_PRINT_MEM") ? true : false; > + options.pretty_print_all = getenv("TRACE_OPTION_PRETTY_PRINT_ALL") ? true : false; > + options.write_decoded_data_to_json_file = getenv("TRACE_OPTION_WRITE_DECODED_TO_JSON_FILE") ? true : false; > + options.write_decoded_data_to_yuv_file = getenv("TRACE_OPTION_WRITE_DECODED_TO_YUV_FILE") ? true : false; > + options.options_are_set = true; > +} > + > +bool option_is_set_verbose(void) > +{ > + return options.verbose; > +} > + > +bool option_is_set_pretty_print_mem(void) > +{ > + return options.pretty_print_mem; > +} > + > +bool option_is_set_write_decoded_data_to_json_file(void) > +{ > + return options.write_decoded_data_to_json_file; > +} > + > +bool is_video_or_media_device(const char *path) > +{ > + std::string dev_path_video = "/dev/video"; > + std::string dev_path_media = "/dev/media"; > + if (strncmp(path, dev_path_video.c_str(), dev_path_video.length()) && > + strncmp(path, dev_path_media.c_str(), dev_path_media.length())) > + return false; > + return true; > +} > + > +void add_device(int fd, std::string path) > +{ > + std::pair<int, std::string> new_pair = std::make_pair(fd, path); > + pthread_mutex_lock(&ctx_trace.lock); > + ctx_trace.devices.insert(new_pair); > + pthread_mutex_unlock(&ctx_trace.lock); > +} > + > +std::string get_device(int fd) > +{ > + std::string path; > + std::unordered_map<int, std::string>::const_iterator it; > + pthread_mutex_lock(&ctx_trace.lock); > + it = ctx_trace.devices.find(fd); > + if (it != ctx_trace.devices.end()) > + path = it->second; > + pthread_mutex_unlock(&ctx_trace.lock); > + return path; > +} > + > +int remove_device(int fd) > +{ > + int ret = 0; > + pthread_mutex_lock(&ctx_trace.lock); > + ret = ctx_trace.devices.erase(fd); > + pthread_mutex_unlock(&ctx_trace.lock); > + return ret; > +} > + > +int count_devices(void) > +{ > + int count = 0; > + pthread_mutex_lock(&ctx_trace.lock); > + count = ctx_trace.devices.size(); > + pthread_mutex_unlock(&ctx_trace.lock); > + return count; > +} > + > +void set_pixelformat_trace(__u32 width, __u32 height, __u32 pixelformat) > +{ > + pthread_mutex_lock(&ctx_trace.lock); > + ctx_trace.width = width; > + ctx_trace.height = height; > + ctx_trace.pixelformat = pixelformat; > + pthread_mutex_unlock(&ctx_trace.lock); > +} > + > +__u32 get_pixelformat(void) > +{ > + __u32 pixelformat = 0; > + pthread_mutex_lock(&ctx_trace.lock); > + pixelformat = ctx_trace.pixelformat; > + pthread_mutex_unlock(&ctx_trace.lock); > + return pixelformat; > +} > + > +void set_compression_format(__u32 compression_format) > +{ > + pthread_mutex_lock(&ctx_trace.lock); > + ctx_trace.compression_format = compression_format; > + pthread_mutex_unlock(&ctx_trace.lock); > +} > + > +__u32 get_compression_format(void) > +{ > + __u32 compression_format = 0; > + pthread_mutex_lock(&ctx_trace.lock); > + compression_format = ctx_trace.compression_format; > + pthread_mutex_unlock(&ctx_trace.lock); > + return compression_format; > +} > + > +void set_compressed_frame_count(int count) > +{ > + pthread_mutex_lock(&ctx_trace.lock); > + ctx_trace.compressed_frame_count = count; > + pthread_mutex_unlock(&ctx_trace.lock); > +} > + > +int get_compressed_frame_count(void) > +{ > + int ret = 0; > + pthread_mutex_lock(&ctx_trace.lock); > + ret = ctx_trace.compressed_frame_count; > + pthread_mutex_unlock(&ctx_trace.lock); > + return ret; > +} > + > +void print_decode_order(void) > +{ > + pthread_mutex_lock(&ctx_trace.lock); > + fprintf(stderr, "Decode order: "); > + for (auto &num : ctx_trace.decode_order) > + fprintf(stderr, "%ld, ", num); > + fprintf(stderr, ".\n"); > + pthread_mutex_unlock(&ctx_trace.lock); > +} > + > +void set_decode_order(long decode_order) > +{ > + if (option_is_set_verbose()) > + fprintf(stderr, "%s: %ld\n", __func__, decode_order); > + > + std::list<long>::iterator it; > + pthread_mutex_lock(&ctx_trace.lock); > + it = find(ctx_trace.decode_order.begin(), ctx_trace.decode_order.end(), decode_order); > + if (it == ctx_trace.decode_order.end()) > + ctx_trace.decode_order.push_front(decode_order); > + pthread_mutex_unlock(&ctx_trace.lock); > + > + if (option_is_set_verbose()) > + print_decode_order(); > +} > + > +long get_decode_order(void) > +{ > + long decode_order = 0; > + pthread_mutex_lock(&ctx_trace.lock); > + if (!ctx_trace.decode_order.empty()) > + decode_order = ctx_trace.decode_order.front(); > + pthread_mutex_unlock(&ctx_trace.lock); > + return decode_order; > +} > + > +void set_max_pic_order_cnt_lsb(int max_pic_order_cnt_lsb) > +{ > + pthread_mutex_lock(&ctx_trace.lock); > + ctx_trace.max_pic_order_cnt_lsb = max_pic_order_cnt_lsb; > + pthread_mutex_unlock(&ctx_trace.lock); > +} > + > +int get_max_pic_order_cnt_lsb(void) > +{ > + int max_pic_order_cnt_lsb = 0; > + pthread_mutex_lock(&ctx_trace.lock); > + max_pic_order_cnt_lsb = ctx_trace.max_pic_order_cnt_lsb; > + pthread_mutex_unlock(&ctx_trace.lock); > + return max_pic_order_cnt_lsb; > +} > + > +void set_pic_order_cnt_lsb(int pic_order_cnt_lsb) > +{ > + pthread_mutex_lock(&ctx_trace.lock); > + ctx_trace.pic_order_cnt_lsb = pic_order_cnt_lsb; > + pthread_mutex_unlock(&ctx_trace.lock); > +} > + > +int get_pic_order_cnt_lsb(void) > +{ > + int pic_order_cnt_lsb = 0; > + pthread_mutex_lock(&ctx_trace.lock); > + pic_order_cnt_lsb = ctx_trace.pic_order_cnt_lsb; > + pthread_mutex_unlock(&ctx_trace.lock); > + return pic_order_cnt_lsb; > +} > + > +void add_buffer_trace(int fd, __u32 type, __u32 index, __u32 offset = 0) > +{ > + struct buffer_trace buf = {}; > + buf.fd = fd; > + buf.type = type; > + buf.index = index; > + buf.offset = offset; > + buf.display_order = -1; > + > + pthread_mutex_lock(&ctx_trace.lock); > + ctx_trace.buffers.push_front(buf); > + pthread_mutex_unlock(&ctx_trace.lock); > +} > + > +void remove_buffer_trace(int fd) > +{ > + pthread_mutex_lock(&ctx_trace.lock); > + for (auto it = ctx_trace.buffers.begin(); it != ctx_trace.buffers.end(); ++it) { > + if (it->fd == fd) { > + ctx_trace.buffers.erase(it); > + break; > + } > + } > + pthread_mutex_unlock(&ctx_trace.lock); > +} > + > +bool buffer_in_trace_context(int fd, __u32 offset) > +{ > + bool buffer_in_trace_context = false; > + pthread_mutex_lock(&ctx_trace.lock); > + for (auto &b : ctx_trace.buffers) { > + if ((b.fd == fd) && (b.offset == offset)) { > + buffer_in_trace_context = true; > + break; > + } > + } > + pthread_mutex_unlock(&ctx_trace.lock); > + return buffer_in_trace_context; > +} > + > +int get_buffer_fd_trace(__u32 type, __u32 index) > +{ > + int fd = 0; > + pthread_mutex_lock(&ctx_trace.lock); > + for (auto &b : ctx_trace.buffers) { > + if ((b.type == type) && (b.index == index)) { > + fd = b.fd; > + break; > + } > + } > + pthread_mutex_unlock(&ctx_trace.lock); > + return fd; > +} > + > +__u32 get_buffer_type_trace(int fd, __u32 offset) > +{ > + __u32 type = 0; > + pthread_mutex_lock(&ctx_trace.lock); > + for (auto &b : ctx_trace.buffers) { > + if ((b.fd == fd) && (b.offset == offset)) { > + type = b.type; > + break; > + } > + } > + pthread_mutex_unlock(&ctx_trace.lock); > + return type; > +} > + > +int get_buffer_index_trace(int fd, __u32 offset) > +{ > + int index = -1; > + pthread_mutex_lock(&ctx_trace.lock); > + for (auto &b : ctx_trace.buffers) { > + if ((b.fd == fd) && (b.offset == offset)) { > + index = b.index; > + break; > + } > + } > + pthread_mutex_unlock(&ctx_trace.lock); > + return index; > +} > + > +__u32 get_buffer_offset_trace(__u32 type, __u32 index) > +{ > + __u32 offset = 0; > + pthread_mutex_lock(&ctx_trace.lock); > + for (auto &b : ctx_trace.buffers) { > + if ((b.type == type) && (b.index == index)) { > + offset = b.offset; > + break; > + } > + } > + pthread_mutex_unlock(&ctx_trace.lock); > + return offset; > +} > + > +void set_buffer_bytesused_trace(int fd, __u32 offset, __u32 bytesused) > +{ > + pthread_mutex_lock(&ctx_trace.lock); > + for (auto &b : ctx_trace.buffers) { > + if ((b.fd == fd) && (b.offset == offset)) { > + b.bytesused = bytesused; > + break; > + } > + } > + pthread_mutex_unlock(&ctx_trace.lock); > +} > + > +long get_buffer_bytesused_trace(int fd, __u32 offset) > +{ > + long bytesused = 0; > + pthread_mutex_lock(&ctx_trace.lock); > + for (auto &b : ctx_trace.buffers) { > + if ((b.fd == fd) && (b.offset == offset)) { > + bytesused = b.bytesused; > + break; > + } > + } > + pthread_mutex_unlock(&ctx_trace.lock); > + return bytesused; > +} > + > +void set_buffer_display_order(int fd, __u32 offset, long display_order) > +{ > + if (option_is_set_verbose()) > + fprintf(stderr, "%s: %ld\n", __func__, display_order); > + pthread_mutex_lock(&ctx_trace.lock); > + for (auto &b : ctx_trace.buffers) { > + if ((b.fd == fd) && (b.offset == offset)) { > + b.display_order = display_order; > + break; > + } > + } > + pthread_mutex_unlock(&ctx_trace.lock); > +} > + > +void set_buffer_address_trace(int fd, __u32 offset, unsigned long address) > +{ > + pthread_mutex_lock(&ctx_trace.lock); > + for (auto &b : ctx_trace.buffers) { > + if ((b.fd == fd) && (b.offset == offset)) { > + b.address = address; > + break; > + } > + } > + pthread_mutex_unlock(&ctx_trace.lock); > +} > + > +unsigned long get_buffer_address_trace(int fd, __u32 offset) > +{ > + unsigned long address = 0; > + pthread_mutex_lock(&ctx_trace.lock); > + for (auto &b : ctx_trace.buffers) { > + if ((b.fd == fd) && (b.offset == offset)) { > + address = b.address; > + break; > + } > + } > + pthread_mutex_unlock(&ctx_trace.lock); > + return address; > +} > + > +bool buffer_is_mapped(unsigned long buffer_address) > +{ > + bool ret = false; > + pthread_mutex_lock(&ctx_trace.lock); > + for (auto &b : ctx_trace.buffers) { > + if (b.address == buffer_address) { > + ret = true; > + break; > + } > + } > + pthread_mutex_unlock(&ctx_trace.lock); > + return ret; > +} > + > +void print_buffers_trace(void) > +{ > + pthread_mutex_unlock(&ctx_trace.lock); > + for (auto &b : ctx_trace.buffers) { > + fprintf(stderr, "fd: %d, %s, index: %d, display_order: %ld, bytesused: %d, ", > + b.fd, buftype2s(b.type).c_str(), b.index, b.display_order, b.bytesused); > + fprintf(stderr, "address: %lu, offset: %u \n", b.address, b.offset); > + } > + pthread_mutex_unlock(&ctx_trace.lock); > +} > + > +std::string get_ioctl_request_str(unsigned long request) > +{ > + __u8 ioctl_type = _IOC_TYPE(request); > + switch (ioctl_type) { > + case 'V': { > + if (option_is_set_verbose()) > + fprintf(stderr, "%s\n", ioctl2s_video(request).c_str()); > + return ioctl2s_video(request); > + } > + case '|': > + return ioctl2s_media(request); > + case 'b': > + if (request == DMA_BUF_IOCTL_SYNC) > + return "DMA_BUF_IOCTL_SYNC"; > + break; > + default: > + break; > + } > + return "unknown ioctl"; > +} > + > +unsigned get_expected_length_trace() > +{ > + unsigned width; > + unsigned height; > + unsigned pixelformat; > + unsigned expected_length; > + > + pthread_mutex_lock(&ctx_trace.lock); > + width = ctx_trace.width; > + height = ctx_trace.height; > + pixelformat = ctx_trace.pixelformat; > + pthread_mutex_unlock(&ctx_trace.lock); > + > + /* > + * TODO: this assumes that the stride is equal to the real width and that the > + * padding follows the end of the chroma plane. It could be improved by > + * following the model in v4l2-ctl-streaming.cpp read_write_padded_frame() > + */ > + expected_length = width * height; > + if (pixelformat == V4L2_PIX_FMT_NV12 || pixelformat == V4L2_PIX_FMT_YUV420) { > + expected_length *= 3; > + expected_length /= 2; > + expected_length += (expected_length % 2); > + } > + return expected_length; > +} > + > +void trace_mem_decoded(void) > +{ > + int displayed_count = 0; > + unsigned expected_length = get_expected_length_trace(); > + > + pthread_mutex_lock(&ctx_trace.lock); > + > + while (!ctx_trace.decode_order.empty()) { > + > + std::list<buffer_trace>::iterator it; > + long next_frame_to_be_displayed = *std::min_element(ctx_trace.decode_order.begin(), > + ctx_trace.decode_order.end()); > + > + for (it = ctx_trace.buffers.begin(); it != ctx_trace.buffers.end(); ++it) { > + if (it->display_order != next_frame_to_be_displayed) > + continue; > + if (!it->address) > + break; > + if (it->bytesused < expected_length) > + break; > + if (option_is_set_verbose()) > + fprintf(stderr, "%s, displaying: %ld, %s, index: %d\n", > + __func__, it->display_order, buftype2s(it->type).c_str(), it->index); > + displayed_count++; > + > + if (options.write_decoded_data_to_yuv_file) { > + std::string filename = getenv("TRACE_ID"); > + filename += ".yuv"; > + FILE *fp = fopen(filename.c_str(), "a"); > + unsigned char *buffer_pointer = (unsigned char*) it->address; > + for (__u32 i = 0; i < expected_length; i++) > + fwrite(&buffer_pointer[i], sizeof(unsigned char), 1, fp); > + fclose(fp); > + } > + int fd = it->fd; > + __u32 offset = it->offset; > + __u32 type = it->type; > + int index = it->index; > + __u32 bytesused = it->bytesused; > + unsigned long start = it->address; > + > + pthread_mutex_unlock(&ctx_trace.lock); > + trace_mem(fd, offset, type, index, bytesused, start); > + pthread_mutex_lock(&ctx_trace.lock); > + > + ctx_trace.decode_order.remove(next_frame_to_be_displayed); > + it->display_order = -1; > + break; > + } > + if (!it->address || it == ctx_trace.buffers.end() || it->bytesused < expected_length) > + break; > + } > + pthread_mutex_unlock(&ctx_trace.lock); > + set_compressed_frame_count(get_compressed_frame_count() - displayed_count); > +} > + > +void s_ext_ctrls_setup(struct v4l2_ext_controls *ext_controls) > +{ > + if (ext_controls->which != V4L2_CTRL_WHICH_REQUEST_VAL) > + return; > + > + if (option_is_set_verbose()) > + fprintf(stderr, "\n%s\n", __func__); > + > + for (__u32 i = 0; i < ext_controls->count; i++) { > + struct v4l2_ext_control ctrl = ext_controls->controls[i]; > + > + switch (ctrl.id) { This could use some comments: why do you need to take special actions for these two controls? > + case V4L2_CID_STATELESS_H264_SPS: { > + set_max_pic_order_cnt_lsb(pow(2, ctrl.p_h264_sps->log2_max_pic_order_cnt_lsb_minus4 + 4)); > + break; > + } > + case V4L2_CID_STATELESS_H264_DECODE_PARAMS: { > + long pic_order_cnt_msb; > + int max = get_max_pic_order_cnt_lsb(); > + long prev_pic_order_cnt_msb = get_decode_order(); > + int prev_pic_order_cnt_lsb = get_pic_order_cnt_lsb(); > + int pic_order_cnt_lsb = ctrl.p_h264_decode_params->pic_order_cnt_lsb; > + > + if (option_is_set_verbose()) { > + fprintf(stderr, "\tprev_pic_order_cnt_lsb: %d\n", prev_pic_order_cnt_lsb); > + fprintf(stderr, "\tprev_pic_order_cnt_msb: %ld\n", prev_pic_order_cnt_msb); > + fprintf(stderr, "\tpic_order_cnt_lsb: %d\n", pic_order_cnt_lsb); > + } > + > + /* The first frame. */ > + if (prev_pic_order_cnt_msb == 0) > + pic_order_cnt_msb = 0; > + > + /* > + * TODO: improve the displaying of decoded frames following H264 specification > + * 8.2.1.1. For now, dump all the previously decoded frames when an IDR_PIC is > + * received to avoid losing frames although this will still sometimes result > + * in frames out of order. > + */ > + if (ctrl.p_h264_decode_params->flags & V4L2_H264_DECODE_PARAM_FLAG_IDR_PIC) { > + if (get_compressed_frame_count()) > + trace_mem_decoded(); > + } > + > + /* > + * When pic_order_cnt_lsb wraps around to zero, adjust the total count using > + * max to keep the correct display order. > + */ > + if ((pic_order_cnt_lsb < prev_pic_order_cnt_lsb) && > + ((prev_pic_order_cnt_lsb - pic_order_cnt_lsb) >= (max / 2))) { > + pic_order_cnt_msb = prev_pic_order_cnt_msb + max; > + } else if ((pic_order_cnt_lsb > prev_pic_order_cnt_lsb) && > + ((pic_order_cnt_lsb - prev_pic_order_cnt_lsb) > (max / 2))) { > + pic_order_cnt_msb = prev_pic_order_cnt_msb - max; > + } else { > + pic_order_cnt_msb = prev_pic_order_cnt_msb + (pic_order_cnt_lsb - prev_pic_order_cnt_lsb); > + } > + > + if (option_is_set_verbose()) > + fprintf(stderr, "\tpic_order_cnt_msb: %ld\n", pic_order_cnt_msb); > + > + set_pic_order_cnt_lsb(pic_order_cnt_lsb); > + set_decode_order(pic_order_cnt_msb); > + break; > + } > + default: > + break; > + } > + } > +} > + > +void qbuf_setup(struct v4l2_buffer *buf) > +{ > + if (option_is_set_verbose()) > + fprintf(stderr, "\n%s: %s, index: %d\n", __func__, buftype2s(buf->type).c_str(), buf->index); > + > + int buf_fd = get_buffer_fd_trace(buf->type, buf->index); > + __u32 buf_offset = get_buffer_offset_trace(buf->type, buf->index); > + > + __u32 bytesused = 0; > + if (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE || > + buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) > + bytesused = buf->m.planes[0].bytesused; > + if (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT || buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) > + bytesused = buf->bytesused; > + set_buffer_bytesused_trace(buf_fd, buf_offset, bytesused); > + > + /* The output buffer should have compressed data just before it is queued, so trace it. */ > + if (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE || > + buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { > + trace_mem_encoded(buf_fd, buf_offset); > + set_compressed_frame_count(get_compressed_frame_count() + 1); > + } > + > + if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE || > + buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { > + > + /* If the capture buffer is queued for reuse, trace it before it is reused. */ > + if (get_compressed_frame_count()) > + trace_mem_decoded(); > + > + /* H264 sets display order in controls, otherwise display just in the order queued. */ > + if (get_compression_format() != V4L2_PIX_FMT_H264_SLICE) > + set_decode_order(get_decode_order() + 1); > + > + set_buffer_display_order(buf_fd, buf_offset, get_decode_order()); > + > + if (option_is_set_verbose()) { > + print_decode_order(); > + print_buffers_trace(); > + } > + } > +} > + > +void streamoff_cleanup(v4l2_buf_type buf_type) > +{ > + if (option_is_set_verbose()) { > + fprintf(stderr, "\nVIDIOC_STREAMOFF: %s\n", buftype2s(buf_type).c_str()); > + fprintf(stderr, "compression: %s, pixelformat: %s %s, width: %d, height: %d\n", > + pixfmt2s(get_compression_format()).c_str(), pixfmt2s(get_pixelformat()).c_str(), > + fcc2s(get_pixelformat()).c_str(), ctx_trace.width, ctx_trace.height); > + } > + > + /* > + * Before turning off the stream, trace any remaining capture buffers that were missed > + * because they were not queued for reuse. > + */ > + if (buf_type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE || > + buf_type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { > + if (get_compressed_frame_count()) > + trace_mem_decoded(); > + } > +} > + > +void g_fmt_setup_trace(struct v4l2_format *format) > +{ > + if (format->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) > + set_pixelformat_trace(format->fmt.pix.width, > + format->fmt.pix.height, format->fmt.pix.pixelformat); > + if (format->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) > + set_pixelformat_trace(format->fmt.pix_mp.width, format->fmt.pix_mp.height, > + format->fmt.pix_mp.pixelformat); > + > + if (format->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) > + set_compression_format(format->fmt.pix.pixelformat); > + if (format->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) > + set_compression_format(format->fmt.pix_mp.pixelformat); > +} > + > +void s_fmt_setup(struct v4l2_format *format) > +{ > + if (format->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) > + set_compression_format(format->fmt.pix.pixelformat); > + if (format->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) > + set_compression_format(format->fmt.pix_mp.pixelformat); > +} > + > +void expbuf_setup(struct v4l2_exportbuffer *export_buffer) > +{ > + __u32 type = export_buffer->type; > + __u32 index = export_buffer->index; > + int fd_found_in_trace_context = get_buffer_fd_trace(type, index); > + > + /* If the buffer was already added to the trace context don't add it again. */ > + if (fd_found_in_trace_context == export_buffer->fd) > + return; > + > + /* > + * If a buffer was previously added to the trace context using the video device > + * file descriptor, replace the video fd with the more specific buffer fd from EXPBUF. > + */ > + if (fd_found_in_trace_context) > + remove_buffer_trace(fd_found_in_trace_context); > + > + add_buffer_trace(export_buffer->fd, type, index); > +} > + > +void querybuf_setup(int fd, struct v4l2_buffer *buf) > +{ > + /* If the buffer was already added to the trace context don't add it again. */ > + if (get_buffer_fd_trace(buf->type, buf->index)) > + return; > + > + switch (buf->memory) { > + case V4L2_MEMORY_MMAP: { > + __u32 offset = 0; > + if ((buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) || > + (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)) > + offset = buf->m.offset; > + if ((buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) || > + (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)) > + offset = buf->m.planes->m.mem_offset; > + add_buffer_trace(fd, buf->type, buf->index, offset); > + break; > + } > + case V4L2_MEMORY_USERPTR: > + case V4L2_MEMORY_DMABUF: > + default: > + break; > + } > +} > + > +void write_json_object_to_json_file(json_object *jobj, int flags) > +{ > + if (options.pretty_print_all) > + flags = JSON_C_TO_STRING_PRETTY; > + > + std::string json_str = json_object_to_json_string_ext(jobj, flags); > + pthread_mutex_lock(&ctx_trace.lock); > + > + if (!ctx_trace.trace_file) { > + ctx_trace.trace_filename = getenv("TRACE_ID"); > + ctx_trace.trace_filename += ".json"; > + ctx_trace.trace_file = fopen(ctx_trace.trace_filename.c_str(), "a"); > + } > + > + fwrite(json_str.c_str(), sizeof(char), json_str.length(), ctx_trace.trace_file); > + fputs(",\n", ctx_trace.trace_file); > + fflush(ctx_trace.trace_file); > + pthread_mutex_unlock(&ctx_trace.lock); > +} > + > +void close_json_file(void) > +{ > + pthread_mutex_lock(&ctx_trace.lock); > + if (ctx_trace.trace_file) { > + fclose(ctx_trace.trace_file); > + ctx_trace.trace_file = 0; > + } > + pthread_mutex_unlock(&ctx_trace.lock); > +} <snip> > diff --git a/utils/v4l2-tracer/v4l2-tracer-gen.pl b/utils/v4l2-tracer/v4l2-tracer-gen.pl > new file mode 100755 > index 00000000..ca17d7e2 > --- /dev/null > +++ b/utils/v4l2-tracer/v4l2-tracer-gen.pl > @@ -0,0 +1,359 @@ > +#!/usr/bin/perl > +# SPDX-License-Identifier: GPL-2.0-only */ > +# Copyright 2022 Collabora Ltd. I am officially impressed! This is very nice work! > + > +sub convert_type_to_json_type { > + my $type = shift; > + if ($type eq __u8 || $type eq __u16 || $type eq __s8 || $type eq __s16 || $type eq __s32) { > + return "int"; > + } > + if ($type eq __u32) { > + return "int64"; > + } > + if ($type eq __u64) { > + return "uint64"; > + } > + if ($type eq struct) { > + return; > + } > + print "v4l2_tracer: error: couldn't convert $type to json_object type.\n"; > + return; > +} > + > +sub get_index_letter { > + my $index = shift; > + if ($index eq 0) {return "i";} > + if ($index eq 1) {return "j";} > + if ($index eq 2) {return "k";} > + if ($index eq 3) {return "l";} > + if ($index eq 4) {return "m";} > + if ($index eq 5) {return "n";} > + if ($index eq 6) {return "o";} # "p" is saved for pointer > + if ($index eq 8) {return "q";} > + return "z"; > +} > + > +$flag_func_name; > + > +sub flag_gen { > + my $flag_type = shift; > + > + if ($flag_type =~ /fwht/) { > + $flag_func_name = v4l2_ctrl_fwht_params_; > + } elsif ($flag_type =~ /vp8_loop_filter/) { > + $flag_func_name = v4l2_vp8_loop_filter_; > + } else { > + ($flag_func_name) = ($_) =~ /#define (\w+_)FLAG_.+/; > + $flag_func_name = lc $flag_func_name; > + } > + printf $fh_trace_info_cpp "std::string %sflag2s(unsigned long flag)\n{\n", $flag_func_name; > + printf $fh_trace_info_h "std::string %sflag2s(unsigned long flag);\n", $flag_func_name; > + printf $fh_trace_info_cpp "\tstatic constexpr flag_def def[] = {\n"; > + ($flag) = ($_) =~ /#define\s+(\w+)\s+.+/; > + printf $fh_trace_info_cpp "\t\t{ $flag, \"$flag\" },\n"; > + while (<>) { > + next if ($_ =~ /^\/?\s?\*.*/); #skip comments between flags if any > + next if $_ =~ /^\s*$/; #skip blank lines between flags if any > + last if ((grep {!/^#define\s+\w+_FL/} $_) && (grep {!/^#define V4L2_VP8_LF/} $_)); > + ($flag) = ($_) =~ /#define\s+(\w+)\s+.+/; > + printf $fh_trace_info_cpp "\t\t{ $flag, \"$flag\" },\n"; > + } > + printf $fh_trace_info_cpp "\t\t{ 0, nullptr }\n\t};\n\treturn flags2s(flag, def);\n}\n\n"; > +} > + > +sub struct_gen { > + ($struct_name) = ($_) =~ /struct (\w+) {/; > + > + printf $fh_trace_h "void trace_%s_gen(void *ptr, json_object *parent_obj);\n", $struct_name; > + printf $fh_trace_cpp "void trace_%s_gen(void *ptr, json_object *parent_obj)\n{\n", $struct_name; > + printf $fh_trace_cpp "\tjson_object *%s_obj = json_object_new_object();\n", $struct_name; > + printf $fh_trace_cpp "\tstruct %s *p = static_cast<struct %s*>(ptr);\n", $struct_name, $struct_name; > + > + printf $fh_retrace_h "struct %s *retrace_%s_gen(json_object *ctrl_obj);\n", $struct_name, $struct_name; > + printf $fh_retrace_cpp "struct %s *retrace_%s_gen(json_object *ctrl_obj)\n{\n", $struct_name, $struct_name; > + printf $fh_retrace_cpp "\tstruct %s *p = (struct %s *) calloc(1, sizeof(%s));\n", $struct_name, $struct_name, $struct_name; > + printf $fh_retrace_cpp "\tjson_object *%s_obj;\n", $struct_name; > + # if a key value isn't found, assume it is retracing an element of an array > + printf $fh_retrace_cpp "\tif (!json_object_object_get_ex(ctrl_obj, \"%s\", &%s_obj))\n", $struct_name, $struct_name; > + printf $fh_retrace_cpp "\t\t%s_obj = ctrl_obj;\n", $struct_name; > + > + while ($line = <> ) { > + chomp($line); > + last if $line =~ /};/; > + next if $line =~ /^\s*$/; # ignore blank lines > + next if $line =~ /\/\*/; # ignore comment lines > + $line =~ s/^\s+//; # remove leading whitespace > + $line =~ s/;$//; # remove semi-colon at the end > + my @words = split /[\s\[]/, $line; # also split on '[' to avoid arrays > + @words = grep {/^\D/} @words; # remove values that start with digit from inside [] > + @words = grep {!/\]/} @words; # remove values with brackets e.g. V4L2_H264_REF_LIST_LEN] > + > + ($type) = $words[0]; > + $json_type = convert_type_to_json_type($type); > + > + ($member) = $words[scalar @words - 1]; > + next if $member =~ /reserved/; # ignore reserved members, they will segfault on retracing > + > + # generate members that are arrays > + if ($line =~ /.*\[.*/) { > + printf $fh_trace_cpp "\t\/\* %s \*\/\n", $line; #trace comment > + printf $fh_trace_cpp "\tjson_object *%s_obj = json_object_new_array();\n", $member; > + > + printf $fh_retrace_cpp "\n\t\/\* %s \*\/\n", $line; #retrace comment > + > + my @dimensions = ($line) =~ /\[(\w+)\]/g; > + $dimensions_count = scalar @dimensions; > + > + if ($dimensions_count > 1) { > + printf $fh_retrace_cpp "\tint count_%s = 0;\n", $member; > + } > + printf $fh_retrace_cpp "\tjson_object *%s_obj;\n", $member; > + printf $fh_retrace_cpp "\tjson_object_object_get_ex(%s_obj, \"%s\", &%s_obj);\n", $struct_name, $member, $member; > + > + for (my $idx = 0; $idx < $dimensions_count; $idx = $idx + 1 ) { > + $size = $dimensions[$idx]; > + $index_letter = get_index_letter($idx); > + printf $fh_trace_cpp "\t" x ($idx + 1); > + printf $fh_trace_cpp "for (size_t %s = 0; %s < %s\; %s++) {\n", $index_letter, $index_letter, $size, $index_letter; > + > + printf $fh_retrace_cpp "\t" x ($idx + 1); > + printf $fh_retrace_cpp "for (size_t %s = 0; %s < %s\; %s++) {\n", $index_letter, $index_letter, $size, $index_letter; > + } > + printf $fh_trace_cpp "\t" x ($dimensions_count + 1); > + printf $fh_retrace_cpp "\t" x ($dimensions_count + 1); > + > + # handle arrays of structs > + if ($type =~ /struct/) { > + my $struct_name_nested = @words[1]; > + printf $fh_trace_cpp "json_object *element_obj = json_object_new_object();\n"; > + printf $fh_trace_cpp "\t" x ($dimensions_count + 1); > + printf $fh_trace_cpp "trace_%s_gen(&(p->%s", $struct_name_nested, $member; > + for (my $idx = 0; $idx < $dimensions_count; $idx = $idx + 1 ) { > + printf $fh_trace_cpp "[%s]", get_index_letter($idx); > + } > + printf $fh_trace_cpp "), element_obj);\n"; > + printf $fh_trace_cpp "\t" x ($dimensions_count + 1); > + printf $fh_trace_cpp "json_object *element_no_key_obj;\n"; > + printf $fh_trace_cpp "\t" x ($dimensions_count + 1); > + printf $fh_trace_cpp "json_object_object_get_ex(element_obj, \"%s\", &element_no_key_obj);\n", $struct_name_nested; > + printf $fh_trace_cpp "\t" x ($dimensions_count + 1); > + printf $fh_trace_cpp "json_object_array_add(%s_obj, element_no_key_obj);\n", $member; > + > + printf $fh_retrace_cpp "p->%s", $member; > + for (my $idx = 0; $idx < $dimensions_count; $idx = $idx + 1) { > + printf $fh_retrace_cpp "[%s]", get_index_letter($idx); > + } > + printf $fh_retrace_cpp " = *retrace_%s_gen(json_object_array_get_idx(%s_obj, ", $struct_name_nested, $member; > + if ($dimensions_count > 1) { > + printf $fh_retrace_cpp "count_%s++", $member; > + } else { > + printf $fh_retrace_cpp "i"; > + } > + printf $fh_retrace_cpp "));\n"; > + } else { > + printf $fh_trace_cpp "json_object_array_add(%s_obj, json_object_new_%s(p->%s", $member, $json_type, $member; > + for (my $idx = 0; $idx < $dimensions_count; $idx = $idx + 1 ) { > + printf $fh_trace_cpp "[%s]", get_index_letter($idx); > + } > + printf $fh_trace_cpp "));\n"; > + > + printf $fh_retrace_cpp "p->%s", $member; > + for (my $idx = 0; $idx < $dimensions_count; $idx = $idx + 1 ) { > + printf $fh_retrace_cpp "[%s]", get_index_letter($idx); > + } > + printf $fh_retrace_cpp " = ($type) json_object_get_%s(json_object_array_get_idx(%s_obj, ", $json_type, $member; > + if ($dimensions_count > 1) { > + printf $fh_retrace_cpp "count_%s++", $member; > + } else { > + printf $fh_retrace_cpp "i"; > + } > + printf $fh_retrace_cpp "));\n"; > + } > + #closing brackets for all array types > + for (my $idx = $dimensions_count - 1; $idx >= 0 ; $idx = $idx - 1) { > + printf $fh_trace_cpp "\t" x ($idx + 1); > + printf $fh_trace_cpp "}\n"; > + > + printf $fh_retrace_cpp "\t" x ($idx + 1); > + printf $fh_retrace_cpp "}\n"; > + } > + printf $fh_trace_cpp "\tjson_object_object_add(%s_obj, \"%s\", %s_obj);\n\n", $struct_name, $member, $member; > + } elsif ($type =~ /struct/) { > + # member that is a struct but not an array of structs > + my $struct_name_nested = @words[1]; > + printf $fh_trace_cpp "\t\/\* %s \*\/\n", $line; > + printf $fh_trace_cpp "\ttrace_%s_gen(&(p->%s), %s_obj);\n", $struct_name_nested, $member, $struct_name; > + > + printf $fh_retrace_cpp "\t\/\* %s \*\/\n", $line; > + printf $fh_retrace_cpp "\tjson_object *%s_obj;\n", $member; > + printf $fh_retrace_cpp "\tjson_object_object_get_ex(%s_obj, \"%s\", &%s_obj);\n", $struct_name, $struct_name_nested, $member; > + printf $fh_retrace_cpp "\tp->%s = \*retrace_%s_gen(%s_obj);\n\n", $member, $struct_name_nested, $member; > + } else { > + # members that are integers > + printf $fh_trace_cpp "\tjson_object_object_add(%s_obj, \"%s\", json_object_new_%s(p->%s));\n", $struct_name, $member, $json_type, $member; > + printf $fh_retrace_cpp "\n\tjson_object *%s_obj;\n", $member; > + printf $fh_retrace_cpp "\tjson_object_object_get_ex(%s_obj, \"%s\", &%s_obj);\n", $struct_name, $member, $member; > + printf $fh_retrace_cpp "\tp->%s = ($type) json_object_get_%s(%s_obj);\n", $member, $json_type, $member; > + > + # add flag strings > + if ($member =~ /^flags/) { > + printf $fh_trace_cpp "\tjson_object_object_add(%s_obj, \"flags_str\", json_object_new_string(%sflag2s(p->flags).c_str()));\n", $struct_name, $flag_func_name; > + } > + } > + } > + printf $fh_trace_cpp "\tjson_object_object_add(parent_obj, \"%s\", %s_obj);\n", $struct_name, $struct_name; > + printf $fh_trace_cpp "}\n\n"; > + > + printf $fh_retrace_cpp "\n\treturn p;\n"; > + printf $fh_retrace_cpp "}\n\n"; > +} > + > +open($fh_trace_cpp, '>', 'trace-gen.cpp') or die "Could not open trace-gen.cpp for writing"; > +printf $fh_trace_cpp "/* SPDX-License-Identifier: GPL-2.0-only */\n/*\n * Copyright 2022 Collabora Ltd.\n */\n\n"; > +printf $fh_trace_cpp "#include \"v4l2-tracer-common.h\"\n#include \"trace-info-gen.h\"\n\n"; > + > +open($fh_trace_h, '>', 'trace-gen.h') or die "Could not open trace-gen.h for writing"; > +printf $fh_trace_h "/* SPDX-License-Identifier: GPL-2.0-only */\n/*\n * Copyright 2022 Collabora Ltd.\n */\n\n"; > +printf $fh_trace_h "\#ifndef TRACE_GEN_H\n"; > +printf $fh_trace_h "\#define TRACE_GEN_H\n\n"; > + > +open($fh_retrace_cpp, '>', 'retrace-gen.cpp') or die "Could not open retrace-gen.cpp for writing"; > +printf $fh_retrace_cpp "/* SPDX-License-Identifier: GPL-2.0-only */\n/*\n * Copyright 2022 Collabora Ltd.\n */\n\n"; > +printf $fh_retrace_cpp "#include \"v4l2-tracer-common.h\"\n\n"; > + > +open($fh_retrace_h, '>', 'retrace-gen.h') or die "Could not open retrace-gen.h for writing"; > +printf $fh_retrace_h "/* SPDX-License-Identifier: GPL-2.0-only */\n/*\n * Copyright 2022 Collabora Ltd.\n */\n\n"; > +printf $fh_retrace_h "\#ifndef RETRACE_GEN_H\n"; > +printf $fh_retrace_h "\#define RETRACE_GEN_H\n\n"; > + > +open($fh_trace_info_cpp, '>', 'trace-info-gen.cpp') or die "Could not open trace-info-gen.cpp for writing"; > +printf $fh_trace_info_cpp "/* SPDX-License-Identifier: GPL-2.0-only */\n/*\n * Copyright 2022 Collabora Ltd.\n */\n\n"; > +printf $fh_trace_info_cpp "#include \"v4l2-tracer-common.h\"\n#include \"trace-helper.h\"\n\n"; > + > +open($fh_trace_info_h, '>', 'trace-info-gen.h') or die "Could not open trace-info-gen.h for writing"; > +printf $fh_trace_info_h "/* SPDX-License-Identifier: GPL-2.0-only */\n/*\n * Copyright 2022 Collabora Ltd.\n */\n\n"; > +printf $fh_trace_info_h "\#ifndef TRACE_INFO_GEN_H\n"; > +printf $fh_trace_info_h "\#define TRACE_INFO_GEN_H\n\n"; > + > +$in_v4l2_controls = true; > + > +while (<>) { > + if (grep {/#define __LINUX_VIDEODEV2_H/} $_) {$in_v4l2_controls = false;} > + > + # only generate flag and struct functions for v4l2-controls.h > + if ($in_v4l2_controls eq true) { > + if (grep {/^#define.+_FLAG_.+/} $_) { > + flag_gen(); > + } elsif (grep {/^#define.+FWHT_FL_.+/} $_) { > + flag_gen("fwht"); > + } elsif (grep {/^#define V4L2_VP8_LF.*/} $_) { > + flag_gen("vp8_loop_filter"); > + } > + > + if (grep {/^struct/} $_) { > + struct_gen(); > + } > + } > + > + if (grep {/^\/\* Control classes \*\//} $_) { > + printf $fh_trace_info_cpp "std::string ctrlclass2s(__u32 id)\n{\n"; > + printf $fh_trace_info_h "std::string ctrlclass2s(__u32 id);\n"; > + printf $fh_trace_info_cpp "\tstatic constexpr definition defs[] = {\n"; > + > + while (<>) { > + last if $_ =~ /^\s*$/; #last if blank line > + ($ctrl_class) = ($_) =~ /#define\s*(\w+)\s+.*/; > + printf $fh_trace_info_cpp "\t\t{ %s,\t\"%s\" },\n", $ctrl_class, $ctrl_class; > + } > + printf $fh_trace_info_cpp "\t\t{ 0, nullptr }\n\t};\n\treturn val2s(id & 0xff0000, defs);\n}\n\n"; > + } > + > + if (grep {/^enum v4l2_memory/} $_) { > + printf $fh_trace_info_cpp "std::string v4l2_memory2s(__u32 val)\n{\n"; > + printf $fh_trace_info_h "std::string v4l2_memory2s(__u32 val);\n"; > + printf $fh_trace_info_cpp "\tstatic constexpr definition defs[] = {\n"; > + > + while (<>) { > + last if $_ =~ /};/; > + ($memory_type) = ($_) =~ /\s*(\w+)\s+.*/; > + printf $fh_trace_info_cpp "\t\t{ %s,\t\"%s\" },\n", $memory_type, $memory_type; > + } > + printf $fh_trace_info_cpp "\t\t{ 0, nullptr }\n\t};\n\treturn val2s(val, defs);\n}\n\n"; > + } > + > + if (grep {/\/\* Values for 'capabilities' field \*\//} $_) { > + printf $fh_trace_info_cpp "std::string capabilities2s(unsigned long flag)\n{\n"; > + printf $fh_trace_info_h "std::string capabilities2s(unsigned long flag);\n"; > + > + printf $fh_trace_info_cpp "\tstatic constexpr flag_def def[] = {\n"; > + while (<>) { > + last if $_ =~ /.*V I D E O I M A G E F O R M A T.*/; > + next if ($_ =~ /^\/?\s?\*.*/); #skip comments > + next if $_ =~ /^\s*$/; #skip blank lines > + ($cap) = ($_) =~ /#define\s+(\w+)\s+.+/; > + printf $fh_trace_info_cpp "\t\t{ $cap, \"$cap\" },\n" > + } > + printf $fh_trace_info_cpp "\t\t{ 0, nullptr }\n\t};\n\treturn flags2s(flag, def);\n}\n\n"; > + } > + > + if (grep {/^enum v4l2_ctrl_type/} $_) { > + printf $fh_trace_info_cpp "std::string ctrltype2s(__u32 val)\n{\n"; > + printf $fh_trace_info_h "std::string ctrltype2s(__u32 val);\n"; > + printf $fh_trace_info_cpp "\tstatic constexpr definition defs[] = {\n"; > + > + while (<>) { > + next if ($_ =~ /^\s?\/?\s?\*.*/); #skip comments > + next if $_ =~ /^\s*$/; #skip blank lines > + last if $_ =~ /};/; > + ($ctrl_type) = ($_) =~ /\s*(\w+)\s+.*/; > + > + printf $fh_trace_info_cpp "\t\t{ %s,\t\"%s\" },\n", $ctrl_type, $ctrl_type; > + } > + printf $fh_trace_info_cpp "\t\t{ 0, nullptr }\n\t};\n\treturn val2s(val, defs);\n}\n\n"; > + } > + > + if (grep {/.*I O C T L C O D E S F O R V I D E O D E V I C E S.*/} $_) { > + printf $fh_trace_info_cpp "std::string ioctl2s_video(unsigned long request)\n{\n"; > + printf $fh_trace_info_h "std::string ioctl2s_video(unsigned long request);\n"; > + printf $fh_trace_info_cpp "\tstatic constexpr definition defs[] = {\n"; > + > + while (<>) { > + next if ($_ =~ /^\/?\s?\*.*/); #skip comments > + next if $_ =~ /^\s*$/; #skip blank lines > + next if $_ =~ /^\s+/; #skip lines that start with a space > + last if $_ =~ /^#define BASE_VIDIOC_PRIVATE/; > + ($ioctl_name) = ($_) =~ /^#define\s*(\w+)\s*/; > + printf $fh_trace_info_cpp "\t\t{ %s,\t\"%s\" },\n", $ioctl_name, $ioctl_name; > + } > + printf $fh_trace_info_cpp "\t\t{ 0, nullptr }\n\t};\n\treturn val2s(request, defs);\n}\n\n"; > + } > + > + # from media.h > + if (grep {/\/\* ioctls \*\//} $_) { > + printf $fh_trace_info_cpp "std::string ioctl2s_media(unsigned long request)\n{\n"; > + printf $fh_trace_info_h "std::string ioctl2s_media(unsigned long request);\n"; > + printf $fh_trace_info_cpp "\tstatic constexpr definition defs[] = {\n"; > + > + while (<>) { > + next if ($_ =~ /^\/?\s?\*.*/); #skip comments > + next if $_ =~ /^\s*$/; #skip blank lines > + next if $_ =~ /^\s+/; #skip lines that start with a space, comments > + last if $_ =~ /^#define MEDIA_ENT_TYPE_SHIFT/; > + ($ioctl_name) = ($_) =~ /^#define\s*(\w+)\s*/; > + printf $fh_trace_info_cpp "\t\t{ %s,\t\"%s\" },\n", $ioctl_name, $ioctl_name; > + } > + printf $fh_trace_info_cpp "\t\t{ 0, nullptr }\n\t};\n\treturn val2s(request, defs);\n}\n\n"; > + } > +} > + > +printf $fh_trace_h "\n#endif\n"; > +close $fh_trace_h; > +close $fh_trace_cpp; > + > +printf $fh_retrace_h "\n#endif\n"; > +close $fh_retrace_h; > +close $fh_retrace_cpp; > + > +printf $fh_trace_info_h "\n#endif\n"; > +close $fh_trace_info_h; > +close $fh_trace_info_cpp; > diff --git a/utils/v4l2-tracer/v4l2-tracer.1.in b/utils/v4l2-tracer/v4l2-tracer.1.in > new file mode 100644 > index 00000000..cd3d089b > --- /dev/null > +++ b/utils/v4l2-tracer/v4l2-tracer.1.in > @@ -0,0 +1,104 @@ > +.TH "V4L2-TRACER" "1" "October 2022" "v4l-utils @PACKAGE_VERSION@" "User Commands" > +.SH NAME > +v4l2-tracer - An application to trace and replay stateless video decoding. > +.SH SYNOPSIS > +\fBv4l2-tracer trace\fR [trace options] -- <\fItracee\fR> > +.RS > +.RE > +\fBv4l2-tracer retrace\fR [retrace options] -- <\fItrace_file\fR>\fB.json\fR > +.RS > +.RE > + > +.SH DESCRIPTION > +The v4l2-tracer utility traces, records and replays userspace applications > +that implement the v4l2 memory-to-memory stateless video decoder interface. > +The trace function intercepts and copies all system calls, stateless codec > +controls and encoded data to a json-formatted trace file. The retrace function > +independently reads and replays the json trace file. The json trace file can > +be retraced independently from its original userspace environment and can be > +edited to inject errors to test a driver's error handling abilities. > + > +.SH OPTIONS > +.SS Trace Options > +.TP > +\fB\-e\fR, \fB\-\-prettymem\fR > +Add whitespace in json file to improve readability of memory arrays. > +.TP > +\fB\-h\fR, \fB\-\-help\fR > +Display trace help. > +.TP > +\fB\-p\fR, \fB\-\-pretty\fR > +Add whitespace in json file to improve readability. > +.TP > +\fB\-r\fR, \fB\-\-raw\fR > +Write decoded data to json file. This is a confusing option description. I assume you mean: "Write the decoded video frame data to the json file." "data" is just too vague. > +.TP > +\fB\-v\fR, \fB\-\-verbose\fR > +Turn on verbose reporting. > +.TP > +\fB\-y\fR, \fB\-\-yuv\fR > +Write decoded data to yuv file. Ditto. Note that the same description is in the usage messages in the code. > + > +.SS Retrace Options > +.TP > +\fB\-d\fR, \fB\-\-device\fR <\fIdev\fR> > +Use a different video device than specified in the trace file. > +.RS > +<\fIdev\fR> must be a digit corresponding to an existing /dev/video<\fIdev\fR> > +.RE > +.TP > +\fB\-h\fR, \fB\-\-help\fR > +Display retrace help. > +.TP > +\fB\-m\fR, \fB\-\-media\fR <\fIdev\fR> > +Use a different media device than specified in the trace file. > +.RS > +<\fIdev\fR> must be a digit corresponding to an existing /dev/media<\fIdev\fR> > +.RE > +.TP > +\fB\-v\fR, \fB\-\-verbose\fR > +Turn on verbose reporting. > + > +.SH EXIT STATUS > +On success, it returns 0. Otherwise, it will return 1 or an error code. > + > +.SH EXAMPLES > +Trace VP8 decoding: > +.TP > +.EX > + v4l2-tracer trace -- gst-launch-1.0 -- filesrc location=test-25fps.vp8 ! parsebin ! v4l2slvp8dec ! videocodectestsink > +.EE > +.TP > +Trace H264 decoding: > +.TP > +.EX > + v4l2-tracer trace -- gst-launch-1.0 -- filesrc location=test-25fps.h264 ! parsebin ! v4l2slh264dec ! videocodectestsink > +.EE > +.TP > +Trace FWHT decoding: > +.TP > +.EX > + v4l2-tracer trace -- v4l2-ctl -d9 --stream-mmap --stream-out-mmap --stream-from-hdr test-25fps.fwht --stream-to out.yuv > +.EE > +.TP > +Retrace: > +.TP > +.EX > + v4l2-tracer retrace 79568_trace.json > +.EE > +.TP > +Retrace using /dev/video6 and /dev/media3: > +.TP > +.EX > + v4l2-tracer retrace -d 6 -m 3 79568_trace.json > +.EE > +.TP > +Self-trace: > +.TP > +.EX > + v4l2-tracer trace -- v4l2-tracer retrace 79568_trace.json > +.EE > + > +.SH BUGS > +Bug reports or questions about this utility should be sent to the > +linux-media@vger.kernel.org mailinglist. > diff --git a/utils/v4l2-tracer/v4l2-tracer.cpp b/utils/v4l2-tracer/v4l2-tracer.cpp > new file mode 100644 > index 00000000..71ac242a > --- /dev/null > +++ b/utils/v4l2-tracer/v4l2-tracer.cpp > @@ -0,0 +1,187 @@ > +/* SPDX-License-Identifier: GPL-2.0-only */ > +/* > + * Copyright 2022 Collabora Ltd. > + */ > + > +#include <time.h> > +#include <dirent.h> > +#include <sys/wait.h> > +#include "v4l2-tracer-common.h" > + > +#define STR(x) #x > +#define STRING(x) STR(x) > + > +void print_help_retracer(void); > +int retracer(int argc, char *argv[]); > + > +enum Option_Tracer { > + TracerOptPrettyPrintMemoryOnly = 'e', > + TracerOptHelp = 'h', > + TracerOptPrettyPrintAll = 'p', > + TracerOptWriteDecodedToJsonFile = 'r', > + TracerOptVerbose = 'v', > + TracerOptWriteDecodedToYUVFile = 'y', > +}; > + > +static struct option long_options_tracer[] = { > + { "prettymem", no_argument, nullptr, TracerOptPrettyPrintMemoryOnly }, > + { "help", no_argument, nullptr, TracerOptHelp }, > + { "pretty", no_argument, nullptr, TracerOptPrettyPrintAll }, > + { "raw", no_argument, nullptr, TracerOptWriteDecodedToJsonFile }, > + { "verbose", no_argument, nullptr, TracerOptVerbose }, > + { "yuv", no_argument, nullptr, TracerOptWriteDecodedToYUVFile }, > + { nullptr, 0, nullptr, 0 } > +}; > + > +char short_options_tracer[] = { > + TracerOptPrettyPrintMemoryOnly, > + TracerOptHelp, > + TracerOptPrettyPrintAll, > + TracerOptWriteDecodedToJsonFile, > + TracerOptVerbose, > + TracerOptWriteDecodedToYUVFile > +}; > + > +void print_help_tracer(void) > +{ > + fprintf(stderr, "v4l2-tracer trace [trace options] -- <tracee>\n" > + > + "\t-e, --prettymem Add whitespace in json file to improve readability of memory arrays.\n" > + "\t-h, --help Display trace help.\n" > + "\t-p, --pretty Add whitespace in json file to improve readability.\n" > + "\t-r --raw Write decoded data to json file.\n" > + "\t-v, --verbose Turn on verbose reporting.\n" > + "\t-y, --yuv Write decoded data to yuv file.\n\n"); > +} > + > +void print_usage(void) > +{ > + print_help_tracer(); > + print_help_retracer(); > +} > + > +void get_options_trace(int argc, char *argv[]) > +{ > + int ch; > + > + do { > + ch = getopt_long(argc, argv, short_options_tracer, long_options_tracer, NULL); > + switch (ch){ > + case TracerOptPrettyPrintMemoryOnly: > + setenv("TRACE_OPTION_PRETTY_PRINT_MEM", "true", 0); > + break; > + case TracerOptHelp: > + break; > + case TracerOptPrettyPrintAll: > + setenv("TRACE_OPTION_PRETTY_PRINT_ALL", "true", 0); > + break; > + case TracerOptWriteDecodedToJsonFile: > + setenv("TRACE_OPTION_WRITE_DECODED_TO_JSON_FILE", "true", 0); > + break; > + case TracerOptVerbose: > + setenv("TRACE_OPTION_VERBOSE", "true", 0); > + break; > + case TracerOptWriteDecodedToYUVFile: > + setenv("TRACE_OPTION_WRITE_DECODED_TO_YUV_FILE", "true", 0); > + break; > + } > + } while (ch != -1); > +} > + > +int tracer(int argc, char *argv[]) > +{ > + get_options_trace(argc, argv); > + > + if (optind == argc) { > + print_help_tracer(); > + return 1; > + } > + > + /* Get the application to be traced from the command line. */ > + int count = 0; > + char *exec_array[argc]; > + while (optind < argc) > + exec_array[count++] = argv[optind++]; > + exec_array[count] = nullptr; > + > + /* Use a substring of the time to create a unique id for the trace. */ > + std::string trace_id = std::to_string(time(nullptr)); > + trace_id = trace_id.substr(5, trace_id.npos) + "_trace"; > + setenv("TRACE_ID", trace_id.c_str(), 0); > + > + /* Create the trace file to hold the json-objects as a json array. */ > + std::string trace_filename = trace_id + ".json"; > + FILE *trace_file = fopen(trace_filename.c_str(), "w"); > + fputs("[\n", trace_file); > + fclose(trace_file); > + > + /* > + * Preload the libv4l2tracer library. If the program is installed, load the library > + * from its installed location, otherwise load it locally. If it's loaded locally, > + * use ./configure --disable-dyn-libv4l. > + */ > + std::string libv4l2tracer_path; > + std::string program = argv[0]; > + std::size_t idx = program.rfind("/v4l2-tracer"); > + if (idx != program.npos) { > + libv4l2tracer_path = program.replace(program.begin() + idx + 1, program.end(), ".libs"); > + DIR *dp = opendir(libv4l2tracer_path.c_str()); > + if (dp == nullptr) > + libv4l2tracer_path = program.replace(program.begin() + idx, program.end(), "./.libs"); > + closedir(dp); > + } else { > + libv4l2tracer_path = STRING(LIBTRACER_PATH); > + } > + libv4l2tracer_path += "/libv4l2tracer.so"; > + fprintf(stderr, "Loading libv4l2tracer: %s\n", libv4l2tracer_path.c_str()); > + setenv("LD_PRELOAD", libv4l2tracer_path.c_str(), 0); > + > + if (fork() == 0) { > + execvpe(exec_array[0], exec_array, environ); > + perror("Option -h for help. Could not execute application to trace."); > + return errno; > + } > + > + int tracee_result; > + wait(&tracee_result); > + > + if (WEXITSTATUS(tracee_result)) { > + fprintf(stderr, "Trace error: %s\n", trace_filename.c_str()); > + exit(EXIT_FAILURE); > + } > + > + /* Close the json-array and the trace file. */ > + trace_file = fopen(trace_filename.c_str(), "a"); > + fseek(trace_file, 0L, SEEK_END); > + fputs("\n]\n", trace_file); > + fclose(trace_file); > + > + fprintf(stderr, "Trace complete: "); > + if (getenv("TRACE_OPTION_WRITE_DECODED_TO_YUV_FILE")) > + fprintf(stderr, "%s, ", (trace_id + ".yuv").c_str()); > + fprintf(stderr, "%s", trace_filename.c_str()); > + fprintf(stderr, "\n"); > + > + return 0; > +} > + > +int main(int argc, char *argv[]) > +{ > + int ret = 1; > + > + if (argc <= 1) { > + print_usage(); > + return ret; > + } > + > + std::string command = argv[1]; > + optind = 2; /* start looking for options only after the trace/retrace command */ > + if (command == "trace") > + ret = tracer(argc, argv); > + else if (command == "retrace") > + ret = retracer(argc, argv); > + else > + print_usage(); > + > + return ret; > +} I have one more comment: I noticed that "mem_array" is output as: "mem_array":[ "50a3 009d 012a 4001 f000 0007 0885 8588", This is confusing since it is not clear if this is little or big endian. Write it as: "50 a3 00 9d 01 2a 40 01 f0 00 00 07 08 85 85 88", You can make it depend on the --prettymem option. If set, then dump it with a space after each byte. If not set, then dump it in a compact format (no spaces at all): "50a3009d012a4001f000000708858588". I think that's a good balance between compact and readable. Regards, Hans
On Fri, Oct 07, 2022 at 03:09:13PM +0200, Hans Verkuil wrote: > Hi Deb, Hi Hans - Thanks very much for your review! I've been working on the changes and have a few responses below. > > On 10/4/22 20:57, Deborah Brouwer wrote: > > The v4l2-tracer utility traces, records and replays userspace applications > > that implement the v4l2 memory-to-memory stateless video decoder > > interface. The trace function intercepts and copies system calls, > > stateless codec controls and buffers to a json-formatted trace file. > > The retrace function independently reads and replays the json trace file. > > The json trace file can be retraced independently from its original > > userspace environment and can be edited to inject errors to test a > > driver's error handling abilities. > > > > Signed-off-by: Deborah Brouwer <deborah.brouwer@collabora.com> > > --- > > Currently supports MPEG2, VP8, H264, FWHT > > > > Changes since v1: > > - auto-generate code from uAPI headers > > - generated code adds some ability to trace > > VP9/HEVC which compiles but still needs work > > - generated code adds full support for MPEG2 > > - fix multiplanar decoding for vicodec > > - change library name to libv4l2tracer > > - update README > > - expand help to show options > > - remove extraneous files from gitignore > > > > Examples: > > > > Trace VP8 decoding: > > > > v4l2-tracer trace -- gst-launch-1.0 -- filesrc > > location=test-25fps.vp8 ! parsebin ! v4l2slvp8dec ! > > videocodectestsink > > > > Trace H264 decoding: > > > > v4l2-tracer trace -y -- gst-launch-1.0 -- filesrc > > location=test-25fps.h264 ! parsebin ! v4l2slh264dec ! > > videocodectestsink > > > > Trace FWHT decoding: > > > > v4l2-tracer trace -- v4l2-ctl -d9 --stream-mmap > > --stream-out-mmap --stream-from-hdr test-25fps.fwht > > --stream-to out.yuv > > > > Note: if tracing another v4l2 utility like > > v4l2-ctl, either used the installed versions > > or configure with: > > --disable-dyn-libv4l --disable-v4l2-ctl-libv4l > > > > Retrace: > > v4l2-tracer retrace 79568_trace.json > > > > Trace a Retrace: > > v4l2-tracer trace -- v4l2-tracer retrace > > 79568_trace.json > > > > README | 8 + > > configure.ac | 12 + > > utils/Makefile.am | 5 + > > utils/v4l2-tracer/.gitignore | 8 + > > utils/v4l2-tracer/Makefile.am | 33 + > > utils/v4l2-tracer/libv4l2tracer.cpp | 222 ++++ > > utils/v4l2-tracer/retrace-helper.cpp | 196 ++++ > > utils/v4l2-tracer/retracer.cpp | 1319 ++++++++++++++++++++++++ > > utils/v4l2-tracer/trace-helper.cpp | 816 +++++++++++++++ > > utils/v4l2-tracer/trace-helper.h | 51 + > > utils/v4l2-tracer/trace.cpp | 669 ++++++++++++ > > utils/v4l2-tracer/v4l2-tracer-common.h | 40 + > > utils/v4l2-tracer/v4l2-tracer-gen.pl | 359 +++++++ > > utils/v4l2-tracer/v4l2-tracer.1.in | 104 ++ > > utils/v4l2-tracer/v4l2-tracer.cpp | 187 ++++ > > 15 files changed, 4029 insertions(+) > > create mode 100644 utils/v4l2-tracer/.gitignore > > create mode 100644 utils/v4l2-tracer/Makefile.am > > create mode 100644 utils/v4l2-tracer/libv4l2tracer.cpp > > create mode 100644 utils/v4l2-tracer/retrace-helper.cpp > > create mode 100644 utils/v4l2-tracer/retracer.cpp > > create mode 100644 utils/v4l2-tracer/trace-helper.cpp > > create mode 100644 utils/v4l2-tracer/trace-helper.h > > create mode 100644 utils/v4l2-tracer/trace.cpp > > create mode 100644 utils/v4l2-tracer/v4l2-tracer-common.h > > create mode 100755 utils/v4l2-tracer/v4l2-tracer-gen.pl > > create mode 100644 utils/v4l2-tracer/v4l2-tracer.1.in > > create mode 100644 utils/v4l2-tracer/v4l2-tracer.cpp > > > > diff --git a/README b/README > > index a7759171..c5b6df16 100644 > > --- a/README > > +++ b/README > > @@ -45,6 +45,8 @@ you'll need also: > > alsa-lib-devel doxygen libjpeg-turbo-devel qt5-qtbase-devel > > libudev-devel mesa-libGLU-devel > > > > +The v4l2-tracer also needs the json-c library. > > +On Debian: libjson-c-dev; on Fedora: json-c-devel. > > > > After downloading and installing the needed packages, you should run: > > > > @@ -227,6 +229,12 @@ v4l2-sysfs-path: > > FIXME add description. > > Installed by make install under <prefix>/bin. > > > > +v4l2-tracer: > > +Tool to trace, record and replay userspace applications > > +that implement the v4l2 memory-to-memory stateless video > > +decoder interface. > > +Installed by make install under <prefix>/bin. > > make install -> 'make install' > > > + > > xc3028-firmware: > > Xceive XC2028/3028 tuner module firmware manipulation tool. > > xc3028-firmware does not get installed by make install. > > <snip> > > > diff --git a/utils/v4l2-tracer/libv4l2tracer.cpp b/utils/v4l2-tracer/libv4l2tracer.cpp > > new file mode 100644 > > index 00000000..c344d8a1 > > --- /dev/null > > +++ b/utils/v4l2-tracer/libv4l2tracer.cpp > > @@ -0,0 +1,222 @@ > > +/* SPDX-License-Identifier: GPL-2.0-only */ > > +/* > > + * Copyright 2022 Collabora Ltd. > > + */ > > + > > +#include <dlfcn.h> > > +#include <stdarg.h> > > +#include <config.h> /* For PROMOTED_MODE_T */ > > +#include "v4l2-tracer-common.h" > > +#include "trace-helper.h" > > + > > +/* from trace.cpp */ > > +void trace_open(int fd, const char *path, int oflag, mode_t mode, bool is_open64); > > +void trace_mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off, > > + unsigned long buf_address, bool is_mmap64); > > +void trace_mem(int fd, __u32 offset, __u32 type, int index, __u32 bytesused, unsigned long start); > > +void trace_mem_encoded(int fd, __u32 offset); > > +json_object *trace_ioctl_args(int fd, unsigned long request, void *arg, > > + bool from_userspace = true); > > + > > +int open(const char *path, int oflag, ...) > > +{ > > + errno = 0; > > + mode_t mode = 0; > > + > > + if (oflag & O_CREAT) { > > + va_list ap; > > + va_start(ap, oflag); > > + mode = va_arg(ap, PROMOTED_MODE_T); > > + va_end(ap); > > + } > > + > > + int (*original_open)(const char *path, int oflag, ...); > > + original_open = (int (*)(const char*, int, ...)) dlsym(RTLD_NEXT, "open"); > > + int fd = (*original_open)(path, oflag, mode); > > + > > + if (is_video_or_media_device(path)) { > > + if (!options_are_set()) > > + set_options(); > > + add_device(fd, path); > > + trace_open(fd, path, oflag, mode, false); > > + } > > + > > + return fd; > > +} > > + > > +int open64(const char *path, int oflag, ...) > > +{ > > + errno = 0; > > + mode_t mode = 0; > > + if (oflag & O_CREAT) { > > + va_list ap; > > + va_start(ap, oflag); > > + mode = va_arg(ap, PROMOTED_MODE_T); > > + va_end(ap); > > + } > > + > > + int (*original_open64)(const char *path, int oflag, ...); > > + original_open64 = (int (*)(const char*, int, ...)) dlsym(RTLD_NEXT, "open64"); > > + int fd = (*original_open64)(path, oflag, mode); > > + > > + if (is_video_or_media_device(path)) { > > + if (!options_are_set()) > > + set_options(); > > + add_device(fd, path); > > + trace_open(fd, path, oflag, mode, true); > > + } > > + > > + return fd; > > +} > > + > > +int close(int fd) > > +{ > > + errno = 0; > > + std::string path = get_device(fd); > > + > > + /* Only trace the close if a corresponding open was also traced. */ > > + if (!path.empty()) { > > + json_object *close_obj = json_object_new_object(); > > + json_object_object_add(close_obj, "syscall_str", json_object_new_string("close")); > > + json_object_object_add(close_obj, "syscall", json_object_new_int(LIBTRACER_SYSCALL_CLOSE)); > > Do you need to provide both a syscall_str and syscall value? Wouldn't the string be sufficient? > > The replayer can just map the string to the syscall number. Yes, that makes sense. Since there are so few syscalls traced, I have been using an enum of the syscalls that the tracer uses, instead of their actual syscall numbers. So it would look like e.g.: {"syscall":"ioctl","fd":4,"cmd":"MEDIA_IOC_G_TOPOLOGY"}, and then then string would map back to the enum e.g.: constexpr val_def defs_libv4l2tracer_syscall[] = { { LIBV4L2TRACER_SYSCALL_IOCTL, "ioctl" }, > > > + json_object_object_add(close_obj, "fd", json_object_new_int(fd)); > > + json_object_object_add(close_obj, "path", json_object_new_string(path.c_str())); > > + write_json_object_to_json_file(close_obj); > > + json_object_put(close_obj); > > + remove_device(fd); > > + > > + /* If we removed the last device, close the json trace file. */ > > + if (!count_devices()) > > + close_json_file(); > > + } > > + > > + int (*original_close)(int fd); > > + original_close = (int (*)(int)) dlsym(RTLD_NEXT, "close"); > > + > > + return (*original_close)(fd); > > +} > > + > > +void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off) > > +{ > > + errno = 0; > > + void *(*original_mmap)(void *addr, size_t len, int prot, int flags, int fildes, off_t off); > > + original_mmap = (void*(*)(void*, size_t, int, int, int, off_t)) dlsym(RTLD_NEXT, "mmap"); > > + void *buf_address_pointer = (*original_mmap)(addr, len, prot, flags, fildes, off); > > + > > + set_buffer_address_trace(fildes, off, (unsigned long) buf_address_pointer); > > + > > + if (buffer_in_trace_context(fildes, off)) > > + trace_mmap(addr, len, prot, flags, fildes, off, (unsigned long) buf_address_pointer, false); > > + > > + return buf_address_pointer; > > +} > > + > > +void *mmap64(void *addr, size_t len, int prot, int flags, int fildes, off_t off) > > +{ > > + errno = 0; > > + void *(*original_mmap64)(void *addr, size_t len, int prot, int flags, int fildes, off_t off); > > + original_mmap64 = (void*(*)(void*, size_t, int, int, int, off_t)) dlsym(RTLD_NEXT, "mmap64"); > > + void *buf_address_pointer = (*original_mmap64)(addr, len, prot, flags, fildes, off); > > + > > + set_buffer_address_trace(fildes, off, (unsigned long) buf_address_pointer); > > + > > + if (buffer_in_trace_context(fildes, off)) > > + trace_mmap(addr, len, prot, flags, fildes, off, (unsigned long) buf_address_pointer, true); > > + > > + return buf_address_pointer; > > +} > > + > > +int munmap(void *start, size_t length) > > +{ > > + errno = 0; > > + int(*original_munmap)(void *start, size_t length); > > + original_munmap = (int(*)(void *, size_t)) dlsym(RTLD_NEXT, "munmap"); > > + int ret = (*original_munmap)(start, length); > > + > > + /* Only trace the unmapping if the original mapping was traced. */ > > + if (!buffer_is_mapped((unsigned long) start)) > > + return ret; > > + > > + json_object *munmap_obj = json_object_new_object(); > > + json_object_object_add(munmap_obj, "syscall_str", json_object_new_string("munmap")); > > + json_object_object_add(munmap_obj, "syscall", json_object_new_int(LIBTRACER_SYSCALL_MUNMAP)); > > + > > + if (errno) { > > + json_object_object_add(munmap_obj, "errno", json_object_new_int(errno)); > > + json_object_object_add(munmap_obj, "errno_str", json_object_new_string(strerror(errno))); > > + } > > + > > + json_object *munmap_args = json_object_new_object(); > > + json_object_object_add(munmap_args, "start", json_object_new_int64((int64_t)start)); > > + json_object_object_add(munmap_args, "length", json_object_new_uint64(length)); > > + json_object_object_add(munmap_obj, "munmap_args", munmap_args); > > + > > + write_json_object_to_json_file(munmap_obj); > > + json_object_put(munmap_obj); > > + > > + return ret; > > +} > > + > > +int ioctl(int fd, unsigned long request, ...) > > I think 'request' is a confusing name, esp. since codecs use the Request API. > I see it comes from the man page, but in this case I would rename it to 'cmd', > which is what it is called in e.g. v4l2-ioctl.c and fs/ioctl.c. Agreed - I had just gotten used to that but it is confusing. > > > +{ > > + errno = 0; > > + va_list ap; > > + va_start(ap, request); > > + void *arg = va_arg(ap, void *); > > + va_end(ap); > > + > > + int (*original_ioctl)(int fd, unsigned long request, ...); > > + original_ioctl = (int (*)(int, long unsigned int, ...)) dlsym(RTLD_NEXT, "ioctl"); > > + > > + if ((request == VIDIOC_S_EXT_CTRLS) && (_IOC_TYPE(request) == 'V')) > > Why check _IOC_TYPE? That's already encoded in VIDIOC_S_EXT_CTRLS, so testing for > that should be sufficient. Ditto for the same checks below. OK, will fix. > > > + s_ext_ctrls_setup(static_cast<struct v4l2_ext_controls*>(arg)); > > + > > + if ((request == VIDIOC_QBUF) && (_IOC_TYPE(request) == 'V')) > > + qbuf_setup(static_cast<struct v4l2_buffer*>(arg)); > > + > > + if ((request == VIDIOC_STREAMOFF) && (_IOC_TYPE(request) == 'V')) > > + streamoff_cleanup(*(static_cast<v4l2_buf_type*>(arg))); > > + > > + json_object *ioctl_obj = json_object_new_object(); > > + json_object_object_add(ioctl_obj, "syscall_str", json_object_new_string("ioctl")); > > + json_object_object_add(ioctl_obj, "syscall", json_object_new_int(LIBTRACER_SYSCALL_IOCTL)); > > + json_object_object_add(ioctl_obj, "fd", json_object_new_int(fd)); > > + json_object_object_add(ioctl_obj, "request", json_object_new_uint64(request)); > > + json_object_object_add(ioctl_obj, "request_str", > > + json_object_new_string(get_ioctl_request_str(request).c_str())); > > So this would be renamed to "cmd" and "cmd_str". But, do you really need both? > I think you could just store the string: > > "syscall_str":"ioctl","syscall":0,"fd":5,"cmd":"VIDIOC_QUERYCAP" > > When you replay it, it is easy enough to map the cmd string to the actual value. > > If for some reason a non-V4L2 cmd value is used, then you can write "0xHHHHHHHH" > as the value, and the replay would take that as the cmd hex value. One issue i've struggled with is whether to trace ioctls that aren't in videodev2.h or media.h. For example one that comes up is DMA_BUF_IOCTL_SYNC. Since the purpose of the v4l2-tracer isn't to comprehensively trace system calls, I'm leaning toward not tracing these ioctls at all. They aren't needed by the retracer, the retracer won't replay them because it won't have all their arguments, and they add clutter making it harder to spot important differences between the trace and retrace results. However, it's different for just flags or other values that aren't recognized, those can be added as a hex string and then replayed e.g.: "flags":"V4L2_VP8_FRAME_FLAG_SHOW_FRAME, 0x40"} From what I understand this could be useful to inject errors through replaying. > > > + > > + /* Trace the ioctl arguments provided by userspace. */ > > + json_object *ioctl_args_userspace = trace_ioctl_args(fd, request, arg); > > + if (json_object_object_length(ioctl_args_userspace)) > > + json_object_object_add(ioctl_obj, "ioctl_args_from_userspace", ioctl_args_userspace); > > + > > + /* Make the original ioctl call. */ > > + int ret = (*original_ioctl)(fd, request, arg); > > + > > + if (errno) { > > + json_object_object_add(ioctl_obj, "errno", json_object_new_int(errno));> + json_object_object_add(ioctl_obj, "errno_str", json_object_new_string(strerror(errno))); > > Why not just store the error define: e.g. "EINVAL". And if the errno code isn't known, > then just store it as a number "12345". It's much shorter than using the strerror() > result, and even easier to read. Yes, I would use strerrorname_np(errno) and get a result like "errno":"EINVAL". > > It's a general theme throughout: whenever you write "foo":F,"foo_str":"Foo bar" you > can also write: "foo":"FOO" where FOO is the V4L2 define, or a decimal or hex value > if the value didn't map to a define. > > So this: > > { > "syscall_str":"ioctl", > "syscall":0, > "fd":5, > "request":3234878981, > "request_str":"VIDIOC_S_FMT", > "ioctl_args_from_userspace":{ > "v4l2_format":{ > "type":10, > "type_str":"Video Output Multiplanar", > "v4l2_pix_format_mplane":{ > "width":320, > "height":240, > "pixelformat":875967059, > "pixelformat_str":"H.264 Parsed Slice Data", > "fcc_str":"S264", > "field":0, > "field_str":"Any", > "colorspace":0, > "colorspace_str":"Default", > "num_planes":0, > "flags":0, > "flags_str":"", > "ycbcr_enc":0, > "ycbcr_enc_str":"Default", > "quantization":0, > "quantization_str":"Default", > "xfer_func":0, > "xfer_func_str":"Default" > } > } > }, > > could become: > > { > "syscall":"ioctl", > "fd":5, > "cmd":"VIDIOC_S_FMT", > "ioctl_args_from_userspace":{ > "v4l2_format":{ > "type":"V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE", > "v4l2_pix_format_mplane":{ > "width":320, > "height":240, > "pixelformat":"V4L2_PIX_FMT_H264_SLICE", > "fcc_str":"S264", // I'm not sure if this is useful or not > "field":"V4L2_FIELD_ANY", > "colorspace":"V4L2_COLORSPACE_DEFAULT", > "num_planes":0, > "flags":0, > "flags_str":"", // I'm not sure if this is useful > "ycbcr_enc":"V4L2_YCBCR_ENC_DEFAULT", > "quantization":"V4L2_QUANTIZATION_DEFAULT", > "xfer_func":"V4L2_XFER_FUNC_DEFAULT", > } > } > }, > > > Here is a more representative example of "flags": > > "flags":8404993, > "flags_str":"mapped, request-fd, ts-copy, ts-src-eof", > Sounds good, I have been working on just using strings and then converting them back to values in the retracer. So there would be just one entry for values that are more informative as strings like buffer type and flags e.g. "flags":"V4L2_VP8_LF_ADJ_ENABLE, V4L2_VP8_LF_DELTA_UPDATE"} Then the retracer would turn it back to an integer before making the call. The autogenerated code from v4l2-controls.h makes this easy to do without typos. > I'm wondering if flags_str is really useful. One question I have is whether > it is possible to direct json to write "flags" as a hex value instead of > decimal. It's much easier to decode a hex value to the corresponding bits > than doing that with a decimal value. No, as far as i know, the json-c library just has decimal integers and strings. But I can just convert unknown numbers to a hex string (and then reverse for the retracer) and this is what I would do for unrecognized flags or other values. > > The other issue is that it makes the json file larger. > > I'm actually wondering if it wouldn't be better to keep the json file > as compact as possible, since it is not human readable regardless of how > you massage it :-). > > Instead I wonder if it would be possible to add an option that reads the > json file and then outputs a really human readable version. > > I'm not sure what is easiest/best here. I have tried it both ways - making the json-file super compact with just numbers and no strings and also, the awkward hybrid that you see with the values and then value_str to follow. The reason I like to see the strings is that I find myself looking at the json file a lot to compare or see what is happening or has gone wrong. I found it annoying to leave that information out of the json file. I think it will improve the readability to get rid of the duplication as you suggest. A separate human-readable file without json and with options for controlling the level of detail would be good too, but maybe a future improvement. > > > + } > > + > > + /* Also trace the ioctl arguments as modified by the driver. */ > > + json_object *ioctl_args_driver = trace_ioctl_args(fd, request, arg, false); > > + if (json_object_object_length(ioctl_args_driver)) > > + json_object_object_add(ioctl_obj, "ioctl_args_from_driver", ioctl_args_driver); > > + > > + write_json_object_to_json_file(ioctl_obj); > > + json_object_put(ioctl_obj); > > + > > + if ((request == VIDIOC_G_FMT) && (_IOC_TYPE(request) == 'V')) > > + g_fmt_setup_trace(static_cast<struct v4l2_format*>(arg)); > > + > > + if ((request == VIDIOC_S_FMT) && (_IOC_TYPE(request) == 'V')) > > + s_fmt_setup(static_cast<struct v4l2_format*>(arg)); > > + > > + if ((request == VIDIOC_EXPBUF) && (_IOC_TYPE(request) == 'V')) > > + expbuf_setup(static_cast<struct v4l2_exportbuffer*>(arg)); > > + > > + if ((request == VIDIOC_QUERYBUF) && (_IOC_TYPE(request) == 'V')) > > + querybuf_setup(fd, static_cast<struct v4l2_buffer*>(arg)); > > + > > + return ret; > > +} > > diff --git a/utils/v4l2-tracer/retrace-helper.cpp b/utils/v4l2-tracer/retrace-helper.cpp > > new file mode 100644 > > index 00000000..37fad2c3 > > --- /dev/null > > +++ b/utils/v4l2-tracer/retrace-helper.cpp > > @@ -0,0 +1,196 @@ > > +/* SPDX-License-Identifier: GPL-2.0-only */ > > +/* > > + * Copyright 2022 Collabora Ltd. > > + */ > > + > > +#include "v4l2-tracer-common.h" > > + > > +struct buffer_retrace { > > + int fd; > > + __u32 type; /* enum v4l2_buf_type */ > > + __u32 index; > > + __u32 offset; > > + long address_trace; > > + long address_retrace; > > +}; > > + > > +struct retrace_context { > > + __u32 width; > > + __u32 height; > > + __u32 pixelformat; > > + pthread_mutex_t lock; > > + /* Key is a file descriptor from the trace, value is the corresponding fd in the retrace. */ > > + std::unordered_map<int, int> retrace_fds; > > + /* List of output and capture buffers being retraced. */ > > + std::list<struct buffer_retrace> buffers; > > +}; > > + > > +static struct retrace_context ctx_retrace = { > > + .lock = PTHREAD_MUTEX_INITIALIZER > > +}; > > + > > +bool buffer_in_retrace_context(int fd, __u32 offset) > > +{ > > + bool buffer_in_retrace_context = false; > > + pthread_mutex_lock(&ctx_retrace.lock); > > + for (auto &b : ctx_retrace.buffers) { > > + if ((b.fd == fd) && (b.offset == offset)) { > > + buffer_in_retrace_context = true; > > + break; > > + } > > + } > > + pthread_mutex_unlock(&ctx_retrace.lock); > > + return buffer_in_retrace_context; > > +} > > + > > +int get_buffer_fd_retrace(__u32 type, __u32 index) > > +{ > > + int fd = 0; > > Hmm, strictly speaking 0 is a valid file descriptor number: > typically it maps to stdin. Usually -1 is used to denote an > invalid fd. Ok, I will fix. > > > + pthread_mutex_lock(&ctx_retrace.lock); > > + for (auto &b : ctx_retrace.buffers) { > > + if ((b.type == type) && (b.index == index)) { > > + fd = b.fd; > > + break; > > + } > > + } > > + pthread_mutex_unlock(&ctx_retrace.lock); > > + return fd; > > +} > > + > > +void add_buffer_retrace(int fd, __u32 type, __u32 index, __u32 offset) > > +{ > > + pthread_mutex_lock(&ctx_retrace.lock); > > + struct buffer_retrace buf = {}; > > + buf.fd = fd; > > + buf.type = type; > > + buf.index = index; > > + buf.offset = offset; > > + ctx_retrace.buffers.push_front(buf); > > + pthread_mutex_unlock(&ctx_retrace.lock); > > +} > > + > > +void remove_buffer_retrace(int fd) > > +{ > > + pthread_mutex_lock(&ctx_retrace.lock); > > + for (auto it = ctx_retrace.buffers.begin(); it != ctx_retrace.buffers.end(); ++it) { > > + if (it->fd == fd) { > > + ctx_retrace.buffers.erase(it); > > + break; > > + } > > + } > > + pthread_mutex_unlock(&ctx_retrace.lock); > > +} > > + > > +void set_buffer_address_retrace(int fd, __u32 offset, long address_trace, long address_retrace) > > +{ > > + pthread_mutex_lock(&ctx_retrace.lock); > > + for (auto &b : ctx_retrace.buffers) { > > + if ((b.fd == fd) && (b.offset == offset)) { > > + b.address_trace = address_trace; > > + b.address_retrace = address_retrace; > > + break; > > + } > > + } > > + pthread_mutex_unlock(&ctx_retrace.lock); > > +} > > + > > +long get_retrace_address_from_trace_address(long address_trace) > > +{ > > + long address_retrace = 0; > > + pthread_mutex_lock(&ctx_retrace.lock); > > + for (auto &b : ctx_retrace.buffers) { > > + if (b.address_trace == address_trace) { > > + address_retrace = b.address_retrace; > > + break; > > + } > > + } > > + pthread_mutex_unlock(&ctx_retrace.lock); > > + return address_retrace; > > +} > > + > > +void print_buffers_retrace(void) > > +{ > > + pthread_mutex_lock(&ctx_retrace.lock); > > + for (auto &b : ctx_retrace.buffers) { > > + fprintf(stderr, "fd: %d, offset: %d, address_trace:%ld, address_retrace:%ld\n", > > + b.fd, b.offset, b.address_trace, b.address_retrace); > > + } > > + pthread_mutex_unlock(&ctx_retrace.lock); > > +} > > + > > +/* > > + * Create a new file descriptor entry in retrace context. > > + * Add both the fd from the trace context and the corresponding fd from the retrace context. > > + */ > > +void add_fd(int fd_trace, int fd_retrace) > > +{ > > + std::pair<int, int> new_pair; > > + new_pair = std::make_pair(fd_trace, fd_retrace); > > + pthread_mutex_lock(&ctx_retrace.lock); > > + ctx_retrace.retrace_fds.insert(new_pair); > > + pthread_mutex_unlock(&ctx_retrace.lock); > > +} > > + > > +int get_fd_retrace_from_fd_trace(int fd_trace) > > +{ > > + int fd_retrace = 0; > > + std::unordered_map<int, int>::const_iterator it; > > + > > + pthread_mutex_lock(&ctx_retrace.lock); > > + it = ctx_retrace.retrace_fds.find(fd_trace); > > + if (it != ctx_retrace.retrace_fds.end()) > > + fd_retrace = it->second; > > + pthread_mutex_unlock(&ctx_retrace.lock); > > + > > + return fd_retrace; > > +} > > + > > +void remove_fd(int fd_trace) > > +{ > > + pthread_mutex_lock(&ctx_retrace.lock); > > + ctx_retrace.retrace_fds.erase(fd_trace); > > + pthread_mutex_unlock(&ctx_retrace.lock); > > +} > > + > > +void print_fds(void) > > +{ > > + pthread_mutex_lock(&ctx_retrace.lock); > > + if (ctx_retrace.retrace_fds.empty()) > > + fprintf(stderr, "all devices closed\n"); > > + for (auto it = ctx_retrace.retrace_fds.cbegin(); it != ctx_retrace.retrace_fds.cend(); ++it) > > + fprintf(stderr, "fd_trace: %d, fd_retrace: %d\n", it->first, it->second); > > + pthread_mutex_unlock(&ctx_retrace.lock); > > +} > > + > > +void set_pixelformat_retrace(__u32 width, __u32 height, __u32 pixelformat) > > +{ > > + pthread_mutex_lock(&ctx_retrace.lock); > > + ctx_retrace.width = width; > > + ctx_retrace.height = height; > > + ctx_retrace.pixelformat = pixelformat; > > + pthread_mutex_unlock(&ctx_retrace.lock); > > +} > > + > > +unsigned get_expected_length_retrace(void) > > +{ > > + pthread_mutex_lock(&ctx_retrace.lock); > > + unsigned width = ctx_retrace.width; > > + unsigned height = ctx_retrace.height; > > + unsigned pixelformat = ctx_retrace.pixelformat; > > + pthread_mutex_unlock(&ctx_retrace.lock); > > + > > + unsigned expected_length = width * height; > > + if (pixelformat == V4L2_PIX_FMT_NV12 || pixelformat == V4L2_PIX_FMT_YUV420) { > > + expected_length *= 3; > > + expected_length /= 2; > > + expected_length += (expected_length % 2); > > + } > > + return expected_length; > > +} > > + > > +void print_context(void) > > +{ > > + print_fds(); > > + print_buffers_retrace(); > > + fprintf(stderr, "\n"); > > +} > > <snip> > > > diff --git a/utils/v4l2-tracer/trace-helper.cpp b/utils/v4l2-tracer/trace-helper.cpp > > new file mode 100644 > > index 00000000..6b4bf567 > > --- /dev/null > > +++ b/utils/v4l2-tracer/trace-helper.cpp > > @@ -0,0 +1,816 @@ > > +/* SPDX-License-Identifier: GPL-2.0-only */ > > +/* > > + * Copyright 2022 Collabora Ltd. > > + */ > > + > > +#include <math.h> /* for pow */ > > +#include "v4l2-tracer-common.h" > > +#include "trace-helper.h" > > +#include "trace-info-gen.h" > > + > > +void trace_mem(int fd, __u32 offset, __u32 type, int index, __u32 bytesused, unsigned long start); > > +void trace_mem_encoded(int fd, __u32 offset); > > + > > +struct buffer_trace { > > + int fd; > > + __u32 type; /* enum v4l2_buf_type */ > > + __u32 index; > > + __u32 offset; > > + __u32 bytesused; > > + long display_order; > > + unsigned long address; > > +}; > > + > > +struct trace_context { > > + __u32 width; > > + __u32 height; > > + FILE *trace_file; > > + __u32 pixelformat; > > + int pic_order_cnt_lsb; > > + int max_pic_order_cnt_lsb; > > + pthread_mutex_t lock; > > + __u32 compression_format; > > + std::string trace_filename; > > + int compressed_frame_count; > > + std::list<long> decode_order; > > + std::list<struct buffer_trace> buffers; > > + std::unordered_map<int, std::string> devices; /* key:fd, value: path of the device */ > > +}; > > + > > +struct trace_options { > > + bool options_are_set; > > + bool verbose; > > + bool pretty_print_all; > > + bool pretty_print_mem; > > + bool write_decoded_data_to_yuv_file; > > + bool write_decoded_data_to_json_file; > > +}; > > + > > +std::string ver2s(unsigned int version) > > +{ > > + char buf[16]; > > + sprintf(buf, "%d.%d.%d", version >> 16, (version >> 8) & 0xff, version & 0xff); > > + return buf; > > +} > > + > > +std::string which2s(unsigned long which) > > +{ > > + std::string s = "unknown"; > > + > > + switch (which) { > > + case V4L2_CTRL_WHICH_CUR_VAL: > > + s = "V4L2_CTRL_WHICH_CUR_VAL"; > > + break; > > + case V4L2_CTRL_WHICH_DEF_VAL: > > + s= "V4L2_CTRL_WHICH_DEF_VAL"; > > + break; > > + case V4L2_CTRL_WHICH_REQUEST_VAL: > > + s = "V4L2_CTRL_WHICH_REQUEST_VAL"; > > + break; > > + default: > > + break; > > + } > > + > > + return s; > > +} > > + > > +std::string val2s(unsigned long val, const definition *def) > > +{ > > + std::string s; > > + > > + if (val == 0) > > + return s; > > + > > + while ((def->val) && (def->val != val)) > > + def++; > > + > > + if (def->val == val) > > + s = def->str; > > + > > + return s; > > +} > > + > > +struct trace_options options; > > + > > +struct trace_context ctx_trace = { > > + .lock = PTHREAD_MUTEX_INITIALIZER > > +}; > > + > > +bool options_are_set(void) > > +{ > > + return options.options_are_set; > > +} > > + > > +void set_options(void) > > +{ > > + options.verbose = getenv("TRACE_OPTION_VERBOSE") ? true : false; > > + options.pretty_print_mem = getenv("TRACE_OPTION_PRETTY_PRINT_MEM") ? true : false; > > + options.pretty_print_all = getenv("TRACE_OPTION_PRETTY_PRINT_ALL") ? true : false; > > + options.write_decoded_data_to_json_file = getenv("TRACE_OPTION_WRITE_DECODED_TO_JSON_FILE") ? true : false; > > + options.write_decoded_data_to_yuv_file = getenv("TRACE_OPTION_WRITE_DECODED_TO_YUV_FILE") ? true : false; > > + options.options_are_set = true; > > +} > > + > > +bool option_is_set_verbose(void) > > +{ > > + return options.verbose; > > +} > > + > > +bool option_is_set_pretty_print_mem(void) > > +{ > > + return options.pretty_print_mem; > > +} > > + > > +bool option_is_set_write_decoded_data_to_json_file(void) > > +{ > > + return options.write_decoded_data_to_json_file; > > +} > > + > > +bool is_video_or_media_device(const char *path) > > +{ > > + std::string dev_path_video = "/dev/video"; > > + std::string dev_path_media = "/dev/media"; > > + if (strncmp(path, dev_path_video.c_str(), dev_path_video.length()) && > > + strncmp(path, dev_path_media.c_str(), dev_path_media.length())) > > + return false; > > + return true; > > +} > > + > > +void add_device(int fd, std::string path) > > +{ > > + std::pair<int, std::string> new_pair = std::make_pair(fd, path); > > + pthread_mutex_lock(&ctx_trace.lock); > > + ctx_trace.devices.insert(new_pair); > > + pthread_mutex_unlock(&ctx_trace.lock); > > +} > > + > > +std::string get_device(int fd) > > +{ > > + std::string path; > > + std::unordered_map<int, std::string>::const_iterator it; > > + pthread_mutex_lock(&ctx_trace.lock); > > + it = ctx_trace.devices.find(fd); > > + if (it != ctx_trace.devices.end()) > > + path = it->second; > > + pthread_mutex_unlock(&ctx_trace.lock); > > + return path; > > +} > > + > > +int remove_device(int fd) > > +{ > > + int ret = 0; > > + pthread_mutex_lock(&ctx_trace.lock); > > + ret = ctx_trace.devices.erase(fd); > > + pthread_mutex_unlock(&ctx_trace.lock); > > + return ret; > > +} > > + > > +int count_devices(void) > > +{ > > + int count = 0; > > + pthread_mutex_lock(&ctx_trace.lock); > > + count = ctx_trace.devices.size(); > > + pthread_mutex_unlock(&ctx_trace.lock); > > + return count; > > +} > > + > > +void set_pixelformat_trace(__u32 width, __u32 height, __u32 pixelformat) > > +{ > > + pthread_mutex_lock(&ctx_trace.lock); > > + ctx_trace.width = width; > > + ctx_trace.height = height; > > + ctx_trace.pixelformat = pixelformat; > > + pthread_mutex_unlock(&ctx_trace.lock); > > +} > > + > > +__u32 get_pixelformat(void) > > +{ > > + __u32 pixelformat = 0; > > + pthread_mutex_lock(&ctx_trace.lock); > > + pixelformat = ctx_trace.pixelformat; > > + pthread_mutex_unlock(&ctx_trace.lock); > > + return pixelformat; > > +} > > + > > +void set_compression_format(__u32 compression_format) > > +{ > > + pthread_mutex_lock(&ctx_trace.lock); > > + ctx_trace.compression_format = compression_format; > > + pthread_mutex_unlock(&ctx_trace.lock); > > +} > > + > > +__u32 get_compression_format(void) > > +{ > > + __u32 compression_format = 0; > > + pthread_mutex_lock(&ctx_trace.lock); > > + compression_format = ctx_trace.compression_format; > > + pthread_mutex_unlock(&ctx_trace.lock); > > + return compression_format; > > +} > > + > > +void set_compressed_frame_count(int count) > > +{ > > + pthread_mutex_lock(&ctx_trace.lock); > > + ctx_trace.compressed_frame_count = count; > > + pthread_mutex_unlock(&ctx_trace.lock); > > +} > > + > > +int get_compressed_frame_count(void) > > +{ > > + int ret = 0; > > + pthread_mutex_lock(&ctx_trace.lock); > > + ret = ctx_trace.compressed_frame_count; > > + pthread_mutex_unlock(&ctx_trace.lock); > > + return ret; > > +} > > + > > +void print_decode_order(void) > > +{ > > + pthread_mutex_lock(&ctx_trace.lock); > > + fprintf(stderr, "Decode order: "); > > + for (auto &num : ctx_trace.decode_order) > > + fprintf(stderr, "%ld, ", num); > > + fprintf(stderr, ".\n"); > > + pthread_mutex_unlock(&ctx_trace.lock); > > +} > > + > > +void set_decode_order(long decode_order) > > +{ > > + if (option_is_set_verbose()) > > + fprintf(stderr, "%s: %ld\n", __func__, decode_order); > > + > > + std::list<long>::iterator it; > > + pthread_mutex_lock(&ctx_trace.lock); > > + it = find(ctx_trace.decode_order.begin(), ctx_trace.decode_order.end(), decode_order); > > + if (it == ctx_trace.decode_order.end()) > > + ctx_trace.decode_order.push_front(decode_order); > > + pthread_mutex_unlock(&ctx_trace.lock); > > + > > + if (option_is_set_verbose()) > > + print_decode_order(); > > +} > > + > > +long get_decode_order(void) > > +{ > > + long decode_order = 0; > > + pthread_mutex_lock(&ctx_trace.lock); > > + if (!ctx_trace.decode_order.empty()) > > + decode_order = ctx_trace.decode_order.front(); > > + pthread_mutex_unlock(&ctx_trace.lock); > > + return decode_order; > > +} > > + > > +void set_max_pic_order_cnt_lsb(int max_pic_order_cnt_lsb) > > +{ > > + pthread_mutex_lock(&ctx_trace.lock); > > + ctx_trace.max_pic_order_cnt_lsb = max_pic_order_cnt_lsb; > > + pthread_mutex_unlock(&ctx_trace.lock); > > +} > > + > > +int get_max_pic_order_cnt_lsb(void) > > +{ > > + int max_pic_order_cnt_lsb = 0; > > + pthread_mutex_lock(&ctx_trace.lock); > > + max_pic_order_cnt_lsb = ctx_trace.max_pic_order_cnt_lsb; > > + pthread_mutex_unlock(&ctx_trace.lock); > > + return max_pic_order_cnt_lsb; > > +} > > + > > +void set_pic_order_cnt_lsb(int pic_order_cnt_lsb) > > +{ > > + pthread_mutex_lock(&ctx_trace.lock); > > + ctx_trace.pic_order_cnt_lsb = pic_order_cnt_lsb; > > + pthread_mutex_unlock(&ctx_trace.lock); > > +} > > + > > +int get_pic_order_cnt_lsb(void) > > +{ > > + int pic_order_cnt_lsb = 0; > > + pthread_mutex_lock(&ctx_trace.lock); > > + pic_order_cnt_lsb = ctx_trace.pic_order_cnt_lsb; > > + pthread_mutex_unlock(&ctx_trace.lock); > > + return pic_order_cnt_lsb; > > +} > > + > > +void add_buffer_trace(int fd, __u32 type, __u32 index, __u32 offset = 0) > > +{ > > + struct buffer_trace buf = {}; > > + buf.fd = fd; > > + buf.type = type; > > + buf.index = index; > > + buf.offset = offset; > > + buf.display_order = -1; > > + > > + pthread_mutex_lock(&ctx_trace.lock); > > + ctx_trace.buffers.push_front(buf); > > + pthread_mutex_unlock(&ctx_trace.lock); > > +} > > + > > +void remove_buffer_trace(int fd) > > +{ > > + pthread_mutex_lock(&ctx_trace.lock); > > + for (auto it = ctx_trace.buffers.begin(); it != ctx_trace.buffers.end(); ++it) { > > + if (it->fd == fd) { > > + ctx_trace.buffers.erase(it); > > + break; > > + } > > + } > > + pthread_mutex_unlock(&ctx_trace.lock); > > +} > > + > > +bool buffer_in_trace_context(int fd, __u32 offset) > > +{ > > + bool buffer_in_trace_context = false; > > + pthread_mutex_lock(&ctx_trace.lock); > > + for (auto &b : ctx_trace.buffers) { > > + if ((b.fd == fd) && (b.offset == offset)) { > > + buffer_in_trace_context = true; > > + break; > > + } > > + } > > + pthread_mutex_unlock(&ctx_trace.lock); > > + return buffer_in_trace_context; > > +} > > + > > +int get_buffer_fd_trace(__u32 type, __u32 index) > > +{ > > + int fd = 0; > > + pthread_mutex_lock(&ctx_trace.lock); > > + for (auto &b : ctx_trace.buffers) { > > + if ((b.type == type) && (b.index == index)) { > > + fd = b.fd; > > + break; > > + } > > + } > > + pthread_mutex_unlock(&ctx_trace.lock); > > + return fd; > > +} > > + > > +__u32 get_buffer_type_trace(int fd, __u32 offset) > > +{ > > + __u32 type = 0; > > + pthread_mutex_lock(&ctx_trace.lock); > > + for (auto &b : ctx_trace.buffers) { > > + if ((b.fd == fd) && (b.offset == offset)) { > > + type = b.type; > > + break; > > + } > > + } > > + pthread_mutex_unlock(&ctx_trace.lock); > > + return type; > > +} > > + > > +int get_buffer_index_trace(int fd, __u32 offset) > > +{ > > + int index = -1; > > + pthread_mutex_lock(&ctx_trace.lock); > > + for (auto &b : ctx_trace.buffers) { > > + if ((b.fd == fd) && (b.offset == offset)) { > > + index = b.index; > > + break; > > + } > > + } > > + pthread_mutex_unlock(&ctx_trace.lock); > > + return index; > > +} > > + > > +__u32 get_buffer_offset_trace(__u32 type, __u32 index) > > +{ > > + __u32 offset = 0; > > + pthread_mutex_lock(&ctx_trace.lock); > > + for (auto &b : ctx_trace.buffers) { > > + if ((b.type == type) && (b.index == index)) { > > + offset = b.offset; > > + break; > > + } > > + } > > + pthread_mutex_unlock(&ctx_trace.lock); > > + return offset; > > +} > > + > > +void set_buffer_bytesused_trace(int fd, __u32 offset, __u32 bytesused) > > +{ > > + pthread_mutex_lock(&ctx_trace.lock); > > + for (auto &b : ctx_trace.buffers) { > > + if ((b.fd == fd) && (b.offset == offset)) { > > + b.bytesused = bytesused; > > + break; > > + } > > + } > > + pthread_mutex_unlock(&ctx_trace.lock); > > +} > > + > > +long get_buffer_bytesused_trace(int fd, __u32 offset) > > +{ > > + long bytesused = 0; > > + pthread_mutex_lock(&ctx_trace.lock); > > + for (auto &b : ctx_trace.buffers) { > > + if ((b.fd == fd) && (b.offset == offset)) { > > + bytesused = b.bytesused; > > + break; > > + } > > + } > > + pthread_mutex_unlock(&ctx_trace.lock); > > + return bytesused; > > +} > > + > > +void set_buffer_display_order(int fd, __u32 offset, long display_order) > > +{ > > + if (option_is_set_verbose()) > > + fprintf(stderr, "%s: %ld\n", __func__, display_order); > > + pthread_mutex_lock(&ctx_trace.lock); > > + for (auto &b : ctx_trace.buffers) { > > + if ((b.fd == fd) && (b.offset == offset)) { > > + b.display_order = display_order; > > + break; > > + } > > + } > > + pthread_mutex_unlock(&ctx_trace.lock); > > +} > > + > > +void set_buffer_address_trace(int fd, __u32 offset, unsigned long address) > > +{ > > + pthread_mutex_lock(&ctx_trace.lock); > > + for (auto &b : ctx_trace.buffers) { > > + if ((b.fd == fd) && (b.offset == offset)) { > > + b.address = address; > > + break; > > + } > > + } > > + pthread_mutex_unlock(&ctx_trace.lock); > > +} > > + > > +unsigned long get_buffer_address_trace(int fd, __u32 offset) > > +{ > > + unsigned long address = 0; > > + pthread_mutex_lock(&ctx_trace.lock); > > + for (auto &b : ctx_trace.buffers) { > > + if ((b.fd == fd) && (b.offset == offset)) { > > + address = b.address; > > + break; > > + } > > + } > > + pthread_mutex_unlock(&ctx_trace.lock); > > + return address; > > +} > > + > > +bool buffer_is_mapped(unsigned long buffer_address) > > +{ > > + bool ret = false; > > + pthread_mutex_lock(&ctx_trace.lock); > > + for (auto &b : ctx_trace.buffers) { > > + if (b.address == buffer_address) { > > + ret = true; > > + break; > > + } > > + } > > + pthread_mutex_unlock(&ctx_trace.lock); > > + return ret; > > +} > > + > > +void print_buffers_trace(void) > > +{ > > + pthread_mutex_unlock(&ctx_trace.lock); > > + for (auto &b : ctx_trace.buffers) { > > + fprintf(stderr, "fd: %d, %s, index: %d, display_order: %ld, bytesused: %d, ", > > + b.fd, buftype2s(b.type).c_str(), b.index, b.display_order, b.bytesused); > > + fprintf(stderr, "address: %lu, offset: %u \n", b.address, b.offset); > > + } > > + pthread_mutex_unlock(&ctx_trace.lock); > > +} > > + > > +std::string get_ioctl_request_str(unsigned long request) > > +{ > > + __u8 ioctl_type = _IOC_TYPE(request); > > + switch (ioctl_type) { > > + case 'V': { > > + if (option_is_set_verbose()) > > + fprintf(stderr, "%s\n", ioctl2s_video(request).c_str()); > > + return ioctl2s_video(request); > > + } > > + case '|': > > + return ioctl2s_media(request); > > + case 'b': > > + if (request == DMA_BUF_IOCTL_SYNC) > > + return "DMA_BUF_IOCTL_SYNC"; > > + break; > > + default: > > + break; > > + } > > + return "unknown ioctl"; > > +} > > + > > +unsigned get_expected_length_trace() > > +{ > > + unsigned width; > > + unsigned height; > > + unsigned pixelformat; > > + unsigned expected_length; > > + > > + pthread_mutex_lock(&ctx_trace.lock); > > + width = ctx_trace.width; > > + height = ctx_trace.height; > > + pixelformat = ctx_trace.pixelformat; > > + pthread_mutex_unlock(&ctx_trace.lock); > > + > > + /* > > + * TODO: this assumes that the stride is equal to the real width and that the > > + * padding follows the end of the chroma plane. It could be improved by > > + * following the model in v4l2-ctl-streaming.cpp read_write_padded_frame() > > + */ > > + expected_length = width * height; > > + if (pixelformat == V4L2_PIX_FMT_NV12 || pixelformat == V4L2_PIX_FMT_YUV420) { > > + expected_length *= 3; > > + expected_length /= 2; > > + expected_length += (expected_length % 2); > > + } > > + return expected_length; > > +} > > + > > +void trace_mem_decoded(void) > > +{ > > + int displayed_count = 0; > > + unsigned expected_length = get_expected_length_trace(); > > + > > + pthread_mutex_lock(&ctx_trace.lock); > > + > > + while (!ctx_trace.decode_order.empty()) { > > + > > + std::list<buffer_trace>::iterator it; > > + long next_frame_to_be_displayed = *std::min_element(ctx_trace.decode_order.begin(), > > + ctx_trace.decode_order.end()); > > + > > + for (it = ctx_trace.buffers.begin(); it != ctx_trace.buffers.end(); ++it) { > > + if (it->display_order != next_frame_to_be_displayed) > > + continue; > > + if (!it->address) > > + break; > > + if (it->bytesused < expected_length) > > + break; > > + if (option_is_set_verbose()) > > + fprintf(stderr, "%s, displaying: %ld, %s, index: %d\n", > > + __func__, it->display_order, buftype2s(it->type).c_str(), it->index); > > + displayed_count++; > > + > > + if (options.write_decoded_data_to_yuv_file) { > > + std::string filename = getenv("TRACE_ID"); > > + filename += ".yuv"; > > + FILE *fp = fopen(filename.c_str(), "a"); > > + unsigned char *buffer_pointer = (unsigned char*) it->address; > > + for (__u32 i = 0; i < expected_length; i++) > > + fwrite(&buffer_pointer[i], sizeof(unsigned char), 1, fp); > > + fclose(fp); > > + } > > + int fd = it->fd; > > + __u32 offset = it->offset; > > + __u32 type = it->type; > > + int index = it->index; > > + __u32 bytesused = it->bytesused; > > + unsigned long start = it->address; > > + > > + pthread_mutex_unlock(&ctx_trace.lock); > > + trace_mem(fd, offset, type, index, bytesused, start); > > + pthread_mutex_lock(&ctx_trace.lock); > > + > > + ctx_trace.decode_order.remove(next_frame_to_be_displayed); > > + it->display_order = -1; > > + break; > > + } > > + if (!it->address || it == ctx_trace.buffers.end() || it->bytesused < expected_length) > > + break; > > + } > > + pthread_mutex_unlock(&ctx_trace.lock); > > + set_compressed_frame_count(get_compressed_frame_count() - displayed_count); > > +} > > + > > +void s_ext_ctrls_setup(struct v4l2_ext_controls *ext_controls) > > +{ > > + if (ext_controls->which != V4L2_CTRL_WHICH_REQUEST_VAL) > > + return; > > + > > + if (option_is_set_verbose()) > > + fprintf(stderr, "\n%s\n", __func__); > > + > > + for (__u32 i = 0; i < ext_controls->count; i++) { > > + struct v4l2_ext_control ctrl = ext_controls->controls[i]; > > + > > + switch (ctrl.id) { > > This could use some comments: why do you need to take special actions for these > two controls? Ok, I will add comments. A lot of this is about trying to get the frames to display in the right order. > > > + case V4L2_CID_STATELESS_H264_SPS: { > > + set_max_pic_order_cnt_lsb(pow(2, ctrl.p_h264_sps->log2_max_pic_order_cnt_lsb_minus4 + 4)); > > + break; > > + } > > + case V4L2_CID_STATELESS_H264_DECODE_PARAMS: { > > + long pic_order_cnt_msb; > > + int max = get_max_pic_order_cnt_lsb(); > > + long prev_pic_order_cnt_msb = get_decode_order(); > > + int prev_pic_order_cnt_lsb = get_pic_order_cnt_lsb(); > > + int pic_order_cnt_lsb = ctrl.p_h264_decode_params->pic_order_cnt_lsb; > > + > > + if (option_is_set_verbose()) { > > + fprintf(stderr, "\tprev_pic_order_cnt_lsb: %d\n", prev_pic_order_cnt_lsb); > > + fprintf(stderr, "\tprev_pic_order_cnt_msb: %ld\n", prev_pic_order_cnt_msb); > > + fprintf(stderr, "\tpic_order_cnt_lsb: %d\n", pic_order_cnt_lsb); > > + } > > + > > + /* The first frame. */ > > + if (prev_pic_order_cnt_msb == 0) > > + pic_order_cnt_msb = 0; > > + > > + /* > > + * TODO: improve the displaying of decoded frames following H264 specification > > + * 8.2.1.1. For now, dump all the previously decoded frames when an IDR_PIC is > > + * received to avoid losing frames although this will still sometimes result > > + * in frames out of order. > > + */ > > + if (ctrl.p_h264_decode_params->flags & V4L2_H264_DECODE_PARAM_FLAG_IDR_PIC) { > > + if (get_compressed_frame_count()) > > + trace_mem_decoded(); > > + } > > + > > + /* > > + * When pic_order_cnt_lsb wraps around to zero, adjust the total count using > > + * max to keep the correct display order. > > + */ > > + if ((pic_order_cnt_lsb < prev_pic_order_cnt_lsb) && > > + ((prev_pic_order_cnt_lsb - pic_order_cnt_lsb) >= (max / 2))) { > > + pic_order_cnt_msb = prev_pic_order_cnt_msb + max; > > + } else if ((pic_order_cnt_lsb > prev_pic_order_cnt_lsb) && > > + ((pic_order_cnt_lsb - prev_pic_order_cnt_lsb) > (max / 2))) { > > + pic_order_cnt_msb = prev_pic_order_cnt_msb - max; > > + } else { > > + pic_order_cnt_msb = prev_pic_order_cnt_msb + (pic_order_cnt_lsb - prev_pic_order_cnt_lsb); > > + } > > + > > + if (option_is_set_verbose()) > > + fprintf(stderr, "\tpic_order_cnt_msb: %ld\n", pic_order_cnt_msb); > > + > > + set_pic_order_cnt_lsb(pic_order_cnt_lsb); > > + set_decode_order(pic_order_cnt_msb); > > + break; > > + } > > + default: > > + break; > > + } > > + } > > +} > > + > > +void qbuf_setup(struct v4l2_buffer *buf) > > +{ > > + if (option_is_set_verbose()) > > + fprintf(stderr, "\n%s: %s, index: %d\n", __func__, buftype2s(buf->type).c_str(), buf->index); > > + > > + int buf_fd = get_buffer_fd_trace(buf->type, buf->index); > > + __u32 buf_offset = get_buffer_offset_trace(buf->type, buf->index); > > + > > + __u32 bytesused = 0; > > + if (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE || > > + buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) > > + bytesused = buf->m.planes[0].bytesused; > > + if (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT || buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) > > + bytesused = buf->bytesused; > > + set_buffer_bytesused_trace(buf_fd, buf_offset, bytesused); > > + > > + /* The output buffer should have compressed data just before it is queued, so trace it. */ > > + if (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE || > > + buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { > > + trace_mem_encoded(buf_fd, buf_offset); > > + set_compressed_frame_count(get_compressed_frame_count() + 1); > > + } > > + > > + if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE || > > + buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { > > + > > + /* If the capture buffer is queued for reuse, trace it before it is reused. */ > > + if (get_compressed_frame_count()) > > + trace_mem_decoded(); > > + > > + /* H264 sets display order in controls, otherwise display just in the order queued. */ > > + if (get_compression_format() != V4L2_PIX_FMT_H264_SLICE) > > + set_decode_order(get_decode_order() + 1); > > + > > + set_buffer_display_order(buf_fd, buf_offset, get_decode_order()); > > + > > + if (option_is_set_verbose()) { > > + print_decode_order(); > > + print_buffers_trace(); > > + } > > + } > > +} > > + > > +void streamoff_cleanup(v4l2_buf_type buf_type) > > +{ > > + if (option_is_set_verbose()) { > > + fprintf(stderr, "\nVIDIOC_STREAMOFF: %s\n", buftype2s(buf_type).c_str()); > > + fprintf(stderr, "compression: %s, pixelformat: %s %s, width: %d, height: %d\n", > > + pixfmt2s(get_compression_format()).c_str(), pixfmt2s(get_pixelformat()).c_str(), > > + fcc2s(get_pixelformat()).c_str(), ctx_trace.width, ctx_trace.height); > > + } > > + > > + /* > > + * Before turning off the stream, trace any remaining capture buffers that were missed > > + * because they were not queued for reuse. > > + */ > > + if (buf_type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE || > > + buf_type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { > > + if (get_compressed_frame_count()) > > + trace_mem_decoded(); > > + } > > +} > > + > > +void g_fmt_setup_trace(struct v4l2_format *format) > > +{ > > + if (format->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) > > + set_pixelformat_trace(format->fmt.pix.width, > > + format->fmt.pix.height, format->fmt.pix.pixelformat); > > + if (format->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) > > + set_pixelformat_trace(format->fmt.pix_mp.width, format->fmt.pix_mp.height, > > + format->fmt.pix_mp.pixelformat); > > + > > + if (format->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) > > + set_compression_format(format->fmt.pix.pixelformat); > > + if (format->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) > > + set_compression_format(format->fmt.pix_mp.pixelformat); > > +} > > + > > +void s_fmt_setup(struct v4l2_format *format) > > +{ > > + if (format->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) > > + set_compression_format(format->fmt.pix.pixelformat); > > + if (format->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) > > + set_compression_format(format->fmt.pix_mp.pixelformat); > > +} > > + > > +void expbuf_setup(struct v4l2_exportbuffer *export_buffer) > > +{ > > + __u32 type = export_buffer->type; > > + __u32 index = export_buffer->index; > > + int fd_found_in_trace_context = get_buffer_fd_trace(type, index); > > + > > + /* If the buffer was already added to the trace context don't add it again. */ > > + if (fd_found_in_trace_context == export_buffer->fd) > > + return; > > + > > + /* > > + * If a buffer was previously added to the trace context using the video device > > + * file descriptor, replace the video fd with the more specific buffer fd from EXPBUF. > > + */ > > + if (fd_found_in_trace_context) > > + remove_buffer_trace(fd_found_in_trace_context); > > + > > + add_buffer_trace(export_buffer->fd, type, index); > > +} > > + > > +void querybuf_setup(int fd, struct v4l2_buffer *buf) > > +{ > > + /* If the buffer was already added to the trace context don't add it again. */ > > + if (get_buffer_fd_trace(buf->type, buf->index)) > > + return; > > + > > + switch (buf->memory) { > > + case V4L2_MEMORY_MMAP: { > > + __u32 offset = 0; > > + if ((buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) || > > + (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)) > > + offset = buf->m.offset; > > + if ((buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) || > > + (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)) > > + offset = buf->m.planes->m.mem_offset; > > + add_buffer_trace(fd, buf->type, buf->index, offset); > > + break; > > + } > > + case V4L2_MEMORY_USERPTR: > > + case V4L2_MEMORY_DMABUF: > > + default: > > + break; > > + } > > +} > > + > > +void write_json_object_to_json_file(json_object *jobj, int flags) > > +{ > > + if (options.pretty_print_all) > > + flags = JSON_C_TO_STRING_PRETTY; > > + > > + std::string json_str = json_object_to_json_string_ext(jobj, flags); > > + pthread_mutex_lock(&ctx_trace.lock); > > + > > + if (!ctx_trace.trace_file) { > > + ctx_trace.trace_filename = getenv("TRACE_ID"); > > + ctx_trace.trace_filename += ".json"; > > + ctx_trace.trace_file = fopen(ctx_trace.trace_filename.c_str(), "a"); > > + } > > + > > + fwrite(json_str.c_str(), sizeof(char), json_str.length(), ctx_trace.trace_file); > > + fputs(",\n", ctx_trace.trace_file); > > + fflush(ctx_trace.trace_file); > > + pthread_mutex_unlock(&ctx_trace.lock); > > +} > > + > > +void close_json_file(void) > > +{ > > + pthread_mutex_lock(&ctx_trace.lock); > > + if (ctx_trace.trace_file) { > > + fclose(ctx_trace.trace_file); > > + ctx_trace.trace_file = 0; > > + } > > + pthread_mutex_unlock(&ctx_trace.lock); > > +} > > <snip> > > > diff --git a/utils/v4l2-tracer/v4l2-tracer-gen.pl b/utils/v4l2-tracer/v4l2-tracer-gen.pl > > new file mode 100755 > > index 00000000..ca17d7e2 > > --- /dev/null > > +++ b/utils/v4l2-tracer/v4l2-tracer-gen.pl > > @@ -0,0 +1,359 @@ > > +#!/usr/bin/perl > > +# SPDX-License-Identifier: GPL-2.0-only */ > > +# Copyright 2022 Collabora Ltd. > > I am officially impressed! This is very nice work! thanks :) perl is my friend now > > > + > > +sub convert_type_to_json_type { > > + my $type = shift; > > + if ($type eq __u8 || $type eq __u16 || $type eq __s8 || $type eq __s16 || $type eq __s32) { > > + return "int"; > > + } > > + if ($type eq __u32) { > > + return "int64"; > > + } > > + if ($type eq __u64) { > > + return "uint64"; > > + } > > + if ($type eq struct) { > > + return; > > + } > > + print "v4l2_tracer: error: couldn't convert $type to json_object type.\n"; > > + return; > > +} > > + > > +sub get_index_letter { > > + my $index = shift; > > + if ($index eq 0) {return "i";} > > + if ($index eq 1) {return "j";} > > + if ($index eq 2) {return "k";} > > + if ($index eq 3) {return "l";} > > + if ($index eq 4) {return "m";} > > + if ($index eq 5) {return "n";} > > + if ($index eq 6) {return "o";} # "p" is saved for pointer > > + if ($index eq 8) {return "q";} > > + return "z"; > > +} > > + > > +$flag_func_name; > > + > > +sub flag_gen { > > + my $flag_type = shift; > > + > > + if ($flag_type =~ /fwht/) { > > + $flag_func_name = v4l2_ctrl_fwht_params_; > > + } elsif ($flag_type =~ /vp8_loop_filter/) { > > + $flag_func_name = v4l2_vp8_loop_filter_; > > + } else { > > + ($flag_func_name) = ($_) =~ /#define (\w+_)FLAG_.+/; > > + $flag_func_name = lc $flag_func_name; > > + } > > + printf $fh_trace_info_cpp "std::string %sflag2s(unsigned long flag)\n{\n", $flag_func_name; > > + printf $fh_trace_info_h "std::string %sflag2s(unsigned long flag);\n", $flag_func_name; > > + printf $fh_trace_info_cpp "\tstatic constexpr flag_def def[] = {\n"; > > + ($flag) = ($_) =~ /#define\s+(\w+)\s+.+/; > > + printf $fh_trace_info_cpp "\t\t{ $flag, \"$flag\" },\n"; > > + while (<>) { > > + next if ($_ =~ /^\/?\s?\*.*/); #skip comments between flags if any > > + next if $_ =~ /^\s*$/; #skip blank lines between flags if any > > + last if ((grep {!/^#define\s+\w+_FL/} $_) && (grep {!/^#define V4L2_VP8_LF/} $_)); > > + ($flag) = ($_) =~ /#define\s+(\w+)\s+.+/; > > + printf $fh_trace_info_cpp "\t\t{ $flag, \"$flag\" },\n"; > > + } > > + printf $fh_trace_info_cpp "\t\t{ 0, nullptr }\n\t};\n\treturn flags2s(flag, def);\n}\n\n"; > > +} > > + > > +sub struct_gen { > > + ($struct_name) = ($_) =~ /struct (\w+) {/; > > + > > + printf $fh_trace_h "void trace_%s_gen(void *ptr, json_object *parent_obj);\n", $struct_name; > > + printf $fh_trace_cpp "void trace_%s_gen(void *ptr, json_object *parent_obj)\n{\n", $struct_name; > > + printf $fh_trace_cpp "\tjson_object *%s_obj = json_object_new_object();\n", $struct_name; > > + printf $fh_trace_cpp "\tstruct %s *p = static_cast<struct %s*>(ptr);\n", $struct_name, $struct_name; > > + > > + printf $fh_retrace_h "struct %s *retrace_%s_gen(json_object *ctrl_obj);\n", $struct_name, $struct_name; > > + printf $fh_retrace_cpp "struct %s *retrace_%s_gen(json_object *ctrl_obj)\n{\n", $struct_name, $struct_name; > > + printf $fh_retrace_cpp "\tstruct %s *p = (struct %s *) calloc(1, sizeof(%s));\n", $struct_name, $struct_name, $struct_name; > > + printf $fh_retrace_cpp "\tjson_object *%s_obj;\n", $struct_name; > > + # if a key value isn't found, assume it is retracing an element of an array > > + printf $fh_retrace_cpp "\tif (!json_object_object_get_ex(ctrl_obj, \"%s\", &%s_obj))\n", $struct_name, $struct_name; > > + printf $fh_retrace_cpp "\t\t%s_obj = ctrl_obj;\n", $struct_name; > > + > > + while ($line = <> ) { > > + chomp($line); > > + last if $line =~ /};/; > > + next if $line =~ /^\s*$/; # ignore blank lines > > + next if $line =~ /\/\*/; # ignore comment lines > > + $line =~ s/^\s+//; # remove leading whitespace > > + $line =~ s/;$//; # remove semi-colon at the end > > + my @words = split /[\s\[]/, $line; # also split on '[' to avoid arrays > > + @words = grep {/^\D/} @words; # remove values that start with digit from inside [] > > + @words = grep {!/\]/} @words; # remove values with brackets e.g. V4L2_H264_REF_LIST_LEN] > > + > > + ($type) = $words[0]; > > + $json_type = convert_type_to_json_type($type); > > + > > + ($member) = $words[scalar @words - 1]; > > + next if $member =~ /reserved/; # ignore reserved members, they will segfault on retracing > > + > > + # generate members that are arrays > > + if ($line =~ /.*\[.*/) { > > + printf $fh_trace_cpp "\t\/\* %s \*\/\n", $line; #trace comment > > + printf $fh_trace_cpp "\tjson_object *%s_obj = json_object_new_array();\n", $member; > > + > > + printf $fh_retrace_cpp "\n\t\/\* %s \*\/\n", $line; #retrace comment > > + > > + my @dimensions = ($line) =~ /\[(\w+)\]/g; > > + $dimensions_count = scalar @dimensions; > > + > > + if ($dimensions_count > 1) { > > + printf $fh_retrace_cpp "\tint count_%s = 0;\n", $member; > > + } > > + printf $fh_retrace_cpp "\tjson_object *%s_obj;\n", $member; > > + printf $fh_retrace_cpp "\tjson_object_object_get_ex(%s_obj, \"%s\", &%s_obj);\n", $struct_name, $member, $member; > > + > > + for (my $idx = 0; $idx < $dimensions_count; $idx = $idx + 1 ) { > > + $size = $dimensions[$idx]; > > + $index_letter = get_index_letter($idx); > > + printf $fh_trace_cpp "\t" x ($idx + 1); > > + printf $fh_trace_cpp "for (size_t %s = 0; %s < %s\; %s++) {\n", $index_letter, $index_letter, $size, $index_letter; > > + > > + printf $fh_retrace_cpp "\t" x ($idx + 1); > > + printf $fh_retrace_cpp "for (size_t %s = 0; %s < %s\; %s++) {\n", $index_letter, $index_letter, $size, $index_letter; > > + } > > + printf $fh_trace_cpp "\t" x ($dimensions_count + 1); > > + printf $fh_retrace_cpp "\t" x ($dimensions_count + 1); > > + > > + # handle arrays of structs > > + if ($type =~ /struct/) { > > + my $struct_name_nested = @words[1]; > > + printf $fh_trace_cpp "json_object *element_obj = json_object_new_object();\n"; > > + printf $fh_trace_cpp "\t" x ($dimensions_count + 1); > > + printf $fh_trace_cpp "trace_%s_gen(&(p->%s", $struct_name_nested, $member; > > + for (my $idx = 0; $idx < $dimensions_count; $idx = $idx + 1 ) { > > + printf $fh_trace_cpp "[%s]", get_index_letter($idx); > > + } > > + printf $fh_trace_cpp "), element_obj);\n"; > > + printf $fh_trace_cpp "\t" x ($dimensions_count + 1); > > + printf $fh_trace_cpp "json_object *element_no_key_obj;\n"; > > + printf $fh_trace_cpp "\t" x ($dimensions_count + 1); > > + printf $fh_trace_cpp "json_object_object_get_ex(element_obj, \"%s\", &element_no_key_obj);\n", $struct_name_nested; > > + printf $fh_trace_cpp "\t" x ($dimensions_count + 1); > > + printf $fh_trace_cpp "json_object_array_add(%s_obj, element_no_key_obj);\n", $member; > > + > > + printf $fh_retrace_cpp "p->%s", $member; > > + for (my $idx = 0; $idx < $dimensions_count; $idx = $idx + 1) { > > + printf $fh_retrace_cpp "[%s]", get_index_letter($idx); > > + } > > + printf $fh_retrace_cpp " = *retrace_%s_gen(json_object_array_get_idx(%s_obj, ", $struct_name_nested, $member; > > + if ($dimensions_count > 1) { > > + printf $fh_retrace_cpp "count_%s++", $member; > > + } else { > > + printf $fh_retrace_cpp "i"; > > + } > > + printf $fh_retrace_cpp "));\n"; > > + } else { > > + printf $fh_trace_cpp "json_object_array_add(%s_obj, json_object_new_%s(p->%s", $member, $json_type, $member; > > + for (my $idx = 0; $idx < $dimensions_count; $idx = $idx + 1 ) { > > + printf $fh_trace_cpp "[%s]", get_index_letter($idx); > > + } > > + printf $fh_trace_cpp "));\n"; > > + > > + printf $fh_retrace_cpp "p->%s", $member; > > + for (my $idx = 0; $idx < $dimensions_count; $idx = $idx + 1 ) { > > + printf $fh_retrace_cpp "[%s]", get_index_letter($idx); > > + } > > + printf $fh_retrace_cpp " = ($type) json_object_get_%s(json_object_array_get_idx(%s_obj, ", $json_type, $member; > > + if ($dimensions_count > 1) { > > + printf $fh_retrace_cpp "count_%s++", $member; > > + } else { > > + printf $fh_retrace_cpp "i"; > > + } > > + printf $fh_retrace_cpp "));\n"; > > + } > > + #closing brackets for all array types > > + for (my $idx = $dimensions_count - 1; $idx >= 0 ; $idx = $idx - 1) { > > + printf $fh_trace_cpp "\t" x ($idx + 1); > > + printf $fh_trace_cpp "}\n"; > > + > > + printf $fh_retrace_cpp "\t" x ($idx + 1); > > + printf $fh_retrace_cpp "}\n"; > > + } > > + printf $fh_trace_cpp "\tjson_object_object_add(%s_obj, \"%s\", %s_obj);\n\n", $struct_name, $member, $member; > > + } elsif ($type =~ /struct/) { > > + # member that is a struct but not an array of structs > > + my $struct_name_nested = @words[1]; > > + printf $fh_trace_cpp "\t\/\* %s \*\/\n", $line; > > + printf $fh_trace_cpp "\ttrace_%s_gen(&(p->%s), %s_obj);\n", $struct_name_nested, $member, $struct_name; > > + > > + printf $fh_retrace_cpp "\t\/\* %s \*\/\n", $line; > > + printf $fh_retrace_cpp "\tjson_object *%s_obj;\n", $member; > > + printf $fh_retrace_cpp "\tjson_object_object_get_ex(%s_obj, \"%s\", &%s_obj);\n", $struct_name, $struct_name_nested, $member; > > + printf $fh_retrace_cpp "\tp->%s = \*retrace_%s_gen(%s_obj);\n\n", $member, $struct_name_nested, $member; > > + } else { > > + # members that are integers > > + printf $fh_trace_cpp "\tjson_object_object_add(%s_obj, \"%s\", json_object_new_%s(p->%s));\n", $struct_name, $member, $json_type, $member; > > + printf $fh_retrace_cpp "\n\tjson_object *%s_obj;\n", $member; > > + printf $fh_retrace_cpp "\tjson_object_object_get_ex(%s_obj, \"%s\", &%s_obj);\n", $struct_name, $member, $member; > > + printf $fh_retrace_cpp "\tp->%s = ($type) json_object_get_%s(%s_obj);\n", $member, $json_type, $member; > > + > > + # add flag strings > > + if ($member =~ /^flags/) { > > + printf $fh_trace_cpp "\tjson_object_object_add(%s_obj, \"flags_str\", json_object_new_string(%sflag2s(p->flags).c_str()));\n", $struct_name, $flag_func_name; > > + } > > + } > > + } > > + printf $fh_trace_cpp "\tjson_object_object_add(parent_obj, \"%s\", %s_obj);\n", $struct_name, $struct_name; > > + printf $fh_trace_cpp "}\n\n"; > > + > > + printf $fh_retrace_cpp "\n\treturn p;\n"; > > + printf $fh_retrace_cpp "}\n\n"; > > +} > > + > > +open($fh_trace_cpp, '>', 'trace-gen.cpp') or die "Could not open trace-gen.cpp for writing"; > > +printf $fh_trace_cpp "/* SPDX-License-Identifier: GPL-2.0-only */\n/*\n * Copyright 2022 Collabora Ltd.\n */\n\n"; > > +printf $fh_trace_cpp "#include \"v4l2-tracer-common.h\"\n#include \"trace-info-gen.h\"\n\n"; > > + > > +open($fh_trace_h, '>', 'trace-gen.h') or die "Could not open trace-gen.h for writing"; > > +printf $fh_trace_h "/* SPDX-License-Identifier: GPL-2.0-only */\n/*\n * Copyright 2022 Collabora Ltd.\n */\n\n"; > > +printf $fh_trace_h "\#ifndef TRACE_GEN_H\n"; > > +printf $fh_trace_h "\#define TRACE_GEN_H\n\n"; > > + > > +open($fh_retrace_cpp, '>', 'retrace-gen.cpp') or die "Could not open retrace-gen.cpp for writing"; > > +printf $fh_retrace_cpp "/* SPDX-License-Identifier: GPL-2.0-only */\n/*\n * Copyright 2022 Collabora Ltd.\n */\n\n"; > > +printf $fh_retrace_cpp "#include \"v4l2-tracer-common.h\"\n\n"; > > + > > +open($fh_retrace_h, '>', 'retrace-gen.h') or die "Could not open retrace-gen.h for writing"; > > +printf $fh_retrace_h "/* SPDX-License-Identifier: GPL-2.0-only */\n/*\n * Copyright 2022 Collabora Ltd.\n */\n\n"; > > +printf $fh_retrace_h "\#ifndef RETRACE_GEN_H\n"; > > +printf $fh_retrace_h "\#define RETRACE_GEN_H\n\n"; > > + > > +open($fh_trace_info_cpp, '>', 'trace-info-gen.cpp') or die "Could not open trace-info-gen.cpp for writing"; > > +printf $fh_trace_info_cpp "/* SPDX-License-Identifier: GPL-2.0-only */\n/*\n * Copyright 2022 Collabora Ltd.\n */\n\n"; > > +printf $fh_trace_info_cpp "#include \"v4l2-tracer-common.h\"\n#include \"trace-helper.h\"\n\n"; > > + > > +open($fh_trace_info_h, '>', 'trace-info-gen.h') or die "Could not open trace-info-gen.h for writing"; > > +printf $fh_trace_info_h "/* SPDX-License-Identifier: GPL-2.0-only */\n/*\n * Copyright 2022 Collabora Ltd.\n */\n\n"; > > +printf $fh_trace_info_h "\#ifndef TRACE_INFO_GEN_H\n"; > > +printf $fh_trace_info_h "\#define TRACE_INFO_GEN_H\n\n"; > > + > > +$in_v4l2_controls = true; > > + > > +while (<>) { > > + if (grep {/#define __LINUX_VIDEODEV2_H/} $_) {$in_v4l2_controls = false;} > > + > > + # only generate flag and struct functions for v4l2-controls.h > > + if ($in_v4l2_controls eq true) { > > + if (grep {/^#define.+_FLAG_.+/} $_) { > > + flag_gen(); > > + } elsif (grep {/^#define.+FWHT_FL_.+/} $_) { > > + flag_gen("fwht"); > > + } elsif (grep {/^#define V4L2_VP8_LF.*/} $_) { > > + flag_gen("vp8_loop_filter"); > > + } > > + > > + if (grep {/^struct/} $_) { > > + struct_gen(); > > + } > > + } > > + > > + if (grep {/^\/\* Control classes \*\//} $_) { > > + printf $fh_trace_info_cpp "std::string ctrlclass2s(__u32 id)\n{\n"; > > + printf $fh_trace_info_h "std::string ctrlclass2s(__u32 id);\n"; > > + printf $fh_trace_info_cpp "\tstatic constexpr definition defs[] = {\n"; > > + > > + while (<>) { > > + last if $_ =~ /^\s*$/; #last if blank line > > + ($ctrl_class) = ($_) =~ /#define\s*(\w+)\s+.*/; > > + printf $fh_trace_info_cpp "\t\t{ %s,\t\"%s\" },\n", $ctrl_class, $ctrl_class; > > + } > > + printf $fh_trace_info_cpp "\t\t{ 0, nullptr }\n\t};\n\treturn val2s(id & 0xff0000, defs);\n}\n\n"; > > + } > > + > > + if (grep {/^enum v4l2_memory/} $_) { > > + printf $fh_trace_info_cpp "std::string v4l2_memory2s(__u32 val)\n{\n"; > > + printf $fh_trace_info_h "std::string v4l2_memory2s(__u32 val);\n"; > > + printf $fh_trace_info_cpp "\tstatic constexpr definition defs[] = {\n"; > > + > > + while (<>) { > > + last if $_ =~ /};/; > > + ($memory_type) = ($_) =~ /\s*(\w+)\s+.*/; > > + printf $fh_trace_info_cpp "\t\t{ %s,\t\"%s\" },\n", $memory_type, $memory_type; > > + } > > + printf $fh_trace_info_cpp "\t\t{ 0, nullptr }\n\t};\n\treturn val2s(val, defs);\n}\n\n"; > > + } > > + > > + if (grep {/\/\* Values for 'capabilities' field \*\//} $_) { > > + printf $fh_trace_info_cpp "std::string capabilities2s(unsigned long flag)\n{\n"; > > + printf $fh_trace_info_h "std::string capabilities2s(unsigned long flag);\n"; > > + > > + printf $fh_trace_info_cpp "\tstatic constexpr flag_def def[] = {\n"; > > + while (<>) { > > + last if $_ =~ /.*V I D E O I M A G E F O R M A T.*/; > > + next if ($_ =~ /^\/?\s?\*.*/); #skip comments > > + next if $_ =~ /^\s*$/; #skip blank lines > > + ($cap) = ($_) =~ /#define\s+(\w+)\s+.+/; > > + printf $fh_trace_info_cpp "\t\t{ $cap, \"$cap\" },\n" > > + } > > + printf $fh_trace_info_cpp "\t\t{ 0, nullptr }\n\t};\n\treturn flags2s(flag, def);\n}\n\n"; > > + } > > + > > + if (grep {/^enum v4l2_ctrl_type/} $_) { > > + printf $fh_trace_info_cpp "std::string ctrltype2s(__u32 val)\n{\n"; > > + printf $fh_trace_info_h "std::string ctrltype2s(__u32 val);\n"; > > + printf $fh_trace_info_cpp "\tstatic constexpr definition defs[] = {\n"; > > + > > + while (<>) { > > + next if ($_ =~ /^\s?\/?\s?\*.*/); #skip comments > > + next if $_ =~ /^\s*$/; #skip blank lines > > + last if $_ =~ /};/; > > + ($ctrl_type) = ($_) =~ /\s*(\w+)\s+.*/; > > + > > + printf $fh_trace_info_cpp "\t\t{ %s,\t\"%s\" },\n", $ctrl_type, $ctrl_type; > > + } > > + printf $fh_trace_info_cpp "\t\t{ 0, nullptr }\n\t};\n\treturn val2s(val, defs);\n}\n\n"; > > + } > > + > > + if (grep {/.*I O C T L C O D E S F O R V I D E O D E V I C E S.*/} $_) { > > + printf $fh_trace_info_cpp "std::string ioctl2s_video(unsigned long request)\n{\n"; > > + printf $fh_trace_info_h "std::string ioctl2s_video(unsigned long request);\n"; > > + printf $fh_trace_info_cpp "\tstatic constexpr definition defs[] = {\n"; > > + > > + while (<>) { > > + next if ($_ =~ /^\/?\s?\*.*/); #skip comments > > + next if $_ =~ /^\s*$/; #skip blank lines > > + next if $_ =~ /^\s+/; #skip lines that start with a space > > + last if $_ =~ /^#define BASE_VIDIOC_PRIVATE/; > > + ($ioctl_name) = ($_) =~ /^#define\s*(\w+)\s*/; > > + printf $fh_trace_info_cpp "\t\t{ %s,\t\"%s\" },\n", $ioctl_name, $ioctl_name; > > + } > > + printf $fh_trace_info_cpp "\t\t{ 0, nullptr }\n\t};\n\treturn val2s(request, defs);\n}\n\n"; > > + } > > + > > + # from media.h > > + if (grep {/\/\* ioctls \*\//} $_) { > > + printf $fh_trace_info_cpp "std::string ioctl2s_media(unsigned long request)\n{\n"; > > + printf $fh_trace_info_h "std::string ioctl2s_media(unsigned long request);\n"; > > + printf $fh_trace_info_cpp "\tstatic constexpr definition defs[] = {\n"; > > + > > + while (<>) { > > + next if ($_ =~ /^\/?\s?\*.*/); #skip comments > > + next if $_ =~ /^\s*$/; #skip blank lines > > + next if $_ =~ /^\s+/; #skip lines that start with a space, comments > > + last if $_ =~ /^#define MEDIA_ENT_TYPE_SHIFT/; > > + ($ioctl_name) = ($_) =~ /^#define\s*(\w+)\s*/; > > + printf $fh_trace_info_cpp "\t\t{ %s,\t\"%s\" },\n", $ioctl_name, $ioctl_name; > > + } > > + printf $fh_trace_info_cpp "\t\t{ 0, nullptr }\n\t};\n\treturn val2s(request, defs);\n}\n\n"; > > + } > > +} > > + > > +printf $fh_trace_h "\n#endif\n"; > > +close $fh_trace_h; > > +close $fh_trace_cpp; > > + > > +printf $fh_retrace_h "\n#endif\n"; > > +close $fh_retrace_h; > > +close $fh_retrace_cpp; > > + > > +printf $fh_trace_info_h "\n#endif\n"; > > +close $fh_trace_info_h; > > +close $fh_trace_info_cpp; > > diff --git a/utils/v4l2-tracer/v4l2-tracer.1.in b/utils/v4l2-tracer/v4l2-tracer.1.in > > new file mode 100644 > > index 00000000..cd3d089b > > --- /dev/null > > +++ b/utils/v4l2-tracer/v4l2-tracer.1.in > > @@ -0,0 +1,104 @@ > > +.TH "V4L2-TRACER" "1" "October 2022" "v4l-utils @PACKAGE_VERSION@" "User Commands" > > +.SH NAME > > +v4l2-tracer - An application to trace and replay stateless video decoding. > > +.SH SYNOPSIS > > +\fBv4l2-tracer trace\fR [trace options] -- <\fItracee\fR> > > +.RS > > +.RE > > +\fBv4l2-tracer retrace\fR [retrace options] -- <\fItrace_file\fR>\fB.json\fR > > +.RS > > +.RE > > + > > +.SH DESCRIPTION > > +The v4l2-tracer utility traces, records and replays userspace applications > > +that implement the v4l2 memory-to-memory stateless video decoder interface. > > +The trace function intercepts and copies all system calls, stateless codec > > +controls and encoded data to a json-formatted trace file. The retrace function > > +independently reads and replays the json trace file. The json trace file can > > +be retraced independently from its original userspace environment and can be > > +edited to inject errors to test a driver's error handling abilities. > > + > > +.SH OPTIONS > > +.SS Trace Options > > +.TP > > +\fB\-e\fR, \fB\-\-prettymem\fR > > +Add whitespace in json file to improve readability of memory arrays. > > +.TP > > +\fB\-h\fR, \fB\-\-help\fR > > +Display trace help. > > +.TP > > +\fB\-p\fR, \fB\-\-pretty\fR > > +Add whitespace in json file to improve readability. > > +.TP > > +\fB\-r\fR, \fB\-\-raw\fR > > +Write decoded data to json file. > > This is a confusing option description. I assume you mean: > > "Write the decoded video frame data to the json file." > > "data" is just too vague. > > > +.TP > > +\fB\-v\fR, \fB\-\-verbose\fR > > +Turn on verbose reporting. > > +.TP > > +\fB\-y\fR, \fB\-\-yuv\fR > > +Write decoded data to yuv file. > > Ditto. > > Note that the same description is in the usage messages in the code. ok, will change. > > > + > > +.SS Retrace Options > > +.TP > > +\fB\-d\fR, \fB\-\-device\fR <\fIdev\fR> > > +Use a different video device than specified in the trace file. > > +.RS > > +<\fIdev\fR> must be a digit corresponding to an existing /dev/video<\fIdev\fR> > > +.RE > > +.TP > > +\fB\-h\fR, \fB\-\-help\fR > > +Display retrace help. > > +.TP > > +\fB\-m\fR, \fB\-\-media\fR <\fIdev\fR> > > +Use a different media device than specified in the trace file. > > +.RS > > +<\fIdev\fR> must be a digit corresponding to an existing /dev/media<\fIdev\fR> > > +.RE > > +.TP > > +\fB\-v\fR, \fB\-\-verbose\fR > > +Turn on verbose reporting. > > + > > +.SH EXIT STATUS > > +On success, it returns 0. Otherwise, it will return 1 or an error code. > > + > > +.SH EXAMPLES > > +Trace VP8 decoding: > > +.TP > > +.EX > > + v4l2-tracer trace -- gst-launch-1.0 -- filesrc location=test-25fps.vp8 ! parsebin ! v4l2slvp8dec ! videocodectestsink > > +.EE > > +.TP > > +Trace H264 decoding: > > +.TP > > +.EX > > + v4l2-tracer trace -- gst-launch-1.0 -- filesrc location=test-25fps.h264 ! parsebin ! v4l2slh264dec ! videocodectestsink > > +.EE > > +.TP > > +Trace FWHT decoding: > > +.TP > > +.EX > > + v4l2-tracer trace -- v4l2-ctl -d9 --stream-mmap --stream-out-mmap --stream-from-hdr test-25fps.fwht --stream-to out.yuv > > +.EE > > +.TP > > +Retrace: > > +.TP > > +.EX > > + v4l2-tracer retrace 79568_trace.json > > +.EE > > +.TP > > +Retrace using /dev/video6 and /dev/media3: > > +.TP > > +.EX > > + v4l2-tracer retrace -d 6 -m 3 79568_trace.json > > +.EE > > +.TP > > +Self-trace: > > +.TP > > +.EX > > + v4l2-tracer trace -- v4l2-tracer retrace 79568_trace.json > > +.EE > > + > > +.SH BUGS > > +Bug reports or questions about this utility should be sent to the > > +linux-media@vger.kernel.org mailinglist. > > diff --git a/utils/v4l2-tracer/v4l2-tracer.cpp b/utils/v4l2-tracer/v4l2-tracer.cpp > > new file mode 100644 > > index 00000000..71ac242a > > --- /dev/null > > +++ b/utils/v4l2-tracer/v4l2-tracer.cpp > > @@ -0,0 +1,187 @@ > > +/* SPDX-License-Identifier: GPL-2.0-only */ > > +/* > > + * Copyright 2022 Collabora Ltd. > > + */ > > + > > +#include <time.h> > > +#include <dirent.h> > > +#include <sys/wait.h> > > +#include "v4l2-tracer-common.h" > > + > > +#define STR(x) #x > > +#define STRING(x) STR(x) > > + > > +void print_help_retracer(void); > > +int retracer(int argc, char *argv[]); > > + > > +enum Option_Tracer { > > + TracerOptPrettyPrintMemoryOnly = 'e', > > + TracerOptHelp = 'h', > > + TracerOptPrettyPrintAll = 'p', > > + TracerOptWriteDecodedToJsonFile = 'r', > > + TracerOptVerbose = 'v', > > + TracerOptWriteDecodedToYUVFile = 'y', > > +}; > > + > > +static struct option long_options_tracer[] = { > > + { "prettymem", no_argument, nullptr, TracerOptPrettyPrintMemoryOnly }, > > + { "help", no_argument, nullptr, TracerOptHelp }, > > + { "pretty", no_argument, nullptr, TracerOptPrettyPrintAll }, > > + { "raw", no_argument, nullptr, TracerOptWriteDecodedToJsonFile }, > > + { "verbose", no_argument, nullptr, TracerOptVerbose }, > > + { "yuv", no_argument, nullptr, TracerOptWriteDecodedToYUVFile }, > > + { nullptr, 0, nullptr, 0 } > > +}; > > + > > +char short_options_tracer[] = { > > + TracerOptPrettyPrintMemoryOnly, > > + TracerOptHelp, > > + TracerOptPrettyPrintAll, > > + TracerOptWriteDecodedToJsonFile, > > + TracerOptVerbose, > > + TracerOptWriteDecodedToYUVFile > > +}; > > + > > +void print_help_tracer(void) > > +{ > > + fprintf(stderr, "v4l2-tracer trace [trace options] -- <tracee>\n" > > + > > + "\t-e, --prettymem Add whitespace in json file to improve readability of memory arrays.\n" > > + "\t-h, --help Display trace help.\n" > > + "\t-p, --pretty Add whitespace in json file to improve readability.\n" > > + "\t-r --raw Write decoded data to json file.\n" > > + "\t-v, --verbose Turn on verbose reporting.\n" > > + "\t-y, --yuv Write decoded data to yuv file.\n\n"); > > +} > > + > > +void print_usage(void) > > +{ > > + print_help_tracer(); > > + print_help_retracer(); > > +} > > + > > +void get_options_trace(int argc, char *argv[]) > > +{ > > + int ch; > > + > > + do { > > + ch = getopt_long(argc, argv, short_options_tracer, long_options_tracer, NULL); > > + switch (ch){ > > + case TracerOptPrettyPrintMemoryOnly: > > + setenv("TRACE_OPTION_PRETTY_PRINT_MEM", "true", 0); > > + break; > > + case TracerOptHelp: > > + break; > > + case TracerOptPrettyPrintAll: > > + setenv("TRACE_OPTION_PRETTY_PRINT_ALL", "true", 0); > > + break; > > + case TracerOptWriteDecodedToJsonFile: > > + setenv("TRACE_OPTION_WRITE_DECODED_TO_JSON_FILE", "true", 0); > > + break; > > + case TracerOptVerbose: > > + setenv("TRACE_OPTION_VERBOSE", "true", 0); > > + break; > > + case TracerOptWriteDecodedToYUVFile: > > + setenv("TRACE_OPTION_WRITE_DECODED_TO_YUV_FILE", "true", 0); > > + break; > > + } > > + } while (ch != -1); > > +} > > + > > +int tracer(int argc, char *argv[]) > > +{ > > + get_options_trace(argc, argv); > > + > > + if (optind == argc) { > > + print_help_tracer(); > > + return 1; > > + } > > + > > + /* Get the application to be traced from the command line. */ > > + int count = 0; > > + char *exec_array[argc]; > > + while (optind < argc) > > + exec_array[count++] = argv[optind++]; > > + exec_array[count] = nullptr; > > + > > + /* Use a substring of the time to create a unique id for the trace. */ > > + std::string trace_id = std::to_string(time(nullptr)); > > + trace_id = trace_id.substr(5, trace_id.npos) + "_trace"; > > + setenv("TRACE_ID", trace_id.c_str(), 0); > > + > > + /* Create the trace file to hold the json-objects as a json array. */ > > + std::string trace_filename = trace_id + ".json"; > > + FILE *trace_file = fopen(trace_filename.c_str(), "w"); > > + fputs("[\n", trace_file); > > + fclose(trace_file); > > + > > + /* > > + * Preload the libv4l2tracer library. If the program is installed, load the library > > + * from its installed location, otherwise load it locally. If it's loaded locally, > > + * use ./configure --disable-dyn-libv4l. > > + */ > > + std::string libv4l2tracer_path; > > + std::string program = argv[0]; > > + std::size_t idx = program.rfind("/v4l2-tracer"); > > + if (idx != program.npos) { > > + libv4l2tracer_path = program.replace(program.begin() + idx + 1, program.end(), ".libs"); > > + DIR *dp = opendir(libv4l2tracer_path.c_str()); > > + if (dp == nullptr) > > + libv4l2tracer_path = program.replace(program.begin() + idx, program.end(), "./.libs"); > > + closedir(dp); > > + } else { > > + libv4l2tracer_path = STRING(LIBTRACER_PATH); > > + } > > + libv4l2tracer_path += "/libv4l2tracer.so"; > > + fprintf(stderr, "Loading libv4l2tracer: %s\n", libv4l2tracer_path.c_str()); > > + setenv("LD_PRELOAD", libv4l2tracer_path.c_str(), 0); > > + > > + if (fork() == 0) { > > + execvpe(exec_array[0], exec_array, environ); > > + perror("Option -h for help. Could not execute application to trace."); > > + return errno; > > + } > > + > > + int tracee_result; > > + wait(&tracee_result); > > + > > + if (WEXITSTATUS(tracee_result)) { > > + fprintf(stderr, "Trace error: %s\n", trace_filename.c_str()); > > + exit(EXIT_FAILURE); > > + } > > + > > + /* Close the json-array and the trace file. */ > > + trace_file = fopen(trace_filename.c_str(), "a"); > > + fseek(trace_file, 0L, SEEK_END); > > + fputs("\n]\n", trace_file); > > + fclose(trace_file); > > + > > + fprintf(stderr, "Trace complete: "); > > + if (getenv("TRACE_OPTION_WRITE_DECODED_TO_YUV_FILE")) > > + fprintf(stderr, "%s, ", (trace_id + ".yuv").c_str()); > > + fprintf(stderr, "%s", trace_filename.c_str()); > > + fprintf(stderr, "\n"); > > + > > + return 0; > > +} > > + > > +int main(int argc, char *argv[]) > > +{ > > + int ret = 1; > > + > > + if (argc <= 1) { > > + print_usage(); > > + return ret; > > + } > > + > > + std::string command = argv[1]; > > + optind = 2; /* start looking for options only after the trace/retrace command */ > > + if (command == "trace") > > + ret = tracer(argc, argv); > > + else if (command == "retrace") > > + ret = retracer(argc, argv); > > + else > > + print_usage(); > > + > > + return ret; > > +} > > I have one more comment: I noticed that "mem_array" is output as: > > "mem_array":[ > "50a3 009d 012a 4001 f000 0007 0885 8588", > > This is confusing since it is not clear if this is little or big endian. > > Write it as: > > "50 a3 00 9d 01 2a 40 01 f0 00 00 07 08 85 85 88", Hmm, from what i can tell it is always printed to string as big endian. How does adding a space between each byte make it clear big/little? But, maybe it would be best just to always dump it without spaces. Since, truly this is not something I ever wanted to manually read. > > You can make it depend on the --prettymem option. If set, then dump it > with a space after each byte. If not set, then dump it in a compact format > (no spaces at all): "50a3009d012a4001f000000708858588". > > I think that's a good balance between compact and readable. > > Regards, > > Hans
Hi Deb, On 10/13/22 07:15, Deborah Brouwer wrote: > On Fri, Oct 07, 2022 at 03:09:13PM +0200, Hans Verkuil wrote: >> Hi Deb, > > Hi Hans - Thanks very much for your review! > I've been working on the changes and have a few responses below. > >> >> On 10/4/22 20:57, Deborah Brouwer wrote: >>> The v4l2-tracer utility traces, records and replays userspace applications >>> that implement the v4l2 memory-to-memory stateless video decoder >>> interface. The trace function intercepts and copies system calls, >>> stateless codec controls and buffers to a json-formatted trace file. >>> The retrace function independently reads and replays the json trace file. >>> The json trace file can be retraced independently from its original >>> userspace environment and can be edited to inject errors to test a >>> driver's error handling abilities. >>> >>> Signed-off-by: Deborah Brouwer <deborah.brouwer@collabora.com> >>> --- >>> Currently supports MPEG2, VP8, H264, FWHT >>> >>> Changes since v1: >>> - auto-generate code from uAPI headers >>> - generated code adds some ability to trace >>> VP9/HEVC which compiles but still needs work >>> - generated code adds full support for MPEG2 >>> - fix multiplanar decoding for vicodec >>> - change library name to libv4l2tracer >>> - update README >>> - expand help to show options >>> - remove extraneous files from gitignore >>> >>> Examples: >>> >>> Trace VP8 decoding: >>> >>> v4l2-tracer trace -- gst-launch-1.0 -- filesrc >>> location=test-25fps.vp8 ! parsebin ! v4l2slvp8dec ! >>> videocodectestsink >>> >>> Trace H264 decoding: >>> >>> v4l2-tracer trace -y -- gst-launch-1.0 -- filesrc >>> location=test-25fps.h264 ! parsebin ! v4l2slh264dec ! >>> videocodectestsink >>> >>> Trace FWHT decoding: >>> >>> v4l2-tracer trace -- v4l2-ctl -d9 --stream-mmap >>> --stream-out-mmap --stream-from-hdr test-25fps.fwht >>> --stream-to out.yuv >>> >>> Note: if tracing another v4l2 utility like >>> v4l2-ctl, either used the installed versions >>> or configure with: >>> --disable-dyn-libv4l --disable-v4l2-ctl-libv4l >>> >>> Retrace: >>> v4l2-tracer retrace 79568_trace.json >>> >>> Trace a Retrace: >>> v4l2-tracer trace -- v4l2-tracer retrace >>> 79568_trace.json >>> >>> README | 8 + >>> configure.ac | 12 + >>> utils/Makefile.am | 5 + >>> utils/v4l2-tracer/.gitignore | 8 + >>> utils/v4l2-tracer/Makefile.am | 33 + >>> utils/v4l2-tracer/libv4l2tracer.cpp | 222 ++++ >>> utils/v4l2-tracer/retrace-helper.cpp | 196 ++++ >>> utils/v4l2-tracer/retracer.cpp | 1319 ++++++++++++++++++++++++ >>> utils/v4l2-tracer/trace-helper.cpp | 816 +++++++++++++++ >>> utils/v4l2-tracer/trace-helper.h | 51 + >>> utils/v4l2-tracer/trace.cpp | 669 ++++++++++++ >>> utils/v4l2-tracer/v4l2-tracer-common.h | 40 + >>> utils/v4l2-tracer/v4l2-tracer-gen.pl | 359 +++++++ >>> utils/v4l2-tracer/v4l2-tracer.1.in | 104 ++ >>> utils/v4l2-tracer/v4l2-tracer.cpp | 187 ++++ >>> 15 files changed, 4029 insertions(+) >>> create mode 100644 utils/v4l2-tracer/.gitignore >>> create mode 100644 utils/v4l2-tracer/Makefile.am >>> create mode 100644 utils/v4l2-tracer/libv4l2tracer.cpp >>> create mode 100644 utils/v4l2-tracer/retrace-helper.cpp >>> create mode 100644 utils/v4l2-tracer/retracer.cpp >>> create mode 100644 utils/v4l2-tracer/trace-helper.cpp >>> create mode 100644 utils/v4l2-tracer/trace-helper.h >>> create mode 100644 utils/v4l2-tracer/trace.cpp >>> create mode 100644 utils/v4l2-tracer/v4l2-tracer-common.h >>> create mode 100755 utils/v4l2-tracer/v4l2-tracer-gen.pl >>> create mode 100644 utils/v4l2-tracer/v4l2-tracer.1.in >>> create mode 100644 utils/v4l2-tracer/v4l2-tracer.cpp >>> >>> diff --git a/README b/README >>> index a7759171..c5b6df16 100644 >>> --- a/README >>> +++ b/README >>> @@ -45,6 +45,8 @@ you'll need also: >>> alsa-lib-devel doxygen libjpeg-turbo-devel qt5-qtbase-devel >>> libudev-devel mesa-libGLU-devel >>> >>> +The v4l2-tracer also needs the json-c library. >>> +On Debian: libjson-c-dev; on Fedora: json-c-devel. >>> >>> After downloading and installing the needed packages, you should run: >>> >>> @@ -227,6 +229,12 @@ v4l2-sysfs-path: >>> FIXME add description. >>> Installed by make install under <prefix>/bin. >>> >>> +v4l2-tracer: >>> +Tool to trace, record and replay userspace applications >>> +that implement the v4l2 memory-to-memory stateless video >>> +decoder interface. >>> +Installed by make install under <prefix>/bin. >> >> make install -> 'make install' >> >>> + >>> xc3028-firmware: >>> Xceive XC2028/3028 tuner module firmware manipulation tool. >>> xc3028-firmware does not get installed by make install. >> >> <snip> >> >>> diff --git a/utils/v4l2-tracer/libv4l2tracer.cpp b/utils/v4l2-tracer/libv4l2tracer.cpp >>> new file mode 100644 >>> index 00000000..c344d8a1 >>> --- /dev/null >>> +++ b/utils/v4l2-tracer/libv4l2tracer.cpp >>> @@ -0,0 +1,222 @@ >>> +/* SPDX-License-Identifier: GPL-2.0-only */ >>> +/* >>> + * Copyright 2022 Collabora Ltd. >>> + */ >>> + >>> +#include <dlfcn.h> >>> +#include <stdarg.h> >>> +#include <config.h> /* For PROMOTED_MODE_T */ >>> +#include "v4l2-tracer-common.h" >>> +#include "trace-helper.h" >>> + >>> +/* from trace.cpp */ >>> +void trace_open(int fd, const char *path, int oflag, mode_t mode, bool is_open64); >>> +void trace_mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off, >>> + unsigned long buf_address, bool is_mmap64); >>> +void trace_mem(int fd, __u32 offset, __u32 type, int index, __u32 bytesused, unsigned long start); >>> +void trace_mem_encoded(int fd, __u32 offset); >>> +json_object *trace_ioctl_args(int fd, unsigned long request, void *arg, >>> + bool from_userspace = true); >>> + >>> +int open(const char *path, int oflag, ...) >>> +{ >>> + errno = 0; >>> + mode_t mode = 0; >>> + >>> + if (oflag & O_CREAT) { >>> + va_list ap; >>> + va_start(ap, oflag); >>> + mode = va_arg(ap, PROMOTED_MODE_T); >>> + va_end(ap); >>> + } >>> + >>> + int (*original_open)(const char *path, int oflag, ...); >>> + original_open = (int (*)(const char*, int, ...)) dlsym(RTLD_NEXT, "open"); >>> + int fd = (*original_open)(path, oflag, mode); >>> + >>> + if (is_video_or_media_device(path)) { >>> + if (!options_are_set()) >>> + set_options(); >>> + add_device(fd, path); >>> + trace_open(fd, path, oflag, mode, false); >>> + } >>> + >>> + return fd; >>> +} >>> + >>> +int open64(const char *path, int oflag, ...) >>> +{ >>> + errno = 0; >>> + mode_t mode = 0; >>> + if (oflag & O_CREAT) { >>> + va_list ap; >>> + va_start(ap, oflag); >>> + mode = va_arg(ap, PROMOTED_MODE_T); >>> + va_end(ap); >>> + } >>> + >>> + int (*original_open64)(const char *path, int oflag, ...); >>> + original_open64 = (int (*)(const char*, int, ...)) dlsym(RTLD_NEXT, "open64"); >>> + int fd = (*original_open64)(path, oflag, mode); >>> + >>> + if (is_video_or_media_device(path)) { >>> + if (!options_are_set()) >>> + set_options(); >>> + add_device(fd, path); >>> + trace_open(fd, path, oflag, mode, true); >>> + } >>> + >>> + return fd; >>> +} >>> + >>> +int close(int fd) >>> +{ >>> + errno = 0; >>> + std::string path = get_device(fd); >>> + >>> + /* Only trace the close if a corresponding open was also traced. */ >>> + if (!path.empty()) { >>> + json_object *close_obj = json_object_new_object(); >>> + json_object_object_add(close_obj, "syscall_str", json_object_new_string("close")); >>> + json_object_object_add(close_obj, "syscall", json_object_new_int(LIBTRACER_SYSCALL_CLOSE)); >> >> Do you need to provide both a syscall_str and syscall value? Wouldn't the string be sufficient? >> >> The replayer can just map the string to the syscall number. > > Yes, that makes sense. Since there are so few syscalls traced, I have > been using an enum of the syscalls that the tracer uses, instead of > their actual syscall numbers. So it would look like e.g.: > {"syscall":"ioctl","fd":4,"cmd":"MEDIA_IOC_G_TOPOLOGY"}, > > and then then string would map back to the enum e.g.: > > constexpr val_def defs_libv4l2tracer_syscall[] = { > { LIBV4L2TRACER_SYSCALL_IOCTL, "ioctl" }, > > >> >>> + json_object_object_add(close_obj, "fd", json_object_new_int(fd)); >>> + json_object_object_add(close_obj, "path", json_object_new_string(path.c_str())); >>> + write_json_object_to_json_file(close_obj); >>> + json_object_put(close_obj); >>> + remove_device(fd); >>> + >>> + /* If we removed the last device, close the json trace file. */ >>> + if (!count_devices()) >>> + close_json_file(); >>> + } >>> + >>> + int (*original_close)(int fd); >>> + original_close = (int (*)(int)) dlsym(RTLD_NEXT, "close"); >>> + >>> + return (*original_close)(fd); >>> +} >>> + >>> +void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off) >>> +{ >>> + errno = 0; >>> + void *(*original_mmap)(void *addr, size_t len, int prot, int flags, int fildes, off_t off); >>> + original_mmap = (void*(*)(void*, size_t, int, int, int, off_t)) dlsym(RTLD_NEXT, "mmap"); >>> + void *buf_address_pointer = (*original_mmap)(addr, len, prot, flags, fildes, off); >>> + >>> + set_buffer_address_trace(fildes, off, (unsigned long) buf_address_pointer); >>> + >>> + if (buffer_in_trace_context(fildes, off)) >>> + trace_mmap(addr, len, prot, flags, fildes, off, (unsigned long) buf_address_pointer, false); >>> + >>> + return buf_address_pointer; >>> +} >>> + >>> +void *mmap64(void *addr, size_t len, int prot, int flags, int fildes, off_t off) >>> +{ >>> + errno = 0; >>> + void *(*original_mmap64)(void *addr, size_t len, int prot, int flags, int fildes, off_t off); >>> + original_mmap64 = (void*(*)(void*, size_t, int, int, int, off_t)) dlsym(RTLD_NEXT, "mmap64"); >>> + void *buf_address_pointer = (*original_mmap64)(addr, len, prot, flags, fildes, off); >>> + >>> + set_buffer_address_trace(fildes, off, (unsigned long) buf_address_pointer); >>> + >>> + if (buffer_in_trace_context(fildes, off)) >>> + trace_mmap(addr, len, prot, flags, fildes, off, (unsigned long) buf_address_pointer, true); >>> + >>> + return buf_address_pointer; >>> +} >>> + >>> +int munmap(void *start, size_t length) >>> +{ >>> + errno = 0; >>> + int(*original_munmap)(void *start, size_t length); >>> + original_munmap = (int(*)(void *, size_t)) dlsym(RTLD_NEXT, "munmap"); >>> + int ret = (*original_munmap)(start, length); >>> + >>> + /* Only trace the unmapping if the original mapping was traced. */ >>> + if (!buffer_is_mapped((unsigned long) start)) >>> + return ret; >>> + >>> + json_object *munmap_obj = json_object_new_object(); >>> + json_object_object_add(munmap_obj, "syscall_str", json_object_new_string("munmap")); >>> + json_object_object_add(munmap_obj, "syscall", json_object_new_int(LIBTRACER_SYSCALL_MUNMAP)); >>> + >>> + if (errno) { >>> + json_object_object_add(munmap_obj, "errno", json_object_new_int(errno)); >>> + json_object_object_add(munmap_obj, "errno_str", json_object_new_string(strerror(errno))); >>> + } >>> + >>> + json_object *munmap_args = json_object_new_object(); >>> + json_object_object_add(munmap_args, "start", json_object_new_int64((int64_t)start)); >>> + json_object_object_add(munmap_args, "length", json_object_new_uint64(length)); >>> + json_object_object_add(munmap_obj, "munmap_args", munmap_args); >>> + >>> + write_json_object_to_json_file(munmap_obj); >>> + json_object_put(munmap_obj); >>> + >>> + return ret; >>> +} >>> + >>> +int ioctl(int fd, unsigned long request, ...) >> >> I think 'request' is a confusing name, esp. since codecs use the Request API. >> I see it comes from the man page, but in this case I would rename it to 'cmd', >> which is what it is called in e.g. v4l2-ioctl.c and fs/ioctl.c. > > Agreed - I had just gotten used to that but it is confusing. > >> >>> +{ >>> + errno = 0; >>> + va_list ap; >>> + va_start(ap, request); >>> + void *arg = va_arg(ap, void *); >>> + va_end(ap); >>> + >>> + int (*original_ioctl)(int fd, unsigned long request, ...); >>> + original_ioctl = (int (*)(int, long unsigned int, ...)) dlsym(RTLD_NEXT, "ioctl"); >>> + >>> + if ((request == VIDIOC_S_EXT_CTRLS) && (_IOC_TYPE(request) == 'V')) >> >> Why check _IOC_TYPE? That's already encoded in VIDIOC_S_EXT_CTRLS, so testing for >> that should be sufficient. Ditto for the same checks below. > > OK, will fix. > >> >>> + s_ext_ctrls_setup(static_cast<struct v4l2_ext_controls*>(arg)); >>> + >>> + if ((request == VIDIOC_QBUF) && (_IOC_TYPE(request) == 'V')) >>> + qbuf_setup(static_cast<struct v4l2_buffer*>(arg)); >>> + >>> + if ((request == VIDIOC_STREAMOFF) && (_IOC_TYPE(request) == 'V')) >>> + streamoff_cleanup(*(static_cast<v4l2_buf_type*>(arg))); >>> + >>> + json_object *ioctl_obj = json_object_new_object(); >>> + json_object_object_add(ioctl_obj, "syscall_str", json_object_new_string("ioctl")); >>> + json_object_object_add(ioctl_obj, "syscall", json_object_new_int(LIBTRACER_SYSCALL_IOCTL)); >>> + json_object_object_add(ioctl_obj, "fd", json_object_new_int(fd)); >>> + json_object_object_add(ioctl_obj, "request", json_object_new_uint64(request)); >>> + json_object_object_add(ioctl_obj, "request_str", >>> + json_object_new_string(get_ioctl_request_str(request).c_str())); >> >> So this would be renamed to "cmd" and "cmd_str". But, do you really need both? >> I think you could just store the string: >> >> "syscall_str":"ioctl","syscall":0,"fd":5,"cmd":"VIDIOC_QUERYCAP" >> >> When you replay it, it is easy enough to map the cmd string to the actual value. >> >> If for some reason a non-V4L2 cmd value is used, then you can write "0xHHHHHHHH" >> as the value, and the replay would take that as the cmd hex value. > > One issue i've struggled with is whether to trace ioctls that aren't in > videodev2.h or media.h. For example one that comes up is DMA_BUF_IOCTL_SYNC. > Since the purpose of the v4l2-tracer isn't to comprehensively trace > system calls, I'm leaning toward not tracing these ioctls at all. They > aren't needed by the retracer, the retracer won't replay them because it > won't have all their arguments, and they add clutter making it harder to spot > important differences between the trace and retrace results. I agree. > > However, it's different for just flags or other values that aren't > recognized, those can be added as a hex string and then replayed > e.g.: > "flags":"V4L2_VP8_FRAME_FLAG_SHOW_FRAME, 0x40"} > > From what I understand this could be useful to inject errors through > replaying. Correct. > >> >>> + >>> + /* Trace the ioctl arguments provided by userspace. */ >>> + json_object *ioctl_args_userspace = trace_ioctl_args(fd, request, arg); >>> + if (json_object_object_length(ioctl_args_userspace)) >>> + json_object_object_add(ioctl_obj, "ioctl_args_from_userspace", ioctl_args_userspace); >>> + >>> + /* Make the original ioctl call. */ >>> + int ret = (*original_ioctl)(fd, request, arg); >>> + >>> + if (errno) { >>> + json_object_object_add(ioctl_obj, "errno", json_object_new_int(errno));> + json_object_object_add(ioctl_obj, "errno_str", json_object_new_string(strerror(errno))); >> >> Why not just store the error define: e.g. "EINVAL". And if the errno code isn't known, >> then just store it as a number "12345". It's much shorter than using the strerror() >> result, and even easier to read. > > Yes, I would use strerrorname_np(errno) and get a result like "errno":"EINVAL". Huh, I didn't know strerrorname_np() existed! I must remember that one. > >> >> It's a general theme throughout: whenever you write "foo":F,"foo_str":"Foo bar" you >> can also write: "foo":"FOO" where FOO is the V4L2 define, or a decimal or hex value >> if the value didn't map to a define. >> >> So this: >> >> { >> "syscall_str":"ioctl", >> "syscall":0, >> "fd":5, >> "request":3234878981, >> "request_str":"VIDIOC_S_FMT", >> "ioctl_args_from_userspace":{ >> "v4l2_format":{ >> "type":10, >> "type_str":"Video Output Multiplanar", >> "v4l2_pix_format_mplane":{ >> "width":320, >> "height":240, >> "pixelformat":875967059, >> "pixelformat_str":"H.264 Parsed Slice Data", >> "fcc_str":"S264", >> "field":0, >> "field_str":"Any", >> "colorspace":0, >> "colorspace_str":"Default", >> "num_planes":0, >> "flags":0, >> "flags_str":"", >> "ycbcr_enc":0, >> "ycbcr_enc_str":"Default", >> "quantization":0, >> "quantization_str":"Default", >> "xfer_func":0, >> "xfer_func_str":"Default" >> } >> } >> }, >> >> could become: >> >> { >> "syscall":"ioctl", >> "fd":5, >> "cmd":"VIDIOC_S_FMT", >> "ioctl_args_from_userspace":{ >> "v4l2_format":{ >> "type":"V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE", >> "v4l2_pix_format_mplane":{ >> "width":320, >> "height":240, >> "pixelformat":"V4L2_PIX_FMT_H264_SLICE", >> "fcc_str":"S264", // I'm not sure if this is useful or not >> "field":"V4L2_FIELD_ANY", >> "colorspace":"V4L2_COLORSPACE_DEFAULT", >> "num_planes":0, >> "flags":0, >> "flags_str":"", // I'm not sure if this is useful >> "ycbcr_enc":"V4L2_YCBCR_ENC_DEFAULT", >> "quantization":"V4L2_QUANTIZATION_DEFAULT", >> "xfer_func":"V4L2_XFER_FUNC_DEFAULT", >> } >> } >> }, >> >> >> Here is a more representative example of "flags": >> >> "flags":8404993, >> "flags_str":"mapped, request-fd, ts-copy, ts-src-eof", >> > > Sounds good, I have been working on just using strings and then > converting them back to values in the retracer. So there would be just > one entry for values that are more informative as strings like buffer > type and flags e.g. > "flags":"V4L2_VP8_LF_ADJ_ENABLE, V4L2_VP8_LF_DELTA_UPDATE"} > > Then the retracer would turn it back to an integer before making the > call. The autogenerated code from v4l2-controls.h makes this easy to do > without typos. > > >> I'm wondering if flags_str is really useful. One question I have is whether >> it is possible to direct json to write "flags" as a hex value instead of >> decimal. It's much easier to decode a hex value to the corresponding bits >> than doing that with a decimal value. > > No, as far as i know, the json-c library just has decimal integers and strings. > But I can just convert unknown numbers to a hex string (and then reverse for the retracer) > and this is what I would do for unrecognized flags or other values. > >> >> The other issue is that it makes the json file larger. >> >> I'm actually wondering if it wouldn't be better to keep the json file >> as compact as possible, since it is not human readable regardless of how >> you massage it :-). >> >> Instead I wonder if it would be possible to add an option that reads the >> json file and then outputs a really human readable version. >> >> I'm not sure what is easiest/best here. > > I have tried it both ways - making the json-file super compact with just > numbers and no strings and also, the awkward hybrid that you see with > the values and then value_str to follow. The reason I like to see the > strings is that I find myself looking at the json file a lot to compare > or see what is happening or has gone wrong. I found it annoying to leave > that information out of the json file. I think it will improve the > readability to get rid of the duplication as you suggest. A separate > human-readable file without json and with options for controlling the > level of detail would be good too, but maybe a future improvement. Let's see what the json file looks like after these changes. > >> >>> + } >>> + >>> + /* Also trace the ioctl arguments as modified by the driver. */ >>> + json_object *ioctl_args_driver = trace_ioctl_args(fd, request, arg, false); >>> + if (json_object_object_length(ioctl_args_driver)) >>> + json_object_object_add(ioctl_obj, "ioctl_args_from_driver", ioctl_args_driver); >>> + >>> + write_json_object_to_json_file(ioctl_obj); >>> + json_object_put(ioctl_obj); >>> + >>> + if ((request == VIDIOC_G_FMT) && (_IOC_TYPE(request) == 'V')) >>> + g_fmt_setup_trace(static_cast<struct v4l2_format*>(arg)); >>> + >>> + if ((request == VIDIOC_S_FMT) && (_IOC_TYPE(request) == 'V')) >>> + s_fmt_setup(static_cast<struct v4l2_format*>(arg)); >>> + >>> + if ((request == VIDIOC_EXPBUF) && (_IOC_TYPE(request) == 'V')) >>> + expbuf_setup(static_cast<struct v4l2_exportbuffer*>(arg)); >>> + >>> + if ((request == VIDIOC_QUERYBUF) && (_IOC_TYPE(request) == 'V')) >>> + querybuf_setup(fd, static_cast<struct v4l2_buffer*>(arg)); >>> + >>> + return ret; >>> +} >>> diff --git a/utils/v4l2-tracer/retrace-helper.cpp b/utils/v4l2-tracer/retrace-helper.cpp >>> new file mode 100644 >>> index 00000000..37fad2c3 >>> --- /dev/null >>> +++ b/utils/v4l2-tracer/retrace-helper.cpp >>> @@ -0,0 +1,196 @@ >>> +/* SPDX-License-Identifier: GPL-2.0-only */ >>> +/* >>> + * Copyright 2022 Collabora Ltd. >>> + */ >>> + >>> +#include "v4l2-tracer-common.h" >>> + >>> +struct buffer_retrace { >>> + int fd; >>> + __u32 type; /* enum v4l2_buf_type */ >>> + __u32 index; >>> + __u32 offset; >>> + long address_trace; >>> + long address_retrace; >>> +}; >>> + >>> +struct retrace_context { >>> + __u32 width; >>> + __u32 height; >>> + __u32 pixelformat; >>> + pthread_mutex_t lock; >>> + /* Key is a file descriptor from the trace, value is the corresponding fd in the retrace. */ >>> + std::unordered_map<int, int> retrace_fds; >>> + /* List of output and capture buffers being retraced. */ >>> + std::list<struct buffer_retrace> buffers; >>> +}; >>> + >>> +static struct retrace_context ctx_retrace = { >>> + .lock = PTHREAD_MUTEX_INITIALIZER >>> +}; >>> + >>> +bool buffer_in_retrace_context(int fd, __u32 offset) >>> +{ >>> + bool buffer_in_retrace_context = false; >>> + pthread_mutex_lock(&ctx_retrace.lock); >>> + for (auto &b : ctx_retrace.buffers) { >>> + if ((b.fd == fd) && (b.offset == offset)) { >>> + buffer_in_retrace_context = true; >>> + break; >>> + } >>> + } >>> + pthread_mutex_unlock(&ctx_retrace.lock); >>> + return buffer_in_retrace_context; >>> +} >>> + >>> +int get_buffer_fd_retrace(__u32 type, __u32 index) >>> +{ >>> + int fd = 0; >> >> Hmm, strictly speaking 0 is a valid file descriptor number: >> typically it maps to stdin. Usually -1 is used to denote an >> invalid fd. > > Ok, I will fix. > >> >>> + pthread_mutex_lock(&ctx_retrace.lock); >>> + for (auto &b : ctx_retrace.buffers) { >>> + if ((b.type == type) && (b.index == index)) { >>> + fd = b.fd; >>> + break; >>> + } >>> + } >>> + pthread_mutex_unlock(&ctx_retrace.lock); >>> + return fd; >>> +} >>> + >>> +void add_buffer_retrace(int fd, __u32 type, __u32 index, __u32 offset) >>> +{ >>> + pthread_mutex_lock(&ctx_retrace.lock); >>> + struct buffer_retrace buf = {}; >>> + buf.fd = fd; >>> + buf.type = type; >>> + buf.index = index; >>> + buf.offset = offset; >>> + ctx_retrace.buffers.push_front(buf); >>> + pthread_mutex_unlock(&ctx_retrace.lock); >>> +} >>> + >>> +void remove_buffer_retrace(int fd) >>> +{ >>> + pthread_mutex_lock(&ctx_retrace.lock); >>> + for (auto it = ctx_retrace.buffers.begin(); it != ctx_retrace.buffers.end(); ++it) { >>> + if (it->fd == fd) { >>> + ctx_retrace.buffers.erase(it); >>> + break; >>> + } >>> + } >>> + pthread_mutex_unlock(&ctx_retrace.lock); >>> +} >>> + >>> +void set_buffer_address_retrace(int fd, __u32 offset, long address_trace, long address_retrace) >>> +{ >>> + pthread_mutex_lock(&ctx_retrace.lock); >>> + for (auto &b : ctx_retrace.buffers) { >>> + if ((b.fd == fd) && (b.offset == offset)) { >>> + b.address_trace = address_trace; >>> + b.address_retrace = address_retrace; >>> + break; >>> + } >>> + } >>> + pthread_mutex_unlock(&ctx_retrace.lock); >>> +} >>> + >>> +long get_retrace_address_from_trace_address(long address_trace) >>> +{ >>> + long address_retrace = 0; >>> + pthread_mutex_lock(&ctx_retrace.lock); >>> + for (auto &b : ctx_retrace.buffers) { >>> + if (b.address_trace == address_trace) { >>> + address_retrace = b.address_retrace; >>> + break; >>> + } >>> + } >>> + pthread_mutex_unlock(&ctx_retrace.lock); >>> + return address_retrace; >>> +} >>> + >>> +void print_buffers_retrace(void) >>> +{ >>> + pthread_mutex_lock(&ctx_retrace.lock); >>> + for (auto &b : ctx_retrace.buffers) { >>> + fprintf(stderr, "fd: %d, offset: %d, address_trace:%ld, address_retrace:%ld\n", >>> + b.fd, b.offset, b.address_trace, b.address_retrace); >>> + } >>> + pthread_mutex_unlock(&ctx_retrace.lock); >>> +} >>> + >>> +/* >>> + * Create a new file descriptor entry in retrace context. >>> + * Add both the fd from the trace context and the corresponding fd from the retrace context. >>> + */ >>> +void add_fd(int fd_trace, int fd_retrace) >>> +{ >>> + std::pair<int, int> new_pair; >>> + new_pair = std::make_pair(fd_trace, fd_retrace); >>> + pthread_mutex_lock(&ctx_retrace.lock); >>> + ctx_retrace.retrace_fds.insert(new_pair); >>> + pthread_mutex_unlock(&ctx_retrace.lock); >>> +} >>> + >>> +int get_fd_retrace_from_fd_trace(int fd_trace) >>> +{ >>> + int fd_retrace = 0; >>> + std::unordered_map<int, int>::const_iterator it; >>> + >>> + pthread_mutex_lock(&ctx_retrace.lock); >>> + it = ctx_retrace.retrace_fds.find(fd_trace); >>> + if (it != ctx_retrace.retrace_fds.end()) >>> + fd_retrace = it->second; >>> + pthread_mutex_unlock(&ctx_retrace.lock); >>> + >>> + return fd_retrace; >>> +} >>> + >>> +void remove_fd(int fd_trace) >>> +{ >>> + pthread_mutex_lock(&ctx_retrace.lock); >>> + ctx_retrace.retrace_fds.erase(fd_trace); >>> + pthread_mutex_unlock(&ctx_retrace.lock); >>> +} >>> + >>> +void print_fds(void) >>> +{ >>> + pthread_mutex_lock(&ctx_retrace.lock); >>> + if (ctx_retrace.retrace_fds.empty()) >>> + fprintf(stderr, "all devices closed\n"); >>> + for (auto it = ctx_retrace.retrace_fds.cbegin(); it != ctx_retrace.retrace_fds.cend(); ++it) >>> + fprintf(stderr, "fd_trace: %d, fd_retrace: %d\n", it->first, it->second); >>> + pthread_mutex_unlock(&ctx_retrace.lock); >>> +} >>> + >>> +void set_pixelformat_retrace(__u32 width, __u32 height, __u32 pixelformat) >>> +{ >>> + pthread_mutex_lock(&ctx_retrace.lock); >>> + ctx_retrace.width = width; >>> + ctx_retrace.height = height; >>> + ctx_retrace.pixelformat = pixelformat; >>> + pthread_mutex_unlock(&ctx_retrace.lock); >>> +} >>> + >>> +unsigned get_expected_length_retrace(void) >>> +{ >>> + pthread_mutex_lock(&ctx_retrace.lock); >>> + unsigned width = ctx_retrace.width; >>> + unsigned height = ctx_retrace.height; >>> + unsigned pixelformat = ctx_retrace.pixelformat; >>> + pthread_mutex_unlock(&ctx_retrace.lock); >>> + >>> + unsigned expected_length = width * height; >>> + if (pixelformat == V4L2_PIX_FMT_NV12 || pixelformat == V4L2_PIX_FMT_YUV420) { >>> + expected_length *= 3; >>> + expected_length /= 2; >>> + expected_length += (expected_length % 2); >>> + } >>> + return expected_length; >>> +} >>> + >>> +void print_context(void) >>> +{ >>> + print_fds(); >>> + print_buffers_retrace(); >>> + fprintf(stderr, "\n"); >>> +} >> >> <snip> >> >>> diff --git a/utils/v4l2-tracer/trace-helper.cpp b/utils/v4l2-tracer/trace-helper.cpp >>> new file mode 100644 >>> index 00000000..6b4bf567 >>> --- /dev/null >>> +++ b/utils/v4l2-tracer/trace-helper.cpp >>> @@ -0,0 +1,816 @@ >>> +/* SPDX-License-Identifier: GPL-2.0-only */ >>> +/* >>> + * Copyright 2022 Collabora Ltd. >>> + */ >>> + >>> +#include <math.h> /* for pow */ >>> +#include "v4l2-tracer-common.h" >>> +#include "trace-helper.h" >>> +#include "trace-info-gen.h" >>> + >>> +void trace_mem(int fd, __u32 offset, __u32 type, int index, __u32 bytesused, unsigned long start); >>> +void trace_mem_encoded(int fd, __u32 offset); >>> + >>> +struct buffer_trace { >>> + int fd; >>> + __u32 type; /* enum v4l2_buf_type */ >>> + __u32 index; >>> + __u32 offset; >>> + __u32 bytesused; >>> + long display_order; >>> + unsigned long address; >>> +}; >>> + >>> +struct trace_context { >>> + __u32 width; >>> + __u32 height; >>> + FILE *trace_file; >>> + __u32 pixelformat; >>> + int pic_order_cnt_lsb; >>> + int max_pic_order_cnt_lsb; >>> + pthread_mutex_t lock; >>> + __u32 compression_format; >>> + std::string trace_filename; >>> + int compressed_frame_count; >>> + std::list<long> decode_order; >>> + std::list<struct buffer_trace> buffers; >>> + std::unordered_map<int, std::string> devices; /* key:fd, value: path of the device */ >>> +}; >>> + >>> +struct trace_options { >>> + bool options_are_set; >>> + bool verbose; >>> + bool pretty_print_all; >>> + bool pretty_print_mem; >>> + bool write_decoded_data_to_yuv_file; >>> + bool write_decoded_data_to_json_file; >>> +}; >>> + >>> +std::string ver2s(unsigned int version) >>> +{ >>> + char buf[16]; >>> + sprintf(buf, "%d.%d.%d", version >> 16, (version >> 8) & 0xff, version & 0xff); >>> + return buf; >>> +} >>> + >>> +std::string which2s(unsigned long which) >>> +{ >>> + std::string s = "unknown"; >>> + >>> + switch (which) { >>> + case V4L2_CTRL_WHICH_CUR_VAL: >>> + s = "V4L2_CTRL_WHICH_CUR_VAL"; >>> + break; >>> + case V4L2_CTRL_WHICH_DEF_VAL: >>> + s= "V4L2_CTRL_WHICH_DEF_VAL"; >>> + break; >>> + case V4L2_CTRL_WHICH_REQUEST_VAL: >>> + s = "V4L2_CTRL_WHICH_REQUEST_VAL"; >>> + break; >>> + default: >>> + break; >>> + } >>> + >>> + return s; >>> +} >>> + >>> +std::string val2s(unsigned long val, const definition *def) >>> +{ >>> + std::string s; >>> + >>> + if (val == 0) >>> + return s; >>> + >>> + while ((def->val) && (def->val != val)) >>> + def++; >>> + >>> + if (def->val == val) >>> + s = def->str; >>> + >>> + return s; >>> +} >>> + >>> +struct trace_options options; >>> + >>> +struct trace_context ctx_trace = { >>> + .lock = PTHREAD_MUTEX_INITIALIZER >>> +}; >>> + >>> +bool options_are_set(void) >>> +{ >>> + return options.options_are_set; >>> +} >>> + >>> +void set_options(void) >>> +{ >>> + options.verbose = getenv("TRACE_OPTION_VERBOSE") ? true : false; >>> + options.pretty_print_mem = getenv("TRACE_OPTION_PRETTY_PRINT_MEM") ? true : false; >>> + options.pretty_print_all = getenv("TRACE_OPTION_PRETTY_PRINT_ALL") ? true : false; >>> + options.write_decoded_data_to_json_file = getenv("TRACE_OPTION_WRITE_DECODED_TO_JSON_FILE") ? true : false; >>> + options.write_decoded_data_to_yuv_file = getenv("TRACE_OPTION_WRITE_DECODED_TO_YUV_FILE") ? true : false; >>> + options.options_are_set = true; >>> +} >>> + >>> +bool option_is_set_verbose(void) >>> +{ >>> + return options.verbose; >>> +} >>> + >>> +bool option_is_set_pretty_print_mem(void) >>> +{ >>> + return options.pretty_print_mem; >>> +} >>> + >>> +bool option_is_set_write_decoded_data_to_json_file(void) >>> +{ >>> + return options.write_decoded_data_to_json_file; >>> +} >>> + >>> +bool is_video_or_media_device(const char *path) >>> +{ >>> + std::string dev_path_video = "/dev/video"; >>> + std::string dev_path_media = "/dev/media"; >>> + if (strncmp(path, dev_path_video.c_str(), dev_path_video.length()) && >>> + strncmp(path, dev_path_media.c_str(), dev_path_media.length())) >>> + return false; >>> + return true; >>> +} >>> + >>> +void add_device(int fd, std::string path) >>> +{ >>> + std::pair<int, std::string> new_pair = std::make_pair(fd, path); >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + ctx_trace.devices.insert(new_pair); >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> +} >>> + >>> +std::string get_device(int fd) >>> +{ >>> + std::string path; >>> + std::unordered_map<int, std::string>::const_iterator it; >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + it = ctx_trace.devices.find(fd); >>> + if (it != ctx_trace.devices.end()) >>> + path = it->second; >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> + return path; >>> +} >>> + >>> +int remove_device(int fd) >>> +{ >>> + int ret = 0; >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + ret = ctx_trace.devices.erase(fd); >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> + return ret; >>> +} >>> + >>> +int count_devices(void) >>> +{ >>> + int count = 0; >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + count = ctx_trace.devices.size(); >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> + return count; >>> +} >>> + >>> +void set_pixelformat_trace(__u32 width, __u32 height, __u32 pixelformat) >>> +{ >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + ctx_trace.width = width; >>> + ctx_trace.height = height; >>> + ctx_trace.pixelformat = pixelformat; >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> +} >>> + >>> +__u32 get_pixelformat(void) >>> +{ >>> + __u32 pixelformat = 0; >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + pixelformat = ctx_trace.pixelformat; >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> + return pixelformat; >>> +} >>> + >>> +void set_compression_format(__u32 compression_format) >>> +{ >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + ctx_trace.compression_format = compression_format; >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> +} >>> + >>> +__u32 get_compression_format(void) >>> +{ >>> + __u32 compression_format = 0; >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + compression_format = ctx_trace.compression_format; >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> + return compression_format; >>> +} >>> + >>> +void set_compressed_frame_count(int count) >>> +{ >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + ctx_trace.compressed_frame_count = count; >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> +} >>> + >>> +int get_compressed_frame_count(void) >>> +{ >>> + int ret = 0; >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + ret = ctx_trace.compressed_frame_count; >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> + return ret; >>> +} >>> + >>> +void print_decode_order(void) >>> +{ >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + fprintf(stderr, "Decode order: "); >>> + for (auto &num : ctx_trace.decode_order) >>> + fprintf(stderr, "%ld, ", num); >>> + fprintf(stderr, ".\n"); >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> +} >>> + >>> +void set_decode_order(long decode_order) >>> +{ >>> + if (option_is_set_verbose()) >>> + fprintf(stderr, "%s: %ld\n", __func__, decode_order); >>> + >>> + std::list<long>::iterator it; >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + it = find(ctx_trace.decode_order.begin(), ctx_trace.decode_order.end(), decode_order); >>> + if (it == ctx_trace.decode_order.end()) >>> + ctx_trace.decode_order.push_front(decode_order); >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> + >>> + if (option_is_set_verbose()) >>> + print_decode_order(); >>> +} >>> + >>> +long get_decode_order(void) >>> +{ >>> + long decode_order = 0; >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + if (!ctx_trace.decode_order.empty()) >>> + decode_order = ctx_trace.decode_order.front(); >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> + return decode_order; >>> +} >>> + >>> +void set_max_pic_order_cnt_lsb(int max_pic_order_cnt_lsb) >>> +{ >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + ctx_trace.max_pic_order_cnt_lsb = max_pic_order_cnt_lsb; >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> +} >>> + >>> +int get_max_pic_order_cnt_lsb(void) >>> +{ >>> + int max_pic_order_cnt_lsb = 0; >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + max_pic_order_cnt_lsb = ctx_trace.max_pic_order_cnt_lsb; >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> + return max_pic_order_cnt_lsb; >>> +} >>> + >>> +void set_pic_order_cnt_lsb(int pic_order_cnt_lsb) >>> +{ >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + ctx_trace.pic_order_cnt_lsb = pic_order_cnt_lsb; >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> +} >>> + >>> +int get_pic_order_cnt_lsb(void) >>> +{ >>> + int pic_order_cnt_lsb = 0; >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + pic_order_cnt_lsb = ctx_trace.pic_order_cnt_lsb; >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> + return pic_order_cnt_lsb; >>> +} >>> + >>> +void add_buffer_trace(int fd, __u32 type, __u32 index, __u32 offset = 0) >>> +{ >>> + struct buffer_trace buf = {}; >>> + buf.fd = fd; >>> + buf.type = type; >>> + buf.index = index; >>> + buf.offset = offset; >>> + buf.display_order = -1; >>> + >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + ctx_trace.buffers.push_front(buf); >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> +} >>> + >>> +void remove_buffer_trace(int fd) >>> +{ >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + for (auto it = ctx_trace.buffers.begin(); it != ctx_trace.buffers.end(); ++it) { >>> + if (it->fd == fd) { >>> + ctx_trace.buffers.erase(it); >>> + break; >>> + } >>> + } >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> +} >>> + >>> +bool buffer_in_trace_context(int fd, __u32 offset) >>> +{ >>> + bool buffer_in_trace_context = false; >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + for (auto &b : ctx_trace.buffers) { >>> + if ((b.fd == fd) && (b.offset == offset)) { >>> + buffer_in_trace_context = true; >>> + break; >>> + } >>> + } >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> + return buffer_in_trace_context; >>> +} >>> + >>> +int get_buffer_fd_trace(__u32 type, __u32 index) >>> +{ >>> + int fd = 0; >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + for (auto &b : ctx_trace.buffers) { >>> + if ((b.type == type) && (b.index == index)) { >>> + fd = b.fd; >>> + break; >>> + } >>> + } >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> + return fd; >>> +} >>> + >>> +__u32 get_buffer_type_trace(int fd, __u32 offset) >>> +{ >>> + __u32 type = 0; >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + for (auto &b : ctx_trace.buffers) { >>> + if ((b.fd == fd) && (b.offset == offset)) { >>> + type = b.type; >>> + break; >>> + } >>> + } >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> + return type; >>> +} >>> + >>> +int get_buffer_index_trace(int fd, __u32 offset) >>> +{ >>> + int index = -1; >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + for (auto &b : ctx_trace.buffers) { >>> + if ((b.fd == fd) && (b.offset == offset)) { >>> + index = b.index; >>> + break; >>> + } >>> + } >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> + return index; >>> +} >>> + >>> +__u32 get_buffer_offset_trace(__u32 type, __u32 index) >>> +{ >>> + __u32 offset = 0; >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + for (auto &b : ctx_trace.buffers) { >>> + if ((b.type == type) && (b.index == index)) { >>> + offset = b.offset; >>> + break; >>> + } >>> + } >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> + return offset; >>> +} >>> + >>> +void set_buffer_bytesused_trace(int fd, __u32 offset, __u32 bytesused) >>> +{ >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + for (auto &b : ctx_trace.buffers) { >>> + if ((b.fd == fd) && (b.offset == offset)) { >>> + b.bytesused = bytesused; >>> + break; >>> + } >>> + } >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> +} >>> + >>> +long get_buffer_bytesused_trace(int fd, __u32 offset) >>> +{ >>> + long bytesused = 0; >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + for (auto &b : ctx_trace.buffers) { >>> + if ((b.fd == fd) && (b.offset == offset)) { >>> + bytesused = b.bytesused; >>> + break; >>> + } >>> + } >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> + return bytesused; >>> +} >>> + >>> +void set_buffer_display_order(int fd, __u32 offset, long display_order) >>> +{ >>> + if (option_is_set_verbose()) >>> + fprintf(stderr, "%s: %ld\n", __func__, display_order); >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + for (auto &b : ctx_trace.buffers) { >>> + if ((b.fd == fd) && (b.offset == offset)) { >>> + b.display_order = display_order; >>> + break; >>> + } >>> + } >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> +} >>> + >>> +void set_buffer_address_trace(int fd, __u32 offset, unsigned long address) >>> +{ >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + for (auto &b : ctx_trace.buffers) { >>> + if ((b.fd == fd) && (b.offset == offset)) { >>> + b.address = address; >>> + break; >>> + } >>> + } >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> +} >>> + >>> +unsigned long get_buffer_address_trace(int fd, __u32 offset) >>> +{ >>> + unsigned long address = 0; >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + for (auto &b : ctx_trace.buffers) { >>> + if ((b.fd == fd) && (b.offset == offset)) { >>> + address = b.address; >>> + break; >>> + } >>> + } >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> + return address; >>> +} >>> + >>> +bool buffer_is_mapped(unsigned long buffer_address) >>> +{ >>> + bool ret = false; >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + for (auto &b : ctx_trace.buffers) { >>> + if (b.address == buffer_address) { >>> + ret = true; >>> + break; >>> + } >>> + } >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> + return ret; >>> +} >>> + >>> +void print_buffers_trace(void) >>> +{ >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> + for (auto &b : ctx_trace.buffers) { >>> + fprintf(stderr, "fd: %d, %s, index: %d, display_order: %ld, bytesused: %d, ", >>> + b.fd, buftype2s(b.type).c_str(), b.index, b.display_order, b.bytesused); >>> + fprintf(stderr, "address: %lu, offset: %u \n", b.address, b.offset); >>> + } >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> +} >>> + >>> +std::string get_ioctl_request_str(unsigned long request) >>> +{ >>> + __u8 ioctl_type = _IOC_TYPE(request); >>> + switch (ioctl_type) { >>> + case 'V': { >>> + if (option_is_set_verbose()) >>> + fprintf(stderr, "%s\n", ioctl2s_video(request).c_str()); >>> + return ioctl2s_video(request); >>> + } >>> + case '|': >>> + return ioctl2s_media(request); >>> + case 'b': >>> + if (request == DMA_BUF_IOCTL_SYNC) >>> + return "DMA_BUF_IOCTL_SYNC"; >>> + break; >>> + default: >>> + break; >>> + } >>> + return "unknown ioctl"; >>> +} >>> + >>> +unsigned get_expected_length_trace() >>> +{ >>> + unsigned width; >>> + unsigned height; >>> + unsigned pixelformat; >>> + unsigned expected_length; >>> + >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + width = ctx_trace.width; >>> + height = ctx_trace.height; >>> + pixelformat = ctx_trace.pixelformat; >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> + >>> + /* >>> + * TODO: this assumes that the stride is equal to the real width and that the >>> + * padding follows the end of the chroma plane. It could be improved by >>> + * following the model in v4l2-ctl-streaming.cpp read_write_padded_frame() >>> + */ >>> + expected_length = width * height; >>> + if (pixelformat == V4L2_PIX_FMT_NV12 || pixelformat == V4L2_PIX_FMT_YUV420) { >>> + expected_length *= 3; >>> + expected_length /= 2; >>> + expected_length += (expected_length % 2); >>> + } >>> + return expected_length; >>> +} >>> + >>> +void trace_mem_decoded(void) >>> +{ >>> + int displayed_count = 0; >>> + unsigned expected_length = get_expected_length_trace(); >>> + >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + >>> + while (!ctx_trace.decode_order.empty()) { >>> + >>> + std::list<buffer_trace>::iterator it; >>> + long next_frame_to_be_displayed = *std::min_element(ctx_trace.decode_order.begin(), >>> + ctx_trace.decode_order.end()); >>> + >>> + for (it = ctx_trace.buffers.begin(); it != ctx_trace.buffers.end(); ++it) { >>> + if (it->display_order != next_frame_to_be_displayed) >>> + continue; >>> + if (!it->address) >>> + break; >>> + if (it->bytesused < expected_length) >>> + break; >>> + if (option_is_set_verbose()) >>> + fprintf(stderr, "%s, displaying: %ld, %s, index: %d\n", >>> + __func__, it->display_order, buftype2s(it->type).c_str(), it->index); >>> + displayed_count++; >>> + >>> + if (options.write_decoded_data_to_yuv_file) { >>> + std::string filename = getenv("TRACE_ID"); >>> + filename += ".yuv"; >>> + FILE *fp = fopen(filename.c_str(), "a"); >>> + unsigned char *buffer_pointer = (unsigned char*) it->address; >>> + for (__u32 i = 0; i < expected_length; i++) >>> + fwrite(&buffer_pointer[i], sizeof(unsigned char), 1, fp); >>> + fclose(fp); >>> + } >>> + int fd = it->fd; >>> + __u32 offset = it->offset; >>> + __u32 type = it->type; >>> + int index = it->index; >>> + __u32 bytesused = it->bytesused; >>> + unsigned long start = it->address; >>> + >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> + trace_mem(fd, offset, type, index, bytesused, start); >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + >>> + ctx_trace.decode_order.remove(next_frame_to_be_displayed); >>> + it->display_order = -1; >>> + break; >>> + } >>> + if (!it->address || it == ctx_trace.buffers.end() || it->bytesused < expected_length) >>> + break; >>> + } >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> + set_compressed_frame_count(get_compressed_frame_count() - displayed_count); >>> +} >>> + >>> +void s_ext_ctrls_setup(struct v4l2_ext_controls *ext_controls) >>> +{ >>> + if (ext_controls->which != V4L2_CTRL_WHICH_REQUEST_VAL) >>> + return; >>> + >>> + if (option_is_set_verbose()) >>> + fprintf(stderr, "\n%s\n", __func__); >>> + >>> + for (__u32 i = 0; i < ext_controls->count; i++) { >>> + struct v4l2_ext_control ctrl = ext_controls->controls[i]; >>> + >>> + switch (ctrl.id) { >> >> This could use some comments: why do you need to take special actions for these >> two controls? > > Ok, I will add comments. A lot of this is about trying to get the frames to > display in the right order. > >> >>> + case V4L2_CID_STATELESS_H264_SPS: { >>> + set_max_pic_order_cnt_lsb(pow(2, ctrl.p_h264_sps->log2_max_pic_order_cnt_lsb_minus4 + 4)); >>> + break; >>> + } >>> + case V4L2_CID_STATELESS_H264_DECODE_PARAMS: { >>> + long pic_order_cnt_msb; >>> + int max = get_max_pic_order_cnt_lsb(); >>> + long prev_pic_order_cnt_msb = get_decode_order(); >>> + int prev_pic_order_cnt_lsb = get_pic_order_cnt_lsb(); >>> + int pic_order_cnt_lsb = ctrl.p_h264_decode_params->pic_order_cnt_lsb; >>> + >>> + if (option_is_set_verbose()) { >>> + fprintf(stderr, "\tprev_pic_order_cnt_lsb: %d\n", prev_pic_order_cnt_lsb); >>> + fprintf(stderr, "\tprev_pic_order_cnt_msb: %ld\n", prev_pic_order_cnt_msb); >>> + fprintf(stderr, "\tpic_order_cnt_lsb: %d\n", pic_order_cnt_lsb); >>> + } >>> + >>> + /* The first frame. */ >>> + if (prev_pic_order_cnt_msb == 0) >>> + pic_order_cnt_msb = 0; >>> + >>> + /* >>> + * TODO: improve the displaying of decoded frames following H264 specification >>> + * 8.2.1.1. For now, dump all the previously decoded frames when an IDR_PIC is >>> + * received to avoid losing frames although this will still sometimes result >>> + * in frames out of order. >>> + */ >>> + if (ctrl.p_h264_decode_params->flags & V4L2_H264_DECODE_PARAM_FLAG_IDR_PIC) { >>> + if (get_compressed_frame_count()) >>> + trace_mem_decoded(); >>> + } >>> + >>> + /* >>> + * When pic_order_cnt_lsb wraps around to zero, adjust the total count using >>> + * max to keep the correct display order. >>> + */ >>> + if ((pic_order_cnt_lsb < prev_pic_order_cnt_lsb) && >>> + ((prev_pic_order_cnt_lsb - pic_order_cnt_lsb) >= (max / 2))) { >>> + pic_order_cnt_msb = prev_pic_order_cnt_msb + max; >>> + } else if ((pic_order_cnt_lsb > prev_pic_order_cnt_lsb) && >>> + ((pic_order_cnt_lsb - prev_pic_order_cnt_lsb) > (max / 2))) { >>> + pic_order_cnt_msb = prev_pic_order_cnt_msb - max; >>> + } else { >>> + pic_order_cnt_msb = prev_pic_order_cnt_msb + (pic_order_cnt_lsb - prev_pic_order_cnt_lsb); >>> + } >>> + >>> + if (option_is_set_verbose()) >>> + fprintf(stderr, "\tpic_order_cnt_msb: %ld\n", pic_order_cnt_msb); >>> + >>> + set_pic_order_cnt_lsb(pic_order_cnt_lsb); >>> + set_decode_order(pic_order_cnt_msb); >>> + break; >>> + } >>> + default: >>> + break; >>> + } >>> + } >>> +} >>> + >>> +void qbuf_setup(struct v4l2_buffer *buf) >>> +{ >>> + if (option_is_set_verbose()) >>> + fprintf(stderr, "\n%s: %s, index: %d\n", __func__, buftype2s(buf->type).c_str(), buf->index); >>> + >>> + int buf_fd = get_buffer_fd_trace(buf->type, buf->index); >>> + __u32 buf_offset = get_buffer_offset_trace(buf->type, buf->index); >>> + >>> + __u32 bytesused = 0; >>> + if (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE || >>> + buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) >>> + bytesused = buf->m.planes[0].bytesused; >>> + if (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT || buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) >>> + bytesused = buf->bytesused; >>> + set_buffer_bytesused_trace(buf_fd, buf_offset, bytesused); >>> + >>> + /* The output buffer should have compressed data just before it is queued, so trace it. */ >>> + if (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE || >>> + buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { >>> + trace_mem_encoded(buf_fd, buf_offset); >>> + set_compressed_frame_count(get_compressed_frame_count() + 1); >>> + } >>> + >>> + if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE || >>> + buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { >>> + >>> + /* If the capture buffer is queued for reuse, trace it before it is reused. */ >>> + if (get_compressed_frame_count()) >>> + trace_mem_decoded(); >>> + >>> + /* H264 sets display order in controls, otherwise display just in the order queued. */ >>> + if (get_compression_format() != V4L2_PIX_FMT_H264_SLICE) >>> + set_decode_order(get_decode_order() + 1); >>> + >>> + set_buffer_display_order(buf_fd, buf_offset, get_decode_order()); >>> + >>> + if (option_is_set_verbose()) { >>> + print_decode_order(); >>> + print_buffers_trace(); >>> + } >>> + } >>> +} >>> + >>> +void streamoff_cleanup(v4l2_buf_type buf_type) >>> +{ >>> + if (option_is_set_verbose()) { >>> + fprintf(stderr, "\nVIDIOC_STREAMOFF: %s\n", buftype2s(buf_type).c_str()); >>> + fprintf(stderr, "compression: %s, pixelformat: %s %s, width: %d, height: %d\n", >>> + pixfmt2s(get_compression_format()).c_str(), pixfmt2s(get_pixelformat()).c_str(), >>> + fcc2s(get_pixelformat()).c_str(), ctx_trace.width, ctx_trace.height); >>> + } >>> + >>> + /* >>> + * Before turning off the stream, trace any remaining capture buffers that were missed >>> + * because they were not queued for reuse. >>> + */ >>> + if (buf_type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE || >>> + buf_type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { >>> + if (get_compressed_frame_count()) >>> + trace_mem_decoded(); >>> + } >>> +} >>> + >>> +void g_fmt_setup_trace(struct v4l2_format *format) >>> +{ >>> + if (format->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) >>> + set_pixelformat_trace(format->fmt.pix.width, >>> + format->fmt.pix.height, format->fmt.pix.pixelformat); >>> + if (format->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) >>> + set_pixelformat_trace(format->fmt.pix_mp.width, format->fmt.pix_mp.height, >>> + format->fmt.pix_mp.pixelformat); >>> + >>> + if (format->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) >>> + set_compression_format(format->fmt.pix.pixelformat); >>> + if (format->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) >>> + set_compression_format(format->fmt.pix_mp.pixelformat); >>> +} >>> + >>> +void s_fmt_setup(struct v4l2_format *format) >>> +{ >>> + if (format->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) >>> + set_compression_format(format->fmt.pix.pixelformat); >>> + if (format->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) >>> + set_compression_format(format->fmt.pix_mp.pixelformat); >>> +} >>> + >>> +void expbuf_setup(struct v4l2_exportbuffer *export_buffer) >>> +{ >>> + __u32 type = export_buffer->type; >>> + __u32 index = export_buffer->index; >>> + int fd_found_in_trace_context = get_buffer_fd_trace(type, index); >>> + >>> + /* If the buffer was already added to the trace context don't add it again. */ >>> + if (fd_found_in_trace_context == export_buffer->fd) >>> + return; >>> + >>> + /* >>> + * If a buffer was previously added to the trace context using the video device >>> + * file descriptor, replace the video fd with the more specific buffer fd from EXPBUF. >>> + */ >>> + if (fd_found_in_trace_context) >>> + remove_buffer_trace(fd_found_in_trace_context); >>> + >>> + add_buffer_trace(export_buffer->fd, type, index); >>> +} >>> + >>> +void querybuf_setup(int fd, struct v4l2_buffer *buf) >>> +{ >>> + /* If the buffer was already added to the trace context don't add it again. */ >>> + if (get_buffer_fd_trace(buf->type, buf->index)) >>> + return; >>> + >>> + switch (buf->memory) { >>> + case V4L2_MEMORY_MMAP: { >>> + __u32 offset = 0; >>> + if ((buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) || >>> + (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)) >>> + offset = buf->m.offset; >>> + if ((buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) || >>> + (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)) >>> + offset = buf->m.planes->m.mem_offset; >>> + add_buffer_trace(fd, buf->type, buf->index, offset); >>> + break; >>> + } >>> + case V4L2_MEMORY_USERPTR: >>> + case V4L2_MEMORY_DMABUF: >>> + default: >>> + break; >>> + } >>> +} >>> + >>> +void write_json_object_to_json_file(json_object *jobj, int flags) >>> +{ >>> + if (options.pretty_print_all) >>> + flags = JSON_C_TO_STRING_PRETTY; >>> + >>> + std::string json_str = json_object_to_json_string_ext(jobj, flags); >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + >>> + if (!ctx_trace.trace_file) { >>> + ctx_trace.trace_filename = getenv("TRACE_ID"); >>> + ctx_trace.trace_filename += ".json"; >>> + ctx_trace.trace_file = fopen(ctx_trace.trace_filename.c_str(), "a"); >>> + } >>> + >>> + fwrite(json_str.c_str(), sizeof(char), json_str.length(), ctx_trace.trace_file); >>> + fputs(",\n", ctx_trace.trace_file); >>> + fflush(ctx_trace.trace_file); >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> +} >>> + >>> +void close_json_file(void) >>> +{ >>> + pthread_mutex_lock(&ctx_trace.lock); >>> + if (ctx_trace.trace_file) { >>> + fclose(ctx_trace.trace_file); >>> + ctx_trace.trace_file = 0; >>> + } >>> + pthread_mutex_unlock(&ctx_trace.lock); >>> +} >> >> <snip> >> >>> diff --git a/utils/v4l2-tracer/v4l2-tracer-gen.pl b/utils/v4l2-tracer/v4l2-tracer-gen.pl >>> new file mode 100755 >>> index 00000000..ca17d7e2 >>> --- /dev/null >>> +++ b/utils/v4l2-tracer/v4l2-tracer-gen.pl >>> @@ -0,0 +1,359 @@ >>> +#!/usr/bin/perl >>> +# SPDX-License-Identifier: GPL-2.0-only */ >>> +# Copyright 2022 Collabora Ltd. >> >> I am officially impressed! This is very nice work! > > thanks :) perl is my friend now > >> >>> + >>> +sub convert_type_to_json_type { >>> + my $type = shift; >>> + if ($type eq __u8 || $type eq __u16 || $type eq __s8 || $type eq __s16 || $type eq __s32) { >>> + return "int"; >>> + } >>> + if ($type eq __u32) { >>> + return "int64"; >>> + } >>> + if ($type eq __u64) { >>> + return "uint64"; >>> + } >>> + if ($type eq struct) { >>> + return; >>> + } >>> + print "v4l2_tracer: error: couldn't convert $type to json_object type.\n"; >>> + return; >>> +} >>> + >>> +sub get_index_letter { >>> + my $index = shift; >>> + if ($index eq 0) {return "i";} >>> + if ($index eq 1) {return "j";} >>> + if ($index eq 2) {return "k";} >>> + if ($index eq 3) {return "l";} >>> + if ($index eq 4) {return "m";} >>> + if ($index eq 5) {return "n";} >>> + if ($index eq 6) {return "o";} # "p" is saved for pointer >>> + if ($index eq 8) {return "q";} >>> + return "z"; >>> +} >>> + >>> +$flag_func_name; >>> + >>> +sub flag_gen { >>> + my $flag_type = shift; >>> + >>> + if ($flag_type =~ /fwht/) { >>> + $flag_func_name = v4l2_ctrl_fwht_params_; >>> + } elsif ($flag_type =~ /vp8_loop_filter/) { >>> + $flag_func_name = v4l2_vp8_loop_filter_; >>> + } else { >>> + ($flag_func_name) = ($_) =~ /#define (\w+_)FLAG_.+/; >>> + $flag_func_name = lc $flag_func_name; >>> + } >>> + printf $fh_trace_info_cpp "std::string %sflag2s(unsigned long flag)\n{\n", $flag_func_name; >>> + printf $fh_trace_info_h "std::string %sflag2s(unsigned long flag);\n", $flag_func_name; >>> + printf $fh_trace_info_cpp "\tstatic constexpr flag_def def[] = {\n"; >>> + ($flag) = ($_) =~ /#define\s+(\w+)\s+.+/; >>> + printf $fh_trace_info_cpp "\t\t{ $flag, \"$flag\" },\n"; >>> + while (<>) { >>> + next if ($_ =~ /^\/?\s?\*.*/); #skip comments between flags if any >>> + next if $_ =~ /^\s*$/; #skip blank lines between flags if any >>> + last if ((grep {!/^#define\s+\w+_FL/} $_) && (grep {!/^#define V4L2_VP8_LF/} $_)); >>> + ($flag) = ($_) =~ /#define\s+(\w+)\s+.+/; >>> + printf $fh_trace_info_cpp "\t\t{ $flag, \"$flag\" },\n"; >>> + } >>> + printf $fh_trace_info_cpp "\t\t{ 0, nullptr }\n\t};\n\treturn flags2s(flag, def);\n}\n\n"; >>> +} >>> + >>> +sub struct_gen { >>> + ($struct_name) = ($_) =~ /struct (\w+) {/; >>> + >>> + printf $fh_trace_h "void trace_%s_gen(void *ptr, json_object *parent_obj);\n", $struct_name; >>> + printf $fh_trace_cpp "void trace_%s_gen(void *ptr, json_object *parent_obj)\n{\n", $struct_name; >>> + printf $fh_trace_cpp "\tjson_object *%s_obj = json_object_new_object();\n", $struct_name; >>> + printf $fh_trace_cpp "\tstruct %s *p = static_cast<struct %s*>(ptr);\n", $struct_name, $struct_name; >>> + >>> + printf $fh_retrace_h "struct %s *retrace_%s_gen(json_object *ctrl_obj);\n", $struct_name, $struct_name; >>> + printf $fh_retrace_cpp "struct %s *retrace_%s_gen(json_object *ctrl_obj)\n{\n", $struct_name, $struct_name; >>> + printf $fh_retrace_cpp "\tstruct %s *p = (struct %s *) calloc(1, sizeof(%s));\n", $struct_name, $struct_name, $struct_name; >>> + printf $fh_retrace_cpp "\tjson_object *%s_obj;\n", $struct_name; >>> + # if a key value isn't found, assume it is retracing an element of an array >>> + printf $fh_retrace_cpp "\tif (!json_object_object_get_ex(ctrl_obj, \"%s\", &%s_obj))\n", $struct_name, $struct_name; >>> + printf $fh_retrace_cpp "\t\t%s_obj = ctrl_obj;\n", $struct_name; >>> + >>> + while ($line = <> ) { >>> + chomp($line); >>> + last if $line =~ /};/; >>> + next if $line =~ /^\s*$/; # ignore blank lines >>> + next if $line =~ /\/\*/; # ignore comment lines >>> + $line =~ s/^\s+//; # remove leading whitespace >>> + $line =~ s/;$//; # remove semi-colon at the end >>> + my @words = split /[\s\[]/, $line; # also split on '[' to avoid arrays >>> + @words = grep {/^\D/} @words; # remove values that start with digit from inside [] >>> + @words = grep {!/\]/} @words; # remove values with brackets e.g. V4L2_H264_REF_LIST_LEN] >>> + >>> + ($type) = $words[0]; >>> + $json_type = convert_type_to_json_type($type); >>> + >>> + ($member) = $words[scalar @words - 1]; >>> + next if $member =~ /reserved/; # ignore reserved members, they will segfault on retracing >>> + >>> + # generate members that are arrays >>> + if ($line =~ /.*\[.*/) { >>> + printf $fh_trace_cpp "\t\/\* %s \*\/\n", $line; #trace comment >>> + printf $fh_trace_cpp "\tjson_object *%s_obj = json_object_new_array();\n", $member; >>> + >>> + printf $fh_retrace_cpp "\n\t\/\* %s \*\/\n", $line; #retrace comment >>> + >>> + my @dimensions = ($line) =~ /\[(\w+)\]/g; >>> + $dimensions_count = scalar @dimensions; >>> + >>> + if ($dimensions_count > 1) { >>> + printf $fh_retrace_cpp "\tint count_%s = 0;\n", $member; >>> + } >>> + printf $fh_retrace_cpp "\tjson_object *%s_obj;\n", $member; >>> + printf $fh_retrace_cpp "\tjson_object_object_get_ex(%s_obj, \"%s\", &%s_obj);\n", $struct_name, $member, $member; >>> + >>> + for (my $idx = 0; $idx < $dimensions_count; $idx = $idx + 1 ) { >>> + $size = $dimensions[$idx]; >>> + $index_letter = get_index_letter($idx); >>> + printf $fh_trace_cpp "\t" x ($idx + 1); >>> + printf $fh_trace_cpp "for (size_t %s = 0; %s < %s\; %s++) {\n", $index_letter, $index_letter, $size, $index_letter; >>> + >>> + printf $fh_retrace_cpp "\t" x ($idx + 1); >>> + printf $fh_retrace_cpp "for (size_t %s = 0; %s < %s\; %s++) {\n", $index_letter, $index_letter, $size, $index_letter; >>> + } >>> + printf $fh_trace_cpp "\t" x ($dimensions_count + 1); >>> + printf $fh_retrace_cpp "\t" x ($dimensions_count + 1); >>> + >>> + # handle arrays of structs >>> + if ($type =~ /struct/) { >>> + my $struct_name_nested = @words[1]; >>> + printf $fh_trace_cpp "json_object *element_obj = json_object_new_object();\n"; >>> + printf $fh_trace_cpp "\t" x ($dimensions_count + 1); >>> + printf $fh_trace_cpp "trace_%s_gen(&(p->%s", $struct_name_nested, $member; >>> + for (my $idx = 0; $idx < $dimensions_count; $idx = $idx + 1 ) { >>> + printf $fh_trace_cpp "[%s]", get_index_letter($idx); >>> + } >>> + printf $fh_trace_cpp "), element_obj);\n"; >>> + printf $fh_trace_cpp "\t" x ($dimensions_count + 1); >>> + printf $fh_trace_cpp "json_object *element_no_key_obj;\n"; >>> + printf $fh_trace_cpp "\t" x ($dimensions_count + 1); >>> + printf $fh_trace_cpp "json_object_object_get_ex(element_obj, \"%s\", &element_no_key_obj);\n", $struct_name_nested; >>> + printf $fh_trace_cpp "\t" x ($dimensions_count + 1); >>> + printf $fh_trace_cpp "json_object_array_add(%s_obj, element_no_key_obj);\n", $member; >>> + >>> + printf $fh_retrace_cpp "p->%s", $member; >>> + for (my $idx = 0; $idx < $dimensions_count; $idx = $idx + 1) { >>> + printf $fh_retrace_cpp "[%s]", get_index_letter($idx); >>> + } >>> + printf $fh_retrace_cpp " = *retrace_%s_gen(json_object_array_get_idx(%s_obj, ", $struct_name_nested, $member; >>> + if ($dimensions_count > 1) { >>> + printf $fh_retrace_cpp "count_%s++", $member; >>> + } else { >>> + printf $fh_retrace_cpp "i"; >>> + } >>> + printf $fh_retrace_cpp "));\n"; >>> + } else { >>> + printf $fh_trace_cpp "json_object_array_add(%s_obj, json_object_new_%s(p->%s", $member, $json_type, $member; >>> + for (my $idx = 0; $idx < $dimensions_count; $idx = $idx + 1 ) { >>> + printf $fh_trace_cpp "[%s]", get_index_letter($idx); >>> + } >>> + printf $fh_trace_cpp "));\n"; >>> + >>> + printf $fh_retrace_cpp "p->%s", $member; >>> + for (my $idx = 0; $idx < $dimensions_count; $idx = $idx + 1 ) { >>> + printf $fh_retrace_cpp "[%s]", get_index_letter($idx); >>> + } >>> + printf $fh_retrace_cpp " = ($type) json_object_get_%s(json_object_array_get_idx(%s_obj, ", $json_type, $member; >>> + if ($dimensions_count > 1) { >>> + printf $fh_retrace_cpp "count_%s++", $member; >>> + } else { >>> + printf $fh_retrace_cpp "i"; >>> + } >>> + printf $fh_retrace_cpp "));\n"; >>> + } >>> + #closing brackets for all array types >>> + for (my $idx = $dimensions_count - 1; $idx >= 0 ; $idx = $idx - 1) { >>> + printf $fh_trace_cpp "\t" x ($idx + 1); >>> + printf $fh_trace_cpp "}\n"; >>> + >>> + printf $fh_retrace_cpp "\t" x ($idx + 1); >>> + printf $fh_retrace_cpp "}\n"; >>> + } >>> + printf $fh_trace_cpp "\tjson_object_object_add(%s_obj, \"%s\", %s_obj);\n\n", $struct_name, $member, $member; >>> + } elsif ($type =~ /struct/) { >>> + # member that is a struct but not an array of structs >>> + my $struct_name_nested = @words[1]; >>> + printf $fh_trace_cpp "\t\/\* %s \*\/\n", $line; >>> + printf $fh_trace_cpp "\ttrace_%s_gen(&(p->%s), %s_obj);\n", $struct_name_nested, $member, $struct_name; >>> + >>> + printf $fh_retrace_cpp "\t\/\* %s \*\/\n", $line; >>> + printf $fh_retrace_cpp "\tjson_object *%s_obj;\n", $member; >>> + printf $fh_retrace_cpp "\tjson_object_object_get_ex(%s_obj, \"%s\", &%s_obj);\n", $struct_name, $struct_name_nested, $member; >>> + printf $fh_retrace_cpp "\tp->%s = \*retrace_%s_gen(%s_obj);\n\n", $member, $struct_name_nested, $member; >>> + } else { >>> + # members that are integers >>> + printf $fh_trace_cpp "\tjson_object_object_add(%s_obj, \"%s\", json_object_new_%s(p->%s));\n", $struct_name, $member, $json_type, $member; >>> + printf $fh_retrace_cpp "\n\tjson_object *%s_obj;\n", $member; >>> + printf $fh_retrace_cpp "\tjson_object_object_get_ex(%s_obj, \"%s\", &%s_obj);\n", $struct_name, $member, $member; >>> + printf $fh_retrace_cpp "\tp->%s = ($type) json_object_get_%s(%s_obj);\n", $member, $json_type, $member; >>> + >>> + # add flag strings >>> + if ($member =~ /^flags/) { >>> + printf $fh_trace_cpp "\tjson_object_object_add(%s_obj, \"flags_str\", json_object_new_string(%sflag2s(p->flags).c_str()));\n", $struct_name, $flag_func_name; >>> + } >>> + } >>> + } >>> + printf $fh_trace_cpp "\tjson_object_object_add(parent_obj, \"%s\", %s_obj);\n", $struct_name, $struct_name; >>> + printf $fh_trace_cpp "}\n\n"; >>> + >>> + printf $fh_retrace_cpp "\n\treturn p;\n"; >>> + printf $fh_retrace_cpp "}\n\n"; >>> +} >>> + >>> +open($fh_trace_cpp, '>', 'trace-gen.cpp') or die "Could not open trace-gen.cpp for writing"; >>> +printf $fh_trace_cpp "/* SPDX-License-Identifier: GPL-2.0-only */\n/*\n * Copyright 2022 Collabora Ltd.\n */\n\n"; >>> +printf $fh_trace_cpp "#include \"v4l2-tracer-common.h\"\n#include \"trace-info-gen.h\"\n\n"; >>> + >>> +open($fh_trace_h, '>', 'trace-gen.h') or die "Could not open trace-gen.h for writing"; >>> +printf $fh_trace_h "/* SPDX-License-Identifier: GPL-2.0-only */\n/*\n * Copyright 2022 Collabora Ltd.\n */\n\n"; >>> +printf $fh_trace_h "\#ifndef TRACE_GEN_H\n"; >>> +printf $fh_trace_h "\#define TRACE_GEN_H\n\n"; >>> + >>> +open($fh_retrace_cpp, '>', 'retrace-gen.cpp') or die "Could not open retrace-gen.cpp for writing"; >>> +printf $fh_retrace_cpp "/* SPDX-License-Identifier: GPL-2.0-only */\n/*\n * Copyright 2022 Collabora Ltd.\n */\n\n"; >>> +printf $fh_retrace_cpp "#include \"v4l2-tracer-common.h\"\n\n"; >>> + >>> +open($fh_retrace_h, '>', 'retrace-gen.h') or die "Could not open retrace-gen.h for writing"; >>> +printf $fh_retrace_h "/* SPDX-License-Identifier: GPL-2.0-only */\n/*\n * Copyright 2022 Collabora Ltd.\n */\n\n"; >>> +printf $fh_retrace_h "\#ifndef RETRACE_GEN_H\n"; >>> +printf $fh_retrace_h "\#define RETRACE_GEN_H\n\n"; >>> + >>> +open($fh_trace_info_cpp, '>', 'trace-info-gen.cpp') or die "Could not open trace-info-gen.cpp for writing"; >>> +printf $fh_trace_info_cpp "/* SPDX-License-Identifier: GPL-2.0-only */\n/*\n * Copyright 2022 Collabora Ltd.\n */\n\n"; >>> +printf $fh_trace_info_cpp "#include \"v4l2-tracer-common.h\"\n#include \"trace-helper.h\"\n\n"; >>> + >>> +open($fh_trace_info_h, '>', 'trace-info-gen.h') or die "Could not open trace-info-gen.h for writing"; >>> +printf $fh_trace_info_h "/* SPDX-License-Identifier: GPL-2.0-only */\n/*\n * Copyright 2022 Collabora Ltd.\n */\n\n"; >>> +printf $fh_trace_info_h "\#ifndef TRACE_INFO_GEN_H\n"; >>> +printf $fh_trace_info_h "\#define TRACE_INFO_GEN_H\n\n"; >>> + >>> +$in_v4l2_controls = true; >>> + >>> +while (<>) { >>> + if (grep {/#define __LINUX_VIDEODEV2_H/} $_) {$in_v4l2_controls = false;} >>> + >>> + # only generate flag and struct functions for v4l2-controls.h >>> + if ($in_v4l2_controls eq true) { >>> + if (grep {/^#define.+_FLAG_.+/} $_) { >>> + flag_gen(); >>> + } elsif (grep {/^#define.+FWHT_FL_.+/} $_) { >>> + flag_gen("fwht"); >>> + } elsif (grep {/^#define V4L2_VP8_LF.*/} $_) { >>> + flag_gen("vp8_loop_filter"); >>> + } >>> + >>> + if (grep {/^struct/} $_) { >>> + struct_gen(); >>> + } >>> + } >>> + >>> + if (grep {/^\/\* Control classes \*\//} $_) { >>> + printf $fh_trace_info_cpp "std::string ctrlclass2s(__u32 id)\n{\n"; >>> + printf $fh_trace_info_h "std::string ctrlclass2s(__u32 id);\n"; >>> + printf $fh_trace_info_cpp "\tstatic constexpr definition defs[] = {\n"; >>> + >>> + while (<>) { >>> + last if $_ =~ /^\s*$/; #last if blank line >>> + ($ctrl_class) = ($_) =~ /#define\s*(\w+)\s+.*/; >>> + printf $fh_trace_info_cpp "\t\t{ %s,\t\"%s\" },\n", $ctrl_class, $ctrl_class; >>> + } >>> + printf $fh_trace_info_cpp "\t\t{ 0, nullptr }\n\t};\n\treturn val2s(id & 0xff0000, defs);\n}\n\n"; >>> + } >>> + >>> + if (grep {/^enum v4l2_memory/} $_) { >>> + printf $fh_trace_info_cpp "std::string v4l2_memory2s(__u32 val)\n{\n"; >>> + printf $fh_trace_info_h "std::string v4l2_memory2s(__u32 val);\n"; >>> + printf $fh_trace_info_cpp "\tstatic constexpr definition defs[] = {\n"; >>> + >>> + while (<>) { >>> + last if $_ =~ /};/; >>> + ($memory_type) = ($_) =~ /\s*(\w+)\s+.*/; >>> + printf $fh_trace_info_cpp "\t\t{ %s,\t\"%s\" },\n", $memory_type, $memory_type; >>> + } >>> + printf $fh_trace_info_cpp "\t\t{ 0, nullptr }\n\t};\n\treturn val2s(val, defs);\n}\n\n"; >>> + } >>> + >>> + if (grep {/\/\* Values for 'capabilities' field \*\//} $_) { >>> + printf $fh_trace_info_cpp "std::string capabilities2s(unsigned long flag)\n{\n"; >>> + printf $fh_trace_info_h "std::string capabilities2s(unsigned long flag);\n"; >>> + >>> + printf $fh_trace_info_cpp "\tstatic constexpr flag_def def[] = {\n"; >>> + while (<>) { >>> + last if $_ =~ /.*V I D E O I M A G E F O R M A T.*/; >>> + next if ($_ =~ /^\/?\s?\*.*/); #skip comments >>> + next if $_ =~ /^\s*$/; #skip blank lines >>> + ($cap) = ($_) =~ /#define\s+(\w+)\s+.+/; >>> + printf $fh_trace_info_cpp "\t\t{ $cap, \"$cap\" },\n" >>> + } >>> + printf $fh_trace_info_cpp "\t\t{ 0, nullptr }\n\t};\n\treturn flags2s(flag, def);\n}\n\n"; >>> + } >>> + >>> + if (grep {/^enum v4l2_ctrl_type/} $_) { >>> + printf $fh_trace_info_cpp "std::string ctrltype2s(__u32 val)\n{\n"; >>> + printf $fh_trace_info_h "std::string ctrltype2s(__u32 val);\n"; >>> + printf $fh_trace_info_cpp "\tstatic constexpr definition defs[] = {\n"; >>> + >>> + while (<>) { >>> + next if ($_ =~ /^\s?\/?\s?\*.*/); #skip comments >>> + next if $_ =~ /^\s*$/; #skip blank lines >>> + last if $_ =~ /};/; >>> + ($ctrl_type) = ($_) =~ /\s*(\w+)\s+.*/; >>> + >>> + printf $fh_trace_info_cpp "\t\t{ %s,\t\"%s\" },\n", $ctrl_type, $ctrl_type; >>> + } >>> + printf $fh_trace_info_cpp "\t\t{ 0, nullptr }\n\t};\n\treturn val2s(val, defs);\n}\n\n"; >>> + } >>> + >>> + if (grep {/.*I O C T L C O D E S F O R V I D E O D E V I C E S.*/} $_) { >>> + printf $fh_trace_info_cpp "std::string ioctl2s_video(unsigned long request)\n{\n"; >>> + printf $fh_trace_info_h "std::string ioctl2s_video(unsigned long request);\n"; >>> + printf $fh_trace_info_cpp "\tstatic constexpr definition defs[] = {\n"; >>> + >>> + while (<>) { >>> + next if ($_ =~ /^\/?\s?\*.*/); #skip comments >>> + next if $_ =~ /^\s*$/; #skip blank lines >>> + next if $_ =~ /^\s+/; #skip lines that start with a space >>> + last if $_ =~ /^#define BASE_VIDIOC_PRIVATE/; >>> + ($ioctl_name) = ($_) =~ /^#define\s*(\w+)\s*/; >>> + printf $fh_trace_info_cpp "\t\t{ %s,\t\"%s\" },\n", $ioctl_name, $ioctl_name; >>> + } >>> + printf $fh_trace_info_cpp "\t\t{ 0, nullptr }\n\t};\n\treturn val2s(request, defs);\n}\n\n"; >>> + } >>> + >>> + # from media.h >>> + if (grep {/\/\* ioctls \*\//} $_) { >>> + printf $fh_trace_info_cpp "std::string ioctl2s_media(unsigned long request)\n{\n"; >>> + printf $fh_trace_info_h "std::string ioctl2s_media(unsigned long request);\n"; >>> + printf $fh_trace_info_cpp "\tstatic constexpr definition defs[] = {\n"; >>> + >>> + while (<>) { >>> + next if ($_ =~ /^\/?\s?\*.*/); #skip comments >>> + next if $_ =~ /^\s*$/; #skip blank lines >>> + next if $_ =~ /^\s+/; #skip lines that start with a space, comments >>> + last if $_ =~ /^#define MEDIA_ENT_TYPE_SHIFT/; >>> + ($ioctl_name) = ($_) =~ /^#define\s*(\w+)\s*/; >>> + printf $fh_trace_info_cpp "\t\t{ %s,\t\"%s\" },\n", $ioctl_name, $ioctl_name; >>> + } >>> + printf $fh_trace_info_cpp "\t\t{ 0, nullptr }\n\t};\n\treturn val2s(request, defs);\n}\n\n"; >>> + } >>> +} >>> + >>> +printf $fh_trace_h "\n#endif\n"; >>> +close $fh_trace_h; >>> +close $fh_trace_cpp; >>> + >>> +printf $fh_retrace_h "\n#endif\n"; >>> +close $fh_retrace_h; >>> +close $fh_retrace_cpp; >>> + >>> +printf $fh_trace_info_h "\n#endif\n"; >>> +close $fh_trace_info_h; >>> +close $fh_trace_info_cpp; >>> diff --git a/utils/v4l2-tracer/v4l2-tracer.1.in b/utils/v4l2-tracer/v4l2-tracer.1.in >>> new file mode 100644 >>> index 00000000..cd3d089b >>> --- /dev/null >>> +++ b/utils/v4l2-tracer/v4l2-tracer.1.in >>> @@ -0,0 +1,104 @@ >>> +.TH "V4L2-TRACER" "1" "October 2022" "v4l-utils @PACKAGE_VERSION@" "User Commands" >>> +.SH NAME >>> +v4l2-tracer - An application to trace and replay stateless video decoding. >>> +.SH SYNOPSIS >>> +\fBv4l2-tracer trace\fR [trace options] -- <\fItracee\fR> >>> +.RS >>> +.RE >>> +\fBv4l2-tracer retrace\fR [retrace options] -- <\fItrace_file\fR>\fB.json\fR >>> +.RS >>> +.RE >>> + >>> +.SH DESCRIPTION >>> +The v4l2-tracer utility traces, records and replays userspace applications >>> +that implement the v4l2 memory-to-memory stateless video decoder interface. >>> +The trace function intercepts and copies all system calls, stateless codec >>> +controls and encoded data to a json-formatted trace file. The retrace function >>> +independently reads and replays the json trace file. The json trace file can >>> +be retraced independently from its original userspace environment and can be >>> +edited to inject errors to test a driver's error handling abilities. >>> + >>> +.SH OPTIONS >>> +.SS Trace Options >>> +.TP >>> +\fB\-e\fR, \fB\-\-prettymem\fR >>> +Add whitespace in json file to improve readability of memory arrays. >>> +.TP >>> +\fB\-h\fR, \fB\-\-help\fR >>> +Display trace help. >>> +.TP >>> +\fB\-p\fR, \fB\-\-pretty\fR >>> +Add whitespace in json file to improve readability. >>> +.TP >>> +\fB\-r\fR, \fB\-\-raw\fR >>> +Write decoded data to json file. >> >> This is a confusing option description. I assume you mean: >> >> "Write the decoded video frame data to the json file." >> >> "data" is just too vague. >> >>> +.TP >>> +\fB\-v\fR, \fB\-\-verbose\fR >>> +Turn on verbose reporting. >>> +.TP >>> +\fB\-y\fR, \fB\-\-yuv\fR >>> +Write decoded data to yuv file. >> >> Ditto. >> >> Note that the same description is in the usage messages in the code. > > ok, will change. > >> >>> + >>> +.SS Retrace Options >>> +.TP >>> +\fB\-d\fR, \fB\-\-device\fR <\fIdev\fR> >>> +Use a different video device than specified in the trace file. >>> +.RS >>> +<\fIdev\fR> must be a digit corresponding to an existing /dev/video<\fIdev\fR> >>> +.RE >>> +.TP >>> +\fB\-h\fR, \fB\-\-help\fR >>> +Display retrace help. >>> +.TP >>> +\fB\-m\fR, \fB\-\-media\fR <\fIdev\fR> >>> +Use a different media device than specified in the trace file. >>> +.RS >>> +<\fIdev\fR> must be a digit corresponding to an existing /dev/media<\fIdev\fR> >>> +.RE >>> +.TP >>> +\fB\-v\fR, \fB\-\-verbose\fR >>> +Turn on verbose reporting. >>> + >>> +.SH EXIT STATUS >>> +On success, it returns 0. Otherwise, it will return 1 or an error code. >>> + >>> +.SH EXAMPLES >>> +Trace VP8 decoding: >>> +.TP >>> +.EX >>> + v4l2-tracer trace -- gst-launch-1.0 -- filesrc location=test-25fps.vp8 ! parsebin ! v4l2slvp8dec ! videocodectestsink >>> +.EE >>> +.TP >>> +Trace H264 decoding: >>> +.TP >>> +.EX >>> + v4l2-tracer trace -- gst-launch-1.0 -- filesrc location=test-25fps.h264 ! parsebin ! v4l2slh264dec ! videocodectestsink >>> +.EE >>> +.TP >>> +Trace FWHT decoding: >>> +.TP >>> +.EX >>> + v4l2-tracer trace -- v4l2-ctl -d9 --stream-mmap --stream-out-mmap --stream-from-hdr test-25fps.fwht --stream-to out.yuv >>> +.EE >>> +.TP >>> +Retrace: >>> +.TP >>> +.EX >>> + v4l2-tracer retrace 79568_trace.json >>> +.EE >>> +.TP >>> +Retrace using /dev/video6 and /dev/media3: >>> +.TP >>> +.EX >>> + v4l2-tracer retrace -d 6 -m 3 79568_trace.json >>> +.EE >>> +.TP >>> +Self-trace: >>> +.TP >>> +.EX >>> + v4l2-tracer trace -- v4l2-tracer retrace 79568_trace.json >>> +.EE >>> + >>> +.SH BUGS >>> +Bug reports or questions about this utility should be sent to the >>> +linux-media@vger.kernel.org mailinglist. >>> diff --git a/utils/v4l2-tracer/v4l2-tracer.cpp b/utils/v4l2-tracer/v4l2-tracer.cpp >>> new file mode 100644 >>> index 00000000..71ac242a >>> --- /dev/null >>> +++ b/utils/v4l2-tracer/v4l2-tracer.cpp >>> @@ -0,0 +1,187 @@ >>> +/* SPDX-License-Identifier: GPL-2.0-only */ >>> +/* >>> + * Copyright 2022 Collabora Ltd. >>> + */ >>> + >>> +#include <time.h> >>> +#include <dirent.h> >>> +#include <sys/wait.h> >>> +#include "v4l2-tracer-common.h" >>> + >>> +#define STR(x) #x >>> +#define STRING(x) STR(x) >>> + >>> +void print_help_retracer(void); >>> +int retracer(int argc, char *argv[]); >>> + >>> +enum Option_Tracer { >>> + TracerOptPrettyPrintMemoryOnly = 'e', >>> + TracerOptHelp = 'h', >>> + TracerOptPrettyPrintAll = 'p', >>> + TracerOptWriteDecodedToJsonFile = 'r', >>> + TracerOptVerbose = 'v', >>> + TracerOptWriteDecodedToYUVFile = 'y', >>> +}; >>> + >>> +static struct option long_options_tracer[] = { >>> + { "prettymem", no_argument, nullptr, TracerOptPrettyPrintMemoryOnly }, >>> + { "help", no_argument, nullptr, TracerOptHelp }, >>> + { "pretty", no_argument, nullptr, TracerOptPrettyPrintAll }, >>> + { "raw", no_argument, nullptr, TracerOptWriteDecodedToJsonFile }, >>> + { "verbose", no_argument, nullptr, TracerOptVerbose }, >>> + { "yuv", no_argument, nullptr, TracerOptWriteDecodedToYUVFile }, >>> + { nullptr, 0, nullptr, 0 } >>> +}; >>> + >>> +char short_options_tracer[] = { >>> + TracerOptPrettyPrintMemoryOnly, >>> + TracerOptHelp, >>> + TracerOptPrettyPrintAll, >>> + TracerOptWriteDecodedToJsonFile, >>> + TracerOptVerbose, >>> + TracerOptWriteDecodedToYUVFile >>> +}; >>> + >>> +void print_help_tracer(void) >>> +{ >>> + fprintf(stderr, "v4l2-tracer trace [trace options] -- <tracee>\n" >>> + >>> + "\t-e, --prettymem Add whitespace in json file to improve readability of memory arrays.\n" >>> + "\t-h, --help Display trace help.\n" >>> + "\t-p, --pretty Add whitespace in json file to improve readability.\n" >>> + "\t-r --raw Write decoded data to json file.\n" >>> + "\t-v, --verbose Turn on verbose reporting.\n" >>> + "\t-y, --yuv Write decoded data to yuv file.\n\n"); >>> +} >>> + >>> +void print_usage(void) >>> +{ >>> + print_help_tracer(); >>> + print_help_retracer(); >>> +} >>> + >>> +void get_options_trace(int argc, char *argv[]) >>> +{ >>> + int ch; >>> + >>> + do { >>> + ch = getopt_long(argc, argv, short_options_tracer, long_options_tracer, NULL); >>> + switch (ch){ >>> + case TracerOptPrettyPrintMemoryOnly: >>> + setenv("TRACE_OPTION_PRETTY_PRINT_MEM", "true", 0); >>> + break; >>> + case TracerOptHelp: >>> + break; >>> + case TracerOptPrettyPrintAll: >>> + setenv("TRACE_OPTION_PRETTY_PRINT_ALL", "true", 0); >>> + break; >>> + case TracerOptWriteDecodedToJsonFile: >>> + setenv("TRACE_OPTION_WRITE_DECODED_TO_JSON_FILE", "true", 0); >>> + break; >>> + case TracerOptVerbose: >>> + setenv("TRACE_OPTION_VERBOSE", "true", 0); >>> + break; >>> + case TracerOptWriteDecodedToYUVFile: >>> + setenv("TRACE_OPTION_WRITE_DECODED_TO_YUV_FILE", "true", 0); >>> + break; >>> + } >>> + } while (ch != -1); >>> +} >>> + >>> +int tracer(int argc, char *argv[]) >>> +{ >>> + get_options_trace(argc, argv); >>> + >>> + if (optind == argc) { >>> + print_help_tracer(); >>> + return 1; >>> + } >>> + >>> + /* Get the application to be traced from the command line. */ >>> + int count = 0; >>> + char *exec_array[argc]; >>> + while (optind < argc) >>> + exec_array[count++] = argv[optind++]; >>> + exec_array[count] = nullptr; >>> + >>> + /* Use a substring of the time to create a unique id for the trace. */ >>> + std::string trace_id = std::to_string(time(nullptr)); >>> + trace_id = trace_id.substr(5, trace_id.npos) + "_trace"; >>> + setenv("TRACE_ID", trace_id.c_str(), 0); >>> + >>> + /* Create the trace file to hold the json-objects as a json array. */ >>> + std::string trace_filename = trace_id + ".json"; >>> + FILE *trace_file = fopen(trace_filename.c_str(), "w"); >>> + fputs("[\n", trace_file); >>> + fclose(trace_file); >>> + >>> + /* >>> + * Preload the libv4l2tracer library. If the program is installed, load the library >>> + * from its installed location, otherwise load it locally. If it's loaded locally, >>> + * use ./configure --disable-dyn-libv4l. >>> + */ >>> + std::string libv4l2tracer_path; >>> + std::string program = argv[0]; >>> + std::size_t idx = program.rfind("/v4l2-tracer"); >>> + if (idx != program.npos) { >>> + libv4l2tracer_path = program.replace(program.begin() + idx + 1, program.end(), ".libs"); >>> + DIR *dp = opendir(libv4l2tracer_path.c_str()); >>> + if (dp == nullptr) >>> + libv4l2tracer_path = program.replace(program.begin() + idx, program.end(), "./.libs"); >>> + closedir(dp); >>> + } else { >>> + libv4l2tracer_path = STRING(LIBTRACER_PATH); >>> + } >>> + libv4l2tracer_path += "/libv4l2tracer.so"; >>> + fprintf(stderr, "Loading libv4l2tracer: %s\n", libv4l2tracer_path.c_str()); >>> + setenv("LD_PRELOAD", libv4l2tracer_path.c_str(), 0); >>> + >>> + if (fork() == 0) { >>> + execvpe(exec_array[0], exec_array, environ); >>> + perror("Option -h for help. Could not execute application to trace."); >>> + return errno; >>> + } >>> + >>> + int tracee_result; >>> + wait(&tracee_result); >>> + >>> + if (WEXITSTATUS(tracee_result)) { >>> + fprintf(stderr, "Trace error: %s\n", trace_filename.c_str()); >>> + exit(EXIT_FAILURE); >>> + } >>> + >>> + /* Close the json-array and the trace file. */ >>> + trace_file = fopen(trace_filename.c_str(), "a"); >>> + fseek(trace_file, 0L, SEEK_END); >>> + fputs("\n]\n", trace_file); >>> + fclose(trace_file); >>> + >>> + fprintf(stderr, "Trace complete: "); >>> + if (getenv("TRACE_OPTION_WRITE_DECODED_TO_YUV_FILE")) >>> + fprintf(stderr, "%s, ", (trace_id + ".yuv").c_str()); >>> + fprintf(stderr, "%s", trace_filename.c_str()); >>> + fprintf(stderr, "\n"); >>> + >>> + return 0; >>> +} >>> + >>> +int main(int argc, char *argv[]) >>> +{ >>> + int ret = 1; >>> + >>> + if (argc <= 1) { >>> + print_usage(); >>> + return ret; >>> + } >>> + >>> + std::string command = argv[1]; >>> + optind = 2; /* start looking for options only after the trace/retrace command */ >>> + if (command == "trace") >>> + ret = tracer(argc, argv); >>> + else if (command == "retrace") >>> + ret = retracer(argc, argv); >>> + else >>> + print_usage(); >>> + >>> + return ret; >>> +} >> >> I have one more comment: I noticed that "mem_array" is output as: >> >> "mem_array":[ >> "50a3 009d 012a 4001 f000 0007 0885 8588", >> >> This is confusing since it is not clear if this is little or big endian. >> >> Write it as: >> >> "50 a3 00 9d 01 2a 40 01 f0 00 00 07 08 85 85 88", > > Hmm, from what i can tell it is always printed to string as big endian. > How does adding a space between each byte make it clear big/little? > But, maybe it would be best just to always dump it without spaces. > Since, truly this is not something I ever wanted to manually read. If I read "50 a3 00 9d", then the spaces make it clear that these are four bytes stored sequentially in memory. With "50a3 009d" I have no idea if this means "50 a3 00 9d" in memory or "a3 50 9d 00" in memory. That would all depend on how the data is read from memory and written to the file. E.g.: const unsigned char *mem; printf("%02x%02x\n", mem[0], mem[1]); vs: const unsigned short *mem; printf("%04x\n", mem[0]); This will produce the same result on a big endian system, but different results on a little endian system. > >> >> You can make it depend on the --prettymem option. If set, then dump it >> with a space after each byte. If not set, then dump it in a compact format >> (no spaces at all): "50a3009d012a4001f000000708858588". >> >> I think that's a good balance between compact and readable. >> >> Regards, >> >> Hans Regards, Hans
diff --git a/README b/README index a7759171..c5b6df16 100644 --- a/README +++ b/README @@ -45,6 +45,8 @@ you'll need also: alsa-lib-devel doxygen libjpeg-turbo-devel qt5-qtbase-devel libudev-devel mesa-libGLU-devel +The v4l2-tracer also needs the json-c library. +On Debian: libjson-c-dev; on Fedora: json-c-devel. After downloading and installing the needed packages, you should run: @@ -227,6 +229,12 @@ v4l2-sysfs-path: FIXME add description. Installed by make install under <prefix>/bin. +v4l2-tracer: +Tool to trace, record and replay userspace applications +that implement the v4l2 memory-to-memory stateless video +decoder interface. +Installed by make install under <prefix>/bin. + xc3028-firmware: Xceive XC2028/3028 tuner module firmware manipulation tool. xc3028-firmware does not get installed by make install. diff --git a/configure.ac b/configure.ac index 05298981..39d83579 100644 --- a/configure.ac +++ b/configure.ac @@ -32,6 +32,8 @@ AC_CONFIG_FILES([Makefile utils/v4l2-ctl/Makefile utils/v4l2-dbg/Makefile utils/v4l2-sysfs-path/Makefile + utils/v4l2-tracer/Makefile + utils/v4l2-tracer/v4l2-tracer.1 utils/qv4l2/Makefile utils/libcecutil/Makefile utils/cec-ctl/Makefile @@ -311,6 +313,15 @@ AS_IF([test "x$with_libudev" != xno -o "x$enable_libdvbv5" != xno], AC_SUBST([JPEG_LIBS]) +JSONC_VERSION_REQUIRED="0.15"; +PKG_CHECK_MODULES(JSONC, [json-c >= $JSONC_VERSION_REQUIRED], [jsonc_pkgconfig=yes], [jsonc_pkgconfig=no]) +AC_SUBST([JSONC_CFLAGS]) +AC_SUBST([JSONC_LIBS]) +AM_CONDITIONAL([HAVE_JSONC], [test x$jsonc_pkgconfig = xyes]) +if test "x$jsonc_pkgconfig" = "xno"; then + AC_MSG_WARN(json-c $JSONC_VERSION_REQUIRED or higher required for v4l2-tracer) +fi + # Check for pthread AS_IF([test x$enable_shared != xno], @@ -645,6 +656,7 @@ compile time options summary QT version : $QT_VERSION ALSA support : $USE_ALSA SDL support : $sdl_pc + JSON-C : $jsonc_pkgconfig >= $JSONC_VERSION_REQUIRED build dynamic libs : $enable_shared build static libs : $enable_static diff --git a/utils/Makefile.am b/utils/Makefile.am index 0e68a612..6f59515e 100644 --- a/utils/Makefile.am +++ b/utils/Makefile.am @@ -15,6 +15,11 @@ SUBDIRS = \ cec-follower \ rds-ctl +if HAVE_JSONC +SUBDIRS += \ + v4l2-tracer +endif + if WITH_LIBDVBV5 SUBDIRS += \ dvb diff --git a/utils/v4l2-tracer/.gitignore b/utils/v4l2-tracer/.gitignore new file mode 100644 index 00000000..c3389d24 --- /dev/null +++ b/utils/v4l2-tracer/.gitignore @@ -0,0 +1,8 @@ +trace-gen.cpp +trace-gen.h +retrace-gen.cpp +retrace-gen.h +trace-info-gen.cpp +trace-info-gen.h +v4l2-tracer +v4l2-tracer.1 diff --git a/utils/v4l2-tracer/Makefile.am b/utils/v4l2-tracer/Makefile.am new file mode 100644 index 00000000..ef21bb7c --- /dev/null +++ b/utils/v4l2-tracer/Makefile.am @@ -0,0 +1,33 @@ +if HAVE_JSONC + +lib_LTLIBRARIES = libv4l2tracer.la +libv4l2tracer_la_SOURCES = libv4l2tracer.cpp trace-gen.cpp trace-info-gen.cpp trace-helper.cpp \ +trace.cpp $(top_srcdir)/utils/common/v4l2-info.cpp +libv4l2tracer_la_CPPFLAGS = -I$(top_srcdir)/utils/common $(JSONC_CFLAGS) +libv4l2tracer_la_LDFLAGS = -avoid-version -module -shared -export-dynamic -ldl $(JSONC_LIBS) +libv4l2tracer_la_LIBTOOLFLAGS = --tag=disable-static + +bin_PROGRAMS = v4l2-tracer +man_MANS = v4l2-tracer.1 + +v4l2_tracer_SOURCES = v4l2-tracer.cpp retrace-gen.cpp retrace-helper.cpp retracer.cpp \ +$(top_srcdir)/utils/common/v4l2-info.cpp $(top_srcdir)/utils/common/codec-fwht.h +v4l2_tracer_CPPFLAGS = -I$(top_srcdir)/utils/common -DLIBTRACER_PATH=$(libdir) $(JSONC_CFLAGS) +v4l2_tracer_LDFLAGS = -lrt -lpthread $(JSONC_LIBS) + +V4L2_TRACER_GEN_PREREQS = $(top_srcdir)/include/linux/v4l2-controls.h \ +$(top_srcdir)/include/linux/videodev2.h $(top_srcdir)/include/linux/media.h +V4L2_TRACER_GEN = trace-gen.cpp trace-gen.h trace-info-gen.cpp trace-info-gen.h \ +retrace-gen.cpp retrace-gen.h +v4l2-tracer.cpp: $(V4L2_TRACER_GEN) +$(V4L2_TRACER_GEN): gen.intermediate; +.INTERMEDIATE: gen.intermediate +gen.intermediate: v4l2-tracer-gen.pl $(V4L2_TRACER_GEN_PREREQS) + $(AM_V_GEN) $(top_srcdir)/utils/v4l2-tracer/v4l2-tracer-gen.pl $(V4L2_TRACER_GEN_PREREQS) + +clean-local: + -rm -vf $(V4L2_TRACER_GEN) + +EXTRA_DIST = v4l2-tracer.1, v4l2-tracer-gen.pl + +endif diff --git a/utils/v4l2-tracer/libv4l2tracer.cpp b/utils/v4l2-tracer/libv4l2tracer.cpp new file mode 100644 index 00000000..c344d8a1 --- /dev/null +++ b/utils/v4l2-tracer/libv4l2tracer.cpp @@ -0,0 +1,222 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2022 Collabora Ltd. + */ + +#include <dlfcn.h> +#include <stdarg.h> +#include <config.h> /* For PROMOTED_MODE_T */ +#include "v4l2-tracer-common.h" +#include "trace-helper.h" + +/* from trace.cpp */ +void trace_open(int fd, const char *path, int oflag, mode_t mode, bool is_open64); +void trace_mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off, + unsigned long buf_address, bool is_mmap64); +void trace_mem(int fd, __u32 offset, __u32 type, int index, __u32 bytesused, unsigned long start); +void trace_mem_encoded(int fd, __u32 offset); +json_object *trace_ioctl_args(int fd, unsigned long request, void *arg, + bool from_userspace = true); + +int open(const char *path, int oflag, ...) +{ + errno = 0; + mode_t mode = 0; + + if (oflag & O_CREAT) { + va_list ap; + va_start(ap, oflag); + mode = va_arg(ap, PROMOTED_MODE_T); + va_end(ap); + } + + int (*original_open)(const char *path, int oflag, ...); + original_open = (int (*)(const char*, int, ...)) dlsym(RTLD_NEXT, "open"); + int fd = (*original_open)(path, oflag, mode); + + if (is_video_or_media_device(path)) { + if (!options_are_set()) + set_options(); + add_device(fd, path); + trace_open(fd, path, oflag, mode, false); + } + + return fd; +} + +int open64(const char *path, int oflag, ...) +{ + errno = 0; + mode_t mode = 0; + if (oflag & O_CREAT) { + va_list ap; + va_start(ap, oflag); + mode = va_arg(ap, PROMOTED_MODE_T); + va_end(ap); + } + + int (*original_open64)(const char *path, int oflag, ...); + original_open64 = (int (*)(const char*, int, ...)) dlsym(RTLD_NEXT, "open64"); + int fd = (*original_open64)(path, oflag, mode); + + if (is_video_or_media_device(path)) { + if (!options_are_set()) + set_options(); + add_device(fd, path); + trace_open(fd, path, oflag, mode, true); + } + + return fd; +} + +int close(int fd) +{ + errno = 0; + std::string path = get_device(fd); + + /* Only trace the close if a corresponding open was also traced. */ + if (!path.empty()) { + json_object *close_obj = json_object_new_object(); + json_object_object_add(close_obj, "syscall_str", json_object_new_string("close")); + json_object_object_add(close_obj, "syscall", json_object_new_int(LIBTRACER_SYSCALL_CLOSE)); + json_object_object_add(close_obj, "fd", json_object_new_int(fd)); + json_object_object_add(close_obj, "path", json_object_new_string(path.c_str())); + write_json_object_to_json_file(close_obj); + json_object_put(close_obj); + remove_device(fd); + + /* If we removed the last device, close the json trace file. */ + if (!count_devices()) + close_json_file(); + } + + int (*original_close)(int fd); + original_close = (int (*)(int)) dlsym(RTLD_NEXT, "close"); + + return (*original_close)(fd); +} + +void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off) +{ + errno = 0; + void *(*original_mmap)(void *addr, size_t len, int prot, int flags, int fildes, off_t off); + original_mmap = (void*(*)(void*, size_t, int, int, int, off_t)) dlsym(RTLD_NEXT, "mmap"); + void *buf_address_pointer = (*original_mmap)(addr, len, prot, flags, fildes, off); + + set_buffer_address_trace(fildes, off, (unsigned long) buf_address_pointer); + + if (buffer_in_trace_context(fildes, off)) + trace_mmap(addr, len, prot, flags, fildes, off, (unsigned long) buf_address_pointer, false); + + return buf_address_pointer; +} + +void *mmap64(void *addr, size_t len, int prot, int flags, int fildes, off_t off) +{ + errno = 0; + void *(*original_mmap64)(void *addr, size_t len, int prot, int flags, int fildes, off_t off); + original_mmap64 = (void*(*)(void*, size_t, int, int, int, off_t)) dlsym(RTLD_NEXT, "mmap64"); + void *buf_address_pointer = (*original_mmap64)(addr, len, prot, flags, fildes, off); + + set_buffer_address_trace(fildes, off, (unsigned long) buf_address_pointer); + + if (buffer_in_trace_context(fildes, off)) + trace_mmap(addr, len, prot, flags, fildes, off, (unsigned long) buf_address_pointer, true); + + return buf_address_pointer; +} + +int munmap(void *start, size_t length) +{ + errno = 0; + int(*original_munmap)(void *start, size_t length); + original_munmap = (int(*)(void *, size_t)) dlsym(RTLD_NEXT, "munmap"); + int ret = (*original_munmap)(start, length); + + /* Only trace the unmapping if the original mapping was traced. */ + if (!buffer_is_mapped((unsigned long) start)) + return ret; + + json_object *munmap_obj = json_object_new_object(); + json_object_object_add(munmap_obj, "syscall_str", json_object_new_string("munmap")); + json_object_object_add(munmap_obj, "syscall", json_object_new_int(LIBTRACER_SYSCALL_MUNMAP)); + + if (errno) { + json_object_object_add(munmap_obj, "errno", json_object_new_int(errno)); + json_object_object_add(munmap_obj, "errno_str", json_object_new_string(strerror(errno))); + } + + json_object *munmap_args = json_object_new_object(); + json_object_object_add(munmap_args, "start", json_object_new_int64((int64_t)start)); + json_object_object_add(munmap_args, "length", json_object_new_uint64(length)); + json_object_object_add(munmap_obj, "munmap_args", munmap_args); + + write_json_object_to_json_file(munmap_obj); + json_object_put(munmap_obj); + + return ret; +} + +int ioctl(int fd, unsigned long request, ...) +{ + errno = 0; + va_list ap; + va_start(ap, request); + void *arg = va_arg(ap, void *); + va_end(ap); + + int (*original_ioctl)(int fd, unsigned long request, ...); + original_ioctl = (int (*)(int, long unsigned int, ...)) dlsym(RTLD_NEXT, "ioctl"); + + if ((request == VIDIOC_S_EXT_CTRLS) && (_IOC_TYPE(request) == 'V')) + s_ext_ctrls_setup(static_cast<struct v4l2_ext_controls*>(arg)); + + if ((request == VIDIOC_QBUF) && (_IOC_TYPE(request) == 'V')) + qbuf_setup(static_cast<struct v4l2_buffer*>(arg)); + + if ((request == VIDIOC_STREAMOFF) && (_IOC_TYPE(request) == 'V')) + streamoff_cleanup(*(static_cast<v4l2_buf_type*>(arg))); + + json_object *ioctl_obj = json_object_new_object(); + json_object_object_add(ioctl_obj, "syscall_str", json_object_new_string("ioctl")); + json_object_object_add(ioctl_obj, "syscall", json_object_new_int(LIBTRACER_SYSCALL_IOCTL)); + json_object_object_add(ioctl_obj, "fd", json_object_new_int(fd)); + json_object_object_add(ioctl_obj, "request", json_object_new_uint64(request)); + json_object_object_add(ioctl_obj, "request_str", + json_object_new_string(get_ioctl_request_str(request).c_str())); + + /* Trace the ioctl arguments provided by userspace. */ + json_object *ioctl_args_userspace = trace_ioctl_args(fd, request, arg); + if (json_object_object_length(ioctl_args_userspace)) + json_object_object_add(ioctl_obj, "ioctl_args_from_userspace", ioctl_args_userspace); + + /* Make the original ioctl call. */ + int ret = (*original_ioctl)(fd, request, arg); + + if (errno) { + json_object_object_add(ioctl_obj, "errno", json_object_new_int(errno)); + json_object_object_add(ioctl_obj, "errno_str", json_object_new_string(strerror(errno))); + } + + /* Also trace the ioctl arguments as modified by the driver. */ + json_object *ioctl_args_driver = trace_ioctl_args(fd, request, arg, false); + if (json_object_object_length(ioctl_args_driver)) + json_object_object_add(ioctl_obj, "ioctl_args_from_driver", ioctl_args_driver); + + write_json_object_to_json_file(ioctl_obj); + json_object_put(ioctl_obj); + + if ((request == VIDIOC_G_FMT) && (_IOC_TYPE(request) == 'V')) + g_fmt_setup_trace(static_cast<struct v4l2_format*>(arg)); + + if ((request == VIDIOC_S_FMT) && (_IOC_TYPE(request) == 'V')) + s_fmt_setup(static_cast<struct v4l2_format*>(arg)); + + if ((request == VIDIOC_EXPBUF) && (_IOC_TYPE(request) == 'V')) + expbuf_setup(static_cast<struct v4l2_exportbuffer*>(arg)); + + if ((request == VIDIOC_QUERYBUF) && (_IOC_TYPE(request) == 'V')) + querybuf_setup(fd, static_cast<struct v4l2_buffer*>(arg)); + + return ret; +} diff --git a/utils/v4l2-tracer/retrace-helper.cpp b/utils/v4l2-tracer/retrace-helper.cpp new file mode 100644 index 00000000..37fad2c3 --- /dev/null +++ b/utils/v4l2-tracer/retrace-helper.cpp @@ -0,0 +1,196 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2022 Collabora Ltd. + */ + +#include "v4l2-tracer-common.h" + +struct buffer_retrace { + int fd; + __u32 type; /* enum v4l2_buf_type */ + __u32 index; + __u32 offset; + long address_trace; + long address_retrace; +}; + +struct retrace_context { + __u32 width; + __u32 height; + __u32 pixelformat; + pthread_mutex_t lock; + /* Key is a file descriptor from the trace, value is the corresponding fd in the retrace. */ + std::unordered_map<int, int> retrace_fds; + /* List of output and capture buffers being retraced. */ + std::list<struct buffer_retrace> buffers; +}; + +static struct retrace_context ctx_retrace = { + .lock = PTHREAD_MUTEX_INITIALIZER +}; + +bool buffer_in_retrace_context(int fd, __u32 offset) +{ + bool buffer_in_retrace_context = false; + pthread_mutex_lock(&ctx_retrace.lock); + for (auto &b : ctx_retrace.buffers) { + if ((b.fd == fd) && (b.offset == offset)) { + buffer_in_retrace_context = true; + break; + } + } + pthread_mutex_unlock(&ctx_retrace.lock); + return buffer_in_retrace_context; +} + +int get_buffer_fd_retrace(__u32 type, __u32 index) +{ + int fd = 0; + pthread_mutex_lock(&ctx_retrace.lock); + for (auto &b : ctx_retrace.buffers) { + if ((b.type == type) && (b.index == index)) { + fd = b.fd; + break; + } + } + pthread_mutex_unlock(&ctx_retrace.lock); + return fd; +} + +void add_buffer_retrace(int fd, __u32 type, __u32 index, __u32 offset) +{ + pthread_mutex_lock(&ctx_retrace.lock); + struct buffer_retrace buf = {}; + buf.fd = fd; + buf.type = type; + buf.index = index; + buf.offset = offset; + ctx_retrace.buffers.push_front(buf); + pthread_mutex_unlock(&ctx_retrace.lock); +} + +void remove_buffer_retrace(int fd) +{ + pthread_mutex_lock(&ctx_retrace.lock); + for (auto it = ctx_retrace.buffers.begin(); it != ctx_retrace.buffers.end(); ++it) { + if (it->fd == fd) { + ctx_retrace.buffers.erase(it); + break; + } + } + pthread_mutex_unlock(&ctx_retrace.lock); +} + +void set_buffer_address_retrace(int fd, __u32 offset, long address_trace, long address_retrace) +{ + pthread_mutex_lock(&ctx_retrace.lock); + for (auto &b : ctx_retrace.buffers) { + if ((b.fd == fd) && (b.offset == offset)) { + b.address_trace = address_trace; + b.address_retrace = address_retrace; + break; + } + } + pthread_mutex_unlock(&ctx_retrace.lock); +} + +long get_retrace_address_from_trace_address(long address_trace) +{ + long address_retrace = 0; + pthread_mutex_lock(&ctx_retrace.lock); + for (auto &b : ctx_retrace.buffers) { + if (b.address_trace == address_trace) { + address_retrace = b.address_retrace; + break; + } + } + pthread_mutex_unlock(&ctx_retrace.lock); + return address_retrace; +} + +void print_buffers_retrace(void) +{ + pthread_mutex_lock(&ctx_retrace.lock); + for (auto &b : ctx_retrace.buffers) { + fprintf(stderr, "fd: %d, offset: %d, address_trace:%ld, address_retrace:%ld\n", + b.fd, b.offset, b.address_trace, b.address_retrace); + } + pthread_mutex_unlock(&ctx_retrace.lock); +} + +/* + * Create a new file descriptor entry in retrace context. + * Add both the fd from the trace context and the corresponding fd from the retrace context. + */ +void add_fd(int fd_trace, int fd_retrace) +{ + std::pair<int, int> new_pair; + new_pair = std::make_pair(fd_trace, fd_retrace); + pthread_mutex_lock(&ctx_retrace.lock); + ctx_retrace.retrace_fds.insert(new_pair); + pthread_mutex_unlock(&ctx_retrace.lock); +} + +int get_fd_retrace_from_fd_trace(int fd_trace) +{ + int fd_retrace = 0; + std::unordered_map<int, int>::const_iterator it; + + pthread_mutex_lock(&ctx_retrace.lock); + it = ctx_retrace.retrace_fds.find(fd_trace); + if (it != ctx_retrace.retrace_fds.end()) + fd_retrace = it->second; + pthread_mutex_unlock(&ctx_retrace.lock); + + return fd_retrace; +} + +void remove_fd(int fd_trace) +{ + pthread_mutex_lock(&ctx_retrace.lock); + ctx_retrace.retrace_fds.erase(fd_trace); + pthread_mutex_unlock(&ctx_retrace.lock); +} + +void print_fds(void) +{ + pthread_mutex_lock(&ctx_retrace.lock); + if (ctx_retrace.retrace_fds.empty()) + fprintf(stderr, "all devices closed\n"); + for (auto it = ctx_retrace.retrace_fds.cbegin(); it != ctx_retrace.retrace_fds.cend(); ++it) + fprintf(stderr, "fd_trace: %d, fd_retrace: %d\n", it->first, it->second); + pthread_mutex_unlock(&ctx_retrace.lock); +} + +void set_pixelformat_retrace(__u32 width, __u32 height, __u32 pixelformat) +{ + pthread_mutex_lock(&ctx_retrace.lock); + ctx_retrace.width = width; + ctx_retrace.height = height; + ctx_retrace.pixelformat = pixelformat; + pthread_mutex_unlock(&ctx_retrace.lock); +} + +unsigned get_expected_length_retrace(void) +{ + pthread_mutex_lock(&ctx_retrace.lock); + unsigned width = ctx_retrace.width; + unsigned height = ctx_retrace.height; + unsigned pixelformat = ctx_retrace.pixelformat; + pthread_mutex_unlock(&ctx_retrace.lock); + + unsigned expected_length = width * height; + if (pixelformat == V4L2_PIX_FMT_NV12 || pixelformat == V4L2_PIX_FMT_YUV420) { + expected_length *= 3; + expected_length /= 2; + expected_length += (expected_length % 2); + } + return expected_length; +} + +void print_context(void) +{ + print_fds(); + print_buffers_retrace(); + fprintf(stderr, "\n"); +} diff --git a/utils/v4l2-tracer/retracer.cpp b/utils/v4l2-tracer/retracer.cpp new file mode 100644 index 00000000..be6eddb2 --- /dev/null +++ b/utils/v4l2-tracer/retracer.cpp @@ -0,0 +1,1319 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2022 Collabora Ltd. + */ +#include <stdexcept> +#include "v4l2-tracer-common.h" +#include "retrace-gen.h" + +bool buffer_in_retrace_context(int fd, __u32 offset = 0); +int get_buffer_fd_retrace(__u32 type, __u32 index); +void add_buffer_retrace(int fd, __u32 type, __u32 index, __u32 offset = 0); +void remove_buffer_retrace(int fd); +void set_buffer_address_retrace(int fd, __u32 offset, long address_trace, long address_retrace); +long get_retrace_address_from_trace_address(long address_trace); +void add_fd(int fd_trace, int fd_retrace); +int get_fd_retrace_from_fd_trace(int fd_trace); +void remove_fd(int fd_trace); +void set_pixelformat_retrace(__u32 width, __u32 height, __u32 pixelformat); +unsigned get_expected_length_retrace(void); +void print_context(void); + +bool verbose = false; +std::string retrace_filename; +std::string dev_path_video; +std::string dev_path_media; + +enum Option_Retracer { + OptSetDevice_Retracer = 'd', + OptSetMediaDevice_Retracer = 'm', + OptVerbose_Retracer = 'v', +}; + +static struct option long_options_retracer[] = { + { "device", required_argument, nullptr, OptSetDevice_Retracer }, + { "media", required_argument, nullptr, OptSetMediaDevice_Retracer }, + { "verbose", no_argument, nullptr, OptVerbose_Retracer }, + { nullptr, 0, nullptr, 0 } +}; + +char short_options_retracer[] = { + OptSetDevice_Retracer, ':', + OptSetMediaDevice_Retracer, ':', + OptVerbose_Retracer +}; + +void retrace_mmap(json_object *mmap_obj, bool is_mmap64) +{ + json_object *mmap_args_obj; + json_object_object_get_ex(mmap_obj, "mmap_args", &mmap_args_obj); + + json_object *len_obj; + json_object_object_get_ex(mmap_args_obj, "len", &len_obj); + size_t len = (size_t) json_object_get_int(len_obj); + + json_object *prot_obj; + json_object_object_get_ex(mmap_args_obj, "prot", &prot_obj); + int prot = json_object_get_int(prot_obj); + + json_object *flags_obj; + json_object_object_get_ex(mmap_args_obj, "flags", &flags_obj); + int flags = json_object_get_int(flags_obj); + + json_object *fildes_obj; + json_object_object_get_ex(mmap_args_obj, "fildes", &fildes_obj); + int fd_trace = json_object_get_int(fildes_obj); + + json_object *off_obj; + json_object_object_get_ex(mmap_args_obj, "off", &off_obj); + off_t off = (off_t) json_object_get_int64(off_obj); + + int fd_retrace = get_fd_retrace_from_fd_trace(fd_trace); + + /* Only retrace mmap calls that map a buffer. */ + if (!buffer_in_retrace_context(fd_retrace, off)) + return; + + void *buf_address_retrace_pointer; + if (is_mmap64) + buf_address_retrace_pointer = mmap64(0, len, prot, flags, fd_retrace, off); + else + buf_address_retrace_pointer = mmap(0, len, prot, flags, fd_retrace, off); + + if (buf_address_retrace_pointer == MAP_FAILED) { + if (is_mmap64) + perror("mmap64"); + else + perror("mmap"); + print_context(); + exit(EXIT_FAILURE); + } + + /* + * Get and store the original trace address so that it can be matched + * with the munmap address later. + */ + json_object *buffer_address_obj; + json_object_object_get_ex(mmap_obj, "buffer_address", &buffer_address_obj); + long buf_address_trace = json_object_get_int64(buffer_address_obj); + set_buffer_address_retrace(fd_retrace, off, buf_address_trace, + (long) buf_address_retrace_pointer); + + if (verbose || (errno != 0)) { + if (is_mmap64) + perror("mmap64"); + else + perror("mmap"); + fprintf(stderr, "fd: %d, offset: %ld\n", fd_retrace, off); + print_context(); + } +} + +void retrace_munmap(json_object *syscall_obj) +{ + json_object *munmap_args_obj; + json_object_object_get_ex(syscall_obj, "munmap_args", &munmap_args_obj); + + json_object *start_obj; + json_object_object_get_ex(munmap_args_obj, "start", &start_obj); + long start = json_object_get_int64(start_obj); + + json_object *length_obj; + json_object_object_get_ex(munmap_args_obj, "length", &length_obj); + size_t length = (size_t) json_object_get_int(length_obj); + + long buffer_address_retrace = get_retrace_address_from_trace_address(start); + + if (buffer_address_retrace < 0) + return; + + munmap((void *)buffer_address_retrace, length); + + if (verbose || (errno != 0)) { + perror("munmap"); + fprintf(stderr, "unmapped: %ld\n", buffer_address_retrace); + fprintf(stderr, "\n"); + } +} + +void retrace_open(json_object *jobj, bool is_open64) +{ + json_object *fd_trace_obj; + json_object_object_get_ex(jobj, "fd", &fd_trace_obj); + int fd_trace = json_object_get_int(fd_trace_obj); + + json_object *open_args_obj; + json_object_object_get_ex(jobj, "open_args", &open_args_obj); + + json_object *path_obj; + json_object_object_get_ex(open_args_obj, "path", &path_obj); + std::string path = json_object_get_string(path_obj); + + json_object *oflag_obj; + json_object_object_get_ex(open_args_obj, "oflag", &oflag_obj); + int oflag = json_object_get_int(oflag_obj); + + json_object *mode_obj; + json_object_object_get_ex(open_args_obj, "mode", &mode_obj); + int mode = json_object_get_int(mode_obj); + + /* Use device from the command line, instead of from the trace file. */ + if ((path.find("video") != path.npos) && !dev_path_video.empty()) + path = dev_path_video; + + if ((path.find("media") != path.npos) && !dev_path_media.empty()) + path = dev_path_media; + + int fd_retrace = 0; + if (is_open64) + fd_retrace = open64(path.c_str(), oflag, mode); + else + fd_retrace = open(path.c_str(), oflag, mode); + + if (fd_retrace <= 0) { + fprintf(stderr, "Cannot open: %s\n", path.c_str()); + exit(fd_retrace); + } + + add_fd(fd_trace, fd_retrace); + + if (verbose || (errno != 0)) { + if (is_open64) + perror("open64"); + else + perror("open"); + fprintf(stderr, "path: %s \n", path.c_str()); + print_context(); + } +} + +void retrace_close(json_object *jobj) +{ + json_object *fd_trace_obj; + json_object_object_get_ex(jobj, "fd", &fd_trace_obj); + int fd_retrace = get_fd_retrace_from_fd_trace(json_object_get_int(fd_trace_obj)); + + /* Only close devices that were opened in the retrace context. */ + if (fd_retrace) { + close(fd_retrace); + remove_fd(json_object_get_int(fd_trace_obj)); + + if (verbose || (errno != 0)) { + perror("close"); + fprintf(stderr, "fd: %d\n\n", fd_retrace); + print_context(); + } + } +} + +struct v4l2_requestbuffers retrace_v4l2_requestbuffers(json_object *ioctl_args) +{ + struct v4l2_requestbuffers request_buffers = {}; + + json_object *requestbuffers_obj; + json_object_object_get_ex(ioctl_args, "v4l2_requestbuffers", &requestbuffers_obj); + + json_object *count_obj; + json_object_object_get_ex(requestbuffers_obj, "count", &count_obj); + request_buffers.count = json_object_get_int(count_obj); + + json_object *type_obj; + json_object_object_get_ex(requestbuffers_obj, "type", &type_obj); + request_buffers.type = json_object_get_int(type_obj); + + json_object *memory_obj; + json_object_object_get_ex(requestbuffers_obj, "memory", &memory_obj); + request_buffers.memory = json_object_get_int(memory_obj); + + json_object *capabilities_obj; + json_object_object_get_ex(requestbuffers_obj, "capabilities", &capabilities_obj); + request_buffers.capabilities = json_object_get_int(capabilities_obj); + + json_object *flags_obj; + json_object_object_get_ex(requestbuffers_obj, "flags", &flags_obj); + request_buffers.flags = json_object_get_int(flags_obj); + + return request_buffers; +} + +void retrace_vidioc_reqbufs(int fd_retrace, json_object *ioctl_args) +{ + struct v4l2_requestbuffers request_buffers = retrace_v4l2_requestbuffers(ioctl_args); + + ioctl(fd_retrace, VIDIOC_REQBUFS, &request_buffers); + + if (verbose || (errno != 0)) { + perror("VIDIOC_REQBUFS"); + fprintf(stderr, "type: %s, request_buffers.count: %d\n", + buftype2s(request_buffers.type).c_str(), request_buffers.count); + print_context(); + } +} + +struct v4l2_plane *retrace_v4l2_plane(json_object *plane_obj, __u32 memory) +{ + struct v4l2_plane *pl = (struct v4l2_plane *) calloc(1, sizeof(v4l2_plane)); + + json_object *bytesused_obj; + json_object_object_get_ex(plane_obj, "bytesused", &bytesused_obj); + pl->bytesused = (__u32) json_object_get_uint64(bytesused_obj); + + json_object *length_obj; + json_object_object_get_ex(plane_obj, "length", &length_obj); + pl->length = (__u32) json_object_get_uint64(length_obj); + + json_object *m_obj; + json_object_object_get_ex(plane_obj, "m", &m_obj); + if (memory == V4L2_MEMORY_MMAP) { + json_object *mem_offset_obj; + json_object_object_get_ex(m_obj, "mem_offset", &mem_offset_obj); + pl->m.mem_offset = (__u32) json_object_get_int64(mem_offset_obj); + } + + json_object *data_offset_obj; + json_object_object_get_ex(plane_obj, "data_offset", &data_offset_obj); + pl->data_offset = (__u32) json_object_get_int64(data_offset_obj); + + return pl; +} + +struct v4l2_buffer *retrace_v4l2_buffer(json_object *ioctl_args) +{ + struct v4l2_buffer *buf = (struct v4l2_buffer *) calloc(1, sizeof(struct v4l2_buffer)); + + json_object *buf_obj; + json_object_object_get_ex(ioctl_args, "v4l2_buffer", &buf_obj); + + json_object *index_obj; + json_object_object_get_ex(buf_obj, "index", &index_obj); + buf->index = (__u32) json_object_get_uint64(index_obj); + + json_object *type_obj; + json_object_object_get_ex(buf_obj, "type", &type_obj); + buf->type = (__u32) json_object_get_uint64(type_obj); + + json_object *bytesused_obj; + json_object_object_get_ex(buf_obj, "bytesused", &bytesused_obj); + buf->bytesused = (__u32) json_object_get_uint64(bytesused_obj); + + json_object *flags_obj; + json_object_object_get_ex(buf_obj, "flags", &flags_obj); + buf->flags = (__u32) json_object_get_uint64(flags_obj); + + json_object *field_obj; + json_object_object_get_ex(buf_obj, "field", &field_obj); + buf->field = (__u32) json_object_get_uint64(field_obj); + + json_object *timestamp_obj; + json_object_object_get_ex(buf_obj, "timestamp", ×tamp_obj); + + struct timeval tv = {}; + json_object *tv_sec_obj; + json_object_object_get_ex(timestamp_obj, "tv_sec", &tv_sec_obj); + tv.tv_sec = json_object_get_int64(tv_sec_obj); + json_object *tv_usec_obj; + json_object_object_get_ex(timestamp_obj, "tv_usec", &tv_usec_obj); + tv.tv_usec = json_object_get_int64(tv_usec_obj); + buf->timestamp = tv; + + json_object *sequence_obj; + json_object_object_get_ex(buf_obj, "sequence", &sequence_obj); + buf->sequence = (__u32) json_object_get_uint64(sequence_obj); + + json_object *memory_obj; + json_object_object_get_ex(buf_obj, "memory", &memory_obj); + buf->memory = (__u32) json_object_get_uint64(memory_obj); + + json_object *length_obj; + json_object_object_get_ex(buf_obj, "length", &length_obj); + buf->length = (__u32) json_object_get_uint64(length_obj); + + json_object *m_obj; + json_object_object_get_ex(buf_obj, "m", &m_obj); + + /* multi-planar API */ + if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE || + buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + json_object *planes_obj; + json_object_object_get_ex(m_obj, "planes", &planes_obj); + /* TODO add planes > 0 */ + json_object *plane_obj = json_object_array_get_idx(planes_obj, 0); + buf->m.planes = retrace_v4l2_plane(plane_obj, buf->memory); + } + + /* single-planar API */ + if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE || + buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + + switch (buf->memory) { + case V4L2_MEMORY_MMAP: { + json_object *offset_obj; + json_object_object_get_ex(m_obj, "offset", &offset_obj); + buf->m.offset = (__u32) json_object_get_uint64(offset_obj); + break; + } + case V4L2_MEMORY_USERPTR: + case V4L2_MEMORY_DMABUF: + break; + } + } + + if (buf->flags & V4L2_BUF_FLAG_REQUEST_FD) { + json_object *request_fd_obj; + json_object_object_get_ex(buf_obj, "request_fd", &request_fd_obj); + buf->request_fd = (__s32) get_fd_retrace_from_fd_trace(json_object_get_int(request_fd_obj)); + } + + return buf; +} + +void retrace_vidioc_querybuf(int fd_retrace, json_object *ioctl_args_user) +{ + struct v4l2_buffer *buf = retrace_v4l2_buffer(ioctl_args_user); + + ioctl(fd_retrace, VIDIOC_QUERYBUF, buf); + + switch (buf->memory) { + case V4L2_MEMORY_MMAP: { + __u32 offset = 0; + if ((buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) || + (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)) + offset = buf->m.offset; + if ((buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) || + (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)) + offset = buf->m.planes->m.mem_offset; + if (!get_buffer_fd_retrace(buf->type, buf->index)) + add_buffer_retrace(fd_retrace, buf->type, buf->index, offset); + break; + } + case V4L2_MEMORY_USERPTR: + case V4L2_MEMORY_DMABUF: + default: + break; + } + + if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE || + buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + if (buf->m.planes != nullptr) { + free(buf->m.planes); + } + } + + if (verbose || (errno != 0)) { + perror("VIDIOC_QUERYBUF"); + fprintf(stderr, "buf->type: %s, buf->index: %d, fd_retrace: %d\n", + buftype2s(buf->type).c_str(), buf->index, fd_retrace); + print_context(); + } + + free(buf); +} + +void retrace_vidioc_qbuf(int fd_retrace, json_object *ioctl_args_user) +{ + struct v4l2_buffer *buf = retrace_v4l2_buffer(ioctl_args_user); + + ioctl(fd_retrace, VIDIOC_QBUF, buf); + + if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE || + buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + if (buf->m.planes != nullptr) { + free(buf->m.planes); + } + } + + if (verbose || (errno != 0)) { + perror("VIDIOC_QBUF"); + fprintf(stderr, "buf->type: %s, buf->index: %d, fd_retrace: %d, \n", + buftype2s(buf->type).c_str(), buf->index, fd_retrace); + print_context(); + } + + free(buf); +} + +struct v4l2_exportbuffer retrace_v4l2_exportbuffer(json_object *ioctl_args) +{ + struct v4l2_exportbuffer export_buffer = {}; + + json_object *exportbuffer_obj; + json_object_object_get_ex(ioctl_args, "v4l2_exportbuffer", &exportbuffer_obj); + + json_object *type_obj; + json_object_object_get_ex(exportbuffer_obj, "type", &type_obj); + export_buffer.type = json_object_get_int(type_obj); + + json_object *index_obj; + json_object_object_get_ex(exportbuffer_obj, "index", &index_obj); + export_buffer.index = json_object_get_int(index_obj); + + json_object *plane_obj; + json_object_object_get_ex(exportbuffer_obj, "plane", &plane_obj); + export_buffer.plane = json_object_get_int(plane_obj); + + json_object *flags_obj; + json_object_object_get_ex(exportbuffer_obj, "flags", &flags_obj); + export_buffer.flags = json_object_get_int(flags_obj); + + json_object *fd_obj; + json_object_object_get_ex(exportbuffer_obj, "fd", &fd_obj); + export_buffer.fd = json_object_get_int(fd_obj); + + return export_buffer; +} + +void retrace_vidioc_expbuf(int fd_retrace, json_object *ioctl_args_user, json_object *ioctl_args_driver) +{ + struct v4l2_exportbuffer export_buffer = retrace_v4l2_exportbuffer(ioctl_args_user); + ioctl(fd_retrace, VIDIOC_EXPBUF, &export_buffer); + + int buf_fd_retrace = export_buffer.fd; + + /* + * If a buffer was previously added to the retrace context using the video device + * file descriptor, replace the video fd with the more specific buffer fd from EXPBUF. + */ + int fd_found_in_retrace_context = get_buffer_fd_retrace(export_buffer.type, export_buffer.index); + if (fd_found_in_retrace_context) + remove_buffer_retrace(fd_found_in_retrace_context); + + add_buffer_retrace(buf_fd_retrace, export_buffer.type, export_buffer.index); + + /* + * Get the export buffer file descriptor as provided by the driver in the original + * trace context. Then associate this original file descriptor with the current + * file descriptor in the retrace context. + */ + memset(&export_buffer, 0, sizeof(v4l2_exportbuffer)); + export_buffer = retrace_v4l2_exportbuffer(ioctl_args_driver); + int buf_fd_trace = export_buffer.fd; + add_fd(buf_fd_trace, buf_fd_retrace); + + if (verbose || (errno != 0)) { + perror("VIDIOC_EXPBUF"); + fprintf(stderr, "type: %s \n", buftype2s(export_buffer.type).c_str()); + fprintf(stderr, "index: %d, fd: %d\n", export_buffer.index, buf_fd_retrace); + print_context(); + } +} + +void retrace_vidioc_dqbuf(int fd_retrace, json_object *ioctl_args_user) +{ + struct v4l2_buffer *buf = retrace_v4l2_buffer(ioctl_args_user); + + struct pollfd *pfds = (struct pollfd *) calloc(1, sizeof(struct pollfd)); + if (pfds == NULL) + exit(EXIT_FAILURE); + pfds[0].fd = fd_retrace; + pfds[0].events = POLLIN; + poll(pfds, 1, 5000); + free(pfds); + + ioctl(fd_retrace, VIDIOC_DQBUF, buf); + + if (verbose || (errno != 0)) { + perror("VIDIOC_DQBUF"); + fprintf(stderr, "fd_retrace: %d\n", fd_retrace); + fprintf(stderr, "buf->index: %d\n", buf->index); + fprintf(stderr, "buf->type: %s,\n", buftype2s(buf->type).c_str()); + fprintf(stderr, "buf->bytesused: %u, \n", buf->bytesused); + fprintf(stderr, "buf->flags: %u\n", buf->flags); + fprintf(stderr, "buf->field: %u, buf->request_fd: %d\n", buf->field, buf->request_fd); + fprintf(stderr, "buf->request_fd: %d\n", buf->request_fd); + print_context(); + } + + if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE || buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + if (buf->m.planes != nullptr) { + free(buf->m.planes); + } + } + + free(buf); +} + +void retrace_vidioc_streamon(int fd_retrace, json_object *ioctl_args) +{ + json_object *buf_type_obj; + json_object_object_get_ex(ioctl_args, "buf_type", &buf_type_obj); + v4l2_buf_type buf_type = (v4l2_buf_type) json_object_get_int(buf_type_obj); + + ioctl(fd_retrace, VIDIOC_STREAMON, &buf_type); + + if (verbose || (errno != 0)) { + perror("VIDIOC_STREAMON"); + fprintf(stderr, "buftype: %s\n\n", buftype2s(buf_type).c_str()); + } +} + +void retrace_vidioc_streamoff(int fd_retrace, json_object *ioctl_args) +{ + json_object *buf_type_obj; + json_object_object_get_ex(ioctl_args, "buf_type", &buf_type_obj); + v4l2_buf_type buf_type = (v4l2_buf_type) json_object_get_int(buf_type_obj); + + ioctl(fd_retrace, VIDIOC_STREAMOFF, &buf_type); + + if (verbose || (errno != 0)) { + perror("VIDIOC_STREAMOFF"); + fprintf(stderr, "buftype: %s\n", buftype2s(buf_type).c_str()); + fprintf(stderr, "\n"); + } +} + +struct v4l2_plane_pix_format get_v4l2_plane_pix_format(json_object *pix_mp_obj, int plane) +{ + std::string key; + struct v4l2_plane_pix_format plane_fmt = {}; + + json_object *plane_fmt_obj, *sizeimage_obj, *bytesperline_obj; + + key = "v4l2_plane_pix_format_"; + key += std::to_string(plane); + json_object_object_get_ex(pix_mp_obj, key.c_str(), &plane_fmt_obj); + + json_object_object_get_ex(plane_fmt_obj, "sizeimage", &sizeimage_obj); + plane_fmt.sizeimage = json_object_get_int64(sizeimage_obj); + + json_object_object_get_ex(plane_fmt_obj, "bytesperline", &bytesperline_obj); + plane_fmt.bytesperline = json_object_get_int64(bytesperline_obj); + + return plane_fmt; +} + +struct v4l2_pix_format_mplane retrace_v4l2_pix_format_mplane(json_object *v4l2_format_obj) +{ + struct v4l2_pix_format_mplane pix_mp = {}; + + json_object *pix_mp_obj; + json_object_object_get_ex(v4l2_format_obj, "v4l2_pix_format_mplane", &pix_mp_obj); + + json_object *width_obj; + json_object_object_get_ex(pix_mp_obj, "width", &width_obj); + pix_mp.width = json_object_get_int(width_obj); + + json_object *height_obj; + json_object_object_get_ex(pix_mp_obj, "height", &height_obj); + pix_mp.height = json_object_get_int(height_obj); + + json_object *pixelformat_obj; + json_object_object_get_ex(pix_mp_obj, "pixelformat", &pixelformat_obj); + pix_mp.pixelformat = json_object_get_int(pixelformat_obj); + + json_object *field_obj; + json_object_object_get_ex(pix_mp_obj, "field", &field_obj); + pix_mp.field = json_object_get_int(field_obj); + + json_object *colorspace_obj; + json_object_object_get_ex(pix_mp_obj, "colorspace", &colorspace_obj); + pix_mp.colorspace = json_object_get_int(colorspace_obj); + + json_object *num_planes_obj; + json_object_object_get_ex(pix_mp_obj, "num_planes", &num_planes_obj); + pix_mp.num_planes = json_object_get_int(num_planes_obj); + + for (int i = 0; i < pix_mp.num_planes; i++) + pix_mp.plane_fmt[i] = get_v4l2_plane_pix_format(pix_mp_obj, i); + + json_object *flags_obj; + json_object_object_get_ex(pix_mp_obj, "flags", &flags_obj); + pix_mp.flags = json_object_get_int(flags_obj); + + json_object *ycbcr_enc_obj; + json_object_object_get_ex(pix_mp_obj, "ycbcr_enc", &ycbcr_enc_obj); + pix_mp.ycbcr_enc = json_object_get_int(ycbcr_enc_obj); + + json_object *quantization_obj; + json_object_object_get_ex(pix_mp_obj, "quantization", &quantization_obj); + pix_mp.quantization = json_object_get_int(quantization_obj); + + json_object *xfer_func_obj; + json_object_object_get_ex(pix_mp_obj, "xfer_func", &xfer_func_obj); + pix_mp.xfer_func = json_object_get_int(xfer_func_obj); + + return pix_mp; +} + +struct v4l2_format retrace_v4l2_format(json_object *ioctl_args) +{ + struct v4l2_format format = {}; + + json_object *v4l2_format_obj; + json_object_object_get_ex(ioctl_args, "v4l2_format", &v4l2_format_obj); + + json_object *type_obj; + json_object_object_get_ex(v4l2_format_obj, "type", &type_obj); + format.type = json_object_get_int(type_obj); + + switch (format.type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY: + break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + format.fmt.pix_mp = retrace_v4l2_pix_format_mplane(v4l2_format_obj); + break; + case V4L2_BUF_TYPE_SDR_CAPTURE: + case V4L2_BUF_TYPE_SDR_OUTPUT: + case V4L2_BUF_TYPE_META_CAPTURE: + case V4L2_BUF_TYPE_META_OUTPUT: + break; + } + + return format; +} + +void retrace_vidioc_g_fmt(int fd_retrace, json_object *ioctl_args_user) +{ + struct v4l2_format format = retrace_v4l2_format(ioctl_args_user); + + ioctl(fd_retrace, VIDIOC_G_FMT, &format); + + if (format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + set_pixelformat_retrace(format.fmt.pix.width, format.fmt.pix.height, + format.fmt.pix.pixelformat); + if (format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + set_pixelformat_retrace(format.fmt.pix_mp.width, format.fmt.pix_mp.height, + format.fmt.pix_mp.pixelformat); + + if (verbose || (errno != 0)) + perror("VIDIOC_G_FMT"); +} + +void retrace_vidioc_s_fmt(int fd_retrace, json_object *ioctl_args_user) +{ + struct v4l2_format format = retrace_v4l2_format(ioctl_args_user); + + ioctl(fd_retrace, VIDIOC_S_FMT, &format); + + if (verbose || (errno != 0)) { + perror("VIDIOC_S_FMT"); + fprintf(stderr, "%s\n", buftype2s(format.type).c_str()); + fprintf(stderr, "format.fmt.pix_mp.pixelformat: %s\n\n", + fcc2s(format.fmt.pix_mp.pixelformat).c_str()); + } +} + +int retrace_v4l2_ext_control_value(json_object *ctrl_obj) +{ + json_object *value_obj; + json_object_object_get_ex(ctrl_obj, "value", &value_obj); + return json_object_get_int(value_obj); +} + +struct v4l2_ext_control retrace_v4l2_ext_control(json_object *ext_controls_obj, int ctrl_idx) +{ + struct v4l2_ext_control ctrl = {}; + + std::string unique_key_for_control = "v4l2_ext_control_"; + unique_key_for_control += std::to_string(ctrl_idx); + + json_object *ctrl_obj; + json_object_object_get_ex(ext_controls_obj, unique_key_for_control.c_str(), &ctrl_obj); + + json_object *id_obj; + json_object_object_get_ex(ctrl_obj, "id", &id_obj); + ctrl.id = json_object_get_int64(id_obj); + + json_object *size_obj; + json_object_object_get_ex(ctrl_obj, "size", &size_obj); + ctrl.size = json_object_get_int64(size_obj); + + if ((ctrl.id & V4L2_CID_CODEC_STATELESS_BASE) == V4L2_CID_CODEC_STATELESS_BASE) { + switch (ctrl.id) { + case V4L2_CID_STATELESS_VP8_FRAME: + ctrl.ptr = retrace_v4l2_ctrl_vp8_frame_gen(ctrl_obj); + break; + case V4L2_CID_STATELESS_H264_DECODE_MODE: + case V4L2_CID_STATELESS_H264_START_CODE: + ctrl.value = retrace_v4l2_ext_control_value(ctrl_obj); + break; + case V4L2_CID_STATELESS_H264_SPS: + ctrl.ptr = retrace_v4l2_ctrl_h264_sps_gen(ctrl_obj); + break; + case V4L2_CID_STATELESS_H264_PPS: + ctrl.ptr = retrace_v4l2_ctrl_h264_pps_gen(ctrl_obj); + break; + case V4L2_CID_STATELESS_H264_SCALING_MATRIX: + ctrl.ptr = retrace_v4l2_ctrl_h264_scaling_matrix_gen(ctrl_obj); + break; + case V4L2_CID_STATELESS_H264_PRED_WEIGHTS: + ctrl.ptr = retrace_v4l2_ctrl_h264_pred_weights_gen(ctrl_obj); + break; + case V4L2_CID_STATELESS_H264_SLICE_PARAMS: + ctrl.ptr = retrace_v4l2_ctrl_h264_slice_params_gen(ctrl_obj); + break; + case V4L2_CID_STATELESS_H264_DECODE_PARAMS: + ctrl.ptr = retrace_v4l2_ctrl_h264_decode_params_gen(ctrl_obj); + break; + case V4L2_CID_STATELESS_FWHT_PARAMS: + ctrl.ptr = retrace_v4l2_ctrl_fwht_params_gen(ctrl_obj); + break; + case V4L2_CID_STATELESS_VP9_FRAME: + ctrl.ptr = retrace_v4l2_ctrl_vp9_frame_gen(ctrl_obj); + break; + case V4L2_CID_STATELESS_VP9_COMPRESSED_HDR: + ctrl.ptr = retrace_v4l2_ctrl_vp9_compressed_hdr_gen(ctrl_obj); + break; + case V4L2_CID_STATELESS_HEVC_SPS: + ctrl.ptr = retrace_v4l2_ctrl_hevc_sps_gen(ctrl_obj); + break; + case V4L2_CID_STATELESS_HEVC_PPS: + ctrl.ptr = retrace_v4l2_ctrl_hevc_pps_gen(ctrl_obj); + break; + case V4L2_CID_STATELESS_HEVC_SLICE_PARAMS: + ctrl.ptr = retrace_v4l2_ctrl_hevc_slice_params_gen(ctrl_obj); + break; + case V4L2_CID_STATELESS_HEVC_SCALING_MATRIX: + ctrl.ptr = retrace_v4l2_ctrl_hevc_scaling_matrix_gen(ctrl_obj); + break; + case V4L2_CID_STATELESS_HEVC_DECODE_PARAMS: + ctrl.ptr = retrace_v4l2_ctrl_hevc_decode_params_gen(ctrl_obj); + break; + case V4L2_CID_STATELESS_MPEG2_SEQUENCE: + ctrl.ptr = retrace_v4l2_ctrl_mpeg2_sequence_gen(ctrl_obj); + break; + case V4L2_CID_STATELESS_MPEG2_PICTURE: + ctrl.ptr = retrace_v4l2_ctrl_mpeg2_picture_gen(ctrl_obj); + break; + case V4L2_CID_STATELESS_MPEG2_QUANTISATION: + ctrl.ptr = retrace_v4l2_ctrl_mpeg2_quantisation_gen(ctrl_obj); + break; + default: + fprintf(stderr, "Unknown control: %d\n", ctrl.id); + break; + } + } + return ctrl; +} + +struct v4l2_ext_control *retrace_v4l2_ext_control_array_pointer(json_object *ext_controls_obj, int count) +{ + struct v4l2_ext_control *ctrl_array_pointer; + ctrl_array_pointer = (struct v4l2_ext_control *) calloc(count, sizeof(v4l2_ext_control)); + + for (int i = 0; i < count; i++) + ctrl_array_pointer[i] = retrace_v4l2_ext_control(ext_controls_obj, i); + + return ctrl_array_pointer; +} + +void retrace_vidioc_s_ext_ctrls(int fd_retrace, json_object *ioctl_args) +{ + struct v4l2_ext_controls ext_controls = {}; + + json_object *ext_controls_obj; + json_object_object_get_ex(ioctl_args, "v4l2_ext_controls", &ext_controls_obj); + + json_object *which_obj; + json_object_object_get_ex(ext_controls_obj, "which", &which_obj); + ext_controls.which = json_object_get_int(which_obj); + + json_object *count_obj; + json_object_object_get_ex(ext_controls_obj, "count", &count_obj); + ext_controls.count = json_object_get_int(count_obj); + + /* request_fd is only valid for V4L2_CTRL_WHICH_REQUEST_VAL */ + if (ext_controls.which == V4L2_CTRL_WHICH_REQUEST_VAL) { + json_object *request_fd_obj; + json_object_object_get_ex(ext_controls_obj, "request_fd", &request_fd_obj); + int request_fd_trace = json_object_get_int(request_fd_obj); + ext_controls.request_fd = get_fd_retrace_from_fd_trace(request_fd_trace); + } + + ext_controls.controls = retrace_v4l2_ext_control_array_pointer(ext_controls_obj, ext_controls.count); + + ioctl(fd_retrace, VIDIOC_S_EXT_CTRLS, &ext_controls); + + if (verbose || (errno != 0)) + perror("VIDIOC_S_EXT_CTRLS"); + + /* Free controls working backwards from the end of the controls array. */ + for (int i = ((int) ext_controls.count - 1); i >= 0 ; i--) { + if (ext_controls.controls[i].ptr != nullptr) + free(ext_controls.controls[i].ptr); + } + + if (ext_controls.controls != nullptr) + free(ext_controls.controls); +} + +void retrace_query_ext_ctrl(int fd_retrace, json_object *ioctl_args) +{ + struct v4l2_query_ext_ctrl query_ext_ctrl = {}; + + json_object *query_ext_ctrl_obj; + json_object_object_get_ex(ioctl_args, "v4l2_query_ext_ctrl", &query_ext_ctrl_obj); + + json_object *id_obj; + json_object_object_get_ex(query_ext_ctrl_obj, "id", &id_obj); + query_ext_ctrl.id = json_object_get_int64(id_obj); + + ioctl(fd_retrace, VIDIOC_QUERY_EXT_CTRL, &query_ext_ctrl); + + if (verbose) { + perror("VIDIOC_QUERY_EXT_CTRL"); + fprintf(stderr, "id: %u\n\n", query_ext_ctrl.id); + } +} + +void retrace_vidioc_enum_fmt(int fd_retrace, json_object *ioctl_args) +{ + struct v4l2_fmtdesc fmtdesc = {}; + + json_object *v4l2_fmtdesc_obj; + json_object_object_get_ex(ioctl_args, "v4l2_fmtdesc", &v4l2_fmtdesc_obj); + + json_object *index_obj; + json_object_object_get_ex(v4l2_fmtdesc_obj, "index", &index_obj); + fmtdesc.index = json_object_get_int(index_obj); + + json_object *type_obj; + json_object_object_get_ex(v4l2_fmtdesc_obj, "type", &type_obj); + fmtdesc.type = json_object_get_int(type_obj); + + json_object *mbus_code_obj; + json_object_object_get_ex(v4l2_fmtdesc_obj, "mbus_code", &mbus_code_obj); + fmtdesc.mbus_code = json_object_get_int64(mbus_code_obj); + + ioctl(fd_retrace, VIDIOC_ENUM_FMT, &fmtdesc); + + if (verbose) { + perror("VIDIOC_ENUM_FMT"); + fprintf(stderr, "index: %u\n", fmtdesc.index); + fprintf(stderr, "type: %u\n", fmtdesc.type); + fprintf(stderr, "flags: %u\n", fmtdesc.flags); + fprintf(stderr, "description: %s\n", fmtdesc.description); + fprintf(stderr, "pixelformat: %u\n", fmtdesc.pixelformat); + fprintf(stderr, "mbus_code: %u\n\n", fmtdesc.mbus_code); + } +} + +void retrace_vidioc_querycap(int fd_retrace) +{ + struct v4l2_capability argp = {}; + + ioctl(fd_retrace, VIDIOC_QUERYCAP, &argp); + + if (verbose || (errno != 0)) + perror("VIDIOC_QUERYCAP"); +} + +void retrace_media_ioc_request_alloc(int fd_retrace, json_object *ioctl_args) +{ + /* Get the original request file descriptor from the original trace file. */ + json_object *request_fd_trace_obj; + json_object_object_get_ex(ioctl_args, "request_fd", &request_fd_trace_obj); + int request_fd_trace = json_object_get_int(request_fd_trace_obj); + + /* Allocate a request in the retrace context. */ + __s32 request_fd_retrace = 0; + ioctl(fd_retrace, MEDIA_IOC_REQUEST_ALLOC, &request_fd_retrace); + + /* Associate the original request file descriptor with the current request file descriptor. */ + add_fd(request_fd_trace, request_fd_retrace); + + if (verbose || (errno != 0)) + perror("MEDIA_IOC_REQUEST_ALLOC"); +} + +void retrace_dma_buf_ioctl_sync(int fd_retrace, json_object *ioctl_args_user) +{ + struct dma_buf_sync sync = {}; + + json_object *sync_obj; + json_object_object_get_ex(ioctl_args_user, "dma_buf_sync",&sync_obj); + + json_object *flags_obj; + json_object_object_get_ex(sync_obj, "flags",&flags_obj); + sync.flags = json_object_get_int(flags_obj); + + ioctl(fd_retrace, DMA_BUF_IOCTL_SYNC, &sync); + + if (verbose || (errno != 0)) + perror("DMA_BUF_IOCTL_SYNC"); +} + +void retrace_ioctl_media(int fd_retrace, long request, json_object *ioctl_args_driver) +{ + switch (request){ + case MEDIA_IOC_DEVICE_INFO: + case MEDIA_IOC_ENUM_ENTITIES: + case MEDIA_IOC_ENUM_LINKS: + case MEDIA_IOC_SETUP_LINK: + case MEDIA_IOC_G_TOPOLOGY: { + struct media_v2_topology top = {}; + ioctl(fd_retrace, MEDIA_IOC_G_TOPOLOGY, &top); + if (verbose || (errno != 0)) + perror("MEDIA_IOC_G_TOPOLOGY"); + break; + } + case MEDIA_IOC_REQUEST_ALLOC: + retrace_media_ioc_request_alloc(fd_retrace, ioctl_args_driver); + break; + case MEDIA_REQUEST_IOC_QUEUE: { + ioctl(fd_retrace, MEDIA_REQUEST_IOC_QUEUE); + if (verbose || (errno != 0)) + perror("MEDIA_REQUEST_IOC_QUEUE"); + break; + } + case MEDIA_REQUEST_IOC_REINIT: { + ioctl(fd_retrace, MEDIA_REQUEST_IOC_REINIT); + if (verbose || (errno != 0)) + perror("MEDIA_REQUEST_IOC_REINIT"); + break; + } + default: + break; + } +} + +void retrace_ioctl_video(int fd_retrace, long request, json_object *ioctl_args_user, json_object *ioctl_args_driver) +{ + switch (request) { + case VIDIOC_QUERYCAP: + retrace_vidioc_querycap(fd_retrace); + break; + case VIDIOC_ENUM_FMT: + retrace_vidioc_enum_fmt(fd_retrace, ioctl_args_user); + break; + case VIDIOC_G_FMT: + retrace_vidioc_g_fmt(fd_retrace, ioctl_args_user); + break; + case VIDIOC_S_FMT: + retrace_vidioc_s_fmt(fd_retrace, ioctl_args_user); + break; + case VIDIOC_REQBUFS: + retrace_vidioc_reqbufs(fd_retrace, ioctl_args_user); + break; + case VIDIOC_QUERYBUF: + retrace_vidioc_querybuf(fd_retrace, ioctl_args_user); + break; + case VIDIOC_QBUF: + retrace_vidioc_qbuf(fd_retrace, ioctl_args_user); + break; + case VIDIOC_EXPBUF: + retrace_vidioc_expbuf(fd_retrace, ioctl_args_user, ioctl_args_driver); + break; + case VIDIOC_DQBUF: + retrace_vidioc_dqbuf(fd_retrace, ioctl_args_user); + break; + case VIDIOC_STREAMON: + retrace_vidioc_streamon(fd_retrace, ioctl_args_user); + break; + case VIDIOC_STREAMOFF: + retrace_vidioc_streamoff(fd_retrace, ioctl_args_user); + break; + case VIDIOC_S_EXT_CTRLS: + retrace_vidioc_s_ext_ctrls(fd_retrace, ioctl_args_user); + break; + case VIDIOC_QUERY_EXT_CTRL: + retrace_query_ext_ctrl(fd_retrace, ioctl_args_user); + break; + default: + break; + } +} + +void retrace_ioctl(json_object *syscall_obj) +{ + int fd_retrace = 0; + __u8 ioctl_type = 0; + long request = 0; + + json_object *fd_trace_obj; + json_object_object_get_ex(syscall_obj, "fd", &fd_trace_obj); + fd_retrace = get_fd_retrace_from_fd_trace(json_object_get_int(fd_trace_obj)); + + json_object *request_obj; + json_object_object_get_ex(syscall_obj, "request", &request_obj); + request = json_object_get_int64(request_obj); + + json_object *ioctl_args_user; + json_object_object_get_ex(syscall_obj, "ioctl_args_from_userspace", &ioctl_args_user); + + json_object *ioctl_args_driver; + json_object_object_get_ex(syscall_obj, "ioctl_args_from_driver", &ioctl_args_driver); + + ioctl_type = _IOC_TYPE(request); + switch (ioctl_type) { + case 'V': + retrace_ioctl_video(fd_retrace, request, ioctl_args_user, ioctl_args_driver); + break; + case '|': + retrace_ioctl_media(fd_retrace, request, ioctl_args_driver); + break; + case 'b': + if (request == DMA_BUF_IOCTL_SYNC) + retrace_dma_buf_ioctl_sync(fd_retrace, ioctl_args_user); + break; + default: + break; + } +} + +void write_to_output_buffer(unsigned char *buffer_pointer, int bytesused, json_object *mem_obj) +{ + std::string data; + int byteswritten = 0; + json_object *line_obj; + size_t number_of_lines; + + json_object *mem_array_obj; + json_object_object_get_ex(mem_obj, "mem_array", &mem_array_obj); + number_of_lines = json_object_array_length(mem_array_obj); + + for (long unsigned int i = 0; i < number_of_lines; i++) { + line_obj = json_object_array_get_idx(mem_array_obj, i); + data = json_object_get_string(line_obj); + + for (long unsigned i = 0; i < data.length(); i++) { + if (std::isspace(data[i])) + continue; + try { + /* Two values from the string e.g. "D9" are needed to write one byte. */ + *buffer_pointer = (char) std::stoi(data.substr(i,2), nullptr, 16); + buffer_pointer++; + i++; + byteswritten++; + } catch (std::invalid_argument& ia) { + fprintf(stderr, "Warning: \'%s\' is an invalid argument; %s line: %d.\n", + data.substr(i,2).c_str(), __func__, __LINE__); + } catch (std::out_of_range& oor) { + fprintf(stderr, "Warning: \'%s\' is out of range; %s line: %d.\n", + data.substr(i,2).c_str(), __func__, __LINE__); + } + } + } + + if (verbose) { + fprintf(stderr, "\nWrite to Output Buffer\n"); + fprintf(stderr, "bytesused: %d, byteswritten: %d\n", bytesused, byteswritten); + fprintf(stderr, "\n"); + } +} + +void write_decoded_frames_to_yuv_file_retrace(unsigned char *buffer_pointer, int bytesused) +{ + int byteswritten = 0; + int expected_length = (int) get_expected_length_retrace(); + + FILE *fp = fopen(retrace_filename.c_str(), "a"); + for (int i = 0; i < bytesused; i++) { + if (i < expected_length) { + fwrite(&buffer_pointer[i], sizeof(unsigned char), 1, fp); + byteswritten++; + } + } + fclose(fp); + + if (verbose){ + fprintf(stderr, "\nWrite to File\n"); + fprintf(stderr, "%s\n", retrace_filename.c_str()); + fprintf(stderr, "buffer_pointer address: %ld, bytesused: %d, byteswritten: %d\n", (long) buffer_pointer, bytesused, byteswritten); + fprintf(stderr, "\n"); + print_context(); + } +} + +void retrace_mem(json_object *mem_obj) +{ + json_object *type_obj; + json_object_object_get_ex(mem_obj, "type", &type_obj); + v4l2_buf_type type = (v4l2_buf_type) json_object_get_int(type_obj); + + json_object *bytesused_obj; + json_object_object_get_ex(mem_obj, "bytesused", &bytesused_obj); + int bytesused = json_object_get_int64(bytesused_obj); + if (!bytesused) + return; + + json_object *offset_obj; + json_object_object_get_ex(mem_obj, "offset", &offset_obj); + __u32 offset = json_object_get_int64(offset_obj); + + json_object *address_obj; + json_object_object_get_ex(mem_obj, "address", &address_obj); + long buffer_address_trace = json_object_get_int64(address_obj); + + long buffer_address_retrace = get_retrace_address_from_trace_address(buffer_address_trace); + + unsigned char *buffer_pointer = (unsigned char *) buffer_address_retrace; + + /* Get the encoded data from the json file and write it to output buffer memory. */ + if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE || type == V4L2_BUF_TYPE_VIDEO_OUTPUT) + write_to_output_buffer(buffer_pointer, bytesused, mem_obj); + + /* Get the decoded capture buffer from memory and write it to a binary yuv file. */ + if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE || type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + write_decoded_frames_to_yuv_file_retrace(buffer_pointer, bytesused); + + if (verbose) { + fprintf(stderr, "\n%s\n", __func__); + fprintf(stderr, "%s, bytesused: %d, offset: %d, buffer_address_retrace: %ld\n", + buftype2s(type).c_str(), bytesused, offset, buffer_address_retrace); + print_context(); + } +} + +void retrace_object(json_object *jobj) +{ + json_object *syscall_obj; + int ret = json_object_object_get_ex(jobj, "syscall", &syscall_obj); + int syscall = json_object_get_int(syscall_obj); + + /* If the json object doesn't hold a syscall, check if it holds a memory dump. */ + if (ret == 0) { + json_object *temp_obj; + if (json_object_object_get_ex(jobj, "mem_dump", &temp_obj)) + retrace_mem(jobj); + return; + } + + errno = 0; + + switch (syscall) { + case LIBTRACER_SYSCALL_IOCTL: + retrace_ioctl(jobj); + break; + case LIBTRACER_SYSCALL_OPEN: + retrace_open(jobj, false); + break; + case LIBTRACER_SYSCALL_OPEN64: + retrace_open(jobj, true); + break; + case LIBTRACER_SYSCALL_CLOSE: + retrace_close(jobj); + break; + case LIBTRACER_SYSCALL_MMAP: + retrace_mmap(jobj, false); + break; + case LIBTRACER_SYSCALL_MMAP64: + retrace_mmap(jobj, true); + break; + case LIBTRACER_SYSCALL_MUNMAP: + retrace_munmap(jobj); + break; + default: + break; + } +} + +void retrace_array(json_object *root_array_obj) +{ + json_object *jobj; + struct array_list *al = json_object_get_array(root_array_obj); + size_t json_objects_in_file = array_list_length(al); + + for (size_t i = 0; i < json_objects_in_file; i++) { + jobj = (json_object *) array_list_get_idx(al, i); + retrace_object(jobj); + } +} + +int get_options_retrace(int argc, char *argv[]) +{ + int ch; + do { + ch = getopt_long(argc, argv, short_options_retracer, long_options_retracer, NULL); + switch (ch){ + case OptSetDevice_Retracer: { + std::string device = optarg; + if (device[0] >= '0' && device[0] <= '9' && device.length() <= 3) { + static char newdev[20]; + sprintf(newdev, "/dev/video%s", optarg); + dev_path_video = newdev; + fprintf(stderr, "Using: %s\n", dev_path_video.c_str()); + } else { + return 1; + } + break; + } + case OptSetMediaDevice_Retracer: { + std::string device = optarg; + if (device[0] >= '0' && device[0] <= '9' && device.length() <= 3) { + static char newdev[20]; + sprintf(newdev, "/dev/media%s", optarg); + device = newdev; + dev_path_media = newdev; + fprintf(stderr, "Using: %s\n", dev_path_media.c_str()); + } else { + return 1; + } + break; + } + case OptVerbose_Retracer: + verbose = true; + break; + case '?': + case ':': + return 1; + default: + break; + } + } while (ch != -1); + + return 0; +} + +void print_help_retracer(void) +{ + fprintf(stderr, "v4l2-tracer retrace [retrace options] -- <trace_file>.json\n" + "\t-d, --device <dev> Use a different video device than specified in the trace file.\n" + "\t <dev> must be a digit corresponding to an existing /dev/video<dev> \n" + "\t-h, --help Display retrace help.\n" + "\t-m, --media <dev> Use a different media device than specified in the trace file.\n" + "\t <dev> must be a digit corresponding to an existing /dev/media<dev> \n" + "\t-v, --verbose Turn on verbose reporting\n\n"); +} + +int retracer(int argc, char *argv[]) +{ + if ((get_options_retrace(argc, argv)) || (optind == argc)) { + print_help_retracer(); + return 1; + } + + std::string trace_filename = argv[optind]; + + if (trace_filename.substr(trace_filename.length()-4, trace_filename.npos) != "json") { + fprintf(stderr, "Trace file must be json-formatted: %s\n", trace_filename.c_str()); + print_help_retracer(); + return 1; + } + + FILE *trace_file = fopen(trace_filename.c_str(), "r"); + if (trace_file == NULL) { + fprintf(stderr, "Trace file error: %s\n", trace_filename.c_str()); + return 1; + } + fclose(trace_file); + + fprintf(stderr, "Retracing: %s\n", trace_filename.c_str()); + + /* Create file to hold the decoded frames. Discard previous retraced file if any. */ + retrace_filename = trace_filename; + retrace_filename = retrace_filename.replace(5, retrace_filename.npos, "_retrace"); + retrace_filename += ".yuv"; + + json_object *root_array_obj = json_object_from_file(trace_filename.c_str()); + if (root_array_obj == nullptr) { + fprintf(stderr, "Retrace error from file: %s\n", trace_filename.c_str()); + return 1; + } + /* Discard any previous retrace with same name. */ + fclose(fopen(retrace_filename.c_str(), "w")); + + retrace_array(root_array_obj); + json_object_put(root_array_obj); + + fprintf(stderr, "Retracing complete in %s\n", retrace_filename.c_str()); + + return 0; +} diff --git a/utils/v4l2-tracer/trace-helper.cpp b/utils/v4l2-tracer/trace-helper.cpp new file mode 100644 index 00000000..6b4bf567 --- /dev/null +++ b/utils/v4l2-tracer/trace-helper.cpp @@ -0,0 +1,816 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2022 Collabora Ltd. + */ + +#include <math.h> /* for pow */ +#include "v4l2-tracer-common.h" +#include "trace-helper.h" +#include "trace-info-gen.h" + +void trace_mem(int fd, __u32 offset, __u32 type, int index, __u32 bytesused, unsigned long start); +void trace_mem_encoded(int fd, __u32 offset); + +struct buffer_trace { + int fd; + __u32 type; /* enum v4l2_buf_type */ + __u32 index; + __u32 offset; + __u32 bytesused; + long display_order; + unsigned long address; +}; + +struct trace_context { + __u32 width; + __u32 height; + FILE *trace_file; + __u32 pixelformat; + int pic_order_cnt_lsb; + int max_pic_order_cnt_lsb; + pthread_mutex_t lock; + __u32 compression_format; + std::string trace_filename; + int compressed_frame_count; + std::list<long> decode_order; + std::list<struct buffer_trace> buffers; + std::unordered_map<int, std::string> devices; /* key:fd, value: path of the device */ +}; + +struct trace_options { + bool options_are_set; + bool verbose; + bool pretty_print_all; + bool pretty_print_mem; + bool write_decoded_data_to_yuv_file; + bool write_decoded_data_to_json_file; +}; + +std::string ver2s(unsigned int version) +{ + char buf[16]; + sprintf(buf, "%d.%d.%d", version >> 16, (version >> 8) & 0xff, version & 0xff); + return buf; +} + +std::string which2s(unsigned long which) +{ + std::string s = "unknown"; + + switch (which) { + case V4L2_CTRL_WHICH_CUR_VAL: + s = "V4L2_CTRL_WHICH_CUR_VAL"; + break; + case V4L2_CTRL_WHICH_DEF_VAL: + s= "V4L2_CTRL_WHICH_DEF_VAL"; + break; + case V4L2_CTRL_WHICH_REQUEST_VAL: + s = "V4L2_CTRL_WHICH_REQUEST_VAL"; + break; + default: + break; + } + + return s; +} + +std::string val2s(unsigned long val, const definition *def) +{ + std::string s; + + if (val == 0) + return s; + + while ((def->val) && (def->val != val)) + def++; + + if (def->val == val) + s = def->str; + + return s; +} + +struct trace_options options; + +struct trace_context ctx_trace = { + .lock = PTHREAD_MUTEX_INITIALIZER +}; + +bool options_are_set(void) +{ + return options.options_are_set; +} + +void set_options(void) +{ + options.verbose = getenv("TRACE_OPTION_VERBOSE") ? true : false; + options.pretty_print_mem = getenv("TRACE_OPTION_PRETTY_PRINT_MEM") ? true : false; + options.pretty_print_all = getenv("TRACE_OPTION_PRETTY_PRINT_ALL") ? true : false; + options.write_decoded_data_to_json_file = getenv("TRACE_OPTION_WRITE_DECODED_TO_JSON_FILE") ? true : false; + options.write_decoded_data_to_yuv_file = getenv("TRACE_OPTION_WRITE_DECODED_TO_YUV_FILE") ? true : false; + options.options_are_set = true; +} + +bool option_is_set_verbose(void) +{ + return options.verbose; +} + +bool option_is_set_pretty_print_mem(void) +{ + return options.pretty_print_mem; +} + +bool option_is_set_write_decoded_data_to_json_file(void) +{ + return options.write_decoded_data_to_json_file; +} + +bool is_video_or_media_device(const char *path) +{ + std::string dev_path_video = "/dev/video"; + std::string dev_path_media = "/dev/media"; + if (strncmp(path, dev_path_video.c_str(), dev_path_video.length()) && + strncmp(path, dev_path_media.c_str(), dev_path_media.length())) + return false; + return true; +} + +void add_device(int fd, std::string path) +{ + std::pair<int, std::string> new_pair = std::make_pair(fd, path); + pthread_mutex_lock(&ctx_trace.lock); + ctx_trace.devices.insert(new_pair); + pthread_mutex_unlock(&ctx_trace.lock); +} + +std::string get_device(int fd) +{ + std::string path; + std::unordered_map<int, std::string>::const_iterator it; + pthread_mutex_lock(&ctx_trace.lock); + it = ctx_trace.devices.find(fd); + if (it != ctx_trace.devices.end()) + path = it->second; + pthread_mutex_unlock(&ctx_trace.lock); + return path; +} + +int remove_device(int fd) +{ + int ret = 0; + pthread_mutex_lock(&ctx_trace.lock); + ret = ctx_trace.devices.erase(fd); + pthread_mutex_unlock(&ctx_trace.lock); + return ret; +} + +int count_devices(void) +{ + int count = 0; + pthread_mutex_lock(&ctx_trace.lock); + count = ctx_trace.devices.size(); + pthread_mutex_unlock(&ctx_trace.lock); + return count; +} + +void set_pixelformat_trace(__u32 width, __u32 height, __u32 pixelformat) +{ + pthread_mutex_lock(&ctx_trace.lock); + ctx_trace.width = width; + ctx_trace.height = height; + ctx_trace.pixelformat = pixelformat; + pthread_mutex_unlock(&ctx_trace.lock); +} + +__u32 get_pixelformat(void) +{ + __u32 pixelformat = 0; + pthread_mutex_lock(&ctx_trace.lock); + pixelformat = ctx_trace.pixelformat; + pthread_mutex_unlock(&ctx_trace.lock); + return pixelformat; +} + +void set_compression_format(__u32 compression_format) +{ + pthread_mutex_lock(&ctx_trace.lock); + ctx_trace.compression_format = compression_format; + pthread_mutex_unlock(&ctx_trace.lock); +} + +__u32 get_compression_format(void) +{ + __u32 compression_format = 0; + pthread_mutex_lock(&ctx_trace.lock); + compression_format = ctx_trace.compression_format; + pthread_mutex_unlock(&ctx_trace.lock); + return compression_format; +} + +void set_compressed_frame_count(int count) +{ + pthread_mutex_lock(&ctx_trace.lock); + ctx_trace.compressed_frame_count = count; + pthread_mutex_unlock(&ctx_trace.lock); +} + +int get_compressed_frame_count(void) +{ + int ret = 0; + pthread_mutex_lock(&ctx_trace.lock); + ret = ctx_trace.compressed_frame_count; + pthread_mutex_unlock(&ctx_trace.lock); + return ret; +} + +void print_decode_order(void) +{ + pthread_mutex_lock(&ctx_trace.lock); + fprintf(stderr, "Decode order: "); + for (auto &num : ctx_trace.decode_order) + fprintf(stderr, "%ld, ", num); + fprintf(stderr, ".\n"); + pthread_mutex_unlock(&ctx_trace.lock); +} + +void set_decode_order(long decode_order) +{ + if (option_is_set_verbose()) + fprintf(stderr, "%s: %ld\n", __func__, decode_order); + + std::list<long>::iterator it; + pthread_mutex_lock(&ctx_trace.lock); + it = find(ctx_trace.decode_order.begin(), ctx_trace.decode_order.end(), decode_order); + if (it == ctx_trace.decode_order.end()) + ctx_trace.decode_order.push_front(decode_order); + pthread_mutex_unlock(&ctx_trace.lock); + + if (option_is_set_verbose()) + print_decode_order(); +} + +long get_decode_order(void) +{ + long decode_order = 0; + pthread_mutex_lock(&ctx_trace.lock); + if (!ctx_trace.decode_order.empty()) + decode_order = ctx_trace.decode_order.front(); + pthread_mutex_unlock(&ctx_trace.lock); + return decode_order; +} + +void set_max_pic_order_cnt_lsb(int max_pic_order_cnt_lsb) +{ + pthread_mutex_lock(&ctx_trace.lock); + ctx_trace.max_pic_order_cnt_lsb = max_pic_order_cnt_lsb; + pthread_mutex_unlock(&ctx_trace.lock); +} + +int get_max_pic_order_cnt_lsb(void) +{ + int max_pic_order_cnt_lsb = 0; + pthread_mutex_lock(&ctx_trace.lock); + max_pic_order_cnt_lsb = ctx_trace.max_pic_order_cnt_lsb; + pthread_mutex_unlock(&ctx_trace.lock); + return max_pic_order_cnt_lsb; +} + +void set_pic_order_cnt_lsb(int pic_order_cnt_lsb) +{ + pthread_mutex_lock(&ctx_trace.lock); + ctx_trace.pic_order_cnt_lsb = pic_order_cnt_lsb; + pthread_mutex_unlock(&ctx_trace.lock); +} + +int get_pic_order_cnt_lsb(void) +{ + int pic_order_cnt_lsb = 0; + pthread_mutex_lock(&ctx_trace.lock); + pic_order_cnt_lsb = ctx_trace.pic_order_cnt_lsb; + pthread_mutex_unlock(&ctx_trace.lock); + return pic_order_cnt_lsb; +} + +void add_buffer_trace(int fd, __u32 type, __u32 index, __u32 offset = 0) +{ + struct buffer_trace buf = {}; + buf.fd = fd; + buf.type = type; + buf.index = index; + buf.offset = offset; + buf.display_order = -1; + + pthread_mutex_lock(&ctx_trace.lock); + ctx_trace.buffers.push_front(buf); + pthread_mutex_unlock(&ctx_trace.lock); +} + +void remove_buffer_trace(int fd) +{ + pthread_mutex_lock(&ctx_trace.lock); + for (auto it = ctx_trace.buffers.begin(); it != ctx_trace.buffers.end(); ++it) { + if (it->fd == fd) { + ctx_trace.buffers.erase(it); + break; + } + } + pthread_mutex_unlock(&ctx_trace.lock); +} + +bool buffer_in_trace_context(int fd, __u32 offset) +{ + bool buffer_in_trace_context = false; + pthread_mutex_lock(&ctx_trace.lock); + for (auto &b : ctx_trace.buffers) { + if ((b.fd == fd) && (b.offset == offset)) { + buffer_in_trace_context = true; + break; + } + } + pthread_mutex_unlock(&ctx_trace.lock); + return buffer_in_trace_context; +} + +int get_buffer_fd_trace(__u32 type, __u32 index) +{ + int fd = 0; + pthread_mutex_lock(&ctx_trace.lock); + for (auto &b : ctx_trace.buffers) { + if ((b.type == type) && (b.index == index)) { + fd = b.fd; + break; + } + } + pthread_mutex_unlock(&ctx_trace.lock); + return fd; +} + +__u32 get_buffer_type_trace(int fd, __u32 offset) +{ + __u32 type = 0; + pthread_mutex_lock(&ctx_trace.lock); + for (auto &b : ctx_trace.buffers) { + if ((b.fd == fd) && (b.offset == offset)) { + type = b.type; + break; + } + } + pthread_mutex_unlock(&ctx_trace.lock); + return type; +} + +int get_buffer_index_trace(int fd, __u32 offset) +{ + int index = -1; + pthread_mutex_lock(&ctx_trace.lock); + for (auto &b : ctx_trace.buffers) { + if ((b.fd == fd) && (b.offset == offset)) { + index = b.index; + break; + } + } + pthread_mutex_unlock(&ctx_trace.lock); + return index; +} + +__u32 get_buffer_offset_trace(__u32 type, __u32 index) +{ + __u32 offset = 0; + pthread_mutex_lock(&ctx_trace.lock); + for (auto &b : ctx_trace.buffers) { + if ((b.type == type) && (b.index == index)) { + offset = b.offset; + break; + } + } + pthread_mutex_unlock(&ctx_trace.lock); + return offset; +} + +void set_buffer_bytesused_trace(int fd, __u32 offset, __u32 bytesused) +{ + pthread_mutex_lock(&ctx_trace.lock); + for (auto &b : ctx_trace.buffers) { + if ((b.fd == fd) && (b.offset == offset)) { + b.bytesused = bytesused; + break; + } + } + pthread_mutex_unlock(&ctx_trace.lock); +} + +long get_buffer_bytesused_trace(int fd, __u32 offset) +{ + long bytesused = 0; + pthread_mutex_lock(&ctx_trace.lock); + for (auto &b : ctx_trace.buffers) { + if ((b.fd == fd) && (b.offset == offset)) { + bytesused = b.bytesused; + break; + } + } + pthread_mutex_unlock(&ctx_trace.lock); + return bytesused; +} + +void set_buffer_display_order(int fd, __u32 offset, long display_order) +{ + if (option_is_set_verbose()) + fprintf(stderr, "%s: %ld\n", __func__, display_order); + pthread_mutex_lock(&ctx_trace.lock); + for (auto &b : ctx_trace.buffers) { + if ((b.fd == fd) && (b.offset == offset)) { + b.display_order = display_order; + break; + } + } + pthread_mutex_unlock(&ctx_trace.lock); +} + +void set_buffer_address_trace(int fd, __u32 offset, unsigned long address) +{ + pthread_mutex_lock(&ctx_trace.lock); + for (auto &b : ctx_trace.buffers) { + if ((b.fd == fd) && (b.offset == offset)) { + b.address = address; + break; + } + } + pthread_mutex_unlock(&ctx_trace.lock); +} + +unsigned long get_buffer_address_trace(int fd, __u32 offset) +{ + unsigned long address = 0; + pthread_mutex_lock(&ctx_trace.lock); + for (auto &b : ctx_trace.buffers) { + if ((b.fd == fd) && (b.offset == offset)) { + address = b.address; + break; + } + } + pthread_mutex_unlock(&ctx_trace.lock); + return address; +} + +bool buffer_is_mapped(unsigned long buffer_address) +{ + bool ret = false; + pthread_mutex_lock(&ctx_trace.lock); + for (auto &b : ctx_trace.buffers) { + if (b.address == buffer_address) { + ret = true; + break; + } + } + pthread_mutex_unlock(&ctx_trace.lock); + return ret; +} + +void print_buffers_trace(void) +{ + pthread_mutex_unlock(&ctx_trace.lock); + for (auto &b : ctx_trace.buffers) { + fprintf(stderr, "fd: %d, %s, index: %d, display_order: %ld, bytesused: %d, ", + b.fd, buftype2s(b.type).c_str(), b.index, b.display_order, b.bytesused); + fprintf(stderr, "address: %lu, offset: %u \n", b.address, b.offset); + } + pthread_mutex_unlock(&ctx_trace.lock); +} + +std::string get_ioctl_request_str(unsigned long request) +{ + __u8 ioctl_type = _IOC_TYPE(request); + switch (ioctl_type) { + case 'V': { + if (option_is_set_verbose()) + fprintf(stderr, "%s\n", ioctl2s_video(request).c_str()); + return ioctl2s_video(request); + } + case '|': + return ioctl2s_media(request); + case 'b': + if (request == DMA_BUF_IOCTL_SYNC) + return "DMA_BUF_IOCTL_SYNC"; + break; + default: + break; + } + return "unknown ioctl"; +} + +unsigned get_expected_length_trace() +{ + unsigned width; + unsigned height; + unsigned pixelformat; + unsigned expected_length; + + pthread_mutex_lock(&ctx_trace.lock); + width = ctx_trace.width; + height = ctx_trace.height; + pixelformat = ctx_trace.pixelformat; + pthread_mutex_unlock(&ctx_trace.lock); + + /* + * TODO: this assumes that the stride is equal to the real width and that the + * padding follows the end of the chroma plane. It could be improved by + * following the model in v4l2-ctl-streaming.cpp read_write_padded_frame() + */ + expected_length = width * height; + if (pixelformat == V4L2_PIX_FMT_NV12 || pixelformat == V4L2_PIX_FMT_YUV420) { + expected_length *= 3; + expected_length /= 2; + expected_length += (expected_length % 2); + } + return expected_length; +} + +void trace_mem_decoded(void) +{ + int displayed_count = 0; + unsigned expected_length = get_expected_length_trace(); + + pthread_mutex_lock(&ctx_trace.lock); + + while (!ctx_trace.decode_order.empty()) { + + std::list<buffer_trace>::iterator it; + long next_frame_to_be_displayed = *std::min_element(ctx_trace.decode_order.begin(), + ctx_trace.decode_order.end()); + + for (it = ctx_trace.buffers.begin(); it != ctx_trace.buffers.end(); ++it) { + if (it->display_order != next_frame_to_be_displayed) + continue; + if (!it->address) + break; + if (it->bytesused < expected_length) + break; + if (option_is_set_verbose()) + fprintf(stderr, "%s, displaying: %ld, %s, index: %d\n", + __func__, it->display_order, buftype2s(it->type).c_str(), it->index); + displayed_count++; + + if (options.write_decoded_data_to_yuv_file) { + std::string filename = getenv("TRACE_ID"); + filename += ".yuv"; + FILE *fp = fopen(filename.c_str(), "a"); + unsigned char *buffer_pointer = (unsigned char*) it->address; + for (__u32 i = 0; i < expected_length; i++) + fwrite(&buffer_pointer[i], sizeof(unsigned char), 1, fp); + fclose(fp); + } + int fd = it->fd; + __u32 offset = it->offset; + __u32 type = it->type; + int index = it->index; + __u32 bytesused = it->bytesused; + unsigned long start = it->address; + + pthread_mutex_unlock(&ctx_trace.lock); + trace_mem(fd, offset, type, index, bytesused, start); + pthread_mutex_lock(&ctx_trace.lock); + + ctx_trace.decode_order.remove(next_frame_to_be_displayed); + it->display_order = -1; + break; + } + if (!it->address || it == ctx_trace.buffers.end() || it->bytesused < expected_length) + break; + } + pthread_mutex_unlock(&ctx_trace.lock); + set_compressed_frame_count(get_compressed_frame_count() - displayed_count); +} + +void s_ext_ctrls_setup(struct v4l2_ext_controls *ext_controls) +{ + if (ext_controls->which != V4L2_CTRL_WHICH_REQUEST_VAL) + return; + + if (option_is_set_verbose()) + fprintf(stderr, "\n%s\n", __func__); + + for (__u32 i = 0; i < ext_controls->count; i++) { + struct v4l2_ext_control ctrl = ext_controls->controls[i]; + + switch (ctrl.id) { + case V4L2_CID_STATELESS_H264_SPS: { + set_max_pic_order_cnt_lsb(pow(2, ctrl.p_h264_sps->log2_max_pic_order_cnt_lsb_minus4 + 4)); + break; + } + case V4L2_CID_STATELESS_H264_DECODE_PARAMS: { + long pic_order_cnt_msb; + int max = get_max_pic_order_cnt_lsb(); + long prev_pic_order_cnt_msb = get_decode_order(); + int prev_pic_order_cnt_lsb = get_pic_order_cnt_lsb(); + int pic_order_cnt_lsb = ctrl.p_h264_decode_params->pic_order_cnt_lsb; + + if (option_is_set_verbose()) { + fprintf(stderr, "\tprev_pic_order_cnt_lsb: %d\n", prev_pic_order_cnt_lsb); + fprintf(stderr, "\tprev_pic_order_cnt_msb: %ld\n", prev_pic_order_cnt_msb); + fprintf(stderr, "\tpic_order_cnt_lsb: %d\n", pic_order_cnt_lsb); + } + + /* The first frame. */ + if (prev_pic_order_cnt_msb == 0) + pic_order_cnt_msb = 0; + + /* + * TODO: improve the displaying of decoded frames following H264 specification + * 8.2.1.1. For now, dump all the previously decoded frames when an IDR_PIC is + * received to avoid losing frames although this will still sometimes result + * in frames out of order. + */ + if (ctrl.p_h264_decode_params->flags & V4L2_H264_DECODE_PARAM_FLAG_IDR_PIC) { + if (get_compressed_frame_count()) + trace_mem_decoded(); + } + + /* + * When pic_order_cnt_lsb wraps around to zero, adjust the total count using + * max to keep the correct display order. + */ + if ((pic_order_cnt_lsb < prev_pic_order_cnt_lsb) && + ((prev_pic_order_cnt_lsb - pic_order_cnt_lsb) >= (max / 2))) { + pic_order_cnt_msb = prev_pic_order_cnt_msb + max; + } else if ((pic_order_cnt_lsb > prev_pic_order_cnt_lsb) && + ((pic_order_cnt_lsb - prev_pic_order_cnt_lsb) > (max / 2))) { + pic_order_cnt_msb = prev_pic_order_cnt_msb - max; + } else { + pic_order_cnt_msb = prev_pic_order_cnt_msb + (pic_order_cnt_lsb - prev_pic_order_cnt_lsb); + } + + if (option_is_set_verbose()) + fprintf(stderr, "\tpic_order_cnt_msb: %ld\n", pic_order_cnt_msb); + + set_pic_order_cnt_lsb(pic_order_cnt_lsb); + set_decode_order(pic_order_cnt_msb); + break; + } + default: + break; + } + } +} + +void qbuf_setup(struct v4l2_buffer *buf) +{ + if (option_is_set_verbose()) + fprintf(stderr, "\n%s: %s, index: %d\n", __func__, buftype2s(buf->type).c_str(), buf->index); + + int buf_fd = get_buffer_fd_trace(buf->type, buf->index); + __u32 buf_offset = get_buffer_offset_trace(buf->type, buf->index); + + __u32 bytesused = 0; + if (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE || + buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + bytesused = buf->m.planes[0].bytesused; + if (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT || buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + bytesused = buf->bytesused; + set_buffer_bytesused_trace(buf_fd, buf_offset, bytesused); + + /* The output buffer should have compressed data just before it is queued, so trace it. */ + if (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE || + buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + trace_mem_encoded(buf_fd, buf_offset); + set_compressed_frame_count(get_compressed_frame_count() + 1); + } + + if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE || + buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + + /* If the capture buffer is queued for reuse, trace it before it is reused. */ + if (get_compressed_frame_count()) + trace_mem_decoded(); + + /* H264 sets display order in controls, otherwise display just in the order queued. */ + if (get_compression_format() != V4L2_PIX_FMT_H264_SLICE) + set_decode_order(get_decode_order() + 1); + + set_buffer_display_order(buf_fd, buf_offset, get_decode_order()); + + if (option_is_set_verbose()) { + print_decode_order(); + print_buffers_trace(); + } + } +} + +void streamoff_cleanup(v4l2_buf_type buf_type) +{ + if (option_is_set_verbose()) { + fprintf(stderr, "\nVIDIOC_STREAMOFF: %s\n", buftype2s(buf_type).c_str()); + fprintf(stderr, "compression: %s, pixelformat: %s %s, width: %d, height: %d\n", + pixfmt2s(get_compression_format()).c_str(), pixfmt2s(get_pixelformat()).c_str(), + fcc2s(get_pixelformat()).c_str(), ctx_trace.width, ctx_trace.height); + } + + /* + * Before turning off the stream, trace any remaining capture buffers that were missed + * because they were not queued for reuse. + */ + if (buf_type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE || + buf_type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + if (get_compressed_frame_count()) + trace_mem_decoded(); + } +} + +void g_fmt_setup_trace(struct v4l2_format *format) +{ + if (format->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + set_pixelformat_trace(format->fmt.pix.width, + format->fmt.pix.height, format->fmt.pix.pixelformat); + if (format->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + set_pixelformat_trace(format->fmt.pix_mp.width, format->fmt.pix_mp.height, + format->fmt.pix_mp.pixelformat); + + if (format->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) + set_compression_format(format->fmt.pix.pixelformat); + if (format->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + set_compression_format(format->fmt.pix_mp.pixelformat); +} + +void s_fmt_setup(struct v4l2_format *format) +{ + if (format->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) + set_compression_format(format->fmt.pix.pixelformat); + if (format->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + set_compression_format(format->fmt.pix_mp.pixelformat); +} + +void expbuf_setup(struct v4l2_exportbuffer *export_buffer) +{ + __u32 type = export_buffer->type; + __u32 index = export_buffer->index; + int fd_found_in_trace_context = get_buffer_fd_trace(type, index); + + /* If the buffer was already added to the trace context don't add it again. */ + if (fd_found_in_trace_context == export_buffer->fd) + return; + + /* + * If a buffer was previously added to the trace context using the video device + * file descriptor, replace the video fd with the more specific buffer fd from EXPBUF. + */ + if (fd_found_in_trace_context) + remove_buffer_trace(fd_found_in_trace_context); + + add_buffer_trace(export_buffer->fd, type, index); +} + +void querybuf_setup(int fd, struct v4l2_buffer *buf) +{ + /* If the buffer was already added to the trace context don't add it again. */ + if (get_buffer_fd_trace(buf->type, buf->index)) + return; + + switch (buf->memory) { + case V4L2_MEMORY_MMAP: { + __u32 offset = 0; + if ((buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) || + (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)) + offset = buf->m.offset; + if ((buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) || + (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)) + offset = buf->m.planes->m.mem_offset; + add_buffer_trace(fd, buf->type, buf->index, offset); + break; + } + case V4L2_MEMORY_USERPTR: + case V4L2_MEMORY_DMABUF: + default: + break; + } +} + +void write_json_object_to_json_file(json_object *jobj, int flags) +{ + if (options.pretty_print_all) + flags = JSON_C_TO_STRING_PRETTY; + + std::string json_str = json_object_to_json_string_ext(jobj, flags); + pthread_mutex_lock(&ctx_trace.lock); + + if (!ctx_trace.trace_file) { + ctx_trace.trace_filename = getenv("TRACE_ID"); + ctx_trace.trace_filename += ".json"; + ctx_trace.trace_file = fopen(ctx_trace.trace_filename.c_str(), "a"); + } + + fwrite(json_str.c_str(), sizeof(char), json_str.length(), ctx_trace.trace_file); + fputs(",\n", ctx_trace.trace_file); + fflush(ctx_trace.trace_file); + pthread_mutex_unlock(&ctx_trace.lock); +} + +void close_json_file(void) +{ + pthread_mutex_lock(&ctx_trace.lock); + if (ctx_trace.trace_file) { + fclose(ctx_trace.trace_file); + ctx_trace.trace_file = 0; + } + pthread_mutex_unlock(&ctx_trace.lock); +} diff --git a/utils/v4l2-tracer/trace-helper.h b/utils/v4l2-tracer/trace-helper.h new file mode 100644 index 00000000..ee5fc5ce --- /dev/null +++ b/utils/v4l2-tracer/trace-helper.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2022 Collabora Ltd. + */ + +#ifndef TRACE_HELPER_H +#define TRACE_HELPER_H + +struct definition { + unsigned long val; + const char *str; +}; + +std::string ver2s(unsigned int version); +std::string which2s(unsigned long which); +std::string val2s(unsigned long val, const definition *def); + +bool options_are_set(void); +void set_options(void); +bool option_is_set_verbose(void); +bool option_is_set_pretty_print_mem(void); +bool option_is_set_write_decoded_data_to_json_file(void); + +bool is_video_or_media_device(const char *path); +void add_device(int fd, std::string path); +std::string get_device(int fd); +int remove_device(int fd); +int count_devices(void); + +bool buffer_in_trace_context(int fd, __u32 offset = 0); +__u32 get_buffer_type_trace(int fd, __u32 offset = 0); +int get_buffer_index_trace(int fd, __u32 offset); +long get_buffer_bytesused_trace(int fd, __u32 offset); +void set_buffer_address_trace(int fd, __u32 offset, unsigned long address); +unsigned long get_buffer_address_trace(int fd, __u32 offset); +bool buffer_is_mapped(unsigned long buffer_address); + +std::string get_ioctl_request_str(unsigned long request); + +void s_ext_ctrls_setup(struct v4l2_ext_controls *ext_controls); +void qbuf_setup(struct v4l2_buffer *buf); +void streamoff_cleanup(v4l2_buf_type buf_type); +void g_fmt_setup_trace(struct v4l2_format *format); +void s_fmt_setup(struct v4l2_format *format); +void expbuf_setup(struct v4l2_exportbuffer *export_buffer); +void querybuf_setup(int fd, struct v4l2_buffer *buf); + +void write_json_object_to_json_file(json_object *jobj, int flags = JSON_C_TO_STRING_PLAIN); +void close_json_file(void); + +#endif diff --git a/utils/v4l2-tracer/trace.cpp b/utils/v4l2-tracer/trace.cpp new file mode 100644 index 00000000..fa3c2d8f --- /dev/null +++ b/utils/v4l2-tracer/trace.cpp @@ -0,0 +1,669 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2022 Collabora Ltd. + */ + +#include "v4l2-tracer-common.h" +#include "trace-helper.h" +#include "trace-gen.h" +#include "trace-info-gen.h" + +void trace_open(int fd, const char *path, int oflag, mode_t mode, bool is_open64) +{ + json_object *open_obj = json_object_new_object(); + + if (is_open64) { + json_object_object_add(open_obj, "syscall_str", json_object_new_string("open64")); + json_object_object_add(open_obj, "syscall", json_object_new_int(LIBTRACER_SYSCALL_OPEN64)); + } else { + json_object_object_add(open_obj, "syscall_str", json_object_new_string("open")); + json_object_object_add(open_obj, "syscall", json_object_new_int(LIBTRACER_SYSCALL_OPEN)); + } + + json_object_object_add(open_obj, "fd", json_object_new_int(fd)); + + json_object *open_args = json_object_new_object(); + json_object_object_add(open_args, "path", json_object_new_string(path)); + json_object_object_add(open_args, "oflag", json_object_new_int(oflag)); + json_object_object_add(open_args, "mode", json_object_new_uint64(mode)); + json_object_object_add(open_obj, "open_args", open_args); + + write_json_object_to_json_file(open_obj); + json_object_put(open_obj); +} + +void trace_mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off, unsigned long buf_address, bool is_mmap64) +{ + json_object *mmap_obj = json_object_new_object(); + + if (errno) { + json_object_object_add(mmap_obj, "errno", json_object_new_int(errno)); + json_object_object_add(mmap_obj, "errno_str", json_object_new_string(strerror(errno))); + } + + if (is_mmap64) { + json_object_object_add(mmap_obj, "syscall_str", json_object_new_string("mmap64")); + json_object_object_add(mmap_obj, "syscall", json_object_new_int(LIBTRACER_SYSCALL_MMAP64)); + } else { + json_object_object_add(mmap_obj, "syscall_str", json_object_new_string("mmap")); + json_object_object_add(mmap_obj, "syscall", json_object_new_int(LIBTRACER_SYSCALL_MMAP)); + } + + json_object *mmap_args = json_object_new_object(); + json_object_object_add(mmap_args, "addr", json_object_new_int64((int64_t)addr)); + json_object_object_add(mmap_args, "len", json_object_new_uint64(len)); + json_object_object_add(mmap_args, "prot", json_object_new_int(prot)); + json_object_object_add(mmap_args, "flags", json_object_new_int(flags)); + json_object_object_add(mmap_args, "fildes", json_object_new_int(fildes)); + json_object_object_add(mmap_args, "off", json_object_new_int64(off)); + json_object_object_add(mmap_obj, "mmap_args", mmap_args); + + json_object_object_add(mmap_obj, "buffer_address", json_object_new_uint64(buf_address)); + + write_json_object_to_json_file(mmap_obj); + json_object_put(mmap_obj); +} + +json_object *trace_buffer(unsigned char *buffer_pointer, __u32 bytesused) +{ + char buf[5]; + std::string s; + int byte_count_per_line = 0; + json_object *mem_array_obj = json_object_new_array(); + + for (__u32 i = 0; i < bytesused; i++) { + memset(buf, 0, sizeof(buf)); + /* + * Each byte e.g. D9 will write a string of two characters "D9". + * The bytes are displayed with the most-significant bits first. + */ + sprintf(buf, "%02x", buffer_pointer[i]); + s += buf; + byte_count_per_line++; + + /* Add a space every two bytes e.g. "012A 4001" and a newline every 16 bytes. */ + if (byte_count_per_line == 16) { + byte_count_per_line = 0; + json_object_array_add(mem_array_obj, json_object_new_string(s.c_str())); + s.clear(); + } else if (i % 2) { + s += " "; + } + } + + /* Trace the last line if it was less than a full 16 bytes. */ + if (byte_count_per_line) + json_object_array_add(mem_array_obj, json_object_new_string(s.c_str())); + + return mem_array_obj; +} + +void trace_mem(int fd, __u32 offset, __u32 type, int index, __u32 bytesused, unsigned long start) +{ + json_object *mem_obj = json_object_new_object(); + json_object_object_add(mem_obj, "mem_dump", json_object_new_string(buftype2s(type).c_str())); + json_object_object_add(mem_obj, "fd", json_object_new_int(fd)); + json_object_object_add(mem_obj, "offset", json_object_new_uint64(offset)); + json_object_object_add(mem_obj, "type", json_object_new_uint64(type)); + json_object_object_add(mem_obj, "index", json_object_new_int(index)); + json_object_object_add(mem_obj, "bytesused", json_object_new_uint64(bytesused)); + json_object_object_add(mem_obj, "address", json_object_new_uint64(start)); + + if ((type == V4L2_BUF_TYPE_VIDEO_OUTPUT || type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) || + (option_is_set_write_decoded_data_to_json_file())) { + json_object *mem_array_obj = trace_buffer((unsigned char*) start, bytesused); + json_object_object_add(mem_obj, "mem_array", mem_array_obj); + } + + if (option_is_set_pretty_print_mem()) + write_json_object_to_json_file(mem_obj, JSON_C_TO_STRING_PRETTY); + else + write_json_object_to_json_file(mem_obj); + + json_object_put(mem_obj); +} + +void trace_mem_encoded(int fd, __u32 offset) +{ + int index; + __u32 type; + __u32 bytesused; + unsigned long start = get_buffer_address_trace(fd, offset); + + if (!start) + return; + + if (option_is_set_verbose()) + fprintf(stderr, "%s\n", __func__); + + bytesused = get_buffer_bytesused_trace(fd, offset); + type = get_buffer_type_trace(fd, offset); + index = get_buffer_index_trace(fd, offset); + trace_mem(fd, offset, type, index, bytesused, start); +} + +void trace_vidioc_querycap(void *arg, json_object *ioctl_args) +{ + json_object *cap_obj = json_object_new_object(); + struct v4l2_capability *cap = static_cast<struct v4l2_capability*>(arg); + + json_object_object_add(cap_obj, "driver", json_object_new_string((const char *)cap->driver)); + json_object_object_add(cap_obj, "card", json_object_new_string((const char *)cap->card)); + json_object_object_add(cap_obj, "bus_info", + json_object_new_string((const char *)cap->bus_info)); + json_object_object_add(cap_obj, "version", + json_object_new_string(ver2s(cap->version).c_str())); + json_object_object_add(cap_obj, "capabilities", json_object_new_int64(cap->capabilities)); + json_object_object_add(cap_obj, "capabilities_str", + json_object_new_string(capabilities2s(cap->capabilities).c_str())); + json_object_object_add(cap_obj, "device_caps", json_object_new_int64(cap->device_caps)); + json_object_object_add(ioctl_args, "v4l2_capability", cap_obj); +} + +void trace_vidioc_enum_fmt(void *arg, json_object *ioctl_args) +{ + json_object *fmtdesc_obj = json_object_new_object(); + struct v4l2_fmtdesc *fmtdesc = static_cast<struct v4l2_fmtdesc*>(arg); + + json_object_object_add(fmtdesc_obj, "index", json_object_new_uint64(fmtdesc->index)); + json_object_object_add(fmtdesc_obj, "type_str", + json_object_new_string(buftype2s(fmtdesc->type).c_str())); + json_object_object_add(fmtdesc_obj, "type", json_object_new_uint64(fmtdesc->type)); + json_object_object_add(fmtdesc_obj, "flags", json_object_new_uint64(fmtdesc->flags)); + json_object_object_add(fmtdesc_obj, "description", + json_object_new_string((const char *)fmtdesc->description)); + json_object_object_add(fmtdesc_obj, "pixelformat", + json_object_new_uint64(fmtdesc->pixelformat)); + json_object_object_add(fmtdesc_obj, "mbus_code", json_object_new_uint64(fmtdesc->mbus_code)); + json_object_object_add(ioctl_args, "v4l2_fmtdesc", fmtdesc_obj); +} + +void trace_v4l2_plane_pix_format(json_object *pix_mp_obj, struct v4l2_plane_pix_format plane_fmt, int plane) +{ + json_object *plane_fmt_obj = json_object_new_object(); + + json_object_object_add(plane_fmt_obj, "sizeimage", + json_object_new_uint64(plane_fmt.sizeimage)); + json_object_object_add(plane_fmt_obj, "bytesperline", + json_object_new_uint64(plane_fmt.bytesperline)); + + /* Create a unique key name for each plane. */ + std::string unique_key_for_plane = "v4l2_plane_pix_format_"; + unique_key_for_plane += std::to_string(plane); + + json_object_object_add(pix_mp_obj, unique_key_for_plane.c_str(), plane_fmt_obj); +} + +void trace_v4l2_pix_format_mplane(json_object *format_obj, struct v4l2_pix_format_mplane pix_mp) +{ + json_object *pix_mp_obj = json_object_new_object(); + + json_object_object_add(pix_mp_obj, "width", json_object_new_uint64(pix_mp.width)); + json_object_object_add(pix_mp_obj, "height", json_object_new_uint64(pix_mp.height)); + json_object_object_add(pix_mp_obj, "pixelformat", json_object_new_uint64(pix_mp.pixelformat)); + json_object_object_add(pix_mp_obj, "pixelformat_str", + json_object_new_string(pixfmt2s(pix_mp.pixelformat).c_str())); + json_object_object_add(pix_mp_obj, "fcc_str", + json_object_new_string(fcc2s(pix_mp.pixelformat).c_str())); + json_object_object_add(pix_mp_obj, "field", json_object_new_uint64(pix_mp.field)); + json_object_object_add(pix_mp_obj, "field_str", + json_object_new_string(field2s(pix_mp.field).c_str())); + json_object_object_add(pix_mp_obj, "colorspace", json_object_new_uint64(pix_mp.colorspace)); + json_object_object_add(pix_mp_obj, "colorspace_str", + json_object_new_string(colorspace2s(pix_mp.colorspace).c_str())); + json_object_object_add(pix_mp_obj, "num_planes", json_object_new_int(pix_mp.num_planes)); + for (int i = 0; i < pix_mp.num_planes; i++) + trace_v4l2_plane_pix_format(pix_mp_obj, pix_mp.plane_fmt[i], i); + + json_object_object_add(pix_mp_obj, "flags", json_object_new_int(pix_mp.flags)); + json_object_object_add(pix_mp_obj, "flags_str", + json_object_new_string(pixflags2s(pix_mp.flags).c_str())); + json_object_object_add(pix_mp_obj, "ycbcr_enc", json_object_new_int(pix_mp.ycbcr_enc)); + json_object_object_add(pix_mp_obj, "ycbcr_enc_str", + json_object_new_string(ycbcr_enc2s(pix_mp.ycbcr_enc).c_str())); + json_object_object_add(pix_mp_obj, "quantization", json_object_new_int(pix_mp.quantization)); + json_object_object_add(pix_mp_obj, "quantization_str", + json_object_new_string(quantization2s(pix_mp.quantization).c_str())); + json_object_object_add(pix_mp_obj, "xfer_func", json_object_new_int(pix_mp.xfer_func)); + json_object_object_add(pix_mp_obj, "xfer_func_str", + json_object_new_string(xfer_func2s(pix_mp.xfer_func).c_str())); + + json_object_object_add(format_obj, "v4l2_pix_format_mplane", pix_mp_obj); +} + +void trace_v4l2_pix_format(json_object *format_obj, struct v4l2_pix_format pix) +{ + json_object *pix_obj = json_object_new_object(); + + json_object_object_add(pix_obj, "width", json_object_new_uint64(pix.width)); + json_object_object_add(pix_obj, "height", json_object_new_uint64(pix.height)); + json_object_object_add(pix_obj, "pixelformat", json_object_new_uint64(pix.pixelformat)); + json_object_object_add(pix_obj, "pixelformat_str", + json_object_new_string(pixfmt2s(pix.pixelformat).c_str())); + json_object_object_add(pix_obj, "fcc_str", + json_object_new_string(fcc2s(pix.pixelformat).c_str())); + json_object_object_add(pix_obj, "field", json_object_new_uint64(pix.field)); + json_object_object_add(pix_obj, "bytesperline", json_object_new_uint64(pix.bytesperline)); + json_object_object_add(pix_obj, "sizeimage", json_object_new_uint64(pix.sizeimage)); + json_object_object_add(pix_obj, "colorspace", json_object_new_uint64(pix.colorspace)); + json_object_object_add(pix_obj, "colorspace_str", + json_object_new_string(colorspace2s(pix.colorspace).c_str())); + json_object_object_add(pix_obj, "priv", json_object_new_uint64(pix.priv)); + + if (pix.priv == V4L2_PIX_FMT_PRIV_MAGIC) { + json_object_object_add(pix_obj, "priv_str", + json_object_new_string("V4L2_PIX_FMT_PRIV_MAGIC")); + json_object_object_add(pix_obj, "flags", json_object_new_uint64(pix.flags)); + json_object_object_add(pix_obj, "flags_str", + json_object_new_string(pixflags2s(pix.flags).c_str())); + json_object_object_add(pix_obj, "ycbcr_enc", json_object_new_uint64(pix.ycbcr_enc)); + json_object_object_add(pix_obj, "ycbcr_enc_str", + json_object_new_string(ycbcr_enc2s(pix.ycbcr_enc).c_str())); + json_object_object_add(pix_obj, "quantization", json_object_new_uint64(pix.quantization)); + json_object_object_add(pix_obj, "quantization_str", + json_object_new_string(quantization2s(pix.quantization).c_str())); + json_object_object_add(pix_obj, "xfer_func", json_object_new_uint64(pix.xfer_func)); + json_object_object_add(pix_obj, "xfer_func_str", + json_object_new_string(xfer_func2s(pix.xfer_func).c_str())); + } + json_object_object_add(format_obj, "v4l2_pix_format", pix_obj); +} + +void trace_v4l2_format(void *arg, json_object *ioctl_args) +{ + json_object *format_obj = json_object_new_object(); + struct v4l2_format *format = static_cast<struct v4l2_format*>(arg); + + json_object_object_add(format_obj, "type", json_object_new_uint64(format->type)); + json_object_object_add(format_obj, "type_str", + json_object_new_string(buftype2s(format->type).c_str())); + + switch (format->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + trace_v4l2_pix_format(format_obj, format->fmt.pix); + break; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY: + break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + trace_v4l2_pix_format_mplane(format_obj, format->fmt.pix_mp); + break; + case V4L2_BUF_TYPE_SDR_CAPTURE: + case V4L2_BUF_TYPE_SDR_OUTPUT: + case V4L2_BUF_TYPE_META_CAPTURE: + case V4L2_BUF_TYPE_META_OUTPUT: + break; + } + json_object_object_add(ioctl_args, "v4l2_format", format_obj); +} + +void trace_v4l2_requestbuffers(void *arg, json_object *ioctl_args) +{ + json_object *request_buffers_obj = json_object_new_object(); + struct v4l2_requestbuffers *request_buffers = static_cast<struct v4l2_requestbuffers*>(arg); + + json_object_object_add(request_buffers_obj, "count", + json_object_new_uint64(request_buffers->count)); + json_object_object_add(request_buffers_obj, "type", + json_object_new_uint64(request_buffers->type)); + json_object_object_add(request_buffers_obj, "type_str", + json_object_new_string(buftype2s(request_buffers->type).c_str())); + json_object_object_add(request_buffers_obj, "memory", + json_object_new_uint64(request_buffers->memory)); + json_object_object_add(request_buffers_obj, "memory_str", + json_object_new_string(v4l2_memory2s(request_buffers->memory).c_str())); + json_object_object_add(request_buffers_obj, "capabilities", + json_object_new_uint64(request_buffers->capabilities)); + json_object_object_add(request_buffers_obj, "capabilities_str", + json_object_new_string(bufcap2s(request_buffers->capabilities).c_str())); + json_object_object_add(request_buffers_obj, "flags", + json_object_new_int(request_buffers->flags)); + json_object_object_add(ioctl_args, "v4l2_requestbuffers", request_buffers_obj); +} + +json_object *trace_v4l2_plane(struct v4l2_plane *p, __u32 memory) +{ + json_object *plane_obj = json_object_new_object(); + + json_object_object_add(plane_obj, "bytesused", json_object_new_uint64(p->bytesused)); + json_object_object_add(plane_obj, "length", json_object_new_uint64(p->length)); + + json_object *m_obj = json_object_new_object(); + if (memory == V4L2_MEMORY_MMAP) + json_object_object_add(m_obj, "mem_offset", json_object_new_int64(p->m.mem_offset)); + json_object_object_add(plane_obj, "m", m_obj); + + json_object_object_add(plane_obj, "data_offset", json_object_new_int64(p->data_offset)); + + return plane_obj; +} + +void trace_v4l2_buffer(void *arg, json_object *ioctl_args) +{ + json_object *buf_obj = json_object_new_object(); + struct v4l2_buffer *buf = static_cast<struct v4l2_buffer*>(arg); + + json_object_object_add(buf_obj, "index", json_object_new_uint64(buf->index)); + json_object_object_add(buf_obj, "type", json_object_new_uint64(buf->type)); + json_object_object_add(buf_obj, "type_str", + json_object_new_string(buftype2s(buf->type).c_str())); + json_object_object_add(buf_obj, "bytesused", json_object_new_uint64(buf->bytesused)); + json_object_object_add(buf_obj, "flags", json_object_new_uint64(buf->flags)); + json_object_object_add(buf_obj, "flags_str", + json_object_new_string(bufferflags2s(buf->flags).c_str())); + json_object_object_add(buf_obj, "field", json_object_new_uint64(buf->field)); + + json_object *timestamp_obj = json_object_new_object(); + json_object_object_add(timestamp_obj, "tv_sec", json_object_new_int64(buf->timestamp.tv_sec)); + json_object_object_add(timestamp_obj, "tv_usec", + json_object_new_int64(buf->timestamp.tv_usec)); + json_object_object_add(buf_obj, "timestamp", timestamp_obj); + json_object_object_add(buf_obj, "timestamp_ns", + json_object_new_uint64(v4l2_timeval_to_ns(&buf->timestamp))); + + json_object_object_add(buf_obj, "sequence", json_object_new_uint64(buf->sequence)); + json_object_object_add(buf_obj, "memory", json_object_new_uint64(buf->memory)); + json_object_object_add(buf_obj, "memory_str", + json_object_new_string(v4l2_memory2s(buf->memory).c_str())); + + json_object *m_obj = json_object_new_object(); + /* multi-planar API */ + if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE || + buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + + json_object *planes_obj = json_object_new_array(); + /* TODO add planes > 0 */ + json_object_array_add(planes_obj, trace_v4l2_plane(buf->m.planes, buf->memory)); + json_object_object_add(m_obj, "planes", planes_obj); + } + + /* single-planar API */ + if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE || + buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + + switch (buf->memory) { + case V4L2_MEMORY_MMAP: + json_object_object_add(m_obj, "offset", json_object_new_uint64(buf->m.offset)); + break; + case V4L2_MEMORY_USERPTR: + case V4L2_MEMORY_DMABUF: + break; + } + } + json_object_object_add(buf_obj, "m", m_obj); + json_object_object_add(buf_obj, "length", json_object_new_uint64(buf->length)); + + if (buf->flags & V4L2_BUF_FLAG_REQUEST_FD) + json_object_object_add(buf_obj, "request_fd", json_object_new_int(buf->request_fd)); + + json_object_object_add(ioctl_args, "v4l2_buffer", buf_obj); +} + +void trace_v4l2_exportbuffer(void *arg, json_object *ioctl_args) +{ + json_object *exportbuffer_obj = json_object_new_object(); + struct v4l2_exportbuffer *export_buffer = static_cast<struct v4l2_exportbuffer*>(arg); + + json_object_object_add(exportbuffer_obj, "type", json_object_new_uint64(export_buffer->type)); + json_object_object_add(exportbuffer_obj, "type_str", + json_object_new_string(buftype2s(export_buffer->type).c_str())); + + json_object_object_add(exportbuffer_obj, "index", json_object_new_uint64(export_buffer->index)); + json_object_object_add(exportbuffer_obj, "plane", json_object_new_uint64(export_buffer->plane)); + json_object_object_add(exportbuffer_obj, "flags", json_object_new_uint64(export_buffer->flags)); + json_object_object_add(exportbuffer_obj, "fd", json_object_new_int(export_buffer->fd)); + + json_object_object_add(ioctl_args, "v4l2_exportbuffer", exportbuffer_obj); +} + +void trace_vidioc_stream(void *arg, json_object *ioctl_args) +{ + v4l2_buf_type buf_type = *(static_cast<v4l2_buf_type*>(arg)); + json_object_object_add(ioctl_args, "buf_type", json_object_new_int(buf_type)); + json_object_object_add(ioctl_args, "buf_type_str", + json_object_new_string(buftype2s(buf_type).c_str())); +} + +void trace_v4l2_ext_control(json_object *ext_controls_obj, struct v4l2_ext_control ctrl, __u32 control_idx) +{ + std::string unique_key_for_control; + + json_object *ctrl_obj = json_object_new_object(); + json_object_object_add(ctrl_obj, "id", json_object_new_uint64(ctrl.id)); + json_object_object_add(ctrl_obj, "control_class_str", + json_object_new_string(ctrlclass2s(ctrl.id).c_str())); + json_object_object_add(ctrl_obj, "size", json_object_new_uint64(ctrl.size)); + + switch (ctrl.id) { + case V4L2_CID_STATELESS_VP8_FRAME: + trace_v4l2_ctrl_vp8_frame_gen(ctrl.p_vp8_frame, ctrl_obj); + break; + case V4L2_CID_STATELESS_H264_DECODE_MODE: { + json_object *h264_decode_mode_obj = json_object_new_object(); + json_object_object_add(h264_decode_mode_obj, "value", + json_object_new_int(ctrl.value)); + json_object_object_add(ctrl_obj, "V4L2_CID_STATELESS_H264_DECODE_MODE", + h264_decode_mode_obj); + break; + } + case V4L2_CID_STATELESS_H264_START_CODE: { + json_object *h264_start_code_obj = json_object_new_object(); + json_object_object_add(h264_start_code_obj, "value", json_object_new_int(ctrl.value)); + json_object_object_add(ctrl_obj, "V4L2_CID_STATELESS_H264_START_CODE", h264_start_code_obj); + break; + } + case V4L2_CID_STATELESS_H264_SPS: + trace_v4l2_ctrl_h264_sps_gen(ctrl.p_h264_sps, ctrl_obj); + break; + case V4L2_CID_STATELESS_H264_PPS: + trace_v4l2_ctrl_h264_pps_gen(ctrl.p_h264_pps, ctrl_obj); + break; + case V4L2_CID_STATELESS_H264_SCALING_MATRIX: + trace_v4l2_ctrl_h264_scaling_matrix_gen(ctrl.p_h264_scaling_matrix, ctrl_obj); + break; + case V4L2_CID_STATELESS_H264_PRED_WEIGHTS: + trace_v4l2_ctrl_h264_pred_weights_gen(ctrl.p_h264_pred_weights, ctrl_obj); + break; + case V4L2_CID_STATELESS_H264_SLICE_PARAMS: + trace_v4l2_ctrl_h264_slice_params_gen(ctrl.p_h264_slice_params, ctrl_obj); + break; + case V4L2_CID_STATELESS_H264_DECODE_PARAMS: + trace_v4l2_ctrl_h264_decode_params_gen(ctrl.p_h264_decode_params, ctrl_obj); + break; + case V4L2_CID_STATELESS_FWHT_PARAMS: + trace_v4l2_ctrl_fwht_params_gen(ctrl.p_fwht_params, ctrl_obj); + break; + case V4L2_CID_STATELESS_VP9_FRAME: + trace_v4l2_ctrl_vp9_frame_gen(ctrl.p_vp9_frame, ctrl_obj); + break; + case V4L2_CID_STATELESS_VP9_COMPRESSED_HDR: + trace_v4l2_ctrl_vp9_compressed_hdr_gen(ctrl.p_vp9_compressed_hdr_probs, ctrl_obj); + break; + case V4L2_CID_STATELESS_HEVC_SPS: + trace_v4l2_ctrl_hevc_sps_gen(ctrl.p_hevc_sps, ctrl_obj); + break; + case V4L2_CID_STATELESS_HEVC_PPS: + trace_v4l2_ctrl_hevc_pps_gen(ctrl.p_hevc_pps, ctrl_obj); + break; + case V4L2_CID_STATELESS_HEVC_SLICE_PARAMS: + trace_v4l2_ctrl_hevc_slice_params_gen(ctrl.p_hevc_slice_params, ctrl_obj); + break; + case V4L2_CID_STATELESS_HEVC_SCALING_MATRIX: + trace_v4l2_ctrl_hevc_scaling_matrix_gen(ctrl.p_hevc_scaling_matrix, ctrl_obj); + break; + case V4L2_CID_STATELESS_HEVC_DECODE_PARAMS: + trace_v4l2_ctrl_hevc_decode_params_gen(ctrl.p_hevc_decode_params, ctrl_obj); + break; + case V4L2_CID_STATELESS_MPEG2_SEQUENCE: + trace_v4l2_ctrl_mpeg2_sequence_gen(ctrl.p_mpeg2_sequence, ctrl_obj); + break; + case V4L2_CID_STATELESS_MPEG2_PICTURE: + trace_v4l2_ctrl_mpeg2_picture_gen(ctrl.p_mpeg2_picture, ctrl_obj); + break; + case V4L2_CID_STATELESS_MPEG2_QUANTISATION: + trace_v4l2_ctrl_mpeg2_quantisation_gen(ctrl.p_mpeg2_quantisation, ctrl_obj); + break; + default: + json_object_object_add(ctrl_obj, "Trace warning", + json_object_new_string("Unknown control id.")); + break; + } + + unique_key_for_control = "v4l2_ext_control_"; + unique_key_for_control += std::to_string(control_idx); + + json_object_object_add(ext_controls_obj, unique_key_for_control.c_str(), ctrl_obj); +} + +void trace_v4l2_ext_controls(void *arg, json_object *ioctl_args) +{ + json_object *ext_controls_obj = json_object_new_object(); + struct v4l2_ext_controls *ext_controls = static_cast<struct v4l2_ext_controls*>(arg); + + json_object_object_add(ext_controls_obj, "which", json_object_new_int64(ext_controls->which)); + json_object_object_add(ext_controls_obj, "which_str", + json_object_new_string(which2s(ext_controls->which).c_str())); + json_object_object_add(ext_controls_obj, "count", json_object_new_uint64(ext_controls->count)); + + /* error_idx is defined only if the ioctl returned an error */ + if (errno) + json_object_object_add(ext_controls_obj, "error_idx", + json_object_new_uint64(ext_controls->error_idx)); + + /* request_fd is only valid when "which" == V4L2_CTRL_WHICH_REQUEST_VAL */ + if (ext_controls->which == V4L2_CTRL_WHICH_REQUEST_VAL) + json_object_object_add(ext_controls_obj, "request_fd", + json_object_new_int(ext_controls->request_fd)); + + for (__u32 i = 0; i < ext_controls->count; i++) + trace_v4l2_ext_control(ext_controls_obj, ext_controls->controls[i], i); + + json_object_object_add(ioctl_args, "v4l2_ext_controls", ext_controls_obj); +} + +void trace_vidioc_query_ext_ctrl(void *arg, json_object *ioctl_args) +{ + json_object *query_ext_ctrl_obj = json_object_new_object(); + struct v4l2_query_ext_ctrl *queryextctrl = static_cast<struct v4l2_query_ext_ctrl*>(arg); + + json_object_object_add(query_ext_ctrl_obj, "id", json_object_new_uint64(queryextctrl->id)); + json_object_object_add(query_ext_ctrl_obj, "control_class_str", + json_object_new_string(ctrlclass2s(queryextctrl->id).c_str())); + json_object_object_add(query_ext_ctrl_obj, "type", json_object_new_uint64(queryextctrl->type)); + json_object_object_add(query_ext_ctrl_obj, "type_str", + json_object_new_string(ctrltype2s(queryextctrl->type).c_str())); + json_object_object_add(query_ext_ctrl_obj, "name", json_object_new_string(queryextctrl->name)); + json_object_object_add(query_ext_ctrl_obj, "minimum", + json_object_new_int64(queryextctrl->minimum)); + json_object_object_add(query_ext_ctrl_obj, "maximum", + json_object_new_int64(queryextctrl->maximum)); + json_object_object_add(query_ext_ctrl_obj, "step", json_object_new_uint64(queryextctrl->step)); + json_object_object_add(query_ext_ctrl_obj, "default_value", + json_object_new_int64(queryextctrl->default_value)); + json_object_object_add(query_ext_ctrl_obj, "flags", + json_object_new_uint64(queryextctrl->flags)); + json_object_object_add(query_ext_ctrl_obj, "flags_str", + json_object_new_string(ctrlflags2s(queryextctrl->flags).c_str())); + json_object_object_add(query_ext_ctrl_obj, "elem_size", + json_object_new_uint64(queryextctrl->elem_size)); + json_object_object_add(query_ext_ctrl_obj, "elems", + json_object_new_uint64(queryextctrl->elems)); + json_object_object_add(query_ext_ctrl_obj, "nr_of_dims", + json_object_new_uint64(queryextctrl->nr_of_dims)); + + /* __u32 dims[V4L2_CTRL_MAX_DIMS] */ + json_object *dim_obj = json_object_new_array(); + + for (unsigned int i = 0; i < queryextctrl->nr_of_dims; i++) + json_object_array_add(dim_obj, json_object_new_uint64(queryextctrl->dims[i])); + + json_object_object_add(query_ext_ctrl_obj, "dims", dim_obj); + json_object_object_add(ioctl_args, "v4l2_query_ext_ctrl", query_ext_ctrl_obj); +} + +void trace_ioctl_media(unsigned long request, void *arg, json_object *ioctl_args) +{ + if (request == MEDIA_IOC_REQUEST_ALLOC) { + __s32 *request_fd = static_cast<__s32*>(arg); + json_object_object_add(ioctl_args, "request_fd", json_object_new_int(*request_fd)); + } +} + +void trace_ioctl_video(unsigned long request, void *arg, json_object *ioctl_args, bool from_userspace) +{ + switch (request) { + case VIDIOC_QUERYCAP: + if (!from_userspace) + trace_vidioc_querycap(arg, ioctl_args); + break; + case VIDIOC_ENUM_FMT: + trace_vidioc_enum_fmt(arg, ioctl_args); + break; + case VIDIOC_G_FMT: + case VIDIOC_S_FMT: + trace_v4l2_format(arg, ioctl_args); + break; + case VIDIOC_REQBUFS: + trace_v4l2_requestbuffers(arg, ioctl_args); + break; + case VIDIOC_QUERYBUF: + case VIDIOC_QBUF: + case VIDIOC_DQBUF: + trace_v4l2_buffer(arg, ioctl_args); + break; + case VIDIOC_EXPBUF: + trace_v4l2_exportbuffer(arg, ioctl_args); + break; + case VIDIOC_STREAMON: + case VIDIOC_STREAMOFF: + if (from_userspace) + trace_vidioc_stream(arg, ioctl_args); + break; + case VIDIOC_G_EXT_CTRLS: + case VIDIOC_S_EXT_CTRLS: + trace_v4l2_ext_controls(arg, ioctl_args); + break; + case VIDIOC_QUERY_EXT_CTRL: + trace_vidioc_query_ext_ctrl(arg, ioctl_args); + break; + default: + break; + } +} + +void trace_dma_buf_ioctl_sync(void *arg, json_object *ioctl_args) +{ + struct dma_buf_sync *sync = static_cast<struct dma_buf_sync*>(arg); + json_object *sync_obj = json_object_new_object(); + json_object_object_add(sync_obj, "flags", json_object_new_uint64(sync->flags)); + json_object_object_add(ioctl_args, "dma_buf_sync", sync_obj); +} + +json_object *trace_ioctl_args(int fd, unsigned long request, void *arg, bool from_userspace) +{ + json_object *ioctl_args = json_object_new_object(); + __u8 ioctl_type = _IOC_TYPE(request); + switch (ioctl_type) { + case 'V': + trace_ioctl_video(request, arg, ioctl_args, from_userspace); + break; + case '|': + trace_ioctl_media(request, arg, ioctl_args); + break; + case 'b': + if (request == DMA_BUF_IOCTL_SYNC && from_userspace) { + trace_dma_buf_ioctl_sync(arg, ioctl_args); + } + break; + default: + break; + } + + return ioctl_args; +} diff --git a/utils/v4l2-tracer/v4l2-tracer-common.h b/utils/v4l2-tracer/v4l2-tracer-common.h new file mode 100644 index 00000000..c1b47c22 --- /dev/null +++ b/utils/v4l2-tracer/v4l2-tracer-common.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2022 Collabora Ltd. + */ + +#ifndef V4L2_TRACER_COMMON_H +#define V4L2_TRACER_COMMON_H + +#include <poll.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <getopt.h> +#include <pthread.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <list> +#include <unordered_map> +#include <algorithm> /* for std::find */ +#include <linux/dma-buf.h> +#include <linux/videodev2.h> +#include <linux/media.h> +#include <json.h> +#include "codec-fwht.h" +#include "v4l2-info.h" + +enum LIBTRACER_SYSCALL { + LIBTRACER_SYSCALL_IOCTL, + LIBTRACER_SYSCALL_OPEN, + LIBTRACER_SYSCALL_OPEN64, + LIBTRACER_SYSCALL_CLOSE, + LIBTRACER_SYSCALL_MMAP, + LIBTRACER_SYSCALL_MMAP64, + LIBTRACER_SYSCALL_MUNMAP, +}; + +#endif diff --git a/utils/v4l2-tracer/v4l2-tracer-gen.pl b/utils/v4l2-tracer/v4l2-tracer-gen.pl new file mode 100755 index 00000000..ca17d7e2 --- /dev/null +++ b/utils/v4l2-tracer/v4l2-tracer-gen.pl @@ -0,0 +1,359 @@ +#!/usr/bin/perl +# SPDX-License-Identifier: GPL-2.0-only */ +# Copyright 2022 Collabora Ltd. + +sub convert_type_to_json_type { + my $type = shift; + if ($type eq __u8 || $type eq __u16 || $type eq __s8 || $type eq __s16 || $type eq __s32) { + return "int"; + } + if ($type eq __u32) { + return "int64"; + } + if ($type eq __u64) { + return "uint64"; + } + if ($type eq struct) { + return; + } + print "v4l2_tracer: error: couldn't convert $type to json_object type.\n"; + return; +} + +sub get_index_letter { + my $index = shift; + if ($index eq 0) {return "i";} + if ($index eq 1) {return "j";} + if ($index eq 2) {return "k";} + if ($index eq 3) {return "l";} + if ($index eq 4) {return "m";} + if ($index eq 5) {return "n";} + if ($index eq 6) {return "o";} # "p" is saved for pointer + if ($index eq 8) {return "q";} + return "z"; +} + +$flag_func_name; + +sub flag_gen { + my $flag_type = shift; + + if ($flag_type =~ /fwht/) { + $flag_func_name = v4l2_ctrl_fwht_params_; + } elsif ($flag_type =~ /vp8_loop_filter/) { + $flag_func_name = v4l2_vp8_loop_filter_; + } else { + ($flag_func_name) = ($_) =~ /#define (\w+_)FLAG_.+/; + $flag_func_name = lc $flag_func_name; + } + printf $fh_trace_info_cpp "std::string %sflag2s(unsigned long flag)\n{\n", $flag_func_name; + printf $fh_trace_info_h "std::string %sflag2s(unsigned long flag);\n", $flag_func_name; + printf $fh_trace_info_cpp "\tstatic constexpr flag_def def[] = {\n"; + ($flag) = ($_) =~ /#define\s+(\w+)\s+.+/; + printf $fh_trace_info_cpp "\t\t{ $flag, \"$flag\" },\n"; + while (<>) { + next if ($_ =~ /^\/?\s?\*.*/); #skip comments between flags if any + next if $_ =~ /^\s*$/; #skip blank lines between flags if any + last if ((grep {!/^#define\s+\w+_FL/} $_) && (grep {!/^#define V4L2_VP8_LF/} $_)); + ($flag) = ($_) =~ /#define\s+(\w+)\s+.+/; + printf $fh_trace_info_cpp "\t\t{ $flag, \"$flag\" },\n"; + } + printf $fh_trace_info_cpp "\t\t{ 0, nullptr }\n\t};\n\treturn flags2s(flag, def);\n}\n\n"; +} + +sub struct_gen { + ($struct_name) = ($_) =~ /struct (\w+) {/; + + printf $fh_trace_h "void trace_%s_gen(void *ptr, json_object *parent_obj);\n", $struct_name; + printf $fh_trace_cpp "void trace_%s_gen(void *ptr, json_object *parent_obj)\n{\n", $struct_name; + printf $fh_trace_cpp "\tjson_object *%s_obj = json_object_new_object();\n", $struct_name; + printf $fh_trace_cpp "\tstruct %s *p = static_cast<struct %s*>(ptr);\n", $struct_name, $struct_name; + + printf $fh_retrace_h "struct %s *retrace_%s_gen(json_object *ctrl_obj);\n", $struct_name, $struct_name; + printf $fh_retrace_cpp "struct %s *retrace_%s_gen(json_object *ctrl_obj)\n{\n", $struct_name, $struct_name; + printf $fh_retrace_cpp "\tstruct %s *p = (struct %s *) calloc(1, sizeof(%s));\n", $struct_name, $struct_name, $struct_name; + printf $fh_retrace_cpp "\tjson_object *%s_obj;\n", $struct_name; + # if a key value isn't found, assume it is retracing an element of an array + printf $fh_retrace_cpp "\tif (!json_object_object_get_ex(ctrl_obj, \"%s\", &%s_obj))\n", $struct_name, $struct_name; + printf $fh_retrace_cpp "\t\t%s_obj = ctrl_obj;\n", $struct_name; + + while ($line = <> ) { + chomp($line); + last if $line =~ /};/; + next if $line =~ /^\s*$/; # ignore blank lines + next if $line =~ /\/\*/; # ignore comment lines + $line =~ s/^\s+//; # remove leading whitespace + $line =~ s/;$//; # remove semi-colon at the end + my @words = split /[\s\[]/, $line; # also split on '[' to avoid arrays + @words = grep {/^\D/} @words; # remove values that start with digit from inside [] + @words = grep {!/\]/} @words; # remove values with brackets e.g. V4L2_H264_REF_LIST_LEN] + + ($type) = $words[0]; + $json_type = convert_type_to_json_type($type); + + ($member) = $words[scalar @words - 1]; + next if $member =~ /reserved/; # ignore reserved members, they will segfault on retracing + + # generate members that are arrays + if ($line =~ /.*\[.*/) { + printf $fh_trace_cpp "\t\/\* %s \*\/\n", $line; #trace comment + printf $fh_trace_cpp "\tjson_object *%s_obj = json_object_new_array();\n", $member; + + printf $fh_retrace_cpp "\n\t\/\* %s \*\/\n", $line; #retrace comment + + my @dimensions = ($line) =~ /\[(\w+)\]/g; + $dimensions_count = scalar @dimensions; + + if ($dimensions_count > 1) { + printf $fh_retrace_cpp "\tint count_%s = 0;\n", $member; + } + printf $fh_retrace_cpp "\tjson_object *%s_obj;\n", $member; + printf $fh_retrace_cpp "\tjson_object_object_get_ex(%s_obj, \"%s\", &%s_obj);\n", $struct_name, $member, $member; + + for (my $idx = 0; $idx < $dimensions_count; $idx = $idx + 1 ) { + $size = $dimensions[$idx]; + $index_letter = get_index_letter($idx); + printf $fh_trace_cpp "\t" x ($idx + 1); + printf $fh_trace_cpp "for (size_t %s = 0; %s < %s\; %s++) {\n", $index_letter, $index_letter, $size, $index_letter; + + printf $fh_retrace_cpp "\t" x ($idx + 1); + printf $fh_retrace_cpp "for (size_t %s = 0; %s < %s\; %s++) {\n", $index_letter, $index_letter, $size, $index_letter; + } + printf $fh_trace_cpp "\t" x ($dimensions_count + 1); + printf $fh_retrace_cpp "\t" x ($dimensions_count + 1); + + # handle arrays of structs + if ($type =~ /struct/) { + my $struct_name_nested = @words[1]; + printf $fh_trace_cpp "json_object *element_obj = json_object_new_object();\n"; + printf $fh_trace_cpp "\t" x ($dimensions_count + 1); + printf $fh_trace_cpp "trace_%s_gen(&(p->%s", $struct_name_nested, $member; + for (my $idx = 0; $idx < $dimensions_count; $idx = $idx + 1 ) { + printf $fh_trace_cpp "[%s]", get_index_letter($idx); + } + printf $fh_trace_cpp "), element_obj);\n"; + printf $fh_trace_cpp "\t" x ($dimensions_count + 1); + printf $fh_trace_cpp "json_object *element_no_key_obj;\n"; + printf $fh_trace_cpp "\t" x ($dimensions_count + 1); + printf $fh_trace_cpp "json_object_object_get_ex(element_obj, \"%s\", &element_no_key_obj);\n", $struct_name_nested; + printf $fh_trace_cpp "\t" x ($dimensions_count + 1); + printf $fh_trace_cpp "json_object_array_add(%s_obj, element_no_key_obj);\n", $member; + + printf $fh_retrace_cpp "p->%s", $member; + for (my $idx = 0; $idx < $dimensions_count; $idx = $idx + 1) { + printf $fh_retrace_cpp "[%s]", get_index_letter($idx); + } + printf $fh_retrace_cpp " = *retrace_%s_gen(json_object_array_get_idx(%s_obj, ", $struct_name_nested, $member; + if ($dimensions_count > 1) { + printf $fh_retrace_cpp "count_%s++", $member; + } else { + printf $fh_retrace_cpp "i"; + } + printf $fh_retrace_cpp "));\n"; + } else { + printf $fh_trace_cpp "json_object_array_add(%s_obj, json_object_new_%s(p->%s", $member, $json_type, $member; + for (my $idx = 0; $idx < $dimensions_count; $idx = $idx + 1 ) { + printf $fh_trace_cpp "[%s]", get_index_letter($idx); + } + printf $fh_trace_cpp "));\n"; + + printf $fh_retrace_cpp "p->%s", $member; + for (my $idx = 0; $idx < $dimensions_count; $idx = $idx + 1 ) { + printf $fh_retrace_cpp "[%s]", get_index_letter($idx); + } + printf $fh_retrace_cpp " = ($type) json_object_get_%s(json_object_array_get_idx(%s_obj, ", $json_type, $member; + if ($dimensions_count > 1) { + printf $fh_retrace_cpp "count_%s++", $member; + } else { + printf $fh_retrace_cpp "i"; + } + printf $fh_retrace_cpp "));\n"; + } + #closing brackets for all array types + for (my $idx = $dimensions_count - 1; $idx >= 0 ; $idx = $idx - 1) { + printf $fh_trace_cpp "\t" x ($idx + 1); + printf $fh_trace_cpp "}\n"; + + printf $fh_retrace_cpp "\t" x ($idx + 1); + printf $fh_retrace_cpp "}\n"; + } + printf $fh_trace_cpp "\tjson_object_object_add(%s_obj, \"%s\", %s_obj);\n\n", $struct_name, $member, $member; + } elsif ($type =~ /struct/) { + # member that is a struct but not an array of structs + my $struct_name_nested = @words[1]; + printf $fh_trace_cpp "\t\/\* %s \*\/\n", $line; + printf $fh_trace_cpp "\ttrace_%s_gen(&(p->%s), %s_obj);\n", $struct_name_nested, $member, $struct_name; + + printf $fh_retrace_cpp "\t\/\* %s \*\/\n", $line; + printf $fh_retrace_cpp "\tjson_object *%s_obj;\n", $member; + printf $fh_retrace_cpp "\tjson_object_object_get_ex(%s_obj, \"%s\", &%s_obj);\n", $struct_name, $struct_name_nested, $member; + printf $fh_retrace_cpp "\tp->%s = \*retrace_%s_gen(%s_obj);\n\n", $member, $struct_name_nested, $member; + } else { + # members that are integers + printf $fh_trace_cpp "\tjson_object_object_add(%s_obj, \"%s\", json_object_new_%s(p->%s));\n", $struct_name, $member, $json_type, $member; + printf $fh_retrace_cpp "\n\tjson_object *%s_obj;\n", $member; + printf $fh_retrace_cpp "\tjson_object_object_get_ex(%s_obj, \"%s\", &%s_obj);\n", $struct_name, $member, $member; + printf $fh_retrace_cpp "\tp->%s = ($type) json_object_get_%s(%s_obj);\n", $member, $json_type, $member; + + # add flag strings + if ($member =~ /^flags/) { + printf $fh_trace_cpp "\tjson_object_object_add(%s_obj, \"flags_str\", json_object_new_string(%sflag2s(p->flags).c_str()));\n", $struct_name, $flag_func_name; + } + } + } + printf $fh_trace_cpp "\tjson_object_object_add(parent_obj, \"%s\", %s_obj);\n", $struct_name, $struct_name; + printf $fh_trace_cpp "}\n\n"; + + printf $fh_retrace_cpp "\n\treturn p;\n"; + printf $fh_retrace_cpp "}\n\n"; +} + +open($fh_trace_cpp, '>', 'trace-gen.cpp') or die "Could not open trace-gen.cpp for writing"; +printf $fh_trace_cpp "/* SPDX-License-Identifier: GPL-2.0-only */\n/*\n * Copyright 2022 Collabora Ltd.\n */\n\n"; +printf $fh_trace_cpp "#include \"v4l2-tracer-common.h\"\n#include \"trace-info-gen.h\"\n\n"; + +open($fh_trace_h, '>', 'trace-gen.h') or die "Could not open trace-gen.h for writing"; +printf $fh_trace_h "/* SPDX-License-Identifier: GPL-2.0-only */\n/*\n * Copyright 2022 Collabora Ltd.\n */\n\n"; +printf $fh_trace_h "\#ifndef TRACE_GEN_H\n"; +printf $fh_trace_h "\#define TRACE_GEN_H\n\n"; + +open($fh_retrace_cpp, '>', 'retrace-gen.cpp') or die "Could not open retrace-gen.cpp for writing"; +printf $fh_retrace_cpp "/* SPDX-License-Identifier: GPL-2.0-only */\n/*\n * Copyright 2022 Collabora Ltd.\n */\n\n"; +printf $fh_retrace_cpp "#include \"v4l2-tracer-common.h\"\n\n"; + +open($fh_retrace_h, '>', 'retrace-gen.h') or die "Could not open retrace-gen.h for writing"; +printf $fh_retrace_h "/* SPDX-License-Identifier: GPL-2.0-only */\n/*\n * Copyright 2022 Collabora Ltd.\n */\n\n"; +printf $fh_retrace_h "\#ifndef RETRACE_GEN_H\n"; +printf $fh_retrace_h "\#define RETRACE_GEN_H\n\n"; + +open($fh_trace_info_cpp, '>', 'trace-info-gen.cpp') or die "Could not open trace-info-gen.cpp for writing"; +printf $fh_trace_info_cpp "/* SPDX-License-Identifier: GPL-2.0-only */\n/*\n * Copyright 2022 Collabora Ltd.\n */\n\n"; +printf $fh_trace_info_cpp "#include \"v4l2-tracer-common.h\"\n#include \"trace-helper.h\"\n\n"; + +open($fh_trace_info_h, '>', 'trace-info-gen.h') or die "Could not open trace-info-gen.h for writing"; +printf $fh_trace_info_h "/* SPDX-License-Identifier: GPL-2.0-only */\n/*\n * Copyright 2022 Collabora Ltd.\n */\n\n"; +printf $fh_trace_info_h "\#ifndef TRACE_INFO_GEN_H\n"; +printf $fh_trace_info_h "\#define TRACE_INFO_GEN_H\n\n"; + +$in_v4l2_controls = true; + +while (<>) { + if (grep {/#define __LINUX_VIDEODEV2_H/} $_) {$in_v4l2_controls = false;} + + # only generate flag and struct functions for v4l2-controls.h + if ($in_v4l2_controls eq true) { + if (grep {/^#define.+_FLAG_.+/} $_) { + flag_gen(); + } elsif (grep {/^#define.+FWHT_FL_.+/} $_) { + flag_gen("fwht"); + } elsif (grep {/^#define V4L2_VP8_LF.*/} $_) { + flag_gen("vp8_loop_filter"); + } + + if (grep {/^struct/} $_) { + struct_gen(); + } + } + + if (grep {/^\/\* Control classes \*\//} $_) { + printf $fh_trace_info_cpp "std::string ctrlclass2s(__u32 id)\n{\n"; + printf $fh_trace_info_h "std::string ctrlclass2s(__u32 id);\n"; + printf $fh_trace_info_cpp "\tstatic constexpr definition defs[] = {\n"; + + while (<>) { + last if $_ =~ /^\s*$/; #last if blank line + ($ctrl_class) = ($_) =~ /#define\s*(\w+)\s+.*/; + printf $fh_trace_info_cpp "\t\t{ %s,\t\"%s\" },\n", $ctrl_class, $ctrl_class; + } + printf $fh_trace_info_cpp "\t\t{ 0, nullptr }\n\t};\n\treturn val2s(id & 0xff0000, defs);\n}\n\n"; + } + + if (grep {/^enum v4l2_memory/} $_) { + printf $fh_trace_info_cpp "std::string v4l2_memory2s(__u32 val)\n{\n"; + printf $fh_trace_info_h "std::string v4l2_memory2s(__u32 val);\n"; + printf $fh_trace_info_cpp "\tstatic constexpr definition defs[] = {\n"; + + while (<>) { + last if $_ =~ /};/; + ($memory_type) = ($_) =~ /\s*(\w+)\s+.*/; + printf $fh_trace_info_cpp "\t\t{ %s,\t\"%s\" },\n", $memory_type, $memory_type; + } + printf $fh_trace_info_cpp "\t\t{ 0, nullptr }\n\t};\n\treturn val2s(val, defs);\n}\n\n"; + } + + if (grep {/\/\* Values for 'capabilities' field \*\//} $_) { + printf $fh_trace_info_cpp "std::string capabilities2s(unsigned long flag)\n{\n"; + printf $fh_trace_info_h "std::string capabilities2s(unsigned long flag);\n"; + + printf $fh_trace_info_cpp "\tstatic constexpr flag_def def[] = {\n"; + while (<>) { + last if $_ =~ /.*V I D E O I M A G E F O R M A T.*/; + next if ($_ =~ /^\/?\s?\*.*/); #skip comments + next if $_ =~ /^\s*$/; #skip blank lines + ($cap) = ($_) =~ /#define\s+(\w+)\s+.+/; + printf $fh_trace_info_cpp "\t\t{ $cap, \"$cap\" },\n" + } + printf $fh_trace_info_cpp "\t\t{ 0, nullptr }\n\t};\n\treturn flags2s(flag, def);\n}\n\n"; + } + + if (grep {/^enum v4l2_ctrl_type/} $_) { + printf $fh_trace_info_cpp "std::string ctrltype2s(__u32 val)\n{\n"; + printf $fh_trace_info_h "std::string ctrltype2s(__u32 val);\n"; + printf $fh_trace_info_cpp "\tstatic constexpr definition defs[] = {\n"; + + while (<>) { + next if ($_ =~ /^\s?\/?\s?\*.*/); #skip comments + next if $_ =~ /^\s*$/; #skip blank lines + last if $_ =~ /};/; + ($ctrl_type) = ($_) =~ /\s*(\w+)\s+.*/; + + printf $fh_trace_info_cpp "\t\t{ %s,\t\"%s\" },\n", $ctrl_type, $ctrl_type; + } + printf $fh_trace_info_cpp "\t\t{ 0, nullptr }\n\t};\n\treturn val2s(val, defs);\n}\n\n"; + } + + if (grep {/.*I O C T L C O D E S F O R V I D E O D E V I C E S.*/} $_) { + printf $fh_trace_info_cpp "std::string ioctl2s_video(unsigned long request)\n{\n"; + printf $fh_trace_info_h "std::string ioctl2s_video(unsigned long request);\n"; + printf $fh_trace_info_cpp "\tstatic constexpr definition defs[] = {\n"; + + while (<>) { + next if ($_ =~ /^\/?\s?\*.*/); #skip comments + next if $_ =~ /^\s*$/; #skip blank lines + next if $_ =~ /^\s+/; #skip lines that start with a space + last if $_ =~ /^#define BASE_VIDIOC_PRIVATE/; + ($ioctl_name) = ($_) =~ /^#define\s*(\w+)\s*/; + printf $fh_trace_info_cpp "\t\t{ %s,\t\"%s\" },\n", $ioctl_name, $ioctl_name; + } + printf $fh_trace_info_cpp "\t\t{ 0, nullptr }\n\t};\n\treturn val2s(request, defs);\n}\n\n"; + } + + # from media.h + if (grep {/\/\* ioctls \*\//} $_) { + printf $fh_trace_info_cpp "std::string ioctl2s_media(unsigned long request)\n{\n"; + printf $fh_trace_info_h "std::string ioctl2s_media(unsigned long request);\n"; + printf $fh_trace_info_cpp "\tstatic constexpr definition defs[] = {\n"; + + while (<>) { + next if ($_ =~ /^\/?\s?\*.*/); #skip comments + next if $_ =~ /^\s*$/; #skip blank lines + next if $_ =~ /^\s+/; #skip lines that start with a space, comments + last if $_ =~ /^#define MEDIA_ENT_TYPE_SHIFT/; + ($ioctl_name) = ($_) =~ /^#define\s*(\w+)\s*/; + printf $fh_trace_info_cpp "\t\t{ %s,\t\"%s\" },\n", $ioctl_name, $ioctl_name; + } + printf $fh_trace_info_cpp "\t\t{ 0, nullptr }\n\t};\n\treturn val2s(request, defs);\n}\n\n"; + } +} + +printf $fh_trace_h "\n#endif\n"; +close $fh_trace_h; +close $fh_trace_cpp; + +printf $fh_retrace_h "\n#endif\n"; +close $fh_retrace_h; +close $fh_retrace_cpp; + +printf $fh_trace_info_h "\n#endif\n"; +close $fh_trace_info_h; +close $fh_trace_info_cpp; diff --git a/utils/v4l2-tracer/v4l2-tracer.1.in b/utils/v4l2-tracer/v4l2-tracer.1.in new file mode 100644 index 00000000..cd3d089b --- /dev/null +++ b/utils/v4l2-tracer/v4l2-tracer.1.in @@ -0,0 +1,104 @@ +.TH "V4L2-TRACER" "1" "October 2022" "v4l-utils @PACKAGE_VERSION@" "User Commands" +.SH NAME +v4l2-tracer - An application to trace and replay stateless video decoding. +.SH SYNOPSIS +\fBv4l2-tracer trace\fR [trace options] -- <\fItracee\fR> +.RS +.RE +\fBv4l2-tracer retrace\fR [retrace options] -- <\fItrace_file\fR>\fB.json\fR +.RS +.RE + +.SH DESCRIPTION +The v4l2-tracer utility traces, records and replays userspace applications +that implement the v4l2 memory-to-memory stateless video decoder interface. +The trace function intercepts and copies all system calls, stateless codec +controls and encoded data to a json-formatted trace file. The retrace function +independently reads and replays the json trace file. The json trace file can +be retraced independently from its original userspace environment and can be +edited to inject errors to test a driver's error handling abilities. + +.SH OPTIONS +.SS Trace Options +.TP +\fB\-e\fR, \fB\-\-prettymem\fR +Add whitespace in json file to improve readability of memory arrays. +.TP +\fB\-h\fR, \fB\-\-help\fR +Display trace help. +.TP +\fB\-p\fR, \fB\-\-pretty\fR +Add whitespace in json file to improve readability. +.TP +\fB\-r\fR, \fB\-\-raw\fR +Write decoded data to json file. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +Turn on verbose reporting. +.TP +\fB\-y\fR, \fB\-\-yuv\fR +Write decoded data to yuv file. + +.SS Retrace Options +.TP +\fB\-d\fR, \fB\-\-device\fR <\fIdev\fR> +Use a different video device than specified in the trace file. +.RS +<\fIdev\fR> must be a digit corresponding to an existing /dev/video<\fIdev\fR> +.RE +.TP +\fB\-h\fR, \fB\-\-help\fR +Display retrace help. +.TP +\fB\-m\fR, \fB\-\-media\fR <\fIdev\fR> +Use a different media device than specified in the trace file. +.RS +<\fIdev\fR> must be a digit corresponding to an existing /dev/media<\fIdev\fR> +.RE +.TP +\fB\-v\fR, \fB\-\-verbose\fR +Turn on verbose reporting. + +.SH EXIT STATUS +On success, it returns 0. Otherwise, it will return 1 or an error code. + +.SH EXAMPLES +Trace VP8 decoding: +.TP +.EX + v4l2-tracer trace -- gst-launch-1.0 -- filesrc location=test-25fps.vp8 ! parsebin ! v4l2slvp8dec ! videocodectestsink +.EE +.TP +Trace H264 decoding: +.TP +.EX + v4l2-tracer trace -- gst-launch-1.0 -- filesrc location=test-25fps.h264 ! parsebin ! v4l2slh264dec ! videocodectestsink +.EE +.TP +Trace FWHT decoding: +.TP +.EX + v4l2-tracer trace -- v4l2-ctl -d9 --stream-mmap --stream-out-mmap --stream-from-hdr test-25fps.fwht --stream-to out.yuv +.EE +.TP +Retrace: +.TP +.EX + v4l2-tracer retrace 79568_trace.json +.EE +.TP +Retrace using /dev/video6 and /dev/media3: +.TP +.EX + v4l2-tracer retrace -d 6 -m 3 79568_trace.json +.EE +.TP +Self-trace: +.TP +.EX + v4l2-tracer trace -- v4l2-tracer retrace 79568_trace.json +.EE + +.SH BUGS +Bug reports or questions about this utility should be sent to the +linux-media@vger.kernel.org mailinglist. diff --git a/utils/v4l2-tracer/v4l2-tracer.cpp b/utils/v4l2-tracer/v4l2-tracer.cpp new file mode 100644 index 00000000..71ac242a --- /dev/null +++ b/utils/v4l2-tracer/v4l2-tracer.cpp @@ -0,0 +1,187 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2022 Collabora Ltd. + */ + +#include <time.h> +#include <dirent.h> +#include <sys/wait.h> +#include "v4l2-tracer-common.h" + +#define STR(x) #x +#define STRING(x) STR(x) + +void print_help_retracer(void); +int retracer(int argc, char *argv[]); + +enum Option_Tracer { + TracerOptPrettyPrintMemoryOnly = 'e', + TracerOptHelp = 'h', + TracerOptPrettyPrintAll = 'p', + TracerOptWriteDecodedToJsonFile = 'r', + TracerOptVerbose = 'v', + TracerOptWriteDecodedToYUVFile = 'y', +}; + +static struct option long_options_tracer[] = { + { "prettymem", no_argument, nullptr, TracerOptPrettyPrintMemoryOnly }, + { "help", no_argument, nullptr, TracerOptHelp }, + { "pretty", no_argument, nullptr, TracerOptPrettyPrintAll }, + { "raw", no_argument, nullptr, TracerOptWriteDecodedToJsonFile }, + { "verbose", no_argument, nullptr, TracerOptVerbose }, + { "yuv", no_argument, nullptr, TracerOptWriteDecodedToYUVFile }, + { nullptr, 0, nullptr, 0 } +}; + +char short_options_tracer[] = { + TracerOptPrettyPrintMemoryOnly, + TracerOptHelp, + TracerOptPrettyPrintAll, + TracerOptWriteDecodedToJsonFile, + TracerOptVerbose, + TracerOptWriteDecodedToYUVFile +}; + +void print_help_tracer(void) +{ + fprintf(stderr, "v4l2-tracer trace [trace options] -- <tracee>\n" + + "\t-e, --prettymem Add whitespace in json file to improve readability of memory arrays.\n" + "\t-h, --help Display trace help.\n" + "\t-p, --pretty Add whitespace in json file to improve readability.\n" + "\t-r --raw Write decoded data to json file.\n" + "\t-v, --verbose Turn on verbose reporting.\n" + "\t-y, --yuv Write decoded data to yuv file.\n\n"); +} + +void print_usage(void) +{ + print_help_tracer(); + print_help_retracer(); +} + +void get_options_trace(int argc, char *argv[]) +{ + int ch; + + do { + ch = getopt_long(argc, argv, short_options_tracer, long_options_tracer, NULL); + switch (ch){ + case TracerOptPrettyPrintMemoryOnly: + setenv("TRACE_OPTION_PRETTY_PRINT_MEM", "true", 0); + break; + case TracerOptHelp: + break; + case TracerOptPrettyPrintAll: + setenv("TRACE_OPTION_PRETTY_PRINT_ALL", "true", 0); + break; + case TracerOptWriteDecodedToJsonFile: + setenv("TRACE_OPTION_WRITE_DECODED_TO_JSON_FILE", "true", 0); + break; + case TracerOptVerbose: + setenv("TRACE_OPTION_VERBOSE", "true", 0); + break; + case TracerOptWriteDecodedToYUVFile: + setenv("TRACE_OPTION_WRITE_DECODED_TO_YUV_FILE", "true", 0); + break; + } + } while (ch != -1); +} + +int tracer(int argc, char *argv[]) +{ + get_options_trace(argc, argv); + + if (optind == argc) { + print_help_tracer(); + return 1; + } + + /* Get the application to be traced from the command line. */ + int count = 0; + char *exec_array[argc]; + while (optind < argc) + exec_array[count++] = argv[optind++]; + exec_array[count] = nullptr; + + /* Use a substring of the time to create a unique id for the trace. */ + std::string trace_id = std::to_string(time(nullptr)); + trace_id = trace_id.substr(5, trace_id.npos) + "_trace"; + setenv("TRACE_ID", trace_id.c_str(), 0); + + /* Create the trace file to hold the json-objects as a json array. */ + std::string trace_filename = trace_id + ".json"; + FILE *trace_file = fopen(trace_filename.c_str(), "w"); + fputs("[\n", trace_file); + fclose(trace_file); + + /* + * Preload the libv4l2tracer library. If the program is installed, load the library + * from its installed location, otherwise load it locally. If it's loaded locally, + * use ./configure --disable-dyn-libv4l. + */ + std::string libv4l2tracer_path; + std::string program = argv[0]; + std::size_t idx = program.rfind("/v4l2-tracer"); + if (idx != program.npos) { + libv4l2tracer_path = program.replace(program.begin() + idx + 1, program.end(), ".libs"); + DIR *dp = opendir(libv4l2tracer_path.c_str()); + if (dp == nullptr) + libv4l2tracer_path = program.replace(program.begin() + idx, program.end(), "./.libs"); + closedir(dp); + } else { + libv4l2tracer_path = STRING(LIBTRACER_PATH); + } + libv4l2tracer_path += "/libv4l2tracer.so"; + fprintf(stderr, "Loading libv4l2tracer: %s\n", libv4l2tracer_path.c_str()); + setenv("LD_PRELOAD", libv4l2tracer_path.c_str(), 0); + + if (fork() == 0) { + execvpe(exec_array[0], exec_array, environ); + perror("Option -h for help. Could not execute application to trace."); + return errno; + } + + int tracee_result; + wait(&tracee_result); + + if (WEXITSTATUS(tracee_result)) { + fprintf(stderr, "Trace error: %s\n", trace_filename.c_str()); + exit(EXIT_FAILURE); + } + + /* Close the json-array and the trace file. */ + trace_file = fopen(trace_filename.c_str(), "a"); + fseek(trace_file, 0L, SEEK_END); + fputs("\n]\n", trace_file); + fclose(trace_file); + + fprintf(stderr, "Trace complete: "); + if (getenv("TRACE_OPTION_WRITE_DECODED_TO_YUV_FILE")) + fprintf(stderr, "%s, ", (trace_id + ".yuv").c_str()); + fprintf(stderr, "%s", trace_filename.c_str()); + fprintf(stderr, "\n"); + + return 0; +} + +int main(int argc, char *argv[]) +{ + int ret = 1; + + if (argc <= 1) { + print_usage(); + return ret; + } + + std::string command = argv[1]; + optind = 2; /* start looking for options only after the trace/retrace command */ + if (command == "trace") + ret = tracer(argc, argv); + else if (command == "retrace") + ret = retracer(argc, argv); + else + print_usage(); + + return ret; +}
The v4l2-tracer utility traces, records and replays userspace applications that implement the v4l2 memory-to-memory stateless video decoder interface. The trace function intercepts and copies system calls, stateless codec controls and buffers to a json-formatted trace file. The retrace function independently reads and replays the json trace file. The json trace file can be retraced independently from its original userspace environment and can be edited to inject errors to test a driver's error handling abilities. Signed-off-by: Deborah Brouwer <deborah.brouwer@collabora.com> --- Currently supports MPEG2, VP8, H264, FWHT Changes since v1: - auto-generate code from uAPI headers - generated code adds some ability to trace VP9/HEVC which compiles but still needs work - generated code adds full support for MPEG2 - fix multiplanar decoding for vicodec - change library name to libv4l2tracer - update README - expand help to show options - remove extraneous files from gitignore Examples: Trace VP8 decoding: v4l2-tracer trace -- gst-launch-1.0 -- filesrc location=test-25fps.vp8 ! parsebin ! v4l2slvp8dec ! videocodectestsink Trace H264 decoding: v4l2-tracer trace -y -- gst-launch-1.0 -- filesrc location=test-25fps.h264 ! parsebin ! v4l2slh264dec ! videocodectestsink Trace FWHT decoding: v4l2-tracer trace -- v4l2-ctl -d9 --stream-mmap --stream-out-mmap --stream-from-hdr test-25fps.fwht --stream-to out.yuv Note: if tracing another v4l2 utility like v4l2-ctl, either used the installed versions or configure with: --disable-dyn-libv4l --disable-v4l2-ctl-libv4l Retrace: v4l2-tracer retrace 79568_trace.json Trace a Retrace: v4l2-tracer trace -- v4l2-tracer retrace 79568_trace.json README | 8 + configure.ac | 12 + utils/Makefile.am | 5 + utils/v4l2-tracer/.gitignore | 8 + utils/v4l2-tracer/Makefile.am | 33 + utils/v4l2-tracer/libv4l2tracer.cpp | 222 ++++ utils/v4l2-tracer/retrace-helper.cpp | 196 ++++ utils/v4l2-tracer/retracer.cpp | 1319 ++++++++++++++++++++++++ utils/v4l2-tracer/trace-helper.cpp | 816 +++++++++++++++ utils/v4l2-tracer/trace-helper.h | 51 + utils/v4l2-tracer/trace.cpp | 669 ++++++++++++ utils/v4l2-tracer/v4l2-tracer-common.h | 40 + utils/v4l2-tracer/v4l2-tracer-gen.pl | 359 +++++++ utils/v4l2-tracer/v4l2-tracer.1.in | 104 ++ utils/v4l2-tracer/v4l2-tracer.cpp | 187 ++++ 15 files changed, 4029 insertions(+) create mode 100644 utils/v4l2-tracer/.gitignore create mode 100644 utils/v4l2-tracer/Makefile.am create mode 100644 utils/v4l2-tracer/libv4l2tracer.cpp create mode 100644 utils/v4l2-tracer/retrace-helper.cpp create mode 100644 utils/v4l2-tracer/retracer.cpp create mode 100644 utils/v4l2-tracer/trace-helper.cpp create mode 100644 utils/v4l2-tracer/trace-helper.h create mode 100644 utils/v4l2-tracer/trace.cpp create mode 100644 utils/v4l2-tracer/v4l2-tracer-common.h create mode 100755 utils/v4l2-tracer/v4l2-tracer-gen.pl create mode 100644 utils/v4l2-tracer/v4l2-tracer.1.in create mode 100644 utils/v4l2-tracer/v4l2-tracer.cpp