From patchwork Wed Apr 26 08:29:17 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yunke Cao X-Patchwork-Id: 677648 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id DC36DC7618E for ; Wed, 26 Apr 2023 08:30:44 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S240114AbjDZIao (ORCPT ); Wed, 26 Apr 2023 04:30:44 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:49826 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230008AbjDZIam (ORCPT ); Wed, 26 Apr 2023 04:30:42 -0400 Received: from mail-yb1-xb49.google.com (mail-yb1-xb49.google.com [IPv6:2607:f8b0:4864:20::b49]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id DC4B33C2D for ; Wed, 26 Apr 2023 01:30:40 -0700 (PDT) Received: by mail-yb1-xb49.google.com with SMTP id 3f1490d57ef6-b99f3aee8e0so9748949276.0 for ; Wed, 26 Apr 2023 01:30:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1682497840; x=1685089840; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=nF4Yffb6o0bjGCl6ueVjEDUbI81vEW/Q29gvaIMznlY=; b=cF3n8N0FgFCpJCm+rsQCbvEyAXZlJssOJnRcFnfE850nEF8bV5k9Y9U3sb/0Te6CrK eb9schRbn4u8dXWU+2d3dDG7A7p8hONhrFUt9Xy2ktqYLbgaxGpZM2pgKrQ6toaeveET CgDnnDQC2ECz32nZlpbO73Dxy2SSx0OUuBEx1Wh5q26FwEZFxqUOy5ZYe20HuqNwRXJR SKfRuN2g6KiwIIraNRyw7YFtXciw3ZS0091x7c9bZyEVWSNgBylQn7m5f6urYCTxaOcC JFc94Vfu/65v+cX+UBHyGMoKkuaC4K8N2jjnRz3Y6siXasmUnr202sdocjApiz5J5zoW CfYw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1682497840; x=1685089840; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=nF4Yffb6o0bjGCl6ueVjEDUbI81vEW/Q29gvaIMznlY=; b=J2Kj7ldGAf1LVvkgCIWvBuZLd5uGWs5QpidGUVH+/kSRYCOFKZ3Qywe0Qe7PkRxhIi XbNW+5+tjyUXKCkLVwbVJvkVZEQ2P7fLTdub81rUuofKzkTm96WbJcfe3hihhSS59j/M +gy4X1DoPKIG6h2lPkDLmf7UfqpWxbz/i+hPqb4SGsp8DUd4OviVsYNwpRYBehAGQFvB AEJ34reL9DKqZC2J1f0jpFEjlCgvBEcEaijx2bfYrJxAcc8VdraTmaluG2jLDbT44jTE 4IPtNeRba+ejiA8T9aRMPTRs/+A5G35L5qlpNECWZNM+9K/sQ7kuhiTs/04uN97rKrcQ SARA== X-Gm-Message-State: AC+VfDwndMOkv+HuuMTSsfVfNGDYvglXfwYIVBk6GDUDgg5Wvlxnpjgh YXHgq4afIIz5GhC2OpJk0RarC81C2Kg= X-Google-Smtp-Source: ACHHUZ6O1xAqLJap9LlKxm+XUSGFQIAqngqLxqp9oOu/ZfKd8BU4e2euh+36VLnk4BgFV2IZovVBEROhx7o= X-Received: from yunkec1.tok.corp.google.com ([2401:fa00:8f:203:1f4e:598b:ed:99bd]) (user=yunkec job=sendgmr) by 2002:a05:690c:986:b0:54c:15ad:11e4 with SMTP id ce6-20020a05690c098600b0054c15ad11e4mr1101885ywb.0.1682497840209; Wed, 26 Apr 2023 01:30:40 -0700 (PDT) Date: Wed, 26 Apr 2023 17:29:17 +0900 In-Reply-To: <20230426082923.132909-1-yunkec@google.com> Mime-Version: 1.0 References: <20230426082923.132909-1-yunkec@google.com> X-Mailer: git-send-email 2.40.1.495.gc816e09b53d-goog Message-ID: <20230426082923.132909-6-yunkec@google.com> Subject: [PATCH v11 05/11] media: uvcvideo: Add support for compound controls From: Yunke Cao To: Hans Verkuil , Laurent Pinchart , Daniel Scally Cc: Tomasz Figa , Sergey Senozhatsky , Ricardo Ribalda , linux-media@vger.kernel.org, Yunke Cao Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org Supports getting/setting current value. Supports getting default value. Handles V4L2_CTRL_FLAG_NEXT_COMPOUND. Reviewed-by: Ricardo Ribalda Signed-off-by: Yunke Cao --- Changelog since v10: - Rewrite some logic in __uvc_find_control(). - Remove a duplicate check in __uvc_ctrl_get_compound_cur(). - Thanks, Daniel! Changelog since v9: - Make __uvc_ctrl_set_compound() static. Changelog since v8: - No change. Changelog since v7: - Fixed comments styles, indentation and a few other style issues. - Renamed uvc_g/set_array() to uvc_g/set_compound(). - Moved size check to __uvc_ctrl_add_mapping(). - After setting a new value, copy it back to user. - In __uvc_ctrl_set_compound(), check size before allocating. drivers/media/usb/uvc/uvc_ctrl.c | 180 +++++++++++++++++++++++++++---- drivers/media/usb/uvc/uvcvideo.h | 4 + 2 files changed, 165 insertions(+), 19 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c index 4984438d85ca..8f0401230649 100644 --- a/drivers/media/usb/uvc/uvc_ctrl.c +++ b/drivers/media/usb/uvc/uvc_ctrl.c @@ -884,6 +884,28 @@ static void uvc_set_le_value(struct uvc_control_mapping *mapping, } } +/* + * Extract the byte array specified by mapping->offset and mapping->data_size + * stored at 'data' to the output array 'data_out'. + */ +static int uvc_get_compound(struct uvc_control_mapping *mapping, const u8 *data, + u8 *data_out) +{ + memcpy(data_out, data + mapping->offset / 8, mapping->data_size / 8); + return 0; +} + +/* + * Copy the byte array 'data_in' to the destination specified by mapping->offset + * and mapping->data_size stored at 'data'. + */ +static int uvc_set_compound(struct uvc_control_mapping *mapping, + const u8 *data_in, u8 *data) +{ + memcpy(data + mapping->offset / 8, data_in, mapping->data_size / 8); + return 0; +} + static bool uvc_ctrl_mapping_is_compound(const struct uvc_control_mapping *mapping) { @@ -906,7 +928,7 @@ static int uvc_entity_match_guid(const struct uvc_entity *entity, static void __uvc_find_control(struct uvc_entity *entity, u32 v4l2_id, struct uvc_control_mapping **mapping, struct uvc_control **control, - int next) + int next, int next_compound) { struct uvc_control *ctrl; struct uvc_control_mapping *map; @@ -921,14 +943,16 @@ static void __uvc_find_control(struct uvc_entity *entity, u32 v4l2_id, continue; list_for_each_entry(map, &ctrl->info.mappings, list) { - if ((map->id == v4l2_id) && !next) { + if (map->id == v4l2_id && !next && !next_compound) { *control = ctrl; *mapping = map; return; } if ((*mapping == NULL || (*mapping)->id > map->id) && - (map->id > v4l2_id) && next) { + (map->id > v4l2_id) && + (uvc_ctrl_mapping_is_compound(map) ? + next_compound : next)) { *control = ctrl; *mapping = map; } @@ -942,6 +966,7 @@ static struct uvc_control *uvc_find_control(struct uvc_video_chain *chain, struct uvc_control *ctrl = NULL; struct uvc_entity *entity; int next = v4l2_id & V4L2_CTRL_FLAG_NEXT_CTRL; + int next_compound = v4l2_id & V4L2_CTRL_FLAG_NEXT_COMPOUND; *mapping = NULL; @@ -950,12 +975,13 @@ static struct uvc_control *uvc_find_control(struct uvc_video_chain *chain, /* Find the control. */ list_for_each_entry(entity, &chain->entities, chain) { - __uvc_find_control(entity, v4l2_id, mapping, &ctrl, next); - if (ctrl && !next) + __uvc_find_control(entity, v4l2_id, mapping, &ctrl, next, + next_compound); + if (ctrl && !next && !next_compound) return ctrl; } - if (ctrl == NULL && !next) + if (!ctrl && !next && !next_compound) uvc_dbg(chain->dev, CONTROL, "Control 0x%08x not found\n", v4l2_id); @@ -1101,12 +1127,59 @@ static int __uvc_ctrl_get_std(struct uvc_video_chain *chain, return 0; } +static int __uvc_ctrl_get_compound(struct uvc_control_mapping *mapping, + struct uvc_control *ctrl, + int id, + struct v4l2_ext_control *xctrl) +{ + u8 size; + u8 *data; + int ret; + + size = mapping->v4l2_size / 8; + if (xctrl->size < size) { + xctrl->size = size; + return -ENOSPC; + } + + data = kmalloc(size, GFP_KERNEL); + if (!data) + return -ENOMEM; + + ret = mapping->get_compound(mapping, uvc_ctrl_data(ctrl, id), data); + if (ret < 0) + goto out; + + ret = copy_to_user(xctrl->ptr, data, size) ? -EFAULT : 0; + +out: + kfree(data); + return ret; +} + +static int __uvc_ctrl_get_compound_cur(struct uvc_video_chain *chain, + struct uvc_control *ctrl, + struct uvc_control_mapping *mapping, + struct v4l2_ext_control *xctrl) +{ + int ret; + + ret = __uvc_ctrl_load_cur(chain, ctrl); + if (ret < 0) + return ret; + + return __uvc_ctrl_get_compound(mapping, ctrl, UVC_CTRL_DATA_CURRENT, + xctrl); +} + static int __uvc_query_v4l2_class(struct uvc_video_chain *chain, u32 req_id, u32 found_id) { - bool find_next = req_id & V4L2_CTRL_FLAG_NEXT_CTRL; unsigned int i; + bool find_next = req_id & + (V4L2_CTRL_FLAG_NEXT_CTRL | V4L2_CTRL_FLAG_NEXT_COMPOUND); + req_id &= V4L2_CTRL_ID_MASK; for (i = 0; i < ARRAY_SIZE(uvc_control_classes); i++) { @@ -1194,7 +1267,7 @@ int uvc_ctrl_is_accessible(struct uvc_video_chain *chain, u32 v4l2_id, } __uvc_find_control(ctrl->entity, mapping->master_id, &master_map, - &master_ctrl, 0); + &master_ctrl, 0, 0); if (!master_ctrl || !(master_ctrl->info.flags & UVC_CTRL_FLAG_GET_CUR)) return 0; @@ -1262,7 +1335,7 @@ static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain, if (mapping->master_id) __uvc_find_control(ctrl->entity, mapping->master_id, - &master_map, &master_ctrl, 0); + &master_map, &master_ctrl, 0, 0); if (master_ctrl && (master_ctrl->info.flags & UVC_CTRL_FLAG_GET_CUR)) { s32 val = 0; int ret; @@ -1278,6 +1351,15 @@ static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain, v4l2_ctrl->flags |= V4L2_CTRL_FLAG_INACTIVE; } + if (v4l2_ctrl->type >= V4L2_CTRL_COMPOUND_TYPES) { + v4l2_ctrl->flags |= V4L2_CTRL_FLAG_HAS_PAYLOAD; + v4l2_ctrl->default_value = 0; + v4l2_ctrl->minimum = 0; + v4l2_ctrl->maximum = 0; + v4l2_ctrl->step = 0; + return 0; + } + if (!ctrl->cached) { int ret = uvc_ctrl_populate_cache(chain, ctrl); if (ret < 0) @@ -1530,7 +1612,7 @@ static void uvc_ctrl_send_slave_event(struct uvc_video_chain *chain, u32 changes = V4L2_EVENT_CTRL_CH_FLAGS; s32 val = 0; - __uvc_find_control(master->entity, slave_id, &mapping, &ctrl, 0); + __uvc_find_control(master->entity, slave_id, &mapping, &ctrl, 0, 0); if (ctrl == NULL) return; @@ -1840,7 +1922,7 @@ static int uvc_ctrl_find_ctrl_idx(struct uvc_entity *entity, for (i = 0; i < ctrls->count; i++) { __uvc_find_control(entity, ctrls->controls[i].id, &mapping, - &ctrl_found, 0); + &ctrl_found, 0, 0); if (uvc_control == ctrl_found) return i; } @@ -1888,7 +1970,7 @@ int uvc_ctrl_get(struct uvc_video_chain *chain, return -EINVAL; if (uvc_ctrl_mapping_is_compound(mapping)) - return -EINVAL; + return __uvc_ctrl_get_compound_cur(chain, ctrl, mapping, xctrl); else return __uvc_ctrl_get_std(chain, ctrl, mapping, &xctrl->value); } @@ -1909,6 +1991,22 @@ static int __uvc_ctrl_get_boundary_std(struct uvc_video_chain *chain, return 0; } +static int __uvc_ctrl_get_boundary_compound(struct uvc_video_chain *chain, + struct uvc_control *ctrl, + struct uvc_control_mapping *mapping, + struct v4l2_ext_control *xctrl) +{ + int ret; + + if (!ctrl->cached) { + ret = uvc_ctrl_populate_cache(chain, ctrl); + if (ret < 0) + return ret; + } + + return __uvc_ctrl_get_compound(mapping, ctrl, UVC_CTRL_DATA_DEF, xctrl); +} + int uvc_ctrl_get_boundary(struct uvc_video_chain *chain, struct v4l2_ext_control *xctrl) { @@ -1926,7 +2024,8 @@ int uvc_ctrl_get_boundary(struct uvc_video_chain *chain, } if (uvc_ctrl_mapping_is_compound(mapping)) - ret = -EINVAL; + ret = __uvc_ctrl_get_boundary_compound(chain, ctrl, mapping, + xctrl); else ret = __uvc_ctrl_get_boundary_std(chain, ctrl, mapping, xctrl); @@ -1935,6 +2034,34 @@ int uvc_ctrl_get_boundary(struct uvc_video_chain *chain, return ret; } +static int __uvc_ctrl_set_compound(struct uvc_control_mapping *mapping, + struct v4l2_ext_control *xctrl, + struct uvc_control *ctrl) +{ + u8 *data; + int ret; + + if (xctrl->size != mapping->v4l2_size / 8) + return -EINVAL; + + data = kmalloc(xctrl->size, GFP_KERNEL); + if (!data) + return -ENOMEM; + + ret = copy_from_user(data, xctrl->ptr, xctrl->size); + if (ret < 0) + goto out; + + ret = mapping->set_compound(mapping, data, + uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT)); + + __uvc_ctrl_get_compound(mapping, ctrl, UVC_CTRL_DATA_CURRENT, xctrl); + +out: + kfree(data); + return ret; +} + int uvc_ctrl_set(struct uvc_fh *handle, struct v4l2_ext_control *xctrl) { @@ -2049,12 +2176,14 @@ int uvc_ctrl_set(struct uvc_fh *handle, ctrl->info.size); } - if (!uvc_ctrl_mapping_is_compound(mapping)) + if (!uvc_ctrl_mapping_is_compound(mapping)) { mapping->set(mapping, value, uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT)); - else - return -EINVAL; - + } else { + ret = __uvc_ctrl_set_compound(mapping, xctrl, ctrl); + if (ret < 0) + return ret; + } if (ctrl->info.flags & UVC_CTRL_FLAG_ASYNCHRONOUS) ctrl->handle = handle; @@ -2465,10 +2594,23 @@ static int __uvc_ctrl_add_mapping(struct uvc_video_chain *chain, goto err_nomem; } - if (map->get == NULL) + if (uvc_ctrl_mapping_is_compound(map)) { + if (map->data_size != map->v4l2_size) + return -EINVAL; + + /* Only supports byte-aligned data. */ + if (WARN_ON(map->offset % 8 || map->data_size % 8)) + return -EINVAL; + } + + if (!map->get && !uvc_ctrl_mapping_is_compound(map)) map->get = uvc_get_le_value; - if (map->set == NULL) + if (!map->set && !uvc_ctrl_mapping_is_compound(map)) map->set = uvc_set_le_value; + if (!map->get_compound && uvc_ctrl_mapping_is_compound(map)) + map->get_compound = uvc_get_compound; + if (!map->set_compound && uvc_ctrl_mapping_is_compound(map)) + map->set_compound = uvc_set_compound; for (i = 0; i < ARRAY_SIZE(uvc_control_classes); i++) { if (V4L2_CTRL_ID2WHICH(uvc_control_classes[i]) == diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h index 1c6c47ab94f0..8d36a3f8c52e 100644 --- a/drivers/media/usb/uvc/uvcvideo.h +++ b/drivers/media/usb/uvc/uvcvideo.h @@ -129,8 +129,12 @@ struct uvc_control_mapping { s32 (*get)(struct uvc_control_mapping *mapping, u8 query, const u8 *data); + int (*get_compound)(struct uvc_control_mapping *mapping, const u8 *data, + u8 *data_out); void (*set)(struct uvc_control_mapping *mapping, s32 value, u8 *data); + int (*set_compound)(struct uvc_control_mapping *mapping, const u8 *data_in, + u8 *data); }; struct uvc_control {