From patchwork Thu Jun 10 11:38:09 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Hans Verkuil X-Patchwork-Id: 458813 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-17.2 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER, INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, URIBL_BLOCKED, USER_AGENT_SANE_1 autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 916F7C48BD1 for ; Thu, 10 Jun 2021 11:38:17 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 7697A613F1 for ; Thu, 10 Jun 2021 11:38:17 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230103AbhFJLkM (ORCPT ); Thu, 10 Jun 2021 07:40:12 -0400 Received: from lb2-smtp-cloud8.xs4all.net ([194.109.24.25]:40469 "EHLO lb2-smtp-cloud8.xs4all.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230001AbhFJLkM (ORCPT ); Thu, 10 Jun 2021 07:40:12 -0400 Received: from cust-b5b5937f ([IPv6:fc0c:c16d:66b8:757f:c639:739b:9d66:799d]) by smtp-cloud8.xs4all.net with ESMTPA id rJ0jl7lP1hqltrJ0mlXjqB; Thu, 10 Jun 2021 13:38:12 +0200 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=xs4all.nl; s=s2; t=1623325092; bh=eBj2x3OCtZbpB7Iflv3SeH/Dv0wSVsmbZBca4ZFz/XE=; h=To:From:Subject:Message-ID:Date:MIME-Version:Content-Type:From: Subject; b=bt89aSVsEjKAbptH/4q9hsfHSR4/4HOkeYDY4HI3dQhPRo2Mw57fKPzQ+p/28Doof zqbKtk8zoZlLhgglErHrOTfIEeI7hDe3jRjkCNA6z7blesj/gD0J1PxzF1hEGubFhe MP09abZ+cjE69vIPV2/R/qPZFRHRKCjQdxRlTthh8MQZ3QNFHERaepDafxjwQmP/jF k792HJ23WGMnbkPTMpPqJRfqWjeqSsgKTmNimTQvgekoppRnLTBfho+MhRqiSh6gHl Onu4L+/R6hKV2CLLQ7m/DS158eJQ/6uQ3S+vGojP4NMNbzsq5mY/Y1fhOSVJ8z7Bt/ 2B9CZ8K5//VeQ== To: Linux Media Mailing List Cc: Ezequiel Garcia , John Cox , Nicolas Dufresne From: Hans Verkuil Subject: [RFC PATCH 1/2] v4l-utils: add support for dynamic array controls Message-ID: <0f8a0bc5-f954-d7c8-e018-f5ea57224d62@xs4all.nl> Date: Thu, 10 Jun 2021 13:38:09 +0200 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0 Thunderbird/78.10.0 MIME-Version: 1.0 Content-Language: en-US X-CMAE-Envelope: MS4xfPbYB8uUJg/3LjII/H3CBQEpgEjhnKrcXQU8I015+ZE+rvkvU0dFYwy6q4/szYrKXTuy2Lorj+Y0UnHphVSgke5m/Tg6O+GZhaTnq2ZmwFdvQIllY7s6 uP+EaxPpIXWUjaHnuWAVeQqbagE/w4uRRqs7774Bt3WfzjujcLffTCj+CWfRqo1+AlYx7Hb32s1NSW166gKFgvREdCU2efkdkKMoVnWTiGzmiz+dt4U5GMeA uT6aF/TGk5pY7vcmpiJ8MBAaQ4lj8E2/9euvN7On2zvwOB9J69cmZD9vPlrnEAAQ Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org Add support for dynamic array controls. Signed-off-by: Hans Verkuil --- include/linux/videodev2.h | 1 + utils/common/v4l2-info.cpp | 1 + utils/v4l2-compliance/v4l2-compliance.h | 1 + utils/v4l2-compliance/v4l2-test-buffers.cpp | 90 ++++++++++++ utils/v4l2-compliance/v4l2-test-controls.cpp | 147 +++++++++++++++++++ utils/v4l2-ctl/v4l2-ctl-common.cpp | 7 + 6 files changed, 247 insertions(+) diff --git a/include/linux/videodev2.h b/include/linux/videodev2.h index 59c466df..4cf0ec25 100644 --- a/include/linux/videodev2.h +++ b/include/linux/videodev2.h @@ -1836,6 +1836,7 @@ struct v4l2_querymenu { #define V4L2_CTRL_FLAG_HAS_PAYLOAD 0x0100 #define V4L2_CTRL_FLAG_EXECUTE_ON_WRITE 0x0200 #define V4L2_CTRL_FLAG_MODIFY_LAYOUT 0x0400 +#define V4L2_CTRL_FLAG_DYNAMIC_ARRAY 0x0800 /* Query flags, to be ORed with the control ID */ #define V4L2_CTRL_FLAG_NEXT_CTRL 0x80000000 diff --git a/utils/common/v4l2-info.cpp b/utils/common/v4l2-info.cpp index b8f2c865..00a5fada 100644 --- a/utils/common/v4l2-info.cpp +++ b/utils/common/v4l2-info.cpp @@ -531,6 +531,7 @@ std::string ctrlflags2s(__u32 flags) { V4L2_CTRL_FLAG_HAS_PAYLOAD,"has-payload" }, { V4L2_CTRL_FLAG_EXECUTE_ON_WRITE, "execute-on-write" }, { V4L2_CTRL_FLAG_MODIFY_LAYOUT, "modify-layout" }, + { V4L2_CTRL_FLAG_DYNAMIC_ARRAY, "dynamic-array" }, { 0, nullptr } }; return flags2s(flags, def); diff --git a/utils/v4l2-compliance/v4l2-compliance.h b/utils/v4l2-compliance/v4l2-compliance.h index 0b05fff7..af5efe62 100644 --- a/utils/v4l2-compliance/v4l2-compliance.h +++ b/utils/v4l2-compliance/v4l2-compliance.h @@ -82,6 +82,7 @@ enum poll_mode { #define VIVID_CID_CUSTOM_BASE (V4L2_CID_USER_BASE | 0xf000) #define VIVID_CID_RO_INTEGER (VIVID_CID_CUSTOM_BASE + 12) +#define VIVID_CID_U32_DYN_ARRAY (VIVID_CID_CUSTOM_BASE + 13) struct test_query_ext_ctrl: v4l2_query_ext_ctrl { __u64 menu_mask; diff --git a/utils/v4l2-compliance/v4l2-test-buffers.cpp b/utils/v4l2-compliance/v4l2-test-buffers.cpp index 1fe1ec1c..02457636 100644 --- a/utils/v4l2-compliance/v4l2-test-buffers.cpp +++ b/utils/v4l2-compliance/v4l2-test-buffers.cpp @@ -1958,6 +1958,30 @@ int testRequests(struct node *node, bool test_streaming) .count = 1, .controls = &vivid_ro_ctrl, }; + // Note: the vivid dynamic array has range 10-90 + // and the maximum number of elements is 100. + __u32 vivid_dyn_array[101] = {}; + // Initialize with these values + static const __u32 vivid_dyn_array_init[16] = { + 6, 12, 18, 24, 30, 36, 42, 48, + 54, 60, 66, 72, 78, 84, 90, 96 + }; + // This is the clamped version to compare against + static const __u32 vivid_dyn_array_clamped[16] = { + 10, 12, 18, 24, 30, 36, 42, 48, + 54, 60, 66, 72, 78, 84, 90, 90 + }; + const unsigned elem_size = sizeof(vivid_dyn_array[0]); + v4l2_ext_control vivid_dyn_array_ctrl = { + .id = VIVID_CID_U32_DYN_ARRAY, + .size = elem_size, + .p_u32 = vivid_dyn_array, + }; + v4l2_ext_controls vivid_dyn_array_ctrls = { + .which = V4L2_CTRL_WHICH_REQUEST_VAL, + .count = 1, + .controls = &vivid_dyn_array_ctrl, + }; bool have_controls; int ret; @@ -2260,6 +2284,34 @@ int testRequests(struct node *node, bool test_streaming) ctrls.request_fd = buf_req_fds[i]; if (i % 3 < 2) fail_on_test(doioctl(node, VIDIOC_S_EXT_CTRLS, &ctrls)); + if (is_vivid) { + // For vivid, check dynamic array support: + vivid_dyn_array_ctrls.request_fd = buf_req_fds[i]; + vivid_dyn_array_ctrl.size = sizeof(vivid_dyn_array); + memset(vivid_dyn_array, 0xff, sizeof(vivid_dyn_array)); + // vivid_dyn_array_ctrl.size is too large, must return ENOSPC + fail_on_test(doioctl(node, VIDIOC_S_EXT_CTRLS, + &vivid_dyn_array_ctrls) != ENOSPC); + // and size is set at 100 elems + fail_on_test(vivid_dyn_array_ctrl.size != 100 * elem_size); + // Check that the array is not overwritten + fail_on_test(vivid_dyn_array[0] != 0xffffffff); + if (i % 3 < 2) { + unsigned size = (2 + 2 * (i % 8)) * elem_size; + + // Set proper size, varies per request + vivid_dyn_array_ctrl.size = size; + memcpy(vivid_dyn_array, vivid_dyn_array_init, size); + fail_on_test(doioctl(node, VIDIOC_S_EXT_CTRLS, + &vivid_dyn_array_ctrls)); + // check that the size is as expected + fail_on_test(vivid_dyn_array_ctrl.size != size); + // and the array values are correctly clamped + fail_on_test(memcmp(vivid_dyn_array, vivid_dyn_array_clamped, size)); + // and the end of the array is not overwritten + fail_on_test(vivid_dyn_array[size / elem_size] != 0xffffffff); + } + } // Re-init the unqueued request fail_on_test(doioctl_fd(buf_req_fds[i], MEDIA_REQUEST_IOC_REINIT, nullptr)); @@ -2272,6 +2324,15 @@ int testRequests(struct node *node, bool test_streaming) ctrls.request_fd = buf_req_fds[i]; if (i % 3 < 2) fail_on_test(doioctl(node, VIDIOC_S_EXT_CTRLS, &ctrls)); + if (is_vivid && i % 3 < 2) { + // Set the dynamic array control again + vivid_dyn_array_ctrls.request_fd = buf_req_fds[i]; + vivid_dyn_array_ctrl.size = (2 + 2 * (i % 8)) * elem_size; + memcpy(vivid_dyn_array, vivid_dyn_array_init, + sizeof(vivid_dyn_array_init)); + fail_on_test(doioctl(node, VIDIOC_S_EXT_CTRLS, + &vivid_dyn_array_ctrls)); + } // After the re-init the buffer is no longer marked for // a request. If a request has been queued before (hence @@ -2376,6 +2437,20 @@ int testRequests(struct node *node, bool test_streaming) fail_on_test(doioctl(node, VIDIOC_G_EXT_CTRLS, &vivid_ro_ctrls)); if (node->is_video && !node->can_output) warn_once_on_test(vivid_ro_ctrl.value != (int)i); + + // Check that the dynamic control array is set as + // expected and with the correct values. + vivid_dyn_array_ctrls.request_fd = buf_req_fds[i]; + memset(vivid_dyn_array, 0xff, sizeof(vivid_dyn_array)); + vivid_dyn_array_ctrl.size = sizeof(vivid_dyn_array); + fail_on_test(doioctl(node, VIDIOC_G_EXT_CTRLS, &vivid_dyn_array_ctrls)); + unsigned size = (2 + 2 * (i % 8)) * elem_size; + if (i % 3 == 2) + size = (2 + 2 * ((i - 1) % 8)) * elem_size; + fail_on_test(vivid_dyn_array_ctrl.size != size); + fail_on_test(memcmp(vivid_dyn_array, vivid_dyn_array_clamped, + vivid_dyn_array_ctrl.size)); + fail_on_test(vivid_dyn_array[size / elem_size] != 0xffffffff); } fail_on_test(buf.querybuf(node, i)); // Check that all the buffers of the stopped stream are @@ -2424,6 +2499,21 @@ int testRequests(struct node *node, bool test_streaming) fail_on_test(doioctl(node, VIDIOC_G_EXT_CTRLS, &vivid_ro_ctrls)); if (node->is_video && !node->can_output) warn_on_test(vivid_ro_ctrl.value != (int)(num_bufs - 1)); + + // and the final dynamic array value + v4l2_query_ext_ctrl q_dyn_array = { + .id = VIVID_CID_U32_DYN_ARRAY, + }; + fail_on_test(doioctl(node, VIDIOC_QUERY_EXT_CTRL, &q_dyn_array)); + unsigned elems = 2 + 2 * ((num_bufs - 1) % 8); + if ((num_bufs - 1) % 3 == 2) + elems = 2 + 2 * ((num_bufs - 2) % 8); + fail_on_test(q_dyn_array.elems != elems); + vivid_dyn_array_ctrls.which = 0; + fail_on_test(doioctl(node, VIDIOC_G_EXT_CTRLS, &vivid_dyn_array_ctrls)); + fail_on_test(vivid_dyn_array_ctrl.size != elems * elem_size); + fail_on_test(memcmp(vivid_dyn_array, vivid_dyn_array_clamped, + vivid_dyn_array_ctrl.size)); } } diff --git a/utils/v4l2-compliance/v4l2-test-controls.cpp b/utils/v4l2-compliance/v4l2-test-controls.cpp index 07685d72..cf5489f7 100644 --- a/utils/v4l2-compliance/v4l2-test-controls.cpp +++ b/utils/v4l2-compliance/v4l2-test-controls.cpp @@ -617,6 +617,150 @@ static int checkExtendedCtrl(const struct v4l2_ext_control &ctrl, const struct t default: break; } + if (qctrl.flags & V4L2_CTRL_FLAG_DYNAMIC_ARRAY) { + fail_on_test(qctrl.nr_of_dims != 1); + unsigned tot_elems = qctrl.dims[0]; + fail_on_test(qctrl.elems > tot_elems); + fail_on_test(!qctrl.elems); + } + return 0; +} + +static int checkVividDynArray(struct node *node, + struct v4l2_ext_control &ctrl, + const struct test_query_ext_ctrl &qctrl) +{ + struct v4l2_query_ext_ctrl qextctrl = {}; + unsigned max_elems = qctrl.dims[0]; + unsigned max_size = qctrl.elem_size * max_elems; + + delete [] ctrl.string; + // Allocate a buffer that's one element more than the max + ctrl.string = new char[max_size + qctrl.elem_size]; + ctrl.size = qctrl.elem_size; + ctrl.p_u32[0] = (qctrl.minimum + qctrl.maximum) / 2; + // Set the last element + 1, must never be overwritten + ctrl.p_u32[max_elems] = 0xdeadbeef; + + struct v4l2_ext_controls ctrls = {}; + ctrls.count = 1; + ctrls.controls = &ctrl; + // Set the array to a single element + fail_on_test(doioctl(node, VIDIOC_S_EXT_CTRLS, &ctrls)); + + qextctrl.id = ctrl.id; + // Check that only one element is reported + fail_on_test(doioctl(node, VIDIOC_QUERY_EXT_CTRL, &qextctrl)); + fail_on_test(qextctrl.elems != 1); + + // If the size is less than elem_size, the ioctl must return -EFAULT + ctrl.size = 0; + fail_on_test(doioctl(node, VIDIOC_TRY_EXT_CTRLS, &ctrls) != EFAULT); + ctrl.size = qctrl.elem_size - 1; + fail_on_test(doioctl(node, VIDIOC_TRY_EXT_CTRLS, &ctrls) != EFAULT); + ctrl.size = max_size + qctrl.elem_size; + fail_on_test(doioctl(node, VIDIOC_G_EXT_CTRLS, &ctrls)); + // Check that ctrl.size is reset to the current size of the array (1 element) + fail_on_test(ctrl.size != qctrl.elem_size); + ctrl.size = max_size + qctrl.elem_size; + // Attempting to set more than max_elems must result in -ENOSPC + fail_on_test(doioctl(node, VIDIOC_TRY_EXT_CTRLS, &ctrls) != ENOSPC); + fail_on_test(ctrl.size != max_size); + ctrl.size = max_size + qctrl.elem_size; + fail_on_test(doioctl(node, VIDIOC_S_EXT_CTRLS, &ctrls) != ENOSPC); + fail_on_test(ctrl.size != max_size); + fail_on_test(doioctl(node, VIDIOC_QUERY_EXT_CTRL, &qextctrl)); + // Verify that the number of elements is still 1 + fail_on_test(qextctrl.elems != 1); + + ctrl.size = max_size; + for (unsigned i = 0; i < max_elems; i++) + ctrl.p_u32[i] = i; + // Try the max number of elements + fail_on_test(doioctl(node, VIDIOC_TRY_EXT_CTRLS, &ctrls)); + // Check that the values are clamped + for (unsigned i = 0; i < max_elems; i++) { + unsigned j = i; + if (j < qctrl.minimum) + j = qctrl.minimum; + else if (j > qctrl.maximum) + j = qctrl.maximum; + fail_on_test(ctrl.p_u32[i] != j); + } + fail_on_test(ctrl.size != max_size); + fail_on_test(doioctl(node, VIDIOC_QUERY_EXT_CTRL, &qextctrl)); + // Verify that the number of elements is still 1 + fail_on_test(qextctrl.elems != 1); + memset(ctrl.string, 0xff, max_size); + fail_on_test(doioctl(node, VIDIOC_G_EXT_CTRLS, &ctrls)); + // Check that there is still just one element returned. + fail_on_test(ctrl.size != qctrl.elem_size); + fail_on_test(ctrl.p_u32[0] != (qctrl.minimum + qctrl.maximum) / 2); + + ctrl.size = max_size; + for (unsigned i = 0; i < max_elems; i++) + ctrl.p_u32[i] = i; + // Set the max number of elements + fail_on_test(doioctl(node, VIDIOC_S_EXT_CTRLS, &ctrls)); + for (unsigned i = 0; i < max_elems; i++) { + unsigned j = i; + if (j < qctrl.minimum) + j = qctrl.minimum; + else if (j > qctrl.maximum) + j = qctrl.maximum; + fail_on_test(ctrl.p_u32[i] != j); + } + fail_on_test(doioctl(node, VIDIOC_QUERY_EXT_CTRL, &qextctrl)); + // Check that it is updated + fail_on_test(qextctrl.elems != max_elems); + memset(ctrl.string, 0xff, max_size); + ctrl.size = qctrl.elem_size; + // Check that ENOSPC is returned if the size is too small + fail_on_test(doioctl(node, VIDIOC_G_EXT_CTRLS, &ctrls) != ENOSPC); + // And updated to the actual required size + fail_on_test(ctrl.size != max_size); + fail_on_test(doioctl(node, VIDIOC_G_EXT_CTRLS, &ctrls)); + for (unsigned i = 0; i < max_elems; i++) { + unsigned j = i; + if (j < qctrl.minimum) + j = qctrl.minimum; + else if (j > qctrl.maximum) + j = qctrl.maximum; + fail_on_test(ctrl.p_u32[i] != j); + } + // Check that the end of the buffer isn't overwritten + fail_on_test(ctrl.p_u32[max_elems] != 0xdeadbeef); + + ctrl.size = qctrl.elem_size; + ctrls.which = V4L2_CTRL_WHICH_DEF_VAL; + // Check that ENOSPC is returned if the size is too small + fail_on_test(doioctl(node, VIDIOC_G_EXT_CTRLS, &ctrls) != ENOSPC); + // And updated to the actual required size + fail_on_test(ctrl.size != max_size); + fail_on_test(doioctl(node, VIDIOC_G_EXT_CTRLS, &ctrls)); + for (unsigned i = 0; i < max_elems; i++) + fail_on_test(ctrl.p_u32[i] != 50); + // Check that the end of the buffer isn't overwritten + fail_on_test(ctrl.p_u32[max_elems] != 0xdeadbeef); + + ctrls.which = 0; + ctrl.size = qctrl.elem_size; + ctrl.p_u32[0] = (qctrl.minimum + qctrl.maximum) / 2; + // Back to just one element + fail_on_test(doioctl(node, VIDIOC_S_EXT_CTRLS, &ctrls)); + fail_on_test(doioctl(node, VIDIOC_QUERY_EXT_CTRL, &qextctrl)); + // Check this. + fail_on_test(qextctrl.elems != 1); + + ctrl.size = max_size; + ctrls.which = V4L2_CTRL_WHICH_DEF_VAL; + memset(ctrl.string, 0xff, max_size); + // And updated to the actual required size + fail_on_test(doioctl(node, VIDIOC_G_EXT_CTRLS, &ctrls)); + fail_on_test(ctrl.size != qctrl.elem_size); + fail_on_test(ctrl.p_u32[0] != 50); + fail_on_test(ctrl.p_u32[1] != 0xffffffff); + return 0; } @@ -739,6 +883,9 @@ int testExtendedControls(struct node *node) return fail("s_ext_ctrls returned invalid control contents (%08x)\n", qctrl.id); } + if (is_vivid && ctrl.id == VIVID_CID_U32_DYN_ARRAY && + checkVividDynArray(node, ctrl, qctrl)) + return fail("dynamic array tests failed\n"); if (qctrl.flags & V4L2_CTRL_FLAG_HAS_PAYLOAD) delete [] ctrl.string; ctrl.string = nullptr; diff --git a/utils/v4l2-ctl/v4l2-ctl-common.cpp b/utils/v4l2-ctl/v4l2-ctl-common.cpp index 51919fa7..9b378211 100644 --- a/utils/v4l2-ctl/v4l2-ctl-common.cpp +++ b/utils/v4l2-ctl/v4l2-ctl-common.cpp @@ -390,6 +390,9 @@ static bool fill_subset(const struct v4l2_query_ext_ctrl &qc, ctrl_subset &subse subset.size[d] = qc.dims[d]; } + if (qc.flags & V4L2_CTRL_FLAG_DYNAMIC_ARRAY) + subset.size[0] = qc.elems; + std::string s = name2var(qc.name); if (ctrl_subsets.find(s) != ctrl_subsets.end()) { @@ -489,6 +492,8 @@ static void print_value(int fd, const v4l2_query_ext_ctrl &qc, const v4l2_ext_co memset(&subset, 0, sizeof(subset)); for (unsigned i = 0; i < qc.nr_of_dims; i++) subset.size[i] = qc.dims[i]; + if (qc.flags & V4L2_CTRL_FLAG_DYNAMIC_ARRAY) + subset.size[0] = qc.elems; } print_array(qc, ctrl, subset); return; @@ -647,6 +652,8 @@ static void print_qctrl(int fd, const v4l2_query_ext_ctrl &qc, printf(" value="); print_value(fd, qc, ctrl, false, false); } else { + if (qc.flags & V4L2_CTRL_FLAG_DYNAMIC_ARRAY) + printf(" elems=%u", qc.elems); printf(" dims="); for (i = 0; i < qc.nr_of_dims; i++) printf("[%u]", qc.dims[i]); From patchwork Thu Jun 10 11:38:59 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Hans Verkuil X-Patchwork-Id: 458134 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-17.2 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER, INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, URIBL_BLOCKED, USER_AGENT_SANE_1 autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id BCA34C47094 for ; Thu, 10 Jun 2021 11:39:05 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 99FBE613DE for ; Thu, 10 Jun 2021 11:39:05 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230154AbhFJLlA (ORCPT ); Thu, 10 Jun 2021 07:41:00 -0400 Received: from lb2-smtp-cloud8.xs4all.net ([194.109.24.25]:49581 "EHLO lb2-smtp-cloud8.xs4all.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230033AbhFJLk7 (ORCPT ); Thu, 10 Jun 2021 07:40:59 -0400 Received: from cust-b5b5937f ([IPv6:fc0c:c16d:66b8:757f:c639:739b:9d66:799d]) by smtp-cloud8.xs4all.net with ESMTPA id rJ1Xl7lm1hqltrJ1alXk18; Thu, 10 Jun 2021 13:39:02 +0200 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=xs4all.nl; s=s2; t=1623325142; bh=TydwBCX+Whg3uu+64Alty1DwcWNO8oFrCrx9tqVxhiU=; h=To:From:Subject:Message-ID:Date:MIME-Version:Content-Type:From: Subject; b=qY+79/2ucUoh9fhj6/FwBGYt/2e3n9oYi0j1XpxZ/dpPWXu5mB6LaZ9gx3u75CH79 szculljCIjq8O+GGlXAJ3cP4O8OpLkEsxASaHBv2vxTXMKxVdMQTkxlSvpZSY7Nzqj gMEws/h3b/lKCvh2zc6tv5pjOIqWZW0yMnaBIGi35fOM32uPbOECKJKVZEtqRfSFZU s3ch5H8v2kjAezqa5OSad+v5ZgBLy9uTIbnw3qrXreMDrQMoAYYiqqEXzzEGwdv20/ g9xipamplbB7TW9Xh1WwbHmLojdui5oQpcngqOj+5xdeSBnpDmjZJXFXGfi/XyIjIC rvu4R49ZN7jBg== To: Linux Media Mailing List Cc: Dikshita Agarwal , Yunfei Dong From: Hans Verkuil Subject: [RFC PATCH 2/2] v4l-utils: add support for read-only requests Message-ID: <794a0757-d36b-a5c2-5a45-044b8cefd1f4@xs4all.nl> Date: Thu, 10 Jun 2021 13:38:59 +0200 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0 Thunderbird/78.10.0 MIME-Version: 1.0 Content-Language: en-US X-CMAE-Envelope: MS4xfDpK6bIPgSam82Hm3LciMBF7tnwYeGDPu/O37vM+NMjtprNQsIUYNCVOkWehCGT1aAqzpyoYoY/1Y2KnnjEJTB4qvE9wanLaUwqyT4mjkvSDCtIFAsSn 9hgLhIH4t34jzdA30TFXXHDMMjijlIzOLGQwDhIzPMd+OAJa9miM4CY0joZbK468n1PvZmsr7pg1xDWFR7YxTjSviNtnHg4qQPD2fJqoUPvTADnFH6ZftwHk Z41feQ3t/+W6iU0lAWOe1MPq4Y2RQEYGmowP7kHVhWU= Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org Add support for read-only requests. Signed-off-by: Hans Verkuil --- contrib/freebsd/include/linux/videodev2.h | 1 + include/linux/videodev2.h | 1 + utils/common/cv4l-helpers.h | 30 ++- utils/common/v4l-helpers.h | 96 +++++++- utils/common/v4l2-info.cpp | 1 + utils/v4l2-compliance/v4l2-compliance.cpp | 3 +- utils/v4l2-compliance/v4l2-compliance.h | 2 +- utils/v4l2-compliance/v4l2-test-buffers.cpp | 98 +++++--- utils/v4l2-ctl/v4l2-ctl-common.cpp | 40 +++- utils/v4l2-ctl/v4l2-ctl-streaming.cpp | 252 +++++++++++--------- utils/v4l2-ctl/v4l2-ctl.cpp | 4 + utils/v4l2-ctl/v4l2-ctl.h | 6 + 12 files changed, 380 insertions(+), 154 deletions(-) diff --git a/contrib/freebsd/include/linux/videodev2.h b/contrib/freebsd/include/linux/videodev2.h index c8a61dfd..13425d24 100644 --- a/contrib/freebsd/include/linux/videodev2.h +++ b/contrib/freebsd/include/linux/videodev2.h @@ -975,6 +975,7 @@ struct v4l2_requestbuffers { #define V4L2_BUF_CAP_SUPPORTS_ORPHANED_BUFS (1 << 4) #define V4L2_BUF_CAP_SUPPORTS_M2M_HOLD_CAPTURE_BUF (1 << 5) #define V4L2_BUF_CAP_SUPPORTS_MMAP_CACHE_HINTS (1 << 6) +#define V4L2_BUF_CAP_SUPPORTS_RO_REQUESTS (1 << 7) /** * struct v4l2_plane - plane info for multi-planar buffers diff --git a/include/linux/videodev2.h b/include/linux/videodev2.h index 4cf0ec25..c4238254 100644 --- a/include/linux/videodev2.h +++ b/include/linux/videodev2.h @@ -941,6 +941,7 @@ struct v4l2_requestbuffers { #define V4L2_BUF_CAP_SUPPORTS_ORPHANED_BUFS (1 << 4) #define V4L2_BUF_CAP_SUPPORTS_M2M_HOLD_CAPTURE_BUF (1 << 5) #define V4L2_BUF_CAP_SUPPORTS_MMAP_CACHE_HINTS (1 << 6) +#define V4L2_BUF_CAP_SUPPORTS_RO_REQUESTS (1 << 7) /** * struct v4l2_plane - plane info for multi-planar buffers diff --git a/utils/common/cv4l-helpers.h b/utils/common/cv4l-helpers.h index 712efde6..5943b329 100644 --- a/utils/common/cv4l-helpers.h +++ b/utils/common/cv4l-helpers.h @@ -766,6 +766,30 @@ public: { return v4l_queue_create_bufs(fd->g_v4l_fd(), this, count, fmt); } + bool has_requests() const + { + return v4l_queue_has_requests(this); + } + int g_request_fd(unsigned index) const + { + return v4l_queue_g_request_fd(this, index); + } + int create_requests(int media_fd) + { + return v4l_queue_create_requests(this, media_fd); + } + void free_requests() + { + v4l_queue_free_requests(this); + } + int reinit_request(unsigned b) + { + return v4l_queue_reinit_request(this, b); + } + void free_request(unsigned b) + { + v4l_queue_free_request(this, b); + } int mmap_bufs(cv4l_fd *fd, unsigned from = 0) { return v4l_queue_mmap_bufs(fd->g_v4l_fd(), this, from); @@ -814,6 +838,10 @@ public: { v4l_queue_buffer_update(this, &buf, index); } + int buffer_queue_request(cv4l_fd *fd, v4l_buffer &buf) + { + return v4l_queue_buffer_queue_request(fd->g_v4l_fd(), this, &buf); + } int queue_all(cv4l_fd *fd); }; @@ -904,7 +932,7 @@ inline int cv4l_queue::queue_all(cv4l_fd *fd) for (unsigned i = 0; i < g_buffers(); i++) { buf.init(*this, i); - int ret = fd->qbuf(buf); + int ret = buffer_queue_request(fd, buf); if (ret) return ret; } diff --git a/utils/common/v4l-helpers.h b/utils/common/v4l-helpers.h index f96b3c38..baf18749 100644 --- a/utils/common/v4l-helpers.h +++ b/utils/common/v4l-helpers.h @@ -9,7 +9,6 @@ #ifndef _V4L_HELPERS_H_ #define _V4L_HELPERS_H_ -#include #include #include #include @@ -20,6 +19,8 @@ #include #include #include +#include +#include #ifdef __cplusplus extern "C" { @@ -1417,6 +1418,8 @@ struct v4l_queue { void *mmappings[VIDEO_MAX_FRAME][VIDEO_MAX_PLANES]; unsigned long userptrs[VIDEO_MAX_FRAME][VIDEO_MAX_PLANES]; int fds[VIDEO_MAX_FRAME][VIDEO_MAX_PLANES]; + int request_fds[VIDEO_MAX_FRAME]; + bool has_requests; }; static inline void v4l_queue_init(struct v4l_queue *q, @@ -1427,9 +1430,11 @@ static inline void v4l_queue_init(struct v4l_queue *q, memset(q, 0, sizeof(*q)); q->type = type; q->memory = memory; - for (i = 0; i < VIDEO_MAX_FRAME; i++) + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + q->request_fds[i] = -1; for (p = 0; p < VIDEO_MAX_PLANES; p++) q->fds[i][p] = -1; + } } static inline unsigned v4l_queue_g_type(const struct v4l_queue *q) { return q->type; } @@ -1571,6 +1576,60 @@ static inline int v4l_queue_create_bufs(struct v4l_fd *f, return v4l_queue_querybufs(f, q, q->buffers - createbufs.count); } +static inline int v4l_queue_g_request_fd(const struct v4l_queue *q, unsigned index) +{ + return q->request_fds[index]; +} + +static inline bool v4l_queue_has_requests(const struct v4l_queue *q) +{ + return q->has_requests; +} + +static inline void v4l_queue_free_request(struct v4l_queue *q, unsigned index) +{ + if (index < v4l_queue_g_buffers(q) && q->request_fds[index] >= 0) { + close(q->request_fds[index]); + q->request_fds[index] = -1; + } +} + +static inline void v4l_queue_free_requests(struct v4l_queue *q) +{ + unsigned b; + + for (b = 0; b < v4l_queue_g_buffers(q) && q->request_fds[b] >= 0; b++) { + close(q->request_fds[b]); + q->request_fds[b] = -1; + } + q->has_requests = false; +} + +static inline int v4l_queue_create_requests(struct v4l_queue *q, int media_fd) +{ + unsigned b; + int ret = 0; + + for (b = 0; b < v4l_queue_g_buffers(q); b++) { + int ret = ioctl(media_fd, MEDIA_IOC_REQUEST_ALLOC, &q->request_fds[b]); + + if (ret) + break; + } + if (ret) + v4l_queue_free_requests(q); + else + q->has_requests = true; + return ret; +} + +static inline int v4l_queue_reinit_request(struct v4l_queue *q, unsigned b) +{ + if (q->request_fds[b] >= 0) + return ioctl(q->request_fds[b], MEDIA_REQUEST_IOC_REINIT, NULL); + return 0; +} + static inline int v4l_queue_mmap_bufs(struct v4l_fd *f, struct v4l_queue *q, unsigned from) { @@ -1732,6 +1791,7 @@ static inline void v4l_queue_free(struct v4l_fd *f, struct v4l_queue *q) { v4l_ioctl(f, VIDIOC_STREAMOFF, &q->type); v4l_queue_release_bufs(f, q, 0); + v4l_queue_free_requests(q); v4l_queue_close_exported_fds(q); v4l_queue_reqbufs(f, q, 0); } @@ -1767,7 +1827,23 @@ static inline void v4l_queue_buffer_init(const struct v4l_queue *q, struct v4l_b v4l_queue_buffer_update(q, buf, index); } -static inline int v4l_query_ext_ctrl(v4l_fd *f, struct v4l2_query_ext_ctrl *qec, +static inline int v4l_queue_buffer_queue_request(struct v4l_fd *f, struct v4l_queue *q, struct v4l_buffer *buf) +{ + unsigned b = v4l_buffer_g_index(buf); + int req_fd = q->request_fds[b]; + int ret; + + if (req_fd >= 0) { + v4l_buffer_s_request_fd(buf, req_fd); + v4l_buffer_or_flags(buf, V4L2_BUF_FLAG_REQUEST_FD); + } + ret = v4l_buffer_qbuf(f, buf); + if (!ret && req_fd >= 0) + ret = ioctl(req_fd, MEDIA_REQUEST_IOC_QUEUE, NULL); + return ret; +} + +static inline int v4l_query_ext_ctrl(struct v4l_fd *f, struct v4l2_query_ext_ctrl *qec, bool next_ctrl, bool next_compound) { struct v4l2_queryctrl qc; @@ -1839,7 +1915,7 @@ static inline int v4l_query_ext_ctrl(v4l_fd *f, struct v4l2_query_ext_ctrl *qec, return 0; } -static inline int v4l_g_ext_ctrls(v4l_fd *f, struct v4l2_ext_controls *ec) +static inline int v4l_g_ext_ctrls(struct v4l_fd *f, struct v4l2_ext_controls *ec) { unsigned i; @@ -1860,7 +1936,7 @@ static inline int v4l_g_ext_ctrls(v4l_fd *f, struct v4l2_ext_controls *ec) return 0; } -static inline int v4l_s_ext_ctrls(v4l_fd *f, struct v4l2_ext_controls *ec) +static inline int v4l_s_ext_ctrls(struct v4l_fd *f, struct v4l2_ext_controls *ec) { unsigned i; @@ -1880,7 +1956,7 @@ static inline int v4l_s_ext_ctrls(v4l_fd *f, struct v4l2_ext_controls *ec) return 0; } -static inline int v4l_try_ext_ctrls(v4l_fd *f, struct v4l2_ext_controls *ec) +static inline int v4l_try_ext_ctrls(struct v4l_fd *f, struct v4l2_ext_controls *ec) { unsigned i; @@ -1905,7 +1981,7 @@ static inline int v4l_try_ext_ctrls(v4l_fd *f, struct v4l2_ext_controls *ec) return 0; } -static inline int v4l_g_selection(v4l_fd *f, struct v4l2_selection *sel) +static inline int v4l_g_selection(struct v4l_fd *f, struct v4l2_selection *sel) { struct v4l2_cropcap cc; struct v4l2_crop crop; @@ -1951,7 +2027,7 @@ static inline int v4l_g_selection(v4l_fd *f, struct v4l2_selection *sel) } } -static inline int v4l_s_selection(v4l_fd *f, struct v4l2_selection *sel) +static inline int v4l_s_selection(struct v4l_fd *f, struct v4l2_selection *sel) { struct v4l2_crop crop; int ret; @@ -2003,7 +2079,7 @@ static inline void v4l_frame_selection(struct v4l2_selection *sel, bool to_frame } } -static inline int v4l_g_frame_selection(v4l_fd *f, struct v4l2_selection *sel, __u32 field) +static inline int v4l_g_frame_selection(struct v4l_fd *f, struct v4l2_selection *sel, __u32 field) { int ret = v4l_g_selection(f, sel); @@ -2012,7 +2088,7 @@ static inline int v4l_g_frame_selection(v4l_fd *f, struct v4l2_selection *sel, _ return ret; } -static inline int v4l_s_frame_selection(v4l_fd *f, struct v4l2_selection *sel, __u32 field) +static inline int v4l_s_frame_selection(struct v4l_fd *f, struct v4l2_selection *sel, __u32 field) { int ret; diff --git a/utils/common/v4l2-info.cpp b/utils/common/v4l2-info.cpp index 00a5fada..0e0966dc 100644 --- a/utils/common/v4l2-info.cpp +++ b/utils/common/v4l2-info.cpp @@ -209,6 +209,7 @@ static constexpr flag_def bufcap_def[] = { { V4L2_BUF_CAP_SUPPORTS_ORPHANED_BUFS, "orphaned-bufs" }, { V4L2_BUF_CAP_SUPPORTS_M2M_HOLD_CAPTURE_BUF, "m2m-hold-capture-buf" }, { V4L2_BUF_CAP_SUPPORTS_MMAP_CACHE_HINTS, "mmap-cache-hints" }, + { V4L2_BUF_CAP_SUPPORTS_RO_REQUESTS, "ro-requests" }, { 0, nullptr } }; diff --git a/utils/v4l2-compliance/v4l2-compliance.cpp b/utils/v4l2-compliance/v4l2-compliance.cpp index a450d487..c41873bc 100644 --- a/utils/v4l2-compliance/v4l2-compliance.cpp +++ b/utils/v4l2-compliance/v4l2-compliance.cpp @@ -1271,7 +1271,8 @@ void testNode(struct node &node, struct node &node_m2m_cap, struct node &expbuf_ node.has_enc_cap_frame_interval = false; node.valid_buftypes = 0; node.valid_memorytype = 0; - node.buf_caps = 0; + node.buf_caps[0] = 0; + node.buf_caps[1] = 0; for (auto &buftype_pixfmt : node.buftype_pixfmts) buftype_pixfmt.clear(); diff --git a/utils/v4l2-compliance/v4l2-compliance.h b/utils/v4l2-compliance/v4l2-compliance.h index af5efe62..5d8ebc00 100644 --- a/utils/v4l2-compliance/v4l2-compliance.h +++ b/utils/v4l2-compliance/v4l2-compliance.h @@ -143,7 +143,7 @@ struct base_node { int frame_interval_pad; int enum_frame_interval_pad; __u32 fbuf_caps; - __u32 buf_caps; + __u32 buf_caps[2]; // [0] = capture, [1] = output buffer caps const char *bus_info; pixfmt_map buftype_pixfmts[V4L2_BUF_TYPE_LAST + 1]; frmsizes_set frmsizes; diff --git a/utils/v4l2-compliance/v4l2-test-buffers.cpp b/utils/v4l2-compliance/v4l2-test-buffers.cpp index 02457636..19457635 100644 --- a/utils/v4l2-compliance/v4l2-test-buffers.cpp +++ b/utils/v4l2-compliance/v4l2-test-buffers.cpp @@ -573,7 +573,7 @@ int testReqBufs(struct node *node) fail_on_test(ret && ret != EINVAL); mmap_valid = !ret; if (mmap_valid) - node->buf_caps = caps = q.g_capabilities(); + node->buf_caps[V4L2_TYPE_IS_OUTPUT(i)] = caps = q.g_capabilities(); if (caps) { fail_on_test(mmap_valid ^ !!(caps & V4L2_BUF_CAP_SUPPORTS_MMAP)); if (caps & V4L2_BUF_CAP_SUPPORTS_ORPHANED_BUFS) @@ -1941,8 +1941,10 @@ int testDmaBuf(struct node *expbuf_node, struct node *node, struct node *node_m2 return 0; } -int testRequests(struct node *node, bool test_streaming) +static int testRequestsType(struct node *node, bool test_streaming, int type) { + const unsigned int req_buf_caps = V4L2_BUF_CAP_SUPPORTS_REQUESTS | + V4L2_BUF_CAP_SUPPORTS_RO_REQUESTS; filehandles fhs; int media_fd = fhs.add(mi_get_media_fd(node->g_fd(), node->bus_info)); int req_fd; @@ -1983,11 +1985,20 @@ int testRequests(struct node *node, bool test_streaming) .controls = &vivid_dyn_array_ctrl, }; bool have_controls; + bool is_output = V4L2_TYPE_IS_OUTPUT(type); + __u32 buf_caps = node->buf_caps[is_output]; + __u32 inv_buf_caps = node->is_m2m ? node->buf_caps[!is_output] : 0; + bool supports_requests = buf_caps & req_buf_caps; + bool supports_ro_requests = buf_caps & V4L2_BUF_CAP_SUPPORTS_RO_REQUESTS; + bool supports_inv_requests = inv_buf_caps & req_buf_caps; int ret; // If requests are supported, then there must be a media device - if (node->buf_caps & V4L2_BUF_CAP_SUPPORTS_REQUESTS) + if (supports_requests) fail_on_test(media_fd < 0); + fail_on_test((buf_caps & req_buf_caps) == req_buf_caps); + fail_on_test((inv_buf_caps & req_buf_caps) == req_buf_caps); + fail_on_test(buf_caps & inv_buf_caps & V4L2_BUF_CAP_SUPPORTS_REQUESTS); // Check if the driver has controls that can be used to test requests memset(&valid_qctrl, 0, sizeof(valid_qctrl)); @@ -2012,8 +2023,7 @@ int testRequests(struct node *node, bool test_streaming) if (ctrl.id == 0) { info("could not test the Request API, no suitable control found\n"); - return (node->buf_caps & V4L2_BUF_CAP_SUPPORTS_REQUESTS) ? - 0 : ENOTTY; + return supports_requests ? 0 : ENOTTY; } // Test if V4L2_CTRL_WHICH_REQUEST_VAL is supported @@ -2024,7 +2034,7 @@ int testRequests(struct node *node, bool test_streaming) if (media_fd < 0 || ret == EBADR) { // Should not happen if requests are supported - fail_on_test(node->buf_caps & V4L2_BUF_CAP_SUPPORTS_REQUESTS); + fail_on_test(supports_requests); return ENOTTY; } if (have_controls) { @@ -2036,7 +2046,7 @@ int testRequests(struct node *node, bool test_streaming) ret = doioctl_fd(media_fd, MEDIA_IOC_REQUEST_ALLOC, &req_fd); if (ret == ENOTTY) { // Should not happen if requests are supported - fail_on_test(node->buf_caps & V4L2_BUF_CAP_SUPPORTS_REQUESTS); + fail_on_test(supports_requests); return ENOTTY; } // Check that a request was allocated with a valid fd @@ -2104,13 +2114,6 @@ int testRequests(struct node *node, bool test_streaming) fhs.del(media_fd); node->reopen(); - int type = node->g_type(); - // For m2m devices g_type() will return the capture type, so - // we need to invert it to get the output type. - // At the moment only the output type of an m2m device can use - // requests. - if (node->is_m2m) - type = v4l_type_invert(type); if (v4l_type_is_vbi(type)) { cv4l_fmt fmt; @@ -2121,10 +2124,9 @@ int testRequests(struct node *node, bool test_streaming) if (!(node->valid_buftypes & (1 << type))) { // If the type is not supported, then check that requests // are also not supported. - fail_on_test(node->buf_caps & V4L2_BUF_CAP_SUPPORTS_REQUESTS); + fail_on_test(supports_requests); return ENOTTY; } - bool supports_requests = node->buf_caps & V4L2_BUF_CAP_SUPPORTS_REQUESTS; buffer_info.clear(); @@ -2147,8 +2149,8 @@ int testRequests(struct node *node, bool test_streaming) // Allocate the requests for (unsigned i = 0; i < num_requests; i++) { fail_on_test(doioctl_fd(media_fd, MEDIA_IOC_REQUEST_ALLOC, &buf_req_fds[i])); - fhs.add(buf_req_fds[i]); fail_on_test(buf_req_fds[i] < 0); + fhs.add(buf_req_fds[i]); // Check that empty requests can't be queued fail_on_test(!doioctl_fd(buf_req_fds[i], MEDIA_REQUEST_IOC_QUEUE, nullptr)); } @@ -2189,12 +2191,14 @@ int testRequests(struct node *node, bool test_streaming) buffer buf(m2m_q); fail_on_test(buf.querybuf(node, 0)); - buf.s_flags(V4L2_BUF_FLAG_REQUEST_FD); - buf.s_request_fd(buf_req_fds[0]); - // Only the output queue can support requests, - // so if the capture queue also supports requests, - // then something is wrong. - fail_on_test(!buf.qbuf(node)); + if (!supports_inv_requests) { + buf.s_flags(V4L2_BUF_FLAG_REQUEST_FD); + buf.s_request_fd(buf_req_fds[0]); + // Only the output queue can support requests, + // so if the capture queue also supports requests, + // then something is wrong. + fail_on_test(!buf.qbuf(node)); + } fail_on_test(node->streamoff(m2m_q.g_type())); fail_on_test(m2m_q.reqbufs(node, 0)); @@ -2245,7 +2249,10 @@ int testRequests(struct node *node, bool test_streaming) // Can only fail if requests are not supported fail_on_test(supports_requests); // and should fail with EBADR in that case - fail_on_test(err != EBADR); + if (!is_output && (node->codec_mask & (STATEFUL_DECODER | STATEFUL_ENCODER))) + fail_on_test(err != EBADR && err != EPERM); + else + fail_on_test(err != EBADR); } if (err) { // Requests are not supported, so clean up and return @@ -2370,15 +2377,22 @@ int testRequests(struct node *node, bool test_streaming) test_streaming = false; break; } - fail_on_test(ret); - fail_on_test(buf.querybuf(node, i)); - // Check that the buffer is now queued up - fail_on_test(buf.g_flags() & V4L2_BUF_FLAG_IN_REQUEST); - fail_on_test(!(buf.g_flags() & V4L2_BUF_FLAG_REQUEST_FD)); - fail_on_test(!(buf.g_flags() & V4L2_BUF_FLAG_QUEUED)); - // Re-initing or requeuing the request is no longer possible - fail_on_test(doioctl_fd(buf_req_fds[i], MEDIA_REQUEST_IOC_REINIT, nullptr) != EBUSY); - fail_on_test(doioctl_fd(buf_req_fds[i], MEDIA_REQUEST_IOC_QUEUE, nullptr) != EBUSY); + if (supports_ro_requests) { + if (i % 3 < 2) + fail_on_test(ret != EINVAL); + else + fail_on_test(ret); + } else { + fail_on_test(ret); + fail_on_test(buf.querybuf(node, i)); + // Check that the buffer is now queued up + fail_on_test(buf.g_flags() & V4L2_BUF_FLAG_IN_REQUEST); + fail_on_test(!(buf.g_flags() & V4L2_BUF_FLAG_REQUEST_FD)); + fail_on_test(!(buf.g_flags() & V4L2_BUF_FLAG_QUEUED)); + // Re-initing or requeuing the request is no longer possible + fail_on_test(doioctl_fd(buf_req_fds[i], MEDIA_REQUEST_IOC_REINIT, 0) != EBUSY); + fail_on_test(doioctl_fd(buf_req_fds[i], MEDIA_REQUEST_IOC_QUEUE, 0) != EBUSY); + } if (i >= min_bufs) { // Close some of the request fds to check that this // is safe to do @@ -2528,6 +2542,24 @@ int testRequests(struct node *node, bool test_streaming) return 0; } +int testRequests(struct node *node, bool test_streaming) +{ + int type = node->g_type(); + int ret, inv_ret = ENOTTY; + + if (node->is_m2m) + type = v4l_type_invert(type); + ret = testRequestsType(node, test_streaming, type); + fail_on_test(ret && ret != ENOTTY); + if (node->is_m2m) { + node->reopen(); + inv_ret = testRequestsType(node, false, node->g_type()); + fail_on_test(inv_ret && inv_ret != ENOTTY); + if (!inv_ret) + ret = 0; + } + return ret; +} /* * This class wraps a pthread in such a way that it simplifies passing diff --git a/utils/v4l2-ctl/v4l2-ctl-common.cpp b/utils/v4l2-ctl/v4l2-ctl-common.cpp index 9b378211..26dd26ca 100644 --- a/utils/v4l2-ctl/v4l2-ctl-common.cpp +++ b/utils/v4l2-ctl/v4l2-ctl-common.cpp @@ -840,6 +840,44 @@ static void find_controls(cv4l_fd &_fd) } } +void common_log_ctrls(cv4l_fd &fd, int req_fd, bool read_only, + const char *prefix) +{ + std::vector ctrl_vec; + std::vector qec_vec; + v4l2_ext_controls ctrls = {}; + v4l2_query_ext_ctrl qec = { 0 }; + cv4l_disable_trace dt(fd); + + while (!fd.query_ext_ctrl(qec, true, true)) { + if (read_only && !(qec.flags & V4L2_CTRL_FLAG_READ_ONLY)) + continue; + if (qec.type == V4L2_CTRL_TYPE_CTRL_CLASS || + qec.type == V4L2_CTRL_TYPE_BUTTON) + continue; + v4l2_ext_control ctrl = { qec.id }; + if (qec.flags & V4L2_CTRL_FLAG_HAS_PAYLOAD) { + ctrl.size = qec.elems * qec.elem_size; + ctrl.ptr = malloc(ctrl.size); + } + ctrl_vec.push_back(ctrl); + qec_vec.push_back(qec); + } + if (ctrl_vec.empty()) + return; + ctrls.count = ctrl_vec.size(); + ctrls.controls = &ctrl_vec[0]; + ctrls.which = V4L2_CTRL_WHICH_REQUEST_VAL; + ctrls.request_fd = req_fd; + ioctl(fd.g_fd(), VIDIOC_G_EXT_CTRLS, &ctrls); + printf("\n"); + for (unsigned i = 0; i < ctrls.count; i++) { + printf("%s%s: ", prefix, qec_vec[i].name); + print_value(fd.g_fd(), qec_vec[i], ctrl_vec[i], true, false); + } + printf("\n"); +} + int common_find_ctrl_id(const char *name) { if (ctrl_str2q.find(name) == ctrl_str2q.end()) @@ -1128,7 +1166,7 @@ void common_set(cv4l_fd &_fd) break; default: fprintf(stderr, "%s: unsupported payload type\n", - qc.name); + qc.name); break; } } else { diff --git a/utils/v4l2-ctl/v4l2-ctl-streaming.cpp b/utils/v4l2-ctl/v4l2-ctl-streaming.cpp index 62424e4c..d5997c45 100644 --- a/utils/v4l2-ctl/v4l2-ctl-streaming.cpp +++ b/utils/v4l2-ctl/v4l2-ctl-streaming.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -20,6 +21,10 @@ static __u32 memory = V4L2_MEMORY_MMAP; static __u32 out_memory = V4L2_MEMORY_MMAP; static int stream_sleep = -1; static bool stream_no_query; +static bool stream_req_ctrls; +static bool stream_req_ro_ctrls; +static bool stream_out_req_ctrls; +static bool stream_out_req_ro_ctrls; static unsigned stream_pat; static bool stream_loop; static bool stream_out_square; @@ -73,7 +78,6 @@ static __u64 last_fwht_bf_ts; static fwht_cframe_hdr last_fwht_hdr; struct request_fwht { - int fd; __u64 ts; struct v4l2_ctrl_fwht_params params; }; @@ -268,6 +272,8 @@ void streaming_usage() #endif " --stream-poll use non-blocking mode and select() to stream.\n" " --stream-buf-caps show capture buffer capabilities\n" + " --stream-req-ctrls show all controls in a capture request\n" + " --stream-req-ro-ctrls show all read-only controls in a capture request\n" "  --stream-show-delta-now\n" "                     output the difference between the buffer timestamp and current\n" "                     clock, if the buffer timestamp source is the monotonic clock.\n" @@ -321,6 +327,8 @@ void streaming_usage() " percentage of the frame to actually fill. The default is 100%%.\n" " --stream-out-buf-caps\n" " show output buffer capabilities\n" + " --stream-out-req-ctrls show all controls in an output request\n" + " --stream-out-req-ro-ctrls show all read-only controls in an output request\n" " --stream-out-mmap \n" " output video using mmap() [VIDIOC_(D)QBUF]\n" " count: the number of buffers to allocate. The default is 4.\n" @@ -663,6 +671,18 @@ void streaming_cmd(int ch, char *optarg) case OptStreamLoop: stream_loop = true; break; + case OptStreamReqCtrls: + stream_req_ctrls = true; + break; + case OptStreamReqROCtrls: + stream_req_ro_ctrls = true; + break; + case OptStreamOutReqCtrls: + stream_out_req_ctrls = true; + break; + case OptStreamOutReqROCtrls: + stream_out_req_ro_ctrls = true; + break; case OptStreamOutPattern: stream_pat = strtoul(optarg, nullptr, 0); for (i = 0; tpg_pattern_strings[i]; i++) ; @@ -834,20 +854,6 @@ static void set_fwht_stateless_params(struct v4l2_ctrl_fwht_params &fwht_params, fwht_params.flags |= V4L2_FWHT_FL_I_FRAME; } -static int alloc_fwht_req(int media_fd, unsigned index) -{ - int rc = 0; - - rc = ioctl(media_fd, MEDIA_IOC_REQUEST_ALLOC, &fwht_reqs[index]); - if (rc < 0) { - fprintf(stderr, "Unable to allocate media request: %s\n", - strerror(errno)); - return rc; - } - - return 0; -} - static void set_fwht_req_by_idx(unsigned idx, const struct fwht_cframe_hdr *hdr, __u64 last_bf_ts, __u64 ts) { @@ -868,23 +874,6 @@ static int get_fwht_req_by_ts(__u64 ts) return -1; } -static bool set_fwht_req_by_fd(const struct fwht_cframe_hdr *hdr, - int req_fd, __u64 last_bf_ts, __u64 ts) -{ - struct v4l2_ctrl_fwht_params fwht_params; - - set_fwht_stateless_params(fwht_params, hdr, last_bf_ts); - - for (auto &fwht_req : fwht_reqs) { - if (fwht_req.fd == req_fd) { - fwht_req.ts = ts; - fwht_req.params = fwht_params; - return true; - } - } - return false; -} - static int set_fwht_ext_ctrl(cv4l_fd &fd, const struct fwht_cframe_hdr *hdr, __u64 last_bf_ts, int req_fd) { @@ -1223,6 +1212,21 @@ static int do_setup_out_buffers(cv4l_fd &fd, cv4l_queue &q, FILE *fin, bool qbuf stream_out_refresh = true; } + if (((q.g_capabilities() & V4L2_BUF_CAP_SUPPORTS_RO_REQUESTS) && + (stream_out_req_ctrls || stream_out_req_ro_ctrls)) || + fmt.g_pixelformat() == V4L2_PIX_FMT_FWHT_STATELESS) { + int media_fd = mi_get_media_fd(fd.g_fd()); + + if (media_fd < 0) { + fprintf(stderr, "%s: mi_get_media_fd failed\n", __func__); + return QUEUE_ERROR; + } + int ret = q.create_requests(media_fd); + close(media_fd); + if (ret) + return QUEUE_ERROR; + } + for (unsigned i = 0; i < q.g_buffers(); i++) { cv4l_buffer buf(q); @@ -1254,20 +1258,8 @@ static int do_setup_out_buffers(cv4l_fd &fd, cv4l_queue &q, FILE *fin, bool qbuf return QUEUE_STOPPED; if (fmt.g_pixelformat() == V4L2_PIX_FMT_FWHT_STATELESS) { - int media_fd = mi_get_media_fd(fd.g_fd()); - - if (media_fd < 0) { - fprintf(stderr, "%s: mi_get_media_fd failed\n", __func__); - return media_fd; - } - - if (alloc_fwht_req(media_fd, i)) - return QUEUE_ERROR; - buf.s_request_fd(fwht_reqs[i].fd); - buf.or_flags(V4L2_BUF_FLAG_REQUEST_FD); - if (set_fwht_ext_ctrl(fd, &last_fwht_hdr, last_fwht_bf_ts, - buf.g_request_fd())) { + q.g_request_fd(i))) { fprintf(stderr, "%s: set_fwht_ext_ctrl failed on %dth buf: %s\n", __func__, i, strerror(errno)); return QUEUE_ERROR; @@ -1277,7 +1269,7 @@ static int do_setup_out_buffers(cv4l_fd &fd, cv4l_queue &q, FILE *fin, bool qbuf fps_timestamps fps_ts; set_time_stamp(buf); - if (fd.qbuf(buf)) + if (q.buffer_queue_request(&fd, buf)) return QUEUE_ERROR; tpg_update_mv_count(&tpg, V4L2_FIELD_HAS_T_OR_B(field)); if (!verbose) @@ -1291,11 +1283,6 @@ static int do_setup_out_buffers(cv4l_fd &fd, cv4l_queue &q, FILE *fin, bool qbuf set_fwht_req_by_idx(i, &last_fwht_hdr, last_fwht_bf_ts, buf.g_timestamp_ns()); last_fwht_bf_ts = buf.g_timestamp_ns(); - if (ioctl(buf.g_request_fd(), MEDIA_REQUEST_IOC_QUEUE) < 0) { - fprintf(stderr, "Unable to queue media request: %s\n", - strerror(errno)); - return QUEUE_ERROR; - } } } if (qbuf) @@ -1377,6 +1364,43 @@ static void write_buffer_to_file(cv4l_fd &fd, cv4l_queue &q, cv4l_buffer &buf, #endif } +static int wait_for_request(cv4l_fd &fd, cv4l_queue &q, unsigned index) +{ + if (!q.has_requests()) + return 0; + + int req_fd = q.g_request_fd(index); + fd_set exception_fds; + struct timeval tv = { 2, 0 }; + int rc; + + FD_ZERO(&exception_fds); + FD_SET(req_fd, &exception_fds); + rc = select(req_fd + 1, NULL, NULL, &exception_fds, &tv); + + if (rc == 0) { + fprintf(stderr, "Timeout when waiting for media request\n"); + return QUEUE_ERROR; + } + if (rc < 0) { + fprintf(stderr, "Unable to select media request: %s\n", + strerror(errno)); + return QUEUE_ERROR; + } + + bool show_ctrls = V4L2_TYPE_IS_CAPTURE(q.g_type()) ? + stream_req_ctrls || stream_req_ro_ctrls : + stream_out_req_ctrls || stream_out_req_ro_ctrls; + bool show_ro_ctrls = V4L2_TYPE_IS_CAPTURE(q.g_type()) ? stream_req_ro_ctrls : + stream_out_req_ro_ctrls; + char prefix[16]; + + sprintf(prefix, "%s[%u]: ", V4L2_TYPE_IS_CAPTURE(q.g_type()) ? "cap" : "out", index); + if (show_ctrls) + common_log_ctrls(fd, req_fd, show_ro_ctrls, prefix); + return 0; +} + static int do_handle_cap(cv4l_fd &fd, cv4l_queue &q, FILE *fout, int *index, unsigned &count, fps_timestamps &fps_ts, cv4l_fmt &fmt, bool ignore_count_skip) @@ -1395,6 +1419,8 @@ static int do_handle_cap(cv4l_fd &fd, cv4l_queue &q, FILE *fout, int *index, fprintf(stderr, "%s: failed: %s\n", "VIDIOC_DQBUF", strerror(errno)); return QUEUE_ERROR; } + if (q.reinit_request(buf.g_index())) + return QUEUE_ERROR; if (buf.g_flags() & V4L2_BUF_FLAG_LAST) { last_buffer = true; break; @@ -1403,7 +1429,7 @@ static int do_handle_cap(cv4l_fd &fd, cv4l_queue &q, FILE *fout, int *index, break; if (verbose) print_concise_buffer(stderr, buf, fmt, q, fps_ts, -1); - if (fd.qbuf(buf)) + if (q.buffer_queue_request(&fd, buf)) return QUEUE_ERROR; } @@ -1435,7 +1461,7 @@ static int do_handle_cap(cv4l_fd &fd, cv4l_queue &q, FILE *fout, int *index, * has the size that fits the old resolution and might not * fit to the new one. */ - if (fd.qbuf(buf) && errno != EINVAL) { + if (q.buffer_queue_request(&fd, buf) && errno != EINVAL) { fprintf(stderr, "%s: qbuf error\n", __func__); return QUEUE_ERROR; } @@ -1555,13 +1581,10 @@ static int do_handle_out(cv4l_fd &fd, cv4l_queue &q, FILE *fin, cv4l_buffer *cap if (is_meta) meta_fillbuffer(buf, fmt, q); - if (fmt.g_pixelformat() == V4L2_PIX_FMT_FWHT_STATELESS) { - if (ioctl(buf.g_request_fd(), MEDIA_REQUEST_IOC_REINIT, NULL)) { - fprintf(stderr, "Unable to reinit media request: %s\n", - strerror(errno)); - return QUEUE_ERROR; - } + if (q.reinit_request(buf.g_index())) + return QUEUE_ERROR; + if (fmt.g_pixelformat() == V4L2_PIX_FMT_FWHT_STATELESS) { if (set_fwht_ext_ctrl(fd, &last_fwht_hdr, last_fwht_bf_ts, buf.g_request_fd())) { fprintf(stderr, "%s: set_fwht_ext_ctrl failed: %s\n", @@ -1572,24 +1595,15 @@ static int do_handle_out(cv4l_fd &fd, cv4l_queue &q, FILE *fin, cv4l_buffer *cap set_time_stamp(buf); - if (fd.qbuf(buf)) { - fprintf(stderr, "%s: failed: %s\n", "VIDIOC_QBUF", strerror(errno)); - return QUEUE_ERROR; - } if (fmt.g_pixelformat() == V4L2_PIX_FMT_FWHT_STATELESS) { - if (!set_fwht_req_by_fd(&last_fwht_hdr, buf.g_request_fd(), last_fwht_bf_ts, - buf.g_timestamp_ns())) { - fprintf(stderr, "%s: request for fd %d does not exist\n", - __func__, buf.g_request_fd()); - return QUEUE_ERROR; - } - + set_fwht_req_by_idx(buf.g_index(), &last_fwht_hdr, + last_fwht_bf_ts, buf.g_timestamp_ns()); last_fwht_bf_ts = buf.g_timestamp_ns(); - if (ioctl(buf.g_request_fd(), MEDIA_REQUEST_IOC_QUEUE) < 0) { - fprintf(stderr, "Unable to queue media request: %s\n", - strerror(errno)); - return QUEUE_ERROR; - } + } + + if (q.buffer_queue_request(&fd, buf)) { + fprintf(stderr, "%s: failed: %s\n", "VIDIOC_QBUF", strerror(errno)); + return QUEUE_ERROR; } tpg_update_mv_count(&tpg, V4L2_FIELD_HAS_T_OR_B(output_field)); @@ -1770,6 +1784,8 @@ static void streaming_set_cap(cv4l_fd &fd, cv4l_fd &exp_fd) if (use_poll) subscribe_event(fd, V4L2_EVENT_SOURCE_CHANGE); + int media_fd = mi_get_media_fd(fd.g_fd()); + recover: eos = false; source_change = false; @@ -1816,6 +1832,11 @@ recover: if (q.obtain_bufs(&fd)) goto done; + if ((q.g_capabilities() & V4L2_BUF_CAP_SUPPORTS_RO_REQUESTS) && + (stream_req_ctrls || stream_req_ro_ctrls) && + q.create_requests(media_fd)) + goto done; + if (q.queue_all(&fd)) goto done; @@ -1899,8 +1920,10 @@ recover: goto recover; done: - if (options[OptStreamDmaBuf]) - exp_q.close_exported_fds(); + q.free(&fd); + exp_q.free(&exp_fd); + close(media_fd); + if (fout && fout != stdout) { if (host_fd_to >= 0) write_u32(fout, V4L_STREAM_PACKET_END); @@ -2157,12 +2180,11 @@ static void streaming_set_out(cv4l_fd &fd, cv4l_fd &exp_fd) fcntl(fd.g_fd(), F_SETFL, fd_flags); fprintf(stderr, "\n"); - q.free(&fd); tpg_free(&tpg); done: - if (options[OptStreamOutDmaBuf]) - exp_q.close_exported_fds(); + q.free(&fd); + exp_q.free(&fd); if (fin && fin != stdin) fclose(fin); } @@ -2178,6 +2200,7 @@ static int capture_setup(cv4l_fd &fd, cv4l_queue &in, cv4l_fd *exp_fd, cv4l_fmt fprintf(stderr, "%s: fd.streamoff error\n", __func__); return -1; } + in.free_requests(); /* release any buffer allocated */ if (in.reqbufs(&fd)) { @@ -2209,10 +2232,25 @@ static int capture_setup(cv4l_fd &fd, cv4l_queue &in, cv4l_fd *exp_fd, cv4l_fmt if (exp_fd && in.export_bufs(exp_fd, exp_fd->g_type())) return -1; - if (in.obtain_bufs(&fd) || in.queue_all(&fd)) { + if ((in.g_capabilities() & V4L2_BUF_CAP_SUPPORTS_RO_REQUESTS) && + (stream_req_ctrls || stream_req_ro_ctrls)) { + int media_fd = mi_get_media_fd(fd.g_fd()); + + if (media_fd < 0) + return -1; + int ret = in.create_requests(media_fd); + close(media_fd); + if (ret) + return -1; + } + if (in.obtain_bufs(&fd)) { fprintf(stderr, "%s: in.obtain_bufs error\n", __func__); return -1; } + if (in.queue_all(&fd)) { + fprintf(stderr, "%s: in.queue_all error\n", __func__); + return -1; + } if (fd.streamon(in.g_type())) { fprintf(stderr, "%s: fd.streamon error\n", __func__); @@ -2429,6 +2467,7 @@ static void stateless_m2m(cv4l_fd &fd, cv4l_queue &in, cv4l_queue &out, unsigned count[2] = { 0, 0 }; int fd_flags = fcntl(fd.g_fd(), F_GETFL); bool stopped = false; + int media_fd = mi_get_media_fd(fd.g_fd()); if (out.reqbufs(&fd, reqbufs_count_out)) { fprintf(stderr, "%s: out.reqbufs failed\n", __func__); @@ -2448,6 +2487,11 @@ static void stateless_m2m(cv4l_fd &fd, cv4l_queue &in, cv4l_queue &out, return; } + if ((in.g_capabilities() & V4L2_BUF_CAP_SUPPORTS_RO_REQUESTS) && + (stream_req_ctrls || stream_req_ro_ctrls) && + in.create_requests(media_fd)) + return; + if (do_setup_out_buffers(fd, out, fout, true, true) == QUEUE_ERROR) { fprintf(stderr, "%s: do_setup_out_buffers failed\n", __func__); return; @@ -2468,45 +2512,38 @@ static void stateless_m2m(cv4l_fd &fd, cv4l_queue &in, cv4l_queue &out, return; } int index = 0; - bool queue_lst_buf = false; + bool queue_last_buf = false; cv4l_buffer last_in_buf; fcntl(fd.g_fd(), F_SETFL, fd_flags | O_NONBLOCK); while (true) { - fd_set except_fds; - int req_fd = fwht_reqs[index].fd; - struct timeval tv = { 2, 0 }; + int buf_idx = -1; + int rc = 0; - if (req_fd < 0) + if (out.g_request_fd(index) < 0) break; - FD_ZERO(&except_fds); - FD_SET(req_fd, &except_fds); - - int rc = select(req_fd + 1, nullptr, nullptr, &except_fds, &tv); - - if (rc == 0) { - fprintf(stderr, "Timeout when waiting for media request\n"); - return; - } - if (rc < 0) { - fprintf(stderr, "Unable to select media request: %s\n", - strerror(errno)); + rc = wait_for_request(fd, out, index); + if (rc) return; - } + /* * it is safe to queue back last cap buffer only after * the following request is done so that the buffer * is not needed anymore as a reference frame */ - if (queue_lst_buf) { - if (fd.qbuf(last_in_buf)) { + if (queue_last_buf) { + if (in.buffer_queue_request(&fd, last_in_buf)) { fprintf(stderr, "%s: qbuf failed\n", __func__); return; } } - int buf_idx = -1; + + rc = wait_for_request(fd, in, index); + if (rc) + return; + /* * fin is not sent to do_handle_cap since the capture buf is * written to the file in current function @@ -2517,6 +2554,7 @@ static void stateless_m2m(cv4l_fd &fd, cv4l_queue &in, cv4l_queue &out, fprintf(stderr, "%s: do_handle_cap err\n", __func__); return; } + /* * in case of an error in the frame, set last ts to 0 as a * means to recover so that next request will not use a @@ -2531,7 +2569,7 @@ static void stateless_m2m(cv4l_fd &fd, cv4l_queue &in, cv4l_queue &out, if (fd.querybuf(cap_buf)) return; last_in_buf = cap_buf; - queue_lst_buf = true; + queue_last_buf = true; if (fin && cap_buf.g_bytesused(0) && !(cap_buf.g_flags() & V4L2_BUF_FLAG_ERROR)) { int idx = get_fwht_req_by_ts(cap_buf.g_timestamp_ns()); @@ -2557,8 +2595,7 @@ static void stateless_m2m(cv4l_fd &fd, cv4l_queue &in, cv4l_queue &out, stopped = true; if (rc != QUEUE_STOPPED) fprintf(stderr, "%s: output stream ended\n", __func__); - close(req_fd); - fwht_reqs[index].fd = -1; + out.free_request(index); } } index = (index + 1) % out.g_buffers(); @@ -2620,8 +2657,9 @@ static void streaming_set_m2m(cv4l_fd &fd, cv4l_fd &exp_fd) stateful_m2m(fd, in, out, file[CAP], file[OUT], fmt[CAP], fmt[OUT], exp_fd_p); done: - if (options[OptStreamDmaBuf] || options[OptStreamOutDmaBuf]) - exp_q.close_exported_fds(); + in.free(&fd); + out.free(&fd); + exp_q.free(&exp_fd); if (file[CAP] && file[CAP] != stdout) fclose(file[CAP]); diff --git a/utils/v4l2-ctl/v4l2-ctl.cpp b/utils/v4l2-ctl/v4l2-ctl.cpp index 95b8a2e7..30842b65 100644 --- a/utils/v4l2-ctl/v4l2-ctl.cpp +++ b/utils/v4l2-ctl/v4l2-ctl.cpp @@ -243,6 +243,8 @@ static struct option long_options[] = { {"stream-to-host", required_argument, nullptr, OptStreamToHost}, #endif {"stream-buf-caps", no_argument, nullptr, OptStreamBufCaps}, + {"stream-req-ctrls", no_argument, 0, OptStreamReqCtrls}, + {"stream-req-ro-ctrls", no_argument, 0, OptStreamReqROCtrls}, {"stream-show-delta-now", no_argument, nullptr, OptStreamShowDeltaNow}, {"stream-mmap", optional_argument, nullptr, OptStreamMmap}, {"stream-user", optional_argument, nullptr, OptStreamUser}, @@ -264,6 +266,8 @@ static struct option long_options[] = { {"stream-out-vert-speed", required_argument, nullptr, OptStreamOutVertSpeed}, {"stream-out-perc-fill", required_argument, nullptr, OptStreamOutPercFill}, {"stream-out-buf-caps", no_argument, nullptr, OptStreamOutBufCaps}, + {"stream-out-req-ctrls", no_argument, 0, OptStreamOutReqCtrls}, + {"stream-out-req-ro-ctrls", no_argument, 0, OptStreamOutReqROCtrls}, {"stream-out-mmap", optional_argument, nullptr, OptStreamOutMmap}, {"stream-out-user", optional_argument, nullptr, OptStreamOutUser}, {"stream-out-dmabuf", no_argument, nullptr, OptStreamOutDmaBuf}, diff --git a/utils/v4l2-ctl/v4l2-ctl.h b/utils/v4l2-ctl/v4l2-ctl.h index 24eee3d7..79bece6c 100644 --- a/utils/v4l2-ctl/v4l2-ctl.h +++ b/utils/v4l2-ctl/v4l2-ctl.h @@ -224,6 +224,8 @@ enum Option { OptStreamLossless, OptStreamShowDeltaNow, OptStreamBufCaps, + OptStreamReqCtrls, + OptStreamReqROCtrls, OptStreamMmap, OptStreamUser, OptStreamDmaBuf, @@ -244,6 +246,8 @@ enum Option { OptStreamOutPixelAspect, OptStreamOutVideoAspect, OptStreamOutBufCaps, + OptStreamOutReqCtrls, + OptStreamOutReqROCtrls, OptStreamOutMmap, OptStreamOutUser, OptStreamOutDmaBuf, @@ -341,6 +345,8 @@ void common_list(cv4l_fd &fd); void common_process_controls(cv4l_fd &fd); void common_control_event(const struct v4l2_event *ev); int common_find_ctrl_id(const char *name); +void common_log_ctrls(cv4l_fd &fd, int req_fd, bool read_only, + const char *prefix); // v4l2-ctl-tuner.cpp void tuner_usage(void);