[{"id":25800,"web_url":"https://patchwork.libcamera.org/comment/25800/","msgid":"<CAHW6GYLYp6+AhKU4+9n3jDpJ9dUMcc5Ut9jJv91kSKAhusi0SQ@mail.gmail.com>","date":"2022-11-15T13:44:38","subject":"Re: [libcamera-devel] [PATCH v6 1/8] pipeline: raspberrypi: Fork\n\tDelayedControls","submitter":{"id":42,"url":"https://patchwork.libcamera.org/api/people/42/","name":"David Plowman","email":"david.plowman@raspberrypi.com"},"content":"Hi Naush\n\nThanks for the update.\n\nOn Tue, 15 Nov 2022 at 09:08, Naushir Patuck via libcamera-devel\n<libcamera-devel@lists.libcamera.org> wrote:\n>\n> Fork the libcamera::DelayedControls implementation in the RPi:: namespace, with\n> the intention of updating the API for Raspberry Pi specific features.\n>\n> Signed-off-by: Naushir Patuck <naush@raspberrypi.com>\n\nReviewed-by: David Plowman <david.plowman@raspberrypi.com>\n\nI assume it's actually all a cut'n'paste job in any case!\n\nThanks\nDavid\n\n> ---\n>  .../pipeline/raspberrypi/delayed_controls.cpp | 291 ++++++++++++++++++\n>  .../pipeline/raspberrypi/delayed_controls.h   |  84 +++++\n>  .../pipeline/raspberrypi/meson.build          |   1 +\n>  3 files changed, 376 insertions(+)\n>  create mode 100644 src/libcamera/pipeline/raspberrypi/delayed_controls.cpp\n>  create mode 100644 src/libcamera/pipeline/raspberrypi/delayed_controls.h\n>\n> diff --git a/src/libcamera/pipeline/raspberrypi/delayed_controls.cpp b/src/libcamera/pipeline/raspberrypi/delayed_controls.cpp\n> new file mode 100644\n> index 000000000000..867e3866cc46\n> --- /dev/null\n> +++ b/src/libcamera/pipeline/raspberrypi/delayed_controls.cpp\n> @@ -0,0 +1,291 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2020, Raspberry Pi Ltd\n> + *\n> + * delayed_controls.cpp - Helper to deal with controls that take effect with a delay\n> + *\n> + * Note: This has been forked from the libcamera core implementation.\n> + */\n> +\n> +#include \"delayed_controls.h\"\n> +\n> +#include <libcamera/base/log.h>\n> +\n> +#include <libcamera/controls.h>\n> +\n> +#include \"libcamera/internal/v4l2_device.h\"\n> +\n> +/**\n> + * \\file delayed_controls.h\n> + * \\brief Helper to deal with controls that take effect with a delay\n> + */\n> +\n> +namespace libcamera {\n> +\n> +LOG_DEFINE_CATEGORY(RPiDelayedControls)\n> +\n> +namespace RPi {\n> +\n> +/**\n> + * \\class DelayedControls\n> + * \\brief Helper to deal with controls that take effect with a delay\n> + *\n> + * Some sensor controls take effect with a delay as the sensor needs time to\n> + * adjust, for example exposure and analog gain. This is a helper class to deal\n> + * with such controls and the intended users are pipeline handlers.\n> + *\n> + * The idea is to extend the concept of the buffer depth of a pipeline the\n> + * application needs to maintain to also cover controls. Just as with buffer\n> + * depth if the application keeps the number of requests queued above the\n> + * control depth the controls are guaranteed to take effect for the correct\n> + * request. The control depth is determined by the control with the greatest\n> + * delay.\n> + */\n> +\n> +/**\n> + * \\struct DelayedControls::ControlParams\n> + * \\brief Parameters associated with controls handled by the \\a DelayedControls\n> + * helper class\n> + *\n> + * \\var ControlParams::delay\n> + * \\brief Frame delay from setting the control on a sensor device to when it is\n> + * consumed during framing.\n> + *\n> + * \\var ControlParams::priorityWrite\n> + * \\brief Flag to indicate that this control must be applied ahead of, and\n> + * separately from the other controls.\n> + *\n> + * Typically set for the \\a V4L2_CID_VBLANK control so that the device driver\n> + * does not reject \\a V4L2_CID_EXPOSURE control values that may be outside of\n> + * the existing vertical blanking specified bounds, but are within the new\n> + * blanking bounds.\n> + */\n> +\n> +/**\n> + * \\brief Construct a DelayedControls instance\n> + * \\param[in] device The V4L2 device the controls have to be applied to\n> + * \\param[in] controlParams Map of the numerical V4L2 control ids to their\n> + * associated control parameters.\n> + *\n> + * The control parameters comprise of delays (in frames) and a priority write\n> + * flag. If this flag is set, the relevant control is written separately from,\n> + * and ahead of the rest of the batched controls.\n> + *\n> + * Only controls specified in \\a controlParams are handled. If it's desired to\n> + * mix delayed controls and controls that take effect immediately the immediate\n> + * controls must be listed in the \\a controlParams map with a delay value of 0.\n> + */\n> +DelayedControls::DelayedControls(V4L2Device *device,\n> +                                const std::unordered_map<uint32_t, ControlParams> &controlParams)\n> +       : device_(device), maxDelay_(0)\n> +{\n> +       const ControlInfoMap &controls = device_->controls();\n> +\n> +       /*\n> +        * Create a map of control ids to delays for controls exposed by the\n> +        * device.\n> +        */\n> +       for (auto const &param : controlParams) {\n> +               auto it = controls.find(param.first);\n> +               if (it == controls.end()) {\n> +                       LOG(RPiDelayedControls, Error)\n> +                               << \"Delay request for control id \"\n> +                               << utils::hex(param.first)\n> +                               << \" but control is not exposed by device \"\n> +                               << device_->deviceNode();\n> +                       continue;\n> +               }\n> +\n> +               const ControlId *id = it->first;\n> +\n> +               controlParams_[id] = param.second;\n> +\n> +               LOG(RPiDelayedControls, Debug)\n> +                       << \"Set a delay of \" << controlParams_[id].delay\n> +                       << \" and priority write flag \" << controlParams_[id].priorityWrite\n> +                       << \" for \" << id->name();\n> +\n> +               maxDelay_ = std::max(maxDelay_, controlParams_[id].delay);\n> +       }\n> +\n> +       reset();\n> +}\n> +\n> +/**\n> + * \\brief Reset state machine\n> + *\n> + * Resets the state machine to a starting position based on control values\n> + * retrieved from the device.\n> + */\n> +void DelayedControls::reset()\n> +{\n> +       queueCount_ = 1;\n> +       writeCount_ = 0;\n> +\n> +       /* Retrieve control as reported by the device. */\n> +       std::vector<uint32_t> ids;\n> +       for (auto const &param : controlParams_)\n> +               ids.push_back(param.first->id());\n> +\n> +       ControlList controls = device_->getControls(ids);\n> +\n> +       /* Seed the control queue with the controls reported by the device. */\n> +       values_.clear();\n> +       for (const auto &ctrl : controls) {\n> +               const ControlId *id = device_->controls().idmap().at(ctrl.first);\n> +               /*\n> +                * Do not mark this control value as updated, it does not need\n> +                * to be written to to device on startup.\n> +                */\n> +               values_[id][0] = Info(ctrl.second, false);\n> +       }\n> +}\n> +\n> +/**\n> + * \\brief Push a set of controls on the queue\n> + * \\param[in] controls List of controls to add to the device queue\n> + *\n> + * Push a set of controls to the control queue. This increases the control queue\n> + * depth by one.\n> + *\n> + * \\returns true if \\a controls are accepted, or false otherwise\n> + */\n> +bool DelayedControls::push(const ControlList &controls)\n> +{\n> +       /* Copy state from previous frame. */\n> +       for (auto &ctrl : values_) {\n> +               Info &info = ctrl.second[queueCount_];\n> +               info = values_[ctrl.first][queueCount_ - 1];\n> +               info.updated = false;\n> +       }\n> +\n> +       /* Update with new controls. */\n> +       const ControlIdMap &idmap = device_->controls().idmap();\n> +       for (const auto &control : controls) {\n> +               const auto &it = idmap.find(control.first);\n> +               if (it == idmap.end()) {\n> +                       LOG(RPiDelayedControls, Warning)\n> +                               << \"Unknown control \" << control.first;\n> +                       return false;\n> +               }\n> +\n> +               const ControlId *id = it->second;\n> +\n> +               if (controlParams_.find(id) == controlParams_.end())\n> +                       return false;\n> +\n> +               Info &info = values_[id][queueCount_];\n> +\n> +               info = Info(control.second);\n> +\n> +               LOG(RPiDelayedControls, Debug)\n> +                       << \"Queuing \" << id->name()\n> +                       << \" to \" << info.toString()\n> +                       << \" at index \" << queueCount_;\n> +       }\n> +\n> +       queueCount_++;\n> +\n> +       return true;\n> +}\n> +\n> +/**\n> + * \\brief Read back controls in effect at a sequence number\n> + * \\param[in] sequence The sequence number to get controls for\n> + *\n> + * Read back what controls where in effect at a specific sequence number. The\n> + * history is a ring buffer of 16 entries where new and old values coexist. It's\n> + * the callers responsibility to not read too old sequence numbers that have been\n> + * pushed out of the history.\n> + *\n> + * Historic values are evicted by pushing new values onto the queue using\n> + * push(). The max history from the current sequence number that yields valid\n> + * values are thus 16 minus number of controls pushed.\n> + *\n> + * \\return The controls at \\a sequence number\n> + */\n> +ControlList DelayedControls::get(uint32_t sequence)\n> +{\n> +       unsigned int index = std::max<int>(0, sequence - maxDelay_);\n> +\n> +       ControlList out(device_->controls());\n> +       for (const auto &ctrl : values_) {\n> +               const ControlId *id = ctrl.first;\n> +               const Info &info = ctrl.second[index];\n> +\n> +               out.set(id->id(), info);\n> +\n> +               LOG(RPiDelayedControls, Debug)\n> +                       << \"Reading \" << id->name()\n> +                       << \" to \" << info.toString()\n> +                       << \" at index \" << index;\n> +       }\n> +\n> +       return out;\n> +}\n> +\n> +/**\n> + * \\brief Inform DelayedControls of the start of a new frame\n> + * \\param[in] sequence Sequence number of the frame that started\n> + *\n> + * Inform the state machine that a new frame has started and of its sequence\n> + * number. Any user of these helpers is responsible to inform the helper about\n> + * the start of any frame. This can be connected with ease to the start of a\n> + * exposure (SOE) V4L2 event.\n> + */\n> +void DelayedControls::applyControls(uint32_t sequence)\n> +{\n> +       LOG(RPiDelayedControls, Debug) << \"frame \" << sequence << \" started\";\n> +\n> +       /*\n> +        * Create control list peeking ahead in the value queue to ensure\n> +        * values are set in time to satisfy the sensor delay.\n> +        */\n> +       ControlList out(device_->controls());\n> +       for (auto &ctrl : values_) {\n> +               const ControlId *id = ctrl.first;\n> +               unsigned int delayDiff = maxDelay_ - controlParams_[id].delay;\n> +               unsigned int index = std::max<int>(0, writeCount_ - delayDiff);\n> +               Info &info = ctrl.second[index];\n> +\n> +               if (info.updated) {\n> +                       if (controlParams_[id].priorityWrite) {\n> +                               /*\n> +                                * This control must be written now, it could\n> +                                * affect validity of the other controls.\n> +                                */\n> +                               ControlList priority(device_->controls());\n> +                               priority.set(id->id(), info);\n> +                               device_->setControls(&priority);\n> +                       } else {\n> +                               /*\n> +                                * Batch up the list of controls and write them\n> +                                * at the end of the function.\n> +                                */\n> +                               out.set(id->id(), info);\n> +                       }\n> +\n> +                       LOG(RPiDelayedControls, Debug)\n> +                               << \"Setting \" << id->name()\n> +                               << \" to \" << info.toString()\n> +                               << \" at index \" << index;\n> +\n> +                       /* Done with this update, so mark as completed. */\n> +                       info.updated = false;\n> +               }\n> +       }\n> +\n> +       writeCount_ = sequence + 1;\n> +\n> +       while (writeCount_ > queueCount_) {\n> +               LOG(RPiDelayedControls, Debug)\n> +                       << \"Queue is empty, auto queue no-op.\";\n> +               push({});\n> +       }\n> +\n> +       device_->setControls(&out);\n> +}\n> +\n> +} /* namespace RPi */\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/libcamera/pipeline/raspberrypi/delayed_controls.h b/src/libcamera/pipeline/raspberrypi/delayed_controls.h\n> new file mode 100644\n> index 000000000000..f7f246482968\n> --- /dev/null\n> +++ b/src/libcamera/pipeline/raspberrypi/delayed_controls.h\n> @@ -0,0 +1,84 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2020, Raspberry Pi Ltd\n> + *\n> + * delayed_controls.h - Helper to deal with controls that take effect with a delay\n> + *\n> + * Note: This has been forked from the libcamera core implementation.\n> + */\n> +\n> +#pragma once\n> +\n> +#include <stdint.h>\n> +#include <unordered_map>\n> +\n> +#include <libcamera/controls.h>\n> +\n> +namespace libcamera {\n> +\n> +class V4L2Device;\n> +\n> +namespace RPi {\n> +\n> +class DelayedControls\n> +{\n> +public:\n> +       struct ControlParams {\n> +               unsigned int delay;\n> +               bool priorityWrite;\n> +       };\n> +\n> +       DelayedControls(V4L2Device *device,\n> +                       const std::unordered_map<uint32_t, ControlParams> &controlParams);\n> +\n> +       void reset();\n> +\n> +       bool push(const ControlList &controls);\n> +       ControlList get(uint32_t sequence);\n> +\n> +       void applyControls(uint32_t sequence);\n> +\n> +private:\n> +       class Info : public ControlValue\n> +       {\n> +       public:\n> +               Info()\n> +                       : updated(false)\n> +               {\n> +               }\n> +\n> +               Info(const ControlValue &v, bool updated_ = true)\n> +                       : ControlValue(v), updated(updated_)\n> +               {\n> +               }\n> +\n> +               bool updated;\n> +       };\n> +\n> +       static constexpr int listSize = 16;\n> +       class ControlRingBuffer : public std::array<Info, listSize>\n> +       {\n> +       public:\n> +               Info &operator[](unsigned int index)\n> +               {\n> +                       return std::array<Info, listSize>::operator[](index % listSize);\n> +               }\n> +\n> +               const Info &operator[](unsigned int index) const\n> +               {\n> +                       return std::array<Info, listSize>::operator[](index % listSize);\n> +               }\n> +       };\n> +\n> +       V4L2Device *device_;\n> +       std::unordered_map<const ControlId *, ControlParams> controlParams_;\n> +       unsigned int maxDelay_;\n> +\n> +       uint32_t queueCount_;\n> +       uint32_t writeCount_;\n> +       std::unordered_map<const ControlId *, ControlRingBuffer> values_;\n> +};\n> +\n> +} /* namespace RPi */\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/libcamera/pipeline/raspberrypi/meson.build b/src/libcamera/pipeline/raspberrypi/meson.build\n> index f1a2f5ee72c2..6064a3f00122 100644\n> --- a/src/libcamera/pipeline/raspberrypi/meson.build\n> +++ b/src/libcamera/pipeline/raspberrypi/meson.build\n> @@ -1,6 +1,7 @@\n>  # SPDX-License-Identifier: CC0-1.0\n>\n>  libcamera_sources += files([\n> +    'delayed_controls.cpp',\n>      'dma_heaps.cpp',\n>      'raspberrypi.cpp',\n>      'rpi_stream.cpp',\n> --\n> 2.25.1\n>","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 3F657BE08B\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 15 Nov 2022 13:44:53 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 9D79D63086;\n\tTue, 15 Nov 2022 14:44:52 +0100 (CET)","from mail-pf1-x42e.google.com (mail-pf1-x42e.google.com\n\t[IPv6:2607:f8b0:4864:20::42e])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id E5EF463079\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 15 Nov 2022 14:44:50 +0100 (CET)","by mail-pf1-x42e.google.com with SMTP id v28so14127869pfi.12\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 15 Nov 2022 05:44:50 -0800 (PST)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1668519892;\n\tbh=QsQsB5GmTHalbo0B8f2OJ+LjBpQvHWNp394NssChgfM=;\n\th=References:In-Reply-To:Date:To:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc:\n\tFrom;\n\tb=IEUjYm4GM584a4m6UhFWV3scGw/xTAmA+mx633AYx1WnuVpjIeT4gAtyhjmdjWjBA\n\t1btp+cmdOJcJI8wN7c0wd6y36z1eYTW5gaj+Eoc1J1/nZVoxNzd5RyT7jz5TamH7ch\n\tkhiQvX1tCWYf2ioZFtN9tugLQ0RbCqKbtxXaAG+JpmftdJZLH7R6XUi+luxleDhN0h\n\tngKLbstNVqmDGwBp3AvHHqfS0xkO2bSJa/KE1uAb5HbtQLK2XQYDsK2ZfBev+/J8pw\n\t5WaljPjSxz5oyKJWmXaxZGSWJMQ3qStg747Am3n1tTqqYd68iMQYoDIMVB3/R1uxGg\n\tgdA1IdoVWDAvg==","v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google;\n\th=cc:to:subject:message-id:date:from:in-reply-to:references\n\t:mime-version:from:to:cc:subject:date:message-id:reply-to;\n\tbh=2BAXmDrYMeSVmGvdRYy+xISKVnoqyrX9WU6Np2vN5AU=;\n\tb=ZDdnu9/7nRVNNsSZ8ZG9RL7ADKfBvBL/fqpvilKD8/dAeS5zK4OHPlXmcHi/sltuak\n\tChkS+0V/RpHaVClejqU8R5MWWwmO5UtMMzHev2CYkHAWUm9TMtBj6Ks350ckt8+j+tt9\n\tWu/a7Wmwus9SU8Mt5o7mX+4GcFpuZa8iqDreuw3H/bGOGL6+52T/b78BJfbqGi8jy8Ko\n\tETrwjaCRCQBcvzZBzZUxQQbRCwTgg9fkRLSro+ZsGrs7zxcbAZFQNCH94Zxd1fK8kZss\n\tKLtK/hgbTh1JnxKyLwkOZkdycLiu1NsbARVtwPdBo8B0Vovw51yZ0ndIiKiWbutilGrD\n\tZ0Jw=="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key; \n\tunprotected) header.d=raspberrypi.com\n\theader.i=@raspberrypi.com\n\theader.b=\"ZDdnu9/7\"; dkim-atps=neutral","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20210112;\n\th=cc:to:subject:message-id:date:from:in-reply-to:references\n\t:mime-version:x-gm-message-state:from:to:cc:subject:date:message-id\n\t:reply-to;\n\tbh=2BAXmDrYMeSVmGvdRYy+xISKVnoqyrX9WU6Np2vN5AU=;\n\tb=XrGmjt7jFob2pc3JKYOXhYEewcim68rKEyz1lOKmlGgxBPNIrByfSeTto2cfhMpFA3\n\tA6+NojL7Hzuur4FHBv9SqUxvXCyd9lzIYI22SNd5Ol0whDYCdBKkCOsHjLq4ot0Yvm2i\n\tjY4+oCS76g7jBW7FbUdrhTMJp6Xzm+f64v8b3ODpJa9N5EZQ2C7AaqV/uwQMCM5Wgbe2\n\tBBet/wI3sScoz0kZ3SFJDEIE+tMeIjYW0E+8fFvx/N0goLl7a84xZhuHjUR/LDIshO9m\n\tBZd1S/4l/4n8bnm43b9olH8AcrXLWK76bmTVTG/seZQ/Y+8zntFBgY5/o1stirUiigb8\n\tQSTQ==","X-Gm-Message-State":"ANoB5pnstB+1dtESwCMtab+os34IYk6Ceu08RT/oLX7kcW98OF2+y6Ao\n\tWixUeatnT8Da4sdOVhe315tCSyEqksBt8D9LM58pVUDrZ0Y=","X-Google-Smtp-Source":"AA0mqf7fIkMnk/BDClk64jkcXMxDlGzTPS6gDw0XqOjodENRT3wSKMPZN7wkRrK/JsBnAXUwELAM2CTQsQwoWg2zIV0=","X-Received":"by 2002:a63:ee07:0:b0:476:c4e1:958b with SMTP id\n\te7-20020a63ee07000000b00476c4e1958bmr1632392pgi.256.1668519889246;\n\tTue, 15 Nov 2022 05:44:49 -0800 (PST)","MIME-Version":"1.0","References":"<20221115090755.2921-1-naush@raspberrypi.com>\n\t<20221115090755.2921-2-naush@raspberrypi.com>","In-Reply-To":"<20221115090755.2921-2-naush@raspberrypi.com>","Date":"Tue, 15 Nov 2022 13:44:38 +0000","Message-ID":"<CAHW6GYLYp6+AhKU4+9n3jDpJ9dUMcc5Ut9jJv91kSKAhusi0SQ@mail.gmail.com>","To":"Naushir Patuck <naush@raspberrypi.com>","Content-Type":"text/plain; charset=\"UTF-8\"","Subject":"Re: [libcamera-devel] [PATCH v6 1/8] pipeline: raspberrypi: Fork\n\tDelayedControls","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","From":"David Plowman via libcamera-devel <libcamera-devel@lists.libcamera.org>","Reply-To":"David Plowman <david.plowman@raspberrypi.com>","Cc":"libcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":25916,"web_url":"https://patchwork.libcamera.org/comment/25916/","msgid":"<166963883221.3066484.11409778448703587124@Monstersaurus>","date":"2022-11-28T12:33:52","subject":"Re: [libcamera-devel] [PATCH v6 1/8] pipeline: raspberrypi: Fork\n\tDelayedControls","submitter":{"id":4,"url":"https://patchwork.libcamera.org/api/people/4/","name":"Kieran Bingham","email":"kieran.bingham@ideasonboard.com"},"content":"Quoting Naushir Patuck via libcamera-devel (2022-11-15 09:07:48)\n> Fork the libcamera::DelayedControls implementation in the RPi:: namespace, with\n> the intention of updating the API for Raspberry Pi specific features.\n\n:-( - but it's all internal, and lets get your required feature supported.\n\nReviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n\n> Signed-off-by: Naushir Patuck <naush@raspberrypi.com>\n> ---\n>  .../pipeline/raspberrypi/delayed_controls.cpp | 291 ++++++++++++++++++\n>  .../pipeline/raspberrypi/delayed_controls.h   |  84 +++++\n>  .../pipeline/raspberrypi/meson.build          |   1 +\n>  3 files changed, 376 insertions(+)\n>  create mode 100644 src/libcamera/pipeline/raspberrypi/delayed_controls.cpp\n>  create mode 100644 src/libcamera/pipeline/raspberrypi/delayed_controls.h\n> \n> diff --git a/src/libcamera/pipeline/raspberrypi/delayed_controls.cpp b/src/libcamera/pipeline/raspberrypi/delayed_controls.cpp\n> new file mode 100644\n> index 000000000000..867e3866cc46\n> --- /dev/null\n> +++ b/src/libcamera/pipeline/raspberrypi/delayed_controls.cpp\n> @@ -0,0 +1,291 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2020, Raspberry Pi Ltd\n> + *\n> + * delayed_controls.cpp - Helper to deal with controls that take effect with a delay\n> + *\n> + * Note: This has been forked from the libcamera core implementation.\n> + */\n> +\n> +#include \"delayed_controls.h\"\n> +\n> +#include <libcamera/base/log.h>\n> +\n> +#include <libcamera/controls.h>\n> +\n> +#include \"libcamera/internal/v4l2_device.h\"\n> +\n> +/**\n> + * \\file delayed_controls.h\n> + * \\brief Helper to deal with controls that take effect with a delay\n> + */\n> +\n> +namespace libcamera {\n> +\n> +LOG_DEFINE_CATEGORY(RPiDelayedControls)\n> +\n> +namespace RPi {\n> +\n> +/**\n> + * \\class DelayedControls\n> + * \\brief Helper to deal with controls that take effect with a delay\n> + *\n> + * Some sensor controls take effect with a delay as the sensor needs time to\n> + * adjust, for example exposure and analog gain. This is a helper class to deal\n> + * with such controls and the intended users are pipeline handlers.\n> + *\n> + * The idea is to extend the concept of the buffer depth of a pipeline the\n> + * application needs to maintain to also cover controls. Just as with buffer\n> + * depth if the application keeps the number of requests queued above the\n> + * control depth the controls are guaranteed to take effect for the correct\n> + * request. The control depth is determined by the control with the greatest\n> + * delay.\n> + */\n> +\n> +/**\n> + * \\struct DelayedControls::ControlParams\n> + * \\brief Parameters associated with controls handled by the \\a DelayedControls\n> + * helper class\n> + *\n> + * \\var ControlParams::delay\n> + * \\brief Frame delay from setting the control on a sensor device to when it is\n> + * consumed during framing.\n> + *\n> + * \\var ControlParams::priorityWrite\n> + * \\brief Flag to indicate that this control must be applied ahead of, and\n> + * separately from the other controls.\n> + *\n> + * Typically set for the \\a V4L2_CID_VBLANK control so that the device driver\n> + * does not reject \\a V4L2_CID_EXPOSURE control values that may be outside of\n> + * the existing vertical blanking specified bounds, but are within the new\n> + * blanking bounds.\n> + */\n> +\n> +/**\n> + * \\brief Construct a DelayedControls instance\n> + * \\param[in] device The V4L2 device the controls have to be applied to\n> + * \\param[in] controlParams Map of the numerical V4L2 control ids to their\n> + * associated control parameters.\n> + *\n> + * The control parameters comprise of delays (in frames) and a priority write\n> + * flag. If this flag is set, the relevant control is written separately from,\n> + * and ahead of the rest of the batched controls.\n> + *\n> + * Only controls specified in \\a controlParams are handled. If it's desired to\n> + * mix delayed controls and controls that take effect immediately the immediate\n> + * controls must be listed in the \\a controlParams map with a delay value of 0.\n> + */\n> +DelayedControls::DelayedControls(V4L2Device *device,\n> +                                const std::unordered_map<uint32_t, ControlParams> &controlParams)\n> +       : device_(device), maxDelay_(0)\n> +{\n> +       const ControlInfoMap &controls = device_->controls();\n> +\n> +       /*\n> +        * Create a map of control ids to delays for controls exposed by the\n> +        * device.\n> +        */\n> +       for (auto const &param : controlParams) {\n> +               auto it = controls.find(param.first);\n> +               if (it == controls.end()) {\n> +                       LOG(RPiDelayedControls, Error)\n> +                               << \"Delay request for control id \"\n> +                               << utils::hex(param.first)\n> +                               << \" but control is not exposed by device \"\n> +                               << device_->deviceNode();\n> +                       continue;\n> +               }\n> +\n> +               const ControlId *id = it->first;\n> +\n> +               controlParams_[id] = param.second;\n> +\n> +               LOG(RPiDelayedControls, Debug)\n> +                       << \"Set a delay of \" << controlParams_[id].delay\n> +                       << \" and priority write flag \" << controlParams_[id].priorityWrite\n> +                       << \" for \" << id->name();\n> +\n> +               maxDelay_ = std::max(maxDelay_, controlParams_[id].delay);\n> +       }\n> +\n> +       reset();\n> +}\n> +\n> +/**\n> + * \\brief Reset state machine\n> + *\n> + * Resets the state machine to a starting position based on control values\n> + * retrieved from the device.\n> + */\n> +void DelayedControls::reset()\n> +{\n> +       queueCount_ = 1;\n> +       writeCount_ = 0;\n> +\n> +       /* Retrieve control as reported by the device. */\n> +       std::vector<uint32_t> ids;\n> +       for (auto const &param : controlParams_)\n> +               ids.push_back(param.first->id());\n> +\n> +       ControlList controls = device_->getControls(ids);\n> +\n> +       /* Seed the control queue with the controls reported by the device. */\n> +       values_.clear();\n> +       for (const auto &ctrl : controls) {\n> +               const ControlId *id = device_->controls().idmap().at(ctrl.first);\n> +               /*\n> +                * Do not mark this control value as updated, it does not need\n> +                * to be written to to device on startup.\n> +                */\n> +               values_[id][0] = Info(ctrl.second, false);\n> +       }\n> +}\n> +\n> +/**\n> + * \\brief Push a set of controls on the queue\n> + * \\param[in] controls List of controls to add to the device queue\n> + *\n> + * Push a set of controls to the control queue. This increases the control queue\n> + * depth by one.\n> + *\n> + * \\returns true if \\a controls are accepted, or false otherwise\n> + */\n> +bool DelayedControls::push(const ControlList &controls)\n> +{\n> +       /* Copy state from previous frame. */\n> +       for (auto &ctrl : values_) {\n> +               Info &info = ctrl.second[queueCount_];\n> +               info = values_[ctrl.first][queueCount_ - 1];\n> +               info.updated = false;\n> +       }\n> +\n> +       /* Update with new controls. */\n> +       const ControlIdMap &idmap = device_->controls().idmap();\n> +       for (const auto &control : controls) {\n> +               const auto &it = idmap.find(control.first);\n> +               if (it == idmap.end()) {\n> +                       LOG(RPiDelayedControls, Warning)\n> +                               << \"Unknown control \" << control.first;\n> +                       return false;\n> +               }\n> +\n> +               const ControlId *id = it->second;\n> +\n> +               if (controlParams_.find(id) == controlParams_.end())\n> +                       return false;\n> +\n> +               Info &info = values_[id][queueCount_];\n> +\n> +               info = Info(control.second);\n> +\n> +               LOG(RPiDelayedControls, Debug)\n> +                       << \"Queuing \" << id->name()\n> +                       << \" to \" << info.toString()\n> +                       << \" at index \" << queueCount_;\n> +       }\n> +\n> +       queueCount_++;\n> +\n> +       return true;\n> +}\n> +\n> +/**\n> + * \\brief Read back controls in effect at a sequence number\n> + * \\param[in] sequence The sequence number to get controls for\n> + *\n> + * Read back what controls where in effect at a specific sequence number. The\n> + * history is a ring buffer of 16 entries where new and old values coexist. It's\n> + * the callers responsibility to not read too old sequence numbers that have been\n> + * pushed out of the history.\n> + *\n> + * Historic values are evicted by pushing new values onto the queue using\n> + * push(). The max history from the current sequence number that yields valid\n> + * values are thus 16 minus number of controls pushed.\n> + *\n> + * \\return The controls at \\a sequence number\n> + */\n> +ControlList DelayedControls::get(uint32_t sequence)\n> +{\n> +       unsigned int index = std::max<int>(0, sequence - maxDelay_);\n> +\n> +       ControlList out(device_->controls());\n> +       for (const auto &ctrl : values_) {\n> +               const ControlId *id = ctrl.first;\n> +               const Info &info = ctrl.second[index];\n> +\n> +               out.set(id->id(), info);\n> +\n> +               LOG(RPiDelayedControls, Debug)\n> +                       << \"Reading \" << id->name()\n> +                       << \" to \" << info.toString()\n> +                       << \" at index \" << index;\n> +       }\n> +\n> +       return out;\n> +}\n> +\n> +/**\n> + * \\brief Inform DelayedControls of the start of a new frame\n> + * \\param[in] sequence Sequence number of the frame that started\n> + *\n> + * Inform the state machine that a new frame has started and of its sequence\n> + * number. Any user of these helpers is responsible to inform the helper about\n> + * the start of any frame. This can be connected with ease to the start of a\n> + * exposure (SOE) V4L2 event.\n> + */\n> +void DelayedControls::applyControls(uint32_t sequence)\n> +{\n> +       LOG(RPiDelayedControls, Debug) << \"frame \" << sequence << \" started\";\n> +\n> +       /*\n> +        * Create control list peeking ahead in the value queue to ensure\n> +        * values are set in time to satisfy the sensor delay.\n> +        */\n> +       ControlList out(device_->controls());\n> +       for (auto &ctrl : values_) {\n> +               const ControlId *id = ctrl.first;\n> +               unsigned int delayDiff = maxDelay_ - controlParams_[id].delay;\n> +               unsigned int index = std::max<int>(0, writeCount_ - delayDiff);\n> +               Info &info = ctrl.second[index];\n> +\n> +               if (info.updated) {\n> +                       if (controlParams_[id].priorityWrite) {\n> +                               /*\n> +                                * This control must be written now, it could\n> +                                * affect validity of the other controls.\n> +                                */\n> +                               ControlList priority(device_->controls());\n> +                               priority.set(id->id(), info);\n> +                               device_->setControls(&priority);\n> +                       } else {\n> +                               /*\n> +                                * Batch up the list of controls and write them\n> +                                * at the end of the function.\n> +                                */\n> +                               out.set(id->id(), info);\n> +                       }\n> +\n> +                       LOG(RPiDelayedControls, Debug)\n> +                               << \"Setting \" << id->name()\n> +                               << \" to \" << info.toString()\n> +                               << \" at index \" << index;\n> +\n> +                       /* Done with this update, so mark as completed. */\n> +                       info.updated = false;\n> +               }\n> +       }\n> +\n> +       writeCount_ = sequence + 1;\n> +\n> +       while (writeCount_ > queueCount_) {\n> +               LOG(RPiDelayedControls, Debug)\n> +                       << \"Queue is empty, auto queue no-op.\";\n> +               push({});\n> +       }\n> +\n> +       device_->setControls(&out);\n> +}\n> +\n> +} /* namespace RPi */\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/libcamera/pipeline/raspberrypi/delayed_controls.h b/src/libcamera/pipeline/raspberrypi/delayed_controls.h\n> new file mode 100644\n> index 000000000000..f7f246482968\n> --- /dev/null\n> +++ b/src/libcamera/pipeline/raspberrypi/delayed_controls.h\n> @@ -0,0 +1,84 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2020, Raspberry Pi Ltd\n> + *\n> + * delayed_controls.h - Helper to deal with controls that take effect with a delay\n> + *\n> + * Note: This has been forked from the libcamera core implementation.\n> + */\n> +\n> +#pragma once\n> +\n> +#include <stdint.h>\n> +#include <unordered_map>\n> +\n> +#include <libcamera/controls.h>\n> +\n> +namespace libcamera {\n> +\n> +class V4L2Device;\n> +\n> +namespace RPi {\n> +\n> +class DelayedControls\n> +{\n> +public:\n> +       struct ControlParams {\n> +               unsigned int delay;\n> +               bool priorityWrite;\n> +       };\n> +\n> +       DelayedControls(V4L2Device *device,\n> +                       const std::unordered_map<uint32_t, ControlParams> &controlParams);\n> +\n> +       void reset();\n> +\n> +       bool push(const ControlList &controls);\n> +       ControlList get(uint32_t sequence);\n> +\n> +       void applyControls(uint32_t sequence);\n> +\n> +private:\n> +       class Info : public ControlValue\n> +       {\n> +       public:\n> +               Info()\n> +                       : updated(false)\n> +               {\n> +               }\n> +\n> +               Info(const ControlValue &v, bool updated_ = true)\n> +                       : ControlValue(v), updated(updated_)\n> +               {\n> +               }\n> +\n> +               bool updated;\n> +       };\n> +\n> +       static constexpr int listSize = 16;\n> +       class ControlRingBuffer : public std::array<Info, listSize>\n> +       {\n> +       public:\n> +               Info &operator[](unsigned int index)\n> +               {\n> +                       return std::array<Info, listSize>::operator[](index % listSize);\n> +               }\n> +\n> +               const Info &operator[](unsigned int index) const\n> +               {\n> +                       return std::array<Info, listSize>::operator[](index % listSize);\n> +               }\n> +       };\n> +\n> +       V4L2Device *device_;\n> +       std::unordered_map<const ControlId *, ControlParams> controlParams_;\n> +       unsigned int maxDelay_;\n> +\n> +       uint32_t queueCount_;\n> +       uint32_t writeCount_;\n> +       std::unordered_map<const ControlId *, ControlRingBuffer> values_;\n> +};\n> +\n> +} /* namespace RPi */\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/libcamera/pipeline/raspberrypi/meson.build b/src/libcamera/pipeline/raspberrypi/meson.build\n> index f1a2f5ee72c2..6064a3f00122 100644\n> --- a/src/libcamera/pipeline/raspberrypi/meson.build\n> +++ b/src/libcamera/pipeline/raspberrypi/meson.build\n> @@ -1,6 +1,7 @@\n>  # SPDX-License-Identifier: CC0-1.0\n>  \n>  libcamera_sources += files([\n> +    'delayed_controls.cpp',\n>      'dma_heaps.cpp',\n>      'raspberrypi.cpp',\n>      'rpi_stream.cpp',\n> -- \n> 2.25.1\n>","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id D2930BE08B\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 28 Nov 2022 12:33:56 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 5001163335;\n\tMon, 28 Nov 2022 13:33:56 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 118A461F2A\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 28 Nov 2022 13:33:55 +0100 (CET)","from pendragon.ideasonboard.com\n\t(cpc89244-aztw30-2-0-cust3082.18-1.cable.virginm.net [86.31.172.11])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 79D03501;\n\tMon, 28 Nov 2022 13:33:54 +0100 (CET)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1669638836;\n\tbh=ASLOu6vnV7RWoUNEn3xV+e7B/vw2eMYD5luZ5UPXzAw=;\n\th=In-Reply-To:References:To:Date:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:\n\tFrom;\n\tb=ITPfmvHUkZMZ5obyvs+U8d7nctvVeh6HYpYuwJNfV4UNkXUT1e82g2vyh69yS3gYF\n\tSkein+efqi0IE5zsEY7IPTFrRVkUtvqBmNpej5zpPURjFKKtk/lZG0A7cNzfuigrJ/\n\tfD0mT1Z6Um70AjVq+cI8gNkO8JbAUPduSXuAjr2WCsVSV5xHUsTEwqW2zSjXHcBOPK\n\tVC1jwPaiBzczLhiJhcnvJALUP9dc/4uchYPCCHtf6C4YAFHgk8xh5e7RLLpq/IRJLJ\n\tKPi7+bc2TcscRklZuwmK/hP3ijFXVtKO+xjLTRh8/KxklqBk0F+L+LlgPNOyiNTEE+\n\tlSD5fjo3Ov0Aw==","v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1669638834;\n\tbh=ASLOu6vnV7RWoUNEn3xV+e7B/vw2eMYD5luZ5UPXzAw=;\n\th=In-Reply-To:References:Subject:From:To:Date:From;\n\tb=SS/65H2hOJUCOdKGx2A4dRm9ce6sb2nasu8cXDu30x8PZ5vPOq9kbObtxZZG8TP8d\n\tQwc1TYbzOkcnAthkPfM/aBOu+tSvDO4Rjd2XEV6pc8QJf1NPwrpd6jWVIATklSuc3d\n\t54BgIvtHD3gDptUwSYr2gGy4ywQnysehltMqugKQ="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"SS/65H2h\"; dkim-atps=neutral","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<20221115090755.2921-2-naush@raspberrypi.com>","References":"<20221115090755.2921-1-naush@raspberrypi.com>\n\t<20221115090755.2921-2-naush@raspberrypi.com>","To":"Naushir Patuck <naush@raspberrypi.com>,\n\tlibcamera-devel@lists.libcamera.org","Date":"Mon, 28 Nov 2022 12:33:52 +0000","Message-ID":"<166963883221.3066484.11409778448703587124@Monstersaurus>","User-Agent":"alot/0.10","Subject":"Re: [libcamera-devel] [PATCH v6 1/8] pipeline: raspberrypi: Fork\n\tDelayedControls","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","From":"Kieran Bingham via libcamera-devel\n\t<libcamera-devel@lists.libcamera.org>","Reply-To":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]