Show a patch.

GET /api/patches/3685/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 3685,
    "url": "https://patchwork.libcamera.org/api/patches/3685/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/3685/",
    "project": {
        "id": 1,
        "url": "https://patchwork.libcamera.org/api/projects/1/?format=api",
        "name": "libcamera",
        "link_name": "libcamera",
        "list_id": "libcamera_core",
        "list_email": "libcamera-devel@lists.libcamera.org",
        "web_url": "",
        "scm_url": "",
        "webscm_url": ""
    },
    "msgid": "<20200504092829.10099-4-laurent.pinchart@ideasonboard.com>",
    "date": "2020-05-04T09:28:26",
    "name": "[libcamera-devel,3/6] libcamera: pipeline: Raspberry Pi pipeline handler",
    "commit_ref": null,
    "pull_url": null,
    "state": "accepted",
    "archived": false,
    "hash": "92547b73773324d5bb6c01947fd9591551cf3c11",
    "submitter": {
        "id": 2,
        "url": "https://patchwork.libcamera.org/api/people/2/?format=api",
        "name": "Laurent Pinchart",
        "email": "laurent.pinchart@ideasonboard.com"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/3685/mbox/",
    "series": [
        {
            "id": 880,
            "url": "https://patchwork.libcamera.org/api/series/880/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=880",
            "date": "2020-05-04T09:28:23",
            "name": "libcamera: Raspberry Pi camera support",
            "version": 1,
            "mbox": "https://patchwork.libcamera.org/series/880/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/3685/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/3685/checks/",
    "tags": {},
    "headers": {
        "Return-Path": "<laurent.pinchart@ideasonboard.com>",
        "Received": [
            "from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 7E937616AB\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon,  4 May 2020 11:28:39 +0200 (CEST)",
            "from pendragon.bb.dnainternet.fi (81-175-216-236.bb.dnainternet.fi\n\t[81.175.216.236])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id C2A934F7;\n\tMon,  4 May 2020 11:28:38 +0200 (CEST)"
        ],
        "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"wfaxEw5r\"; dkim-atps=neutral",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1588584519;\n\tbh=1sGhb+ryCPRBlU04AOjbfK9zzKuNeaHv8ZGhueas5jI=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=wfaxEw5rkD9hp2qb8U6AkGN4YJ2KMmjfqxjOvYUBJEB2QS0ZOU2uE2kw1GGxlgx/v\n\tbveuGGxVZmbR4t+3vQJa8/jw9YZ62JJY2fpZW8byJUxhsXa+L4nslqHqMMuwsHLPrw\n\toO03YLk8PQKwjIXtRK/O+AmrwW4Y56QwFej8+ZHM=",
        "From": "Laurent Pinchart <laurent.pinchart@ideasonboard.com>",
        "To": "libcamera-devel@lists.libcamera.org",
        "Date": "Mon,  4 May 2020 12:28:26 +0300",
        "Message-Id": "<20200504092829.10099-4-laurent.pinchart@ideasonboard.com>",
        "X-Mailer": "git-send-email 2.26.2",
        "In-Reply-To": "<20200504092829.10099-1-laurent.pinchart@ideasonboard.com>",
        "References": "<20200504092829.10099-1-laurent.pinchart@ideasonboard.com>",
        "MIME-Version": "1.0",
        "Content-Transfer-Encoding": "8bit",
        "Subject": "[libcamera-devel] [PATCH 3/6] libcamera: pipeline: Raspberry Pi\n\tpipeline handler",
        "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>",
        "X-List-Received-Date": "Mon, 04 May 2020 09:28:39 -0000"
    },
    "content": "From: Naushir Patuck <naush@raspberrypi.com>\n\nInitial implementation of the Raspberry Pi (BCM2835) ISP pipeline\nhandler.\n\nAll code is licensed under the BSD-2-Clause terms.\nCopyright (c) 2019-2020 Raspberry Pi Trading Ltd.\n\nSigned-off-by: Naushir Patuck <naush@raspberrypi.com>\nSigned-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n---\n include/ipa/raspberrypi.h                     |   58 +\n .../pipeline/raspberrypi/meson.build          |    3 +\n .../pipeline/raspberrypi/raspberrypi.cpp      | 1598 +++++++++++++++++\n .../pipeline/raspberrypi/staggered_ctrl.h     |  236 +++\n src/libcamera/pipeline/raspberrypi/vcsm.h     |  144 ++\n 5 files changed, 2039 insertions(+)\n create mode 100644 include/ipa/raspberrypi.h\n create mode 100644 src/libcamera/pipeline/raspberrypi/meson.build\n create mode 100644 src/libcamera/pipeline/raspberrypi/raspberrypi.cpp\n create mode 100644 src/libcamera/pipeline/raspberrypi/staggered_ctrl.h\n create mode 100644 src/libcamera/pipeline/raspberrypi/vcsm.h",
    "diff": "diff --git a/include/ipa/raspberrypi.h b/include/ipa/raspberrypi.h\nnew file mode 100644\nindex 000000000000..3df56e8a1306\n--- /dev/null\n+++ b/include/ipa/raspberrypi.h\n@@ -0,0 +1,58 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2019-2020, Raspberry Pi Ltd.\n+ *\n+ * raspberrypi.h - Image Processing Algorithm interface for Raspberry Pi\n+ */\n+#ifndef __LIBCAMERA_IPA_INTERFACE_RASPBERRYPI_H__\n+#define __LIBCAMERA_IPA_INTERFACE_RASPBERRYPI_H__\n+\n+#include <libcamera/control_ids.h>\n+#include <libcamera/controls.h>\n+\n+enum RPiOperations {\n+\tRPI_IPA_ACTION_V4L2_SET_STAGGERED = 1,\n+\tRPI_IPA_ACTION_V4L2_SET_ISP,\n+\tRPI_IPA_ACTION_STATS_METADATA_COMPLETE,\n+\tRPI_IPA_ACTION_RUN_ISP,\n+\tRPI_IPA_ACTION_RUN_ISP_AND_DROP_FRAME,\n+\tRPI_IPA_ACTION_SET_SENSOR_CONFIG,\n+\tRPI_IPA_ACTION_EMBEDDED_COMPLETE,\n+\tRPI_IPA_EVENT_SIGNAL_STAT_READY,\n+\tRPI_IPA_EVENT_SIGNAL_ISP_PREPARE,\n+\tRPI_IPA_EVENT_QUEUE_REQUEST,\n+\tRPI_IPA_EVENT_LS_TABLE_ALLOCATION,\n+};\n+\n+enum RPiIpaMask {\n+\tID = 0x0ffff,\n+\tSTATS = 0x10000,\n+\tEMBEDDED_DATA = 0x20000,\n+\tBAYER_DATA = 0x40000\n+};\n+\n+/* Size of the LS grid allocation. */\n+#define MAX_LS_GRID_SIZE (32 << 10)\n+\n+namespace libcamera {\n+\n+/* List of controls handled by the Raspberry Pi IPA */\n+static const ControlInfoMap RPiControls = {\n+\t{ &controls::AeEnable, ControlInfo(false, true) },\n+\t{ &controls::ExposureTime, ControlInfo(0, 999999) },\n+\t{ &controls::AnalogueGain, ControlInfo(1.0f, 32.0f) },\n+\t{ &controls::AeMeteringMode, ControlInfo(0, static_cast<int32_t>(controls::MeteringModeMax)) },\n+\t{ &controls::AeConstraintMode, ControlInfo(0, static_cast<int32_t>(controls::ConstraintModeMax)) },\n+\t{ &controls::AeExposureMode, ControlInfo(0, static_cast<int32_t>(controls::ExposureModeMax)) },\n+\t{ &controls::ExposureValue, ControlInfo(0.0f, 16.0f) },\n+\t{ &controls::AwbEnable, ControlInfo(false, true) },\n+\t{ &controls::ColourGains, ControlInfo(0.0f, 32.0f) },\n+\t{ &controls::AwbMode, ControlInfo(0, static_cast<int32_t>(controls::AwbModeMax)) },\n+\t{ &controls::Brightness, ControlInfo(-1.0f, 1.0f) },\n+\t{ &controls::Contrast, ControlInfo(0.0f, 32.0f) },\n+\t{ &controls::Saturation, ControlInfo(0.0f, 32.0f) },\n+};\n+\n+} /* namespace libcamera */\n+\n+#endif /* __LIBCAMERA_IPA_INTERFACE_RASPBERRYPI_H__ */\ndiff --git a/src/libcamera/pipeline/raspberrypi/meson.build b/src/libcamera/pipeline/raspberrypi/meson.build\nnew file mode 100644\nindex 000000000000..737857977831\n--- /dev/null\n+++ b/src/libcamera/pipeline/raspberrypi/meson.build\n@@ -0,0 +1,3 @@\n+libcamera_sources += files([\n+    'raspberrypi.cpp'\n+])\ndiff --git a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp\nnew file mode 100644\nindex 000000000000..1685081997e5\n--- /dev/null\n+++ b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp\n@@ -0,0 +1,1598 @@\n+/* SPDX-License-Identifier: BSD-2-Clause */\n+/*\n+ * Copyright (C) 2019-2020, Raspberry Pi (Trading) Ltd.\n+ *\n+ * raspberrypi.cpp - Pipeline handler for Raspberry Pi devices\n+ */\n+#include <algorithm>\n+#include <assert.h>\n+#include <fcntl.h>\n+#include <mutex>\n+#include <queue>\n+#include <sys/mman.h>\n+\n+#include <ipa/raspberrypi.h>\n+#include <libcamera/camera.h>\n+#include <libcamera/control_ids.h>\n+#include <libcamera/logging.h>\n+#include <libcamera/request.h>\n+#include <libcamera/stream.h>\n+\n+#include <linux/drm_fourcc.h>\n+#include <linux/videodev2.h>\n+\n+#include \"camera_sensor.h\"\n+#include \"device_enumerator.h\"\n+#include \"ipa_manager.h\"\n+#include \"media_device.h\"\n+#include \"pipeline_handler.h\"\n+#include \"staggered_ctrl.h\"\n+#include \"utils.h\"\n+#include \"v4l2_controls.h\"\n+#include \"v4l2_videodevice.h\"\n+#include \"vcsm.h\"\n+\n+namespace libcamera {\n+\n+LOG_DEFINE_CATEGORY(RPI)\n+\n+using V4L2PixFmtMap = std::map<V4L2PixelFormat, std::vector<SizeRange>>;\n+\n+namespace {\n+\n+bool isRaw(PixelFormat &pixFmt)\n+{\n+\t/*\n+\t * The isRaw test might be redundant right now the pipeline handler only\n+\t * supports RAW sensors. Leave it in for now, just as a sanity check.\n+\t */\n+\tconst PixelFormatInfo &info = PixelFormatInfo::info(pixFmt);\n+\tif (!info.isValid())\n+\t\treturn false;\n+\n+\treturn info.colourEncoding == PixelFormatInfo::ColourEncodingRAW;\n+}\n+\n+double scoreFormat(double desired, double actual)\n+{\n+\tdouble score = desired - actual;\n+\t/* Smaller desired dimensions are preferred. */\n+\tif (score < 0.0)\n+\t\tscore = (-score) / 8;\n+\t/* Penalise non-exact matches. */\n+\tif (actual != desired)\n+\t\tscore *= 2;\n+\n+\treturn score;\n+}\n+\n+V4L2DeviceFormat findBestMode(V4L2PixFmtMap &formatsMap, const Size &req)\n+{\n+\tdouble bestScore = 9e9, score;\n+\tV4L2DeviceFormat bestMode = {};\n+\n+#define PENALTY_AR\t\t1500.0\n+#define PENALTY_8BIT\t\t2000.0\n+#define PENALTY_10BIT\t\t1000.0\n+#define PENALTY_12BIT\t\t   0.0\n+#define PENALTY_UNPACKED\t 500.0\n+\n+\t/* Calculate the closest/best mode from the user requested size. */\n+\tfor (const auto &iter : formatsMap) {\n+\t\tV4L2PixelFormat v4l2Format = iter.first;\n+\t\tPixelFormat pixelFormat = v4l2Format.toPixelFormat();\n+\t\tconst PixelFormatInfo &info = PixelFormatInfo::info(pixelFormat);\n+\n+\t\tfor (const SizeRange &sz : iter.second) {\n+\t\t\tdouble modeWidth = sz.contains(req) ? req.width : sz.max.width;\n+\t\t\tdouble modeHeight = sz.contains(req) ? req.height : sz.max.height;\n+\t\t\tdouble reqAr = static_cast<double>(req.width) / req.height;\n+\t\t\tdouble modeAr = modeWidth / modeHeight;\n+\n+\t\t\t/* Score the dimensions for closeness. */\n+\t\t\tscore = scoreFormat(req.width, modeWidth);\n+\t\t\tscore += scoreFormat(req.height, modeHeight);\n+\t\t\tscore += PENALTY_AR * scoreFormat(reqAr, modeAr);\n+\n+\t\t\t/* Add any penalties... this is not an exact science! */\n+\t\t\tif (!info.packed)\n+\t\t\t\tscore += PENALTY_UNPACKED;\n+\n+\t\t\tif (info.bitsPerPixel == 12)\n+\t\t\t\tscore += PENALTY_12BIT;\n+\t\t\telse if (info.bitsPerPixel == 10)\n+\t\t\t\tscore += PENALTY_10BIT;\n+\t\t\telse if (info.bitsPerPixel == 8)\n+\t\t\t\tscore += PENALTY_8BIT;\n+\n+\t\t\tif (score <= bestScore) {\n+\t\t\t\tbestScore = score;\n+\t\t\t\tbestMode.fourcc = v4l2Format;\n+\t\t\t\tbestMode.size = Size(modeWidth, modeHeight);\n+\t\t\t}\n+\n+\t\t\tLOG(RPI, Info) << \"Mode: \" << modeWidth << \"x\" << modeHeight\n+\t\t\t\t       << \" fmt \" << v4l2Format.toString()\n+\t\t\t\t       << \" Score: \" << score\n+\t\t\t\t       << \" (best \" << bestScore << \")\";\n+\t\t}\n+\t}\n+\n+\treturn bestMode;\n+}\n+\n+} /* namespace */\n+\n+/*\n+ * Device stream abstraction for either an internal or external stream.\n+ * Used for both Unicam and the ISP.\n+ */\n+class RPiStream : public Stream\n+{\n+public:\n+\tRPiStream()\n+\t{\n+\t}\n+\n+\tRPiStream(const char *name, MediaEntity *dev, bool importOnly = false)\n+\t\t: external_(false), importOnly_(importOnly), name_(name),\n+\t\t  dev_(std::make_unique<V4L2VideoDevice>(dev))\n+\t{\n+\t}\n+\n+\tV4L2VideoDevice *dev() const\n+\t{\n+\t\treturn dev_.get();\n+\t}\n+\n+\tvoid setExternal(bool external)\n+\t{\n+\t\texternal_ = external;\n+\t}\n+\n+\tbool isExternal() const\n+\t{\n+\t\t/*\n+\t\t * Import streams cannot be external.\n+\t\t *\n+\t\t * RAW capture is a special case where we simply copy the RAW\n+\t\t * buffer out of the request.  All other buffer handling happens\n+\t\t * as if the stream is internal.\n+\t\t */\n+\t\treturn external_ && !importOnly_;\n+\t}\n+\n+\tbool isImporter() const\n+\t{\n+\t\treturn importOnly_;\n+\t}\n+\n+\tvoid reset()\n+\t{\n+\t\texternal_ = false;\n+\t\tinternalBuffers_.clear();\n+\t}\n+\n+\tstd::string name() const\n+\t{\n+\t\treturn name_;\n+\t}\n+\n+\tvoid setExternalBuffers(std::vector<std::unique_ptr<FrameBuffer>> *buffers)\n+\t{\n+\t\texternalBuffers_ = buffers;\n+\t}\n+\n+\tconst std::vector<std::unique_ptr<FrameBuffer>> *getBuffers() const\n+\t{\n+\t\treturn external_ ? externalBuffers_ : &internalBuffers_;\n+\t}\n+\n+\tvoid releaseBuffers()\n+\t{\n+\t\tdev_->releaseBuffers();\n+\t\tif (!external_ && !importOnly_)\n+\t\t\tinternalBuffers_.clear();\n+\t}\n+\n+\tint importBuffers(unsigned int count)\n+\t{\n+\t\treturn dev_->importBuffers(count);\n+\t}\n+\n+\tint allocateBuffers(unsigned int count)\n+\t{\n+\t\treturn dev_->allocateBuffers(count, &internalBuffers_);\n+\t}\n+\n+\tint queueBuffers()\n+\t{\n+\t\tif (external_)\n+\t\t\treturn 0;\n+\n+\t\tfor (auto &b : internalBuffers_) {\n+\t\t\tint ret = dev_->queueBuffer(b.get());\n+\t\t\tif (ret) {\n+\t\t\t\tLOG(RPI, Error) << \"Failed to queue buffers for \"\n+\t\t\t\t\t\t<< name_;\n+\t\t\t\treturn ret;\n+\t\t\t}\n+\t\t}\n+\n+\t\treturn 0;\n+\t}\n+\n+\tbool findFrameBuffer(FrameBuffer *buffer) const\n+\t{\n+\t\tauto start = external_ ? externalBuffers_->begin() : internalBuffers_.begin();\n+\t\tauto end = external_ ? externalBuffers_->end() : internalBuffers_.end();\n+\n+\t\tif (importOnly_)\n+\t\t\treturn false;\n+\n+\t\tif (std::find_if(start, end,\n+\t\t\t\t [buffer](std::unique_ptr<FrameBuffer> const &ref) { return ref.get() == buffer; }) != end)\n+\t\t\treturn true;\n+\n+\t\treturn false;\n+\t}\n+\n+private:\n+\t/*\n+\t * Indicates that this stream is active externally, i.e. the buffers\n+\t * are provided by the application.\n+\t */\n+\tbool external_;\n+\t/* Indicates that this stream only imports buffers, e.g. ISP input. */\n+\tbool importOnly_;\n+\t/* Stream name identifier. */\n+\tstd::string name_;\n+\t/* The actual device stream. */\n+\tstd::unique_ptr<V4L2VideoDevice> dev_;\n+\t/* Internally allocated framebuffers associated with this device stream. */\n+\tstd::vector<std::unique_ptr<FrameBuffer>> internalBuffers_;\n+\t/* Externally allocated framebuffers associated with this device stream. */\n+\tstd::vector<std::unique_ptr<FrameBuffer>> *externalBuffers_;\n+};\n+\n+/*\n+ * The following class is just a convenient (and typesafe) array of device\n+ * streams indexed with an enum class.\n+ */\n+enum class Unicam : unsigned int { Image, Embedded };\n+enum class Isp : unsigned int { Input, Output0, Output1, Stats };\n+\n+template<typename E, std::size_t N>\n+class RPiDevice : public std::array<class RPiStream, N>\n+{\n+private:\n+\tconstexpr auto index(E e) const noexcept\n+\t{\n+\t\treturn static_cast<std::underlying_type_t<E>>(e);\n+\t}\n+public:\n+\tRPiStream &operator[](E e)\n+\t{\n+\t\treturn std::array<class RPiStream, N>::operator[](index(e));\n+\t}\n+\tconst RPiStream &operator[](E e) const\n+\t{\n+\t\treturn std::array<class RPiStream, N>::operator[](index(e));\n+\t}\n+};\n+\n+class RPiCameraData : public CameraData\n+{\n+public:\n+\tRPiCameraData(PipelineHandler *pipe)\n+\t\t: CameraData(pipe), sensor_(nullptr), lsTable_(nullptr),\n+\t\t  state_(State::Stopped), dropFrame_(false), ispOutputCount_(0)\n+\t{\n+\t}\n+\n+\t~RPiCameraData()\n+\t{\n+\t\t/*\n+\t\t * Free the LS table if we have allocated one. Another\n+\t\t * allocation will occur in applyLS() with the appropriate\n+\t\t * size.\n+\t\t */\n+\t\tif (lsTable_) {\n+\t\t\tvcsm_.free(lsTable_);\n+\t\t\tlsTable_ = nullptr;\n+\t\t}\n+\n+\t\t/* Stop the IPA proxy thread. */\n+\t\tipa_->stop();\n+\t}\n+\n+\tvoid frameStarted(uint32_t sequence);\n+\n+\tint loadIPA();\n+\tvoid queueFrameAction(unsigned int frame, const IPAOperationData &action);\n+\n+\t/* bufferComplete signal handlers. */\n+\tvoid unicamBufferDequeue(FrameBuffer *buffer);\n+\tvoid ispInputDequeue(FrameBuffer *buffer);\n+\tvoid ispOutputDequeue(FrameBuffer *buffer);\n+\n+\tvoid clearIncompleteRequests();\n+\tvoid handleStreamBuffer(FrameBuffer *buffer, const RPiStream *stream);\n+\tvoid handleState();\n+\n+\tCameraSensor *sensor_;\n+\t/* Array of Unicam and ISP device streams and associated buffers/streams. */\n+\tRPiDevice<Unicam, 2> unicam_;\n+\tRPiDevice<Isp, 4> isp_;\n+\t/* The vector below is just for convenience when iterating over all streams. */\n+\tstd::vector<RPiStream *> streams_;\n+\t/* Buffers passed to the IPA. */\n+\tstd::vector<IPABuffer> ipaBuffers_;\n+\n+\t/* VCSM allocation helper. */\n+\tRPi::Vcsm vcsm_;\n+\tvoid *lsTable_;\n+\n+\tRPi::StaggeredCtrl staggeredCtrl_;\n+\tuint32_t expectedSequence_;\n+\tbool sensorMetadata_;\n+\n+\t/*\n+\t * All the functions in this class are called from a single calling\n+\t * thread. So, we do not need to have any mutex to protect access to any\n+\t * of the variables below.\n+\t */\n+\tenum class State { Stopped, Idle, Busy, IpaComplete };\n+\tState state_;\n+\tstd::queue<FrameBuffer *> bayerQueue_;\n+\tstd::queue<FrameBuffer *> embeddedQueue_;\n+\tstd::deque<Request *> requestQueue_;\n+\n+private:\n+\tvoid checkRequestCompleted();\n+\tvoid tryRunPipeline();\n+\tvoid tryFlushQueues();\n+\tFrameBuffer *updateQueue(std::queue<FrameBuffer *> &q, uint64_t timestamp, V4L2VideoDevice *dev);\n+\n+\tbool dropFrame_;\n+\tint ispOutputCount_;\n+};\n+\n+class RPiCameraConfiguration : public CameraConfiguration\n+{\n+public:\n+\tRPiCameraConfiguration(const RPiCameraData *data);\n+\n+\tStatus validate() override;\n+\n+private:\n+\tconst RPiCameraData *data_;\n+};\n+\n+class PipelineHandlerRPi : public PipelineHandler\n+{\n+public:\n+\tPipelineHandlerRPi(CameraManager *manager);\n+\t~PipelineHandlerRPi();\n+\n+\tCameraConfiguration *generateConfiguration(Camera *camera, const StreamRoles &roles) override;\n+\tint configure(Camera *camera, CameraConfiguration *config) override;\n+\n+\tint exportFrameBuffers(Camera *camera, Stream *stream,\n+\t\t\t       std::vector<std::unique_ptr<FrameBuffer>> *buffers) override;\n+\n+\tint start(Camera *camera) override;\n+\tvoid stop(Camera *camera) override;\n+\n+\tint queueRequestDevice(Camera *camera, Request *request) override;\n+\n+\tbool match(DeviceEnumerator *enumerator) override;\n+\n+private:\n+\tRPiCameraData *cameraData(const Camera *camera)\n+\t{\n+\t\treturn static_cast<RPiCameraData *>(PipelineHandler::cameraData(camera));\n+\t}\n+\n+\tint configureIPA(Camera *camera);\n+\n+\tint queueAllBuffers(Camera *camera);\n+\tint prepareBuffers(Camera *camera);\n+\tvoid freeBuffers(Camera *camera);\n+\n+\tstd::shared_ptr<MediaDevice> unicam_;\n+\tstd::shared_ptr<MediaDevice> isp_;\n+};\n+\n+RPiCameraConfiguration::RPiCameraConfiguration(const RPiCameraData *data)\n+\t: CameraConfiguration(), data_(data)\n+{\n+}\n+\n+CameraConfiguration::Status RPiCameraConfiguration::validate()\n+{\n+\tStatus status = Valid;\n+\n+\tif (config_.empty())\n+\t\treturn Invalid;\n+\n+\tunsigned int rawCount = 0, outCount = 0, count = 0, maxIndex = 0;\n+\tstd::pair<int, Size> outSize[2];\n+\tSize maxSize = {};\n+\tfor (StreamConfiguration &cfg : config_) {\n+\t\tif (isRaw(cfg.pixelFormat)) {\n+\t\t\t/*\n+\t\t\t * Calculate the best sensor mode we can use based on\n+\t\t\t * the user request.\n+\t\t\t */\n+\t\t\tV4L2PixFmtMap fmts = data_->unicam_[Unicam::Image].dev()->formats();\n+\t\t\tV4L2DeviceFormat sensorFormat = findBestMode(fmts, cfg.size);\n+\t\t\tPixelFormat sensorPixFormat = sensorFormat.fourcc.toPixelFormat();\n+\t\t\tif (cfg.size != sensorFormat.size ||\n+\t\t\t    cfg.pixelFormat != sensorPixFormat) {\n+\t\t\t\tcfg.size = sensorFormat.size;\n+\t\t\t\tcfg.pixelFormat = sensorPixFormat;\n+\t\t\t\tstatus = Adjusted;\n+\t\t\t}\n+\t\t\trawCount++;\n+\t\t} else {\n+\t\t\toutSize[outCount] = std::make_pair(count, cfg.size);\n+\t\t\t/* Record the largest resolution for fixups later. */\n+\t\t\tif (maxSize < cfg.size) {\n+\t\t\t\tmaxSize = cfg.size;\n+\t\t\t\tmaxIndex = outCount;\n+\t\t\t}\n+\t\t\toutCount++;\n+\t\t}\n+\n+\t\tcount++;\n+\n+\t\t/* Can only output 1 RAW stream, or 2 YUV/RGB streams. */\n+\t\tif (rawCount > 1 || outCount > 2) {\n+\t\t\tLOG(RPI, Error) << \"Invalid number of streams requested\";\n+\t\t\treturn Invalid;\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Now do any fixups needed. For the two ISP outputs, one stream must be\n+\t * equal or smaller than the other in all dimensions.\n+\t */\n+\tfor (unsigned int i = 0; i < outCount; i++) {\n+\t\toutSize[i].second.width = std::min(outSize[i].second.width,\n+\t\t\t\t\t\t   maxSize.width);\n+\t\toutSize[i].second.height = std::min(outSize[i].second.height,\n+\t\t\t\t\t\t    maxSize.height);\n+\n+\t\tif (config_.at(outSize[i].first).size != outSize[i].second) {\n+\t\t\tconfig_.at(outSize[i].first).size = outSize[i].second;\n+\t\t\tstatus = Adjusted;\n+\t\t}\n+\n+\t\t/*\n+\t\t * Also validate the correct pixel formats here.\n+\t\t * Note that Output0 and Output1 support a different\n+\t\t * set of formats.\n+\t\t *\n+\t\t * Output 0 must be for the largest resolution. We will\n+\t\t * have that fixed up in the code above.\n+\t\t *\n+\t\t */\n+\t\tPixelFormat &cfgPixFmt = config_.at(outSize[i].first).pixelFormat;\n+\t\tV4L2PixFmtMap fmts;\n+\n+\t\tif (i == maxIndex)\n+\t\t\tfmts = data_->isp_[Isp::Output0].dev()->formats();\n+\t\telse\n+\t\t\tfmts = data_->isp_[Isp::Output1].dev()->formats();\n+\n+\t\tif (fmts.find(V4L2PixelFormat::fromPixelFormat(cfgPixFmt, false)) == fmts.end()) {\n+\t\t\t/* If we cannot find a native format, use a default one. */\n+\t\t\tcfgPixFmt = PixelFormat(DRM_FORMAT_NV12);\n+\t\t\tstatus = Adjusted;\n+\t\t}\n+\t}\n+\n+\treturn status;\n+}\n+\n+PipelineHandlerRPi::PipelineHandlerRPi(CameraManager *manager)\n+\t: PipelineHandler(manager), unicam_(nullptr), isp_(nullptr)\n+{\n+}\n+\n+PipelineHandlerRPi::~PipelineHandlerRPi()\n+{\n+\tif (unicam_)\n+\t\tunicam_->release();\n+\n+\tif (isp_)\n+\t\tisp_->release();\n+}\n+\n+CameraConfiguration *PipelineHandlerRPi::generateConfiguration(Camera *camera,\n+\t\t\t\t\t\t\t       const StreamRoles &roles)\n+{\n+\tRPiCameraData *data = cameraData(camera);\n+\tCameraConfiguration *config = new RPiCameraConfiguration(data);\n+\tV4L2DeviceFormat sensorFormat;\n+\tV4L2PixFmtMap fmts;\n+\n+\tif (roles.empty())\n+\t\treturn config;\n+\n+\tfor (const StreamRole role : roles) {\n+\t\tStreamConfiguration cfg{};\n+\n+\t\tswitch (role) {\n+\t\tcase StreamRole::StillCaptureRaw:\n+\t\t\tcfg.size = data->sensor_->resolution();\n+\t\t\tfmts = data->unicam_[Unicam::Image].dev()->formats();\n+\t\t\tsensorFormat = findBestMode(fmts, cfg.size);\n+\t\t\tcfg.pixelFormat = sensorFormat.fourcc.toPixelFormat();\n+\t\t\tASSERT(cfg.pixelFormat.isValid());\n+\t\t\tcfg.bufferCount = 1;\n+\t\t\tbreak;\n+\n+\t\tcase StreamRole::StillCapture:\n+\t\t\tcfg.pixelFormat = PixelFormat(DRM_FORMAT_NV12);\n+\t\t\t/* Return the largest sensor resolution. */\n+\t\t\tcfg.size = data->sensor_->resolution();\n+\t\t\tcfg.bufferCount = 1;\n+\t\t\tbreak;\n+\n+\t\tcase StreamRole::VideoRecording:\n+\t\t\tcfg.pixelFormat = PixelFormat(DRM_FORMAT_NV12);\n+\t\t\tcfg.size = { 1920, 1080 };\n+\t\t\tcfg.bufferCount = 4;\n+\t\t\tbreak;\n+\n+\t\tcase StreamRole::Viewfinder:\n+\t\t\tcfg.pixelFormat = PixelFormat(DRM_FORMAT_ARGB8888);\n+\t\t\tcfg.size = { 800, 600 };\n+\t\t\tcfg.bufferCount = 4;\n+\t\t\tbreak;\n+\n+\t\tdefault:\n+\t\t\tLOG(RPI, Error) << \"Requested stream role not supported: \"\n+\t\t\t\t\t<< role;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tconfig->addConfiguration(cfg);\n+\t}\n+\n+\tconfig->validate();\n+\n+\treturn config;\n+}\n+\n+int PipelineHandlerRPi::configure(Camera *camera, CameraConfiguration *config)\n+{\n+\tRPiCameraData *data = cameraData(camera);\n+\tint ret;\n+\n+\t/* Start by resetting the Unicam and ISP stream states. */\n+\tfor (auto const stream : data->streams_)\n+\t\tstream->reset();\n+\n+\tSize maxSize = {}, sensorSize = {};\n+\tunsigned int maxIndex = 0;\n+\tbool rawStream = false;\n+\n+\t/*\n+\t * Look for the RAW stream (if given) size as well as the largest\n+\t * ISP output size.\n+\t */\n+\tfor (unsigned i = 0; i < config->size(); i++) {\n+\t\tStreamConfiguration &cfg = config->at(i);\n+\n+\t\tif (isRaw(cfg.pixelFormat)) {\n+\t\t\t/*\n+\t\t\t * If we have been given a RAW stream, use that size\n+\t\t\t * for setting up the sensor.\n+\t\t\t */\n+\t\t\tsensorSize = cfg.size;\n+\t\t\trawStream = true;\n+\t\t} else {\n+\t\t\tif (cfg.size > maxSize) {\n+\t\t\t\tmaxSize = config->at(i).size;\n+\t\t\t\tmaxIndex = i;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\t/* First calculate the best sensor mode we can use based on the user request. */\n+\tV4L2PixFmtMap fmts = data->unicam_[Unicam::Image].dev()->formats();\n+\tV4L2DeviceFormat sensorFormat = findBestMode(fmts, rawStream ? sensorSize : maxSize);\n+\n+\t/*\n+\t * Unicam image output format.  The ISP input format gets set at\n+\t * start, just in case we have swapped bayer orders due to flips\n+\t */\n+\tret = data->unicam_[Unicam::Image].dev()->setFormat(&sensorFormat);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tLOG(RPI, Info) << \"Sensor: \" << camera->name()\n+\t\t       << \" - Selected mode: \" << sensorFormat.toString();\n+\n+\t/*\n+\t * This format may be reset on start() if the bayer order has changed\n+\t * because of flips in the sensor.\n+\t */\n+\tret = data->isp_[Isp::Input].dev()->setFormat(&sensorFormat);\n+\n+\t/*\n+\t * See which streams are requested, and route the user\n+\t * StreamConfiguration appropriately.\n+\t */\n+\tV4L2DeviceFormat format = {};\n+\tfor (unsigned i = 0; i < config->size(); i++) {\n+\t\tStreamConfiguration &cfg = config->at(i);\n+\n+\t\tif (isRaw(cfg.pixelFormat)) {\n+\t\t\tcfg.setStream(&data->isp_[Isp::Input]);\n+\t\t\tcfg.stride = sensorFormat.planes[0].bpl;\n+\t\t\tdata->isp_[Isp::Input].setExternal(true);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (i == maxIndex) {\n+\t\t\t/* ISP main output format. */\n+\t\t\tV4L2VideoDevice *dev = data->isp_[Isp::Output0].dev();\n+\t\t\tV4L2PixelFormat fourcc = dev->toV4L2PixelFormat(cfg.pixelFormat);\n+\t\t\tformat.size = cfg.size;\n+\t\t\tformat.fourcc = fourcc;\n+\n+\t\t\tret = dev->setFormat(&format);\n+\t\t\tif (ret)\n+\t\t\t\treturn -EINVAL;\n+\n+\t\t\tif (format.size != cfg.size || format.fourcc != fourcc) {\n+\t\t\t\tLOG(RPI, Error)\n+\t\t\t\t\t<< \"Failed to set format on ISP capture0 device: \"\n+\t\t\t\t\t<< format.toString();\n+\t\t\t\treturn -EINVAL;\n+\t\t\t}\n+\n+\t\t\tcfg.setStream(&data->isp_[Isp::Output0]);\n+\t\t\tcfg.stride = format.planes[0].bpl;\n+\t\t\tdata->isp_[Isp::Output0].setExternal(true);\n+\t\t}\n+\n+\t\t/*\n+\t\t * ISP second output format. This fallthrough means that if a\n+\t\t * second output stream has not been configured, we simply use\n+\t\t * the Output0 configuration.\n+\t\t */\n+\t\tV4L2VideoDevice *dev = data->isp_[Isp::Output1].dev();\n+\t\tformat.fourcc = dev->toV4L2PixelFormat(cfg.pixelFormat);\n+\t\tformat.size = cfg.size;\n+\n+\t\tret = dev->setFormat(&format);\n+\t\tif (ret) {\n+\t\t\tLOG(RPI, Error)\n+\t\t\t\t<< \"Failed to set format on ISP capture1 device: \"\n+\t\t\t\t<< format.toString();\n+\t\t\treturn ret;\n+\t\t}\n+\t\t/*\n+\t\t * If we have not yet provided a stream for this config, it\n+\t\t * means this is to be routed from Output1.\n+\t\t */\n+\t\tif (!cfg.stream()) {\n+\t\t\tcfg.setStream(&data->isp_[Isp::Output1]);\n+\t\t\tcfg.stride = format.planes[0].bpl;\n+\t\t\tdata->isp_[Isp::Output1].setExternal(true);\n+\t\t}\n+\t}\n+\n+\t/* ISP statistics output format. */\n+\tformat = {};\n+\tformat.fourcc = V4L2PixelFormat(V4L2_META_FMT_BCM2835_ISP_STATS);\n+\tret = data->isp_[Isp::Stats].dev()->setFormat(&format);\n+\tif (ret) {\n+\t\tLOG(RPI, Error) << \"Failed to set format on ISP stats stream: \"\n+\t\t\t\t<< format.toString();\n+\t\treturn ret;\n+\t}\n+\n+\t/* Unicam embedded data output format. */\n+\tformat = {};\n+\tformat.fourcc = V4L2PixelFormat(V4L2_META_FMT_SENSOR_DATA);\n+\tLOG(RPI, Debug) << \"Setting embedded data format.\";\n+\tret = data->unicam_[Unicam::Embedded].dev()->setFormat(&format);\n+\tif (ret) {\n+\t\tLOG(RPI, Error) << \"Failed to set format on Unicam embedded: \"\n+\t\t\t\t<< format.toString();\n+\t\treturn ret;\n+\t}\n+\n+\t/* Adjust aspect ratio by providing crops on the input image. */\n+\tRectangle crop = {\n+\t\t.x = 0,\n+\t\t.y = 0,\n+\t\t.width = sensorFormat.size.width,\n+\t\t.height = sensorFormat.size.height\n+\t};\n+\n+\tint ar = maxSize.height * sensorFormat.size.width - maxSize.width * sensorFormat.size.height;\n+\tif (ar > 0)\n+\t\tcrop.width = maxSize.width * sensorFormat.size.height / maxSize.height;\n+\telse if (ar < 0)\n+\t\tcrop.height = maxSize.height * sensorFormat.size.width / maxSize.width;\n+\n+\tcrop.width &= ~1;\n+\tcrop.height &= ~1;\n+\n+\tcrop.x = (sensorFormat.size.width - crop.width) >> 1;\n+\tcrop.y = (sensorFormat.size.height - crop.height) >> 1;\n+\tdata->isp_[Isp::Input].dev()->setSelection(V4L2_SEL_TGT_CROP, &crop);\n+\n+\tret = configureIPA(camera);\n+\tif (ret)\n+\t\tLOG(RPI, Error) << \"Failed to configure the IPA: \" << ret;\n+\n+\treturn ret;\n+}\n+\n+int PipelineHandlerRPi::exportFrameBuffers(Camera *camera, Stream *stream,\n+\t\t\t\t\t   std::vector<std::unique_ptr<FrameBuffer>> *buffers)\n+{\n+\tRPiStream *s = static_cast<RPiStream *>(stream);\n+\tunsigned int count = stream->configuration().bufferCount;\n+\tint ret = s->dev()->exportBuffers(count, buffers);\n+\n+\ts->setExternalBuffers(buffers);\n+\n+\treturn ret;\n+}\n+\n+int PipelineHandlerRPi::start(Camera *camera)\n+{\n+\tRPiCameraData *data = cameraData(camera);\n+\tControlList controls(data->sensor_->controls());\n+\tint ret;\n+\n+\t/* Allocate buffers for internal pipeline usage. */\n+\tret = prepareBuffers(camera);\n+\tif (ret) {\n+\t\tLOG(RPI, Error) << \"Failed to allocate buffers\";\n+\t\treturn ret;\n+\t}\n+\n+\tret = queueAllBuffers(camera);\n+\tif (ret) {\n+\t\tLOG(RPI, Error) << \"Failed to queue buffers\";\n+\t\treturn ret;\n+\t}\n+\n+\t/*\n+\t * IPA configure may have changed the sensor flips - hence the bayer\n+\t * order. Get the sensor format and set the ISP input now.\n+\t */\n+\tV4L2DeviceFormat sensorFormat;\n+\tdata->unicam_[Unicam::Image].dev()->getFormat(&sensorFormat);\n+\tret = data->isp_[Isp::Input].dev()->setFormat(&sensorFormat);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/* Enable SOF event generation. */\n+\tdata->unicam_[Unicam::Image].dev()->setFrameStartEnabled(true);\n+\n+\t/*\n+\t * Write the last set of gain and exposure values to the camera before\n+\t * starting.  First check that the staggered ctrl has been initialised\n+\t * by the IPA action.\n+\t */\n+\tASSERT(data->staggeredCtrl_);\n+\tdata->staggeredCtrl_.reset();\n+\tdata->staggeredCtrl_.write();\n+\tdata->expectedSequence_ = 0;\n+\n+\tdata->state_ = RPiCameraData::State::Idle;\n+\n+\t/* Start all streams. */\n+\tfor (auto const stream : data->streams_) {\n+\t\tret = stream->dev()->streamOn();\n+\t\tif (ret) {\n+\t\t\tstop(camera);\n+\t\t\treturn ret;\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+void PipelineHandlerRPi::stop(Camera *camera)\n+{\n+\tRPiCameraData *data = cameraData(camera);\n+\n+\tdata->state_ = RPiCameraData::State::Stopped;\n+\n+\t/* Disable SOF event generation. */\n+\tdata->unicam_[Unicam::Image].dev()->setFrameStartEnabled(false);\n+\n+\t/* This also stops the streams. */\n+\tdata->clearIncompleteRequests();\n+\t/* The default std::queue constructor is explicit with gcc 5 and 6. */\n+\tdata->bayerQueue_ = std::queue<FrameBuffer *>{};\n+\tdata->embeddedQueue_ = std::queue<FrameBuffer *>{};\n+\n+\tfreeBuffers(camera);\n+}\n+\n+int PipelineHandlerRPi::queueRequestDevice(Camera *camera, Request *request)\n+{\n+\tRPiCameraData *data = cameraData(camera);\n+\n+\tif (data->state_ == RPiCameraData::State::Stopped)\n+\t\treturn -EINVAL;\n+\n+\t/* Ensure all external streams have associated buffers! */\n+\tfor (auto &stream : data->isp_) {\n+\t\tif (!stream.isExternal())\n+\t\t\tcontinue;\n+\n+\t\tif (!request->findBuffer(&stream)) {\n+\t\t\tLOG(RPI, Error) << \"Attempt to queue request with invalid stream.\";\n+\t\t\treturn -ENOENT;\n+\t\t}\n+\t}\n+\n+\t/* Push the request to the back of the queue. */\n+\tdata->requestQueue_.push_back(request);\n+\tdata->handleState();\n+\n+\treturn 0;\n+}\n+\n+bool PipelineHandlerRPi::match(DeviceEnumerator *enumerator)\n+{\n+\tDeviceMatch unicam(\"unicam\");\n+\tDeviceMatch isp(\"bcm2835-isp\");\n+\n+\tunicam.add(\"unicam-embedded\");\n+\tunicam.add(\"unicam-image\");\n+\n+\tisp.add(\"bcm2835-isp0-output0\"); /* Input */\n+\tisp.add(\"bcm2835-isp0-capture1\"); /* Output 0 */\n+\tisp.add(\"bcm2835-isp0-capture2\"); /* Output 1 */\n+\tisp.add(\"bcm2835-isp0-capture3\"); /* Stats */\n+\n+\tunicam_ = enumerator->search(unicam);\n+\tif (!unicam_)\n+\t\treturn false;\n+\n+\tisp_ = enumerator->search(isp);\n+\tif (!isp_)\n+\t\treturn false;\n+\n+\tunicam_->acquire();\n+\tisp_->acquire();\n+\n+\tstd::unique_ptr<RPiCameraData> data = std::make_unique<RPiCameraData>(this);\n+\n+\t/* Locate and open the unicam video streams. */\n+\tdata->unicam_[Unicam::Embedded] = RPiStream(\"Unicam Embedded\", unicam_->getEntityByName(\"unicam-embedded\"));\n+\tdata->unicam_[Unicam::Image] = RPiStream(\"Unicam Image\", unicam_->getEntityByName(\"unicam-image\"));\n+\n+\t/* Tag the ISP input stream as an import stream. */\n+\tdata->isp_[Isp::Input] = RPiStream(\"ISP Input\", isp_->getEntityByName(\"bcm2835-isp0-output0\"), true);\n+\tdata->isp_[Isp::Output0] = RPiStream(\"ISP Output0\", isp_->getEntityByName(\"bcm2835-isp0-capture1\"));\n+\tdata->isp_[Isp::Output1] = RPiStream(\"ISP Output1\", isp_->getEntityByName(\"bcm2835-isp0-capture2\"));\n+\tdata->isp_[Isp::Stats] = RPiStream(\"ISP Stats\", isp_->getEntityByName(\"bcm2835-isp0-capture3\"));\n+\n+\t/* This is just for convenience so that we can easily iterate over all streams. */\n+\tfor (auto &stream : data->unicam_)\n+\t\tdata->streams_.push_back(&stream);\n+\tfor (auto &stream : data->isp_)\n+\t\tdata->streams_.push_back(&stream);\n+\n+\t/* Open all Unicam and ISP streams. */\n+\tfor (auto const stream : data->streams_) {\n+\t\tif (stream->dev()->open())\n+\t\t\treturn false;\n+\t}\n+\n+\t/* Wire up all the buffer connections. */\n+\tdata->unicam_[Unicam::Image].dev()->frameStart.connect(data.get(), &RPiCameraData::frameStarted);\n+\tdata->unicam_[Unicam::Image].dev()->bufferReady.connect(data.get(), &RPiCameraData::unicamBufferDequeue);\n+\tdata->unicam_[Unicam::Embedded].dev()->bufferReady.connect(data.get(), &RPiCameraData::unicamBufferDequeue);\n+\tdata->isp_[Isp::Input].dev()->bufferReady.connect(data.get(), &RPiCameraData::ispInputDequeue);\n+\tdata->isp_[Isp::Output0].dev()->bufferReady.connect(data.get(), &RPiCameraData::ispOutputDequeue);\n+\tdata->isp_[Isp::Output1].dev()->bufferReady.connect(data.get(), &RPiCameraData::ispOutputDequeue);\n+\tdata->isp_[Isp::Stats].dev()->bufferReady.connect(data.get(), &RPiCameraData::ispOutputDequeue);\n+\n+\t/* Identify the sensor. */\n+\tfor (MediaEntity *entity : unicam_->entities()) {\n+\t\tif (entity->function() == MEDIA_ENT_F_CAM_SENSOR) {\n+\t\t\tdata->sensor_ = new CameraSensor(entity);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tif (!data->sensor_)\n+\t\treturn false;\n+\n+\tif (data->sensor_->init())\n+\t\treturn false;\n+\n+\tif (data->loadIPA()) {\n+\t\tLOG(RPI, Error) << \"Failed to load a suitable IPA library\";\n+\t\treturn false;\n+\t}\n+\n+\t/* Register the controls that the Raspberry Pi IPA can handle. */\n+\tdata->controlInfo_ = RPiControls;\n+\t/* Initialize the camera properties. */\n+\tdata->properties_ = data->sensor_->properties();\n+\n+\t/*\n+\t * List the available output streams.\n+\t * Currently cannot do Unicam streams!\n+\t */\n+\tstd::set<Stream *> streams;\n+\tstreams.insert(&data->isp_[Isp::Input]);\n+\tstreams.insert(&data->isp_[Isp::Output0]);\n+\tstreams.insert(&data->isp_[Isp::Output1]);\n+\tstreams.insert(&data->isp_[Isp::Stats]);\n+\n+\t/* Create and register the camera. */\n+\tstd::shared_ptr<Camera> camera = Camera::create(this, data->sensor_->model(), streams);\n+\tregisterCamera(std::move(camera), std::move(data));\n+\n+\treturn true;\n+}\n+\n+int PipelineHandlerRPi::configureIPA(Camera *camera)\n+{\n+\tstd::map<unsigned int, IPAStream> streamConfig;\n+\tstd::map<unsigned int, const ControlInfoMap &> entityControls;\n+\tRPiCameraData *data = cameraData(camera);\n+\n+\t/* Get the device format to pass to the IPA. */\n+\tV4L2DeviceFormat sensorFormat;\n+\tdata->unicam_[Unicam::Image].dev()->getFormat(&sensorFormat);\n+\t/* Inform IPA of stream configuration and sensor controls. */\n+\tint i = 0;\n+\tfor (auto const &stream : data->isp_) {\n+\t\tif (stream.isExternal()) {\n+\t\t\tstreamConfig[i] = {\n+\t\t\t\t.pixelFormat = stream.configuration().pixelFormat,\n+\t\t\t\t.size = stream.configuration().size\n+\t\t\t};\n+\t\t}\n+\t}\n+\tentityControls.emplace(0, data->unicam_[Unicam::Image].dev()->controls());\n+\tentityControls.emplace(1, data->isp_[Isp::Input].dev()->controls());\n+\n+\t/* Allocate the lens shading table via vcsm and pass to the IPA. */\n+\tif (!data->lsTable_) {\n+\t\tdata->lsTable_ = data->vcsm_.alloc(\"ls_grid\", MAX_LS_GRID_SIZE);\n+\t\tuintptr_t ptr = reinterpret_cast<uintptr_t>(data->lsTable_);\n+\n+\t\tif (!data->lsTable_)\n+\t\t\treturn -ENOMEM;\n+\n+\t\t/*\n+\t\t * The vcsm allocation will always be in the memory region\n+\t\t * < 32-bits to allow Videocore to access the memory.\n+\t\t */\n+\t\tIPAOperationData op;\n+\t\top.operation = RPI_IPA_EVENT_LS_TABLE_ALLOCATION;\n+\t\top.data = { static_cast<uint32_t>(ptr & 0xffffffff),\n+\t\t\t    data->vcsm_.getVCHandle(data->lsTable_) };\n+\t\tdata->ipa_->processEvent(op);\n+\t}\n+\n+\tCameraSensorInfo sensorInfo = {};\n+\tint ret = data->sensor_->sensorInfo(&sensorInfo);\n+\tif (ret) {\n+\t\tLOG(RPI, Error) << \"Failed to retrieve camera sensor info\";\n+\t\treturn ret;\n+\t}\n+\n+\t/* Ready the IPA - it must know about the sensor resolution. */\n+\tdata->ipa_->configure(sensorInfo, streamConfig, entityControls);\n+\n+\treturn 0;\n+}\n+\n+int PipelineHandlerRPi::queueAllBuffers(Camera *camera)\n+{\n+\tRPiCameraData *data = cameraData(camera);\n+\tint ret;\n+\n+\tfor (auto const stream : data->streams_) {\n+\t\tret = stream->queueBuffers();\n+\t\tif (ret < 0)\n+\t\t\treturn ret;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int PipelineHandlerRPi::prepareBuffers(Camera *camera)\n+{\n+\tRPiCameraData *data = cameraData(camera);\n+\tint count, ret;\n+\n+\t/*\n+\t * Decide how many internal buffers to allocate.  For now, simply\n+\t * look at how many external buffers will be provided.\n+\t * Will need to improve this logic.\n+\t */\n+\tunsigned int maxBuffers = 0;\n+\tfor (const Stream *s : camera->streams())\n+\t\tif (static_cast<const RPiStream *>(s)->isExternal())\n+\t\t\tmaxBuffers = std::max(maxBuffers, s->configuration().bufferCount);\n+\n+\tfor (auto const stream : data->streams_) {\n+\t\tif (stream->isExternal() || stream->isImporter()) {\n+\t\t\t/*\n+\t\t\t * If a stream is marked as external reserve memory to\n+\t\t\t * prepare to import as many buffers are requested in\n+\t\t\t * the stream configuration.\n+\t\t\t *\n+\t\t\t * If a stream is an internal stream with importer\n+\t\t\t * role, reserve as many buffers as possible.\n+\t\t\t */\n+\t\t\tunsigned int count = stream->isExternal()\n+\t\t\t\t\t\t     ? stream->configuration().bufferCount\n+\t\t\t\t\t\t     : maxBuffers;\n+\t\t\tret = stream->importBuffers(count);\n+\t\t\tif (ret < 0)\n+\t\t\t\treturn ret;\n+\t\t} else {\n+\t\t\t/*\n+\t\t\t * If the stream is an internal exporter allocate and\n+\t\t\t * export as many buffers as possible to its internal\n+\t\t\t * pool.\n+\t\t\t */\n+\t\t\tret = stream->allocateBuffers(maxBuffers);\n+\t\t\tif (ret < 0) {\n+\t\t\t\tfreeBuffers(camera);\n+\t\t\t\treturn ret;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Add cookies to the ISP Input buffers so that we can link them with\n+\t * the IPA and RPI_IPA_EVENT_SIGNAL_ISP_PREPARE event.\n+\t */\n+\tcount = 0;\n+\tfor (auto const &b : *data->unicam_[Unicam::Image].getBuffers()) {\n+\t\tb->setCookie(count++);\n+\t}\n+\n+\t/*\n+\t * Add cookies to the stats and embedded data buffers and link them with\n+\t * the IPA.\n+\t */\n+\tcount = 0;\n+\tfor (auto const &b : *data->isp_[Isp::Stats].getBuffers()) {\n+\t\tb->setCookie(count++);\n+\t\tdata->ipaBuffers_.push_back({ .id = RPiIpaMask::STATS | b->cookie(),\n+\t\t\t\t\t      .planes = b->planes() });\n+\t}\n+\n+\tcount = 0;\n+\tfor (auto const &b : *data->unicam_[Unicam::Embedded].getBuffers()) {\n+\t\tb->setCookie(count++);\n+\t\tdata->ipaBuffers_.push_back({ .id = RPiIpaMask::EMBEDDED_DATA | b->cookie(),\n+\t\t\t\t\t      .planes = b->planes() });\n+\t}\n+\n+\tdata->ipa_->mapBuffers(data->ipaBuffers_);\n+\n+\treturn 0;\n+}\n+\n+void PipelineHandlerRPi::freeBuffers(Camera *camera)\n+{\n+\tRPiCameraData *data = cameraData(camera);\n+\n+\tstd::vector<unsigned int> ids;\n+\tfor (IPABuffer &ipabuf : data->ipaBuffers_)\n+\t\tids.push_back(ipabuf.id);\n+\n+\tdata->ipa_->unmapBuffers(ids);\n+\tdata->ipaBuffers_.clear();\n+\n+\tfor (auto const stream : data->streams_)\n+\t\tstream->releaseBuffers();\n+}\n+\n+void RPiCameraData::frameStarted(uint32_t sequence)\n+{\n+\tLOG(RPI, Debug) << \"frame start \" << sequence;\n+\n+\t/* Write any controls for the next frame as soon as we can. */\n+\tstaggeredCtrl_.write();\n+}\n+\n+int RPiCameraData::loadIPA()\n+{\n+\tipa_ = IPAManager::instance()->createIPA(pipe_, 1, 1);\n+\tif (!ipa_)\n+\t\treturn -ENOENT;\n+\n+\tipa_->queueFrameAction.connect(this, &RPiCameraData::queueFrameAction);\n+\n+\tIPASettings settings{\n+\t\t.configurationFile = ipa_->configurationFile(sensor_->model() + \".json\")\n+\t};\n+\n+\tipa_->init(settings);\n+\n+\t/*\n+\t * Startup the IPA thread now. Without this call, none of the IPA API\n+\t * functions will run.\n+\t *\n+\t * It only gets stopped in the class destructor.\n+\t */\n+\treturn ipa_->start();\n+}\n+\n+void RPiCameraData::queueFrameAction(unsigned int frame, const IPAOperationData &action)\n+{\n+\t/*\n+\t * The following actions can be handled when the pipeline handler is in\n+\t * a stopped state.\n+\t */\n+\tswitch (action.operation) {\n+\tcase RPI_IPA_ACTION_V4L2_SET_STAGGERED: {\n+\t\tControlList controls = action.controls[0];\n+\t\tif (!staggeredCtrl_.set(controls))\n+\t\t\tLOG(RPI, Error) << \"V4L2 staggered set failed\";\n+\t\tgoto done;\n+\t}\n+\n+\tcase RPI_IPA_ACTION_SET_SENSOR_CONFIG: {\n+\t\t/*\n+\t\t * Setup our staggered control writer with the sensor default\n+\t\t * gain and exposure delays.\n+\t\t */\n+\t\tif (!staggeredCtrl_) {\n+\t\t\tstaggeredCtrl_.init(unicam_[Unicam::Image].dev(),\n+\t\t\t\t\t    { { V4L2_CID_ANALOGUE_GAIN, action.data[0] },\n+\t\t\t\t\t      { V4L2_CID_EXPOSURE, action.data[1] } });\n+\t\t\tsensorMetadata_ = action.data[2];\n+\t\t}\n+\n+\t\t/* Set the sensor orientation here as well. */\n+\t\tControlList controls = action.controls[0];\n+\t\tunicam_[Unicam::Image].dev()->setControls(&controls);\n+\t\tgoto done;\n+\t}\n+\n+\tcase RPI_IPA_ACTION_V4L2_SET_ISP: {\n+\t\tControlList controls = action.controls[0];\n+\t\tisp_[Isp::Input].dev()->setControls(&controls);\n+\t\tgoto done;\n+\t}\n+\t}\n+\n+\tif (state_ == State::Stopped)\n+\t\tgoto done;\n+\n+\t/*\n+\t * The following actions must not be handled when the pipeline handler\n+\t * is in a stopped state.\n+\t */\n+\tswitch (action.operation) {\n+\tcase RPI_IPA_ACTION_STATS_METADATA_COMPLETE: {\n+\t\tunsigned int bufferId = action.data[0];\n+\t\tFrameBuffer *buffer = isp_[Isp::Stats].getBuffers()->at(bufferId).get();\n+\n+\t\thandleStreamBuffer(buffer, &isp_[Isp::Stats]);\n+\t\t/* Fill the Request metadata buffer with what the IPA has provided */\n+\t\trequestQueue_.front()->metadata() = std::move(action.controls[0]);\n+\t\tstate_ = State::IpaComplete;\n+\t\tbreak;\n+\t}\n+\n+\tcase RPI_IPA_ACTION_EMBEDDED_COMPLETE: {\n+\t\tunsigned int bufferId = action.data[0];\n+\t\tFrameBuffer *buffer = unicam_[Unicam::Embedded].getBuffers()->at(bufferId).get();\n+\t\thandleStreamBuffer(buffer, &unicam_[Unicam::Embedded]);\n+\t\tbreak;\n+\t}\n+\n+\tcase RPI_IPA_ACTION_RUN_ISP_AND_DROP_FRAME:\n+\tcase RPI_IPA_ACTION_RUN_ISP: {\n+\t\tunsigned int bufferId = action.data[0];\n+\t\tFrameBuffer *buffer = unicam_[Unicam::Image].getBuffers()->at(bufferId).get();\n+\n+\t\tLOG(RPI, Debug) << \"Input re-queue to ISP, buffer id \" << buffer->cookie()\n+\t\t\t\t<< \", timestamp: \" << buffer->metadata().timestamp;\n+\n+\t\tisp_[Isp::Input].dev()->queueBuffer(buffer);\n+\t\tdropFrame_ = (action.operation == RPI_IPA_ACTION_RUN_ISP_AND_DROP_FRAME) ? true : false;\n+\t\tispOutputCount_ = 0;\n+\t\tbreak;\n+\t}\n+\n+\tdefault:\n+\t\tLOG(RPI, Error) << \"Unknown action \" << action.operation;\n+\t\tbreak;\n+\t}\n+\n+done:\n+\thandleState();\n+}\n+\n+void RPiCameraData::unicamBufferDequeue(FrameBuffer *buffer)\n+{\n+\tconst RPiStream *stream = nullptr;\n+\n+\tif (state_ == State::Stopped)\n+\t\treturn;\n+\n+\tfor (RPiStream const &s : unicam_) {\n+\t\tif (s.findFrameBuffer(buffer)) {\n+\t\t\tstream = &s;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\t/* The buffer must belong to one of our streams. */\n+\tASSERT(stream);\n+\n+\tLOG(RPI, Debug) << \"Stream \" << stream->name() << \" buffer dequeue\"\n+\t\t\t<< \", buffer id \" << buffer->cookie()\n+\t\t\t<< \", timestamp: \" << buffer->metadata().timestamp;\n+\n+\tif (stream == &unicam_[Unicam::Image]) {\n+\t\tbayerQueue_.push(buffer);\n+\t} else {\n+\t\tembeddedQueue_.push(buffer);\n+\n+\t\tstd::unordered_map<uint32_t, int32_t> ctrl;\n+\t\tint offset = buffer->metadata().sequence - expectedSequence_;\n+\t\tstaggeredCtrl_.get(ctrl, offset);\n+\n+\t\texpectedSequence_ = buffer->metadata().sequence + 1;\n+\n+\t\t/*\n+\t\t * Sensor metadata is unavailable, so put the expected ctrl\n+\t\t * values (accounting for the staggered delays) into the empty\n+\t\t * metadata buffer.\n+\t\t */\n+\t\tif (!sensorMetadata_) {\n+\t\t\tconst FrameBuffer &fb = buffer->planes();\n+\t\t\tuint32_t *mem = static_cast<uint32_t *>(::mmap(NULL, fb.planes()[0].length,\n+\t\t\t\t\t\t\t\t       PROT_READ | PROT_WRITE,\n+\t\t\t\t\t\t\t\t       MAP_SHARED,\n+\t\t\t\t\t\t\t\t       fb.planes()[0].fd.fd(), 0));\n+\t\t\tmem[0] = ctrl[V4L2_CID_EXPOSURE];\n+\t\t\tmem[1] = ctrl[V4L2_CID_ANALOGUE_GAIN];\n+\t\t\tmunmap(mem, fb.planes()[0].length);\n+\t\t}\n+\t}\n+\n+\thandleState();\n+}\n+\n+void RPiCameraData::ispInputDequeue(FrameBuffer *buffer)\n+{\n+\tif (state_ == State::Stopped)\n+\t\treturn;\n+\n+\thandleStreamBuffer(buffer, &unicam_[Unicam::Image]);\n+\thandleState();\n+}\n+\n+void RPiCameraData::ispOutputDequeue(FrameBuffer *buffer)\n+{\n+\tconst RPiStream *stream = nullptr;\n+\n+\tif (state_ == State::Stopped)\n+\t\treturn;\n+\n+\tfor (RPiStream const &s : isp_) {\n+\t\tif (s.findFrameBuffer(buffer)) {\n+\t\t\tstream = &s;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\t/* The buffer must belong to one of our ISP output streams. */\n+\tASSERT(stream);\n+\n+\tLOG(RPI, Debug) << \"Stream \" << stream->name() << \" buffer complete\"\n+\t\t\t<< \", buffer id \" << buffer->cookie()\n+\t\t\t<< \", timestamp: \" << buffer->metadata().timestamp;\n+\n+\thandleStreamBuffer(buffer, stream);\n+\n+\t/*\n+\t * Increment the number of ISP outputs generated.\n+\t * This is needed to track dropped frames.\n+\t */\n+\tispOutputCount_++;\n+\n+\t/* If this is a stats output, hand it to the IPA now. */\n+\tif (stream == &isp_[Isp::Stats]) {\n+\t\tIPAOperationData op;\n+\t\top.operation = RPI_IPA_EVENT_SIGNAL_STAT_READY;\n+\t\top.data = { RPiIpaMask::STATS | buffer->cookie() };\n+\t\tipa_->processEvent(op);\n+\t}\n+\n+\thandleState();\n+}\n+\n+void RPiCameraData::clearIncompleteRequests()\n+{\n+\t/*\n+\t * Queue up any buffers passed in the request.\n+\t * This is needed because streamOff() will then mark the buffers as\n+\t * cancelled.\n+\t */\n+\tfor (auto const request : requestQueue_) {\n+\t\tfor (auto const stream : streams_) {\n+\t\t\tif (stream->isExternal())\n+\t\t\t\tstream->dev()->queueBuffer(request->findBuffer(stream));\n+\t\t}\n+\t}\n+\n+\t/* Stop all streams. */\n+\tfor (auto const stream : streams_)\n+\t\tstream->dev()->streamOff();\n+\n+\t/*\n+\t * All outstanding requests (and associated buffers) must be returned\n+\t * back to the pipeline. The buffers would have been marked as\n+\t * cancelled by the call to streamOff() earlier.\n+\t */\n+\twhile (!requestQueue_.empty()) {\n+\t\tRequest *request = requestQueue_.front();\n+\t\t/*\n+\t\t * A request could be partially complete,\n+\t\t * i.e. we have returned some buffers, but still waiting\n+\t\t * for others or waiting for metadata.\n+\t\t */\n+\t\tfor (auto const stream : streams_) {\n+\t\t\tif (!stream->isExternal())\n+\t\t\t\tcontinue;\n+\n+\t\t\tFrameBuffer *buffer = request->findBuffer(stream);\n+\t\t\t/*\n+\t\t\t * Has the buffer already been handed back to the\n+\t\t\t * request? If not, do so now.\n+\t\t\t */\n+\t\t\tif (buffer->request())\n+\t\t\t\tpipe_->completeBuffer(camera_, request, buffer);\n+\t\t}\n+\n+\t\tpipe_->completeRequest(camera_, request);\n+\t\trequestQueue_.pop_front();\n+\t}\n+}\n+\n+void RPiCameraData::handleStreamBuffer(FrameBuffer *buffer, const RPiStream *stream)\n+{\n+\tif (stream->isExternal()) {\n+\t\tif (!dropFrame_) {\n+\t\t\tRequest *request = buffer->request();\n+\t\t\tpipe_->completeBuffer(camera_, request, buffer);\n+\t\t}\n+\t} else {\n+\t\t/* Special handling for RAW buffer Requests.\n+\t\t *\n+\t\t * The ISP input stream is alway an import stream, but if the\n+\t\t * current Request has been made for a buffer on the stream,\n+\t\t * simply memcpy to the Request buffer and requeue back to the\n+\t\t * device.\n+\t\t */\n+\t\tif (stream == &unicam_[Unicam::Image] && !dropFrame_) {\n+\t\t\tconst Stream *rawStream = static_cast<const Stream *>(&isp_[Isp::Input]);\n+\t\t\tRequest *request = requestQueue_.front();\n+\t\t\tFrameBuffer *raw = request->findBuffer(const_cast<Stream *>(rawStream));\n+\t\t\tif (raw) {\n+\t\t\t\traw->copyFrom(buffer);\n+\t\t\t\tpipe_->completeBuffer(camera_, request, raw);\n+\t\t\t}\n+\t\t}\n+\n+\t\t/* Simply requeue the buffer. */\n+\t\tstream->dev()->queueBuffer(buffer);\n+\t}\n+}\n+\n+void RPiCameraData::handleState()\n+{\n+\tswitch (state_) {\n+\tcase State::Stopped:\n+\tcase State::Busy:\n+\t\tbreak;\n+\n+\tcase State::IpaComplete:\n+\t\t/* If the request is completed, we will switch to Idle state. */\n+\t\tcheckRequestCompleted();\n+\t\t/*\n+\t\t * No break here, we want to try running the pipeline again.\n+\t\t * The fallthrough clause below suppresses compiler warnings.\n+\t\t */\n+\t\t/* Fall through */\n+\n+\tcase State::Idle:\n+\t\ttryRunPipeline();\n+\t\ttryFlushQueues();\n+\t\tbreak;\n+\t}\n+}\n+\n+void RPiCameraData::checkRequestCompleted()\n+{\n+\tbool requestCompleted = false;\n+\t/*\n+\t * If we are dropping this frame, do not touch the request, simply\n+\t * change the state to IDLE when ready.\n+\t */\n+\tif (!dropFrame_) {\n+\t\tRequest *request = requestQueue_.front();\n+\t\tif (request->hasPendingBuffers())\n+\t\t\treturn;\n+\n+\t\t/* Must wait for metadata to be filled in before completing. */\n+\t\tif (state_ != State::IpaComplete)\n+\t\t\treturn;\n+\n+\t\tpipe_->completeRequest(camera_, request);\n+\t\trequestQueue_.pop_front();\n+\t\trequestCompleted = true;\n+\t}\n+\n+\t/*\n+\t * Make sure we have three outputs completed in the case of a dropped\n+\t * frame.\n+\t */\n+\tif (state_ == State::IpaComplete &&\n+\t    ((ispOutputCount_ == 3 && dropFrame_) || requestCompleted)) {\n+\t\tstate_ = State::Idle;\n+\t\tif (dropFrame_)\n+\t\t\tLOG(RPI, Info) << \"Dropping frame at the request of the IPA\";\n+\t}\n+}\n+\n+void RPiCameraData::tryRunPipeline()\n+{\n+\tFrameBuffer *bayerBuffer, *embeddedBuffer;\n+\tIPAOperationData op;\n+\n+\t/* If any of our request or buffer queues are empty, we cannot proceed. */\n+\tif (state_ != State::Idle || requestQueue_.empty() ||\n+\t    bayerQueue_.empty() || embeddedQueue_.empty())\n+\t\treturn;\n+\n+\t/* Start with the front of the bayer buffer queue. */\n+\tbayerBuffer = bayerQueue_.front();\n+\n+\t/*\n+\t * Find the embedded data buffer with a matching timestamp to pass to\n+\t * the IPA. Any embedded buffers with a timestamp lower than the\n+\t * current bayer buffer will be removed and re-queued to the driver.\n+\t */\n+\tembeddedBuffer = updateQueue(embeddedQueue_, bayerBuffer->metadata().timestamp,\n+\t\t\t\t     unicam_[Unicam::Embedded].dev());\n+\n+\tif (!embeddedBuffer) {\n+\t\tLOG(RPI, Debug) << \"Could not find matching embedded buffer\";\n+\n+\t\t/*\n+\t\t * Look the other way, try to match a bayer buffer with the\n+\t\t * first embedded buffer in the queue. This will also do some\n+\t\t * housekeeping on the bayer image queue - clear out any\n+\t\t * buffers that are older than the first buffer in the embedded\n+\t\t * queue.\n+\t\t *\n+\t\t * But first check if the embedded queue has emptied out.\n+\t\t */\n+\t\tif (embeddedQueue_.empty())\n+\t\t\treturn;\n+\n+\t\tembeddedBuffer = embeddedQueue_.front();\n+\t\tbayerBuffer = updateQueue(bayerQueue_, embeddedBuffer->metadata().timestamp,\n+\t\t\t\t\t  unicam_[Unicam::Image].dev());\n+\n+\t\tif (!bayerBuffer) {\n+\t\t\tLOG(RPI, Debug) << \"Could not find matching bayer buffer - ending.\";\n+\t\t\treturn;\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Take the first request from the queue and action the IPA.\n+\t * Unicam buffers for the request have already been queued as they come\n+\t * in.\n+\t */\n+\tRequest *request = requestQueue_.front();\n+\n+\t/*\n+\t * Process all the user controls by the IPA.  Once this is complete, we\n+\t * queue the ISP output buffer listed in the request to start the HW\n+\t * pipeline.\n+\t */\n+\top.operation = RPI_IPA_EVENT_QUEUE_REQUEST;\n+\top.controls = { request->controls() };\n+\tipa_->processEvent(op);\n+\n+\t/* Queue up any ISP buffers passed into the request. */\n+\tfor (auto &stream : isp_) {\n+\t\tif (stream.isExternal())\n+\t\t\tstream.dev()->queueBuffer(request->findBuffer(&stream));\n+\t}\n+\n+\t/* Ready to use the buffers, pop them off the queue. */\n+\tbayerQueue_.pop();\n+\tembeddedQueue_.pop();\n+\n+\t/* Set our state to say the pipeline is active. */\n+\tstate_ = State::Busy;\n+\n+\tLOG(RPI, Debug) << \"Signalling RPI_IPA_EVENT_SIGNAL_ISP_PREPARE:\"\n+\t\t\t<< \" Bayer buffer id: \" << bayerBuffer->cookie()\n+\t\t\t<< \" Embedded buffer id: \" << embeddedBuffer->cookie();\n+\n+\top.operation = RPI_IPA_EVENT_SIGNAL_ISP_PREPARE;\n+\top.data = { RPiIpaMask::EMBEDDED_DATA | embeddedBuffer->cookie(),\n+\t\t    RPiIpaMask::BAYER_DATA | bayerBuffer->cookie() };\n+\tipa_->processEvent(op);\n+}\n+\n+void RPiCameraData::tryFlushQueues()\n+{\n+\t/*\n+\t * It is possible for us to end up in a situation where all available\n+\t * Unicam buffers have been dequeued but do not match.  This can happen\n+\t * when the system is heavily loaded and we get out of lock-step with\n+\t * the two channels.\n+\t *\n+\t * In such cases, the best thing to do is the re-queue all the buffers\n+\t * and give a chance for the hardware to return to lock-step.  We do\n+\t * have to drop all interim frames.\n+\t */\n+\tif (unicam_[Unicam::Image].getBuffers()->size() == bayerQueue_.size() &&\n+\t    unicam_[Unicam::Embedded].getBuffers()->size() == embeddedQueue_.size()) {\n+\t\tLOG(RPI, Warning) << \"Flushing all buffer queues!\";\n+\n+\t\twhile (!bayerQueue_.empty()) {\n+\t\t\tunicam_[Unicam::Image].dev()->queueBuffer(bayerQueue_.front());\n+\t\t\tbayerQueue_.pop();\n+\t\t}\n+\n+\t\twhile (!embeddedQueue_.empty()) {\n+\t\t\tunicam_[Unicam::Embedded].dev()->queueBuffer(embeddedQueue_.front());\n+\t\t\tembeddedQueue_.pop();\n+\t\t}\n+\t}\n+}\n+\n+FrameBuffer *RPiCameraData::updateQueue(std::queue<FrameBuffer *> &q, uint64_t timestamp,\n+\t\t\t\t\tV4L2VideoDevice *dev)\n+{\n+\twhile (!q.empty()) {\n+\t\tFrameBuffer *b = q.front();\n+\t\tif (b->metadata().timestamp < timestamp) {\n+\t\t\tq.pop();\n+\t\t\tdev->queueBuffer(b);\n+\t\t\tLOG(RPI, Error) << \"Dropping input frame!\";\n+\t\t} else if (b->metadata().timestamp == timestamp) {\n+\t\t\t/* The calling function will pop the item from the queue. */\n+\t\t\treturn b;\n+\t\t} else {\n+\t\t\tbreak; /* Only higher timestamps from here. */\n+\t\t}\n+\t}\n+\n+\treturn nullptr;\n+}\n+\n+REGISTER_PIPELINE_HANDLER(PipelineHandlerRPi);\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/pipeline/raspberrypi/staggered_ctrl.h b/src/libcamera/pipeline/raspberrypi/staggered_ctrl.h\nnew file mode 100644\nindex 000000000000..0403c087c686\n--- /dev/null\n+++ b/src/libcamera/pipeline/raspberrypi/staggered_ctrl.h\n@@ -0,0 +1,236 @@\n+/* SPDX-License-Identifier: BSD-2-Clause */\n+/*\n+ * Copyright (C) 2020, Raspberry Pi (Trading) Ltd.\n+ *\n+ * staggered_ctrl.h - Helper for writing staggered ctrls to a V4L2 device.\n+ */\n+#pragma once\n+\n+#include <algorithm>\n+#include <initializer_list>\n+#include <mutex>\n+#include <unordered_map>\n+\n+#include <libcamera/controls.h>\n+#include \"log.h\"\n+#include \"utils.h\"\n+#include \"v4l2_videodevice.h\"\n+\n+/* For logging... */\n+using libcamera::LogCategory;\n+using libcamera::LogDebug;\n+using libcamera::LogInfo;\n+using libcamera::utils::hex;\n+\n+LOG_DEFINE_CATEGORY(RPI_S_W);\n+\n+namespace RPi {\n+\n+class StaggeredCtrl\n+{\n+public:\n+\tStaggeredCtrl()\n+\t\t: init_(false), setCount_(0), getCount_(0), maxDelay_(0)\n+\t{\n+\t}\n+\n+\t~StaggeredCtrl()\n+\t{\n+\t}\n+\n+\toperator bool() const\n+\t{\n+\t\treturn init_;\n+\t}\n+\n+\tvoid init(libcamera::V4L2VideoDevice *dev,\n+\t\t  std::initializer_list<std::pair<const uint32_t, uint8_t>> delayList)\n+\t{\n+\t\tstd::lock_guard<std::mutex> lock(lock_);\n+\n+\t\tdev_ = dev;\n+\t\tdelay_ = delayList;\n+\t\tctrl_.clear();\n+\n+\t\t/* Find the largest delay across all controls. */\n+\t\tmaxDelay_ = 0;\n+\t\tfor (auto const &p : delay_) {\n+\t\t\tLOG(RPI_S_W, Info) << \"Init ctrl \"\n+\t\t\t\t\t   << hex(p.first) << \" with delay \"\n+\t\t\t\t\t   << static_cast<int>(p.second);\n+\t\t\tmaxDelay_ = std::max(maxDelay_, p.second);\n+\t\t}\n+\n+\t\tinit_ = true;\n+\t}\n+\n+\tvoid reset()\n+\t{\n+\t\tstd::lock_guard<std::mutex> lock(lock_);\n+\n+\t\tint lastSetCount = std::max<int>(0, setCount_ - 1);\n+\t\tstd::unordered_map<uint32_t, int32_t> lastVal;\n+\n+\t\t/* Reset the counters. */\n+\t\tsetCount_ = getCount_ = 0;\n+\n+\t\t/* Look for the last set values. */\n+\t\tfor (auto const &c : ctrl_)\n+\t\t\tlastVal[c.first] = c.second[lastSetCount].value;\n+\n+\t\t/* Apply the last set values as the next to be applied. */\n+\t\tctrl_.clear();\n+\t\tfor (auto &c : lastVal)\n+\t\t\tctrl_[c.first][setCount_] = CtrlInfo(c.second);\n+\t}\n+\n+\tbool set(uint32_t ctrl, int32_t value)\n+\t{\n+\t\tstd::lock_guard<std::mutex> lock(lock_);\n+\n+\t\t/* Can we find this ctrl as one that is registered? */\n+\t\tif (delay_.find(ctrl) == delay_.end())\n+\t\t\treturn false;\n+\n+\t\tctrl_[ctrl][setCount_].value = value;\n+\t\tctrl_[ctrl][setCount_].updated = true;\n+\n+\t\treturn true;\n+\t}\n+\n+\tbool set(std::initializer_list<std::pair<const uint32_t, int32_t>> ctrlList)\n+\t{\n+\t\tstd::lock_guard<std::mutex> lock(lock_);\n+\n+\t\tfor (auto const &p : ctrlList) {\n+\t\t\t/* Can we find this ctrl? */\n+\t\t\tif (delay_.find(p.first) == delay_.end())\n+\t\t\t\treturn false;\n+\n+\t\t\tctrl_[p.first][setCount_] = CtrlInfo(p.second);\n+\t\t}\n+\n+\t\treturn true;\n+\t}\n+\n+\tbool set(libcamera::ControlList &controls)\n+\t{\n+\t\tstd::lock_guard<std::mutex> lock(lock_);\n+\n+\t\tfor (auto const &p : controls) {\n+\t\t\t/* Can we find this ctrl? */\n+\t\t\tif (delay_.find(p.first) == delay_.end())\n+\t\t\t\treturn false;\n+\n+\t\t\tctrl_[p.first][setCount_] = CtrlInfo(p.second.get<int32_t>());\n+\t\t\tLOG(RPI_S_W, Debug) << \"Setting ctrl \"\n+\t\t\t\t\t    << hex(p.first) << \" to \"\n+\t\t\t\t\t    << ctrl_[p.first][setCount_].value\n+\t\t\t\t\t    << \" at index \"\n+\t\t\t\t\t    << setCount_;\n+\t\t}\n+\n+\t\treturn true;\n+\t}\n+\n+\tint write()\n+\t{\n+\t\tstd::lock_guard<std::mutex> lock(lock_);\n+\t\tlibcamera::ControlList controls(dev_->controls());\n+\n+\t\tfor (auto &p : ctrl_) {\n+\t\t\tint delayDiff = maxDelay_ - delay_[p.first];\n+\t\t\tint index = std::max<int>(0, setCount_ - delayDiff);\n+\n+\t\t\tif (p.second[index].updated) {\n+\t\t\t\t/* We need to write this value out. */\n+\t\t\t\tcontrols.set(p.first, p.second[index].value);\n+\t\t\t\tp.second[index].updated = false;\n+\t\t\t\tLOG(RPI_S_W, Debug) << \"Writing ctrl \"\n+\t\t\t\t\t\t    << hex(p.first) << \" to \"\n+\t\t\t\t\t\t    << p.second[index].value\n+\t\t\t\t\t\t    << \" at index \"\n+\t\t\t\t\t\t    << index;\n+\t\t\t}\n+\t\t}\n+\n+\t\tnextFrame();\n+\t\treturn dev_->setControls(&controls);\n+\t}\n+\n+\tvoid get(std::unordered_map<uint32_t, int32_t> &ctrl, uint8_t offset = 0)\n+\t{\n+\t\tstd::lock_guard<std::mutex> lock(lock_);\n+\n+\t\t/* Account for the offset to reset the getCounter. */\n+\t\tgetCount_ += offset + 1;\n+\n+\t\tctrl.clear();\n+\t\tfor (auto &p : ctrl_) {\n+\t\t\tint index = std::max<int>(0, getCount_ - maxDelay_);\n+\t\t\tctrl[p.first] = p.second[index].value;\n+\t\t\tLOG(RPI_S_W, Debug) << \"Getting ctrl \"\n+\t\t\t\t\t    << hex(p.first) << \" to \"\n+\t\t\t\t\t    << p.second[index].value\n+\t\t\t\t\t    << \" at index \"\n+\t\t\t\t\t    << index;\n+\t\t}\n+\t}\n+\n+private:\n+\tvoid nextFrame()\n+\t{\n+\t\t/* Advance the control history to the next frame */\n+\t\tint prevCount = setCount_;\n+\t\tsetCount_++;\n+\n+\t\tLOG(RPI_S_W, Debug) << \"Next frame, set index is \" << setCount_;\n+\n+\t\tfor (auto &p : ctrl_) {\n+\t\t\tp.second[setCount_].value = p.second[prevCount].value;\n+\t\t\tp.second[setCount_].updated = false;\n+\t\t}\n+\t}\n+\n+\t/* listSize must be a power of 2. */\n+\tstatic constexpr int listSize = (1 << 4);\n+\tstruct CtrlInfo {\n+\t\tCtrlInfo()\n+\t\t\t: value(0), updated(false)\n+\t\t{\n+\t\t}\n+\n+\t\tCtrlInfo(int32_t value_)\n+\t\t\t: value(value_), updated(true)\n+\t\t{\n+\t\t}\n+\n+\t\tint32_t value;\n+\t\tbool updated;\n+\t};\n+\n+\tclass CircularArray : public std::array<CtrlInfo, listSize>\n+\t{\n+\tpublic:\n+\t\tCtrlInfo &operator[](int index)\n+\t\t{\n+\t\t\treturn std::array<CtrlInfo, listSize>::operator[](index & (listSize - 1));\n+\t\t}\n+\n+\t\tconst CtrlInfo &operator[](int index) const\n+\t\t{\n+\t\t\treturn std::array<CtrlInfo, listSize>::operator[](index & (listSize - 1));\n+\t\t}\n+\t};\n+\n+\tbool init_;\n+\tuint32_t setCount_;\n+\tuint32_t getCount_;\n+\tuint8_t maxDelay_;\n+\tlibcamera::V4L2VideoDevice *dev_;\n+\tstd::unordered_map<uint32_t, uint8_t> delay_;\n+\tstd::unordered_map<uint32_t, CircularArray> ctrl_;\n+\tstd::mutex lock_;\n+};\n+\n+} /* namespace RPi */\ndiff --git a/src/libcamera/pipeline/raspberrypi/vcsm.h b/src/libcamera/pipeline/raspberrypi/vcsm.h\nnew file mode 100644\nindex 000000000000..fdce0050c26b\n--- /dev/null\n+++ b/src/libcamera/pipeline/raspberrypi/vcsm.h\n@@ -0,0 +1,144 @@\n+/* SPDX-License-Identifier: BSD-2-Clause */\n+/*\n+ * Copyright (C) 2019, Raspberry Pi (Trading) Limited\n+ *\n+ * vcsm.h - Helper class for vcsm allocations.\n+ */\n+#pragma once\n+\n+#include <iostream>\n+#include <mutex>\n+\n+#include <fcntl.h>\n+#include <linux/vc_sm_cma_ioctl.h>\n+#include <sys/ioctl.h>\n+#include <sys/mman.h>\n+#include <unistd.h>\n+\n+namespace RPi {\n+\n+#define VCSM_CMA_DEVICE_NAME \"/dev/vcsm-cma\"\n+\n+class Vcsm\n+{\n+public:\n+\tVcsm()\n+\t{\n+\t\tvcsmHandle_ = ::open(VCSM_CMA_DEVICE_NAME, O_RDWR, 0);\n+\t\tif (vcsmHandle_ == -1) {\n+\t\t\tstd::cerr << \"Could not open vcsm device: \"\n+\t\t\t\t  << VCSM_CMA_DEVICE_NAME;\n+\t\t}\n+\t}\n+\n+\t~Vcsm()\n+\t{\n+\t\t/* Free all existing allocations. */\n+\t\tauto it = allocMap_.begin();\n+\t\twhile (it != allocMap_.end())\n+\t\t\tit = remove(it->first);\n+\n+\t\tif (vcsmHandle_)\n+\t\t\t::close(vcsmHandle_);\n+\t}\n+\n+\tvoid *alloc(const char *name, unsigned int size,\n+\t\t    vc_sm_cma_cache_e cache = VC_SM_CMA_CACHE_NONE)\n+\t{\n+\t\tunsigned int pageSize = getpagesize();\n+\t\tvoid *user_ptr;\n+\t\tint ret;\n+\n+\t\t/* Ask for page aligned allocation. */\n+\t\tsize = (size + pageSize - 1) & ~(pageSize - 1);\n+\n+\t\tstruct vc_sm_cma_ioctl_alloc alloc;\n+\t\tmemset(&alloc, 0, sizeof(alloc));\n+\t\talloc.size = size;\n+\t\talloc.num = 1;\n+\t\talloc.cached = cache;\n+\t\talloc.handle = 0;\n+\t\tif (name != NULL)\n+\t\t\tmemcpy(alloc.name, name, 32);\n+\n+\t\tret = ::ioctl(vcsmHandle_, VC_SM_CMA_IOCTL_MEM_ALLOC, &alloc);\n+\n+\t\tif (ret < 0 || alloc.handle < 0) {\n+\t\t\tstd::cerr << \"vcsm allocation failure for \"\n+\t\t\t\t  << name << std::endl;\n+\t\t\treturn nullptr;\n+\t\t}\n+\n+\t\t/* Map the buffer into user space. */\n+\t\tuser_ptr = ::mmap(0, alloc.size, PROT_READ | PROT_WRITE,\n+\t\t\t\t  MAP_SHARED, alloc.handle, 0);\n+\n+\t\tif (user_ptr == MAP_FAILED) {\n+\t\t\tstd::cerr << \"vcsm mmap failure for \" << name << std::endl;\n+\t\t\t::close(alloc.handle);\n+\t\t\treturn nullptr;\n+\t\t}\n+\n+\t\tstd::lock_guard<std::mutex> lock(lock_);\n+\t\tallocMap_.emplace(user_ptr, AllocInfo(alloc.handle,\n+\t\t\t\t\t\t      alloc.size, alloc.vc_handle));\n+\n+\t\treturn user_ptr;\n+\t}\n+\n+\tvoid free(void *user_ptr)\n+\t{\n+\t\tstd::lock_guard<std::mutex> lock(lock_);\n+\t\tremove(user_ptr);\n+\t}\n+\n+\tunsigned int getVCHandle(void *user_ptr)\n+\t{\n+\t\tstd::lock_guard<std::mutex> lock(lock_);\n+\t\tauto it = allocMap_.find(user_ptr);\n+\t\tif (it != allocMap_.end())\n+\t\t\treturn it->second.vcHandle;\n+\n+\t\treturn 0;\n+\t}\n+\n+private:\n+\tstruct AllocInfo {\n+\t\tAllocInfo(int handle_, int size_, int vcHandle_)\n+\t\t\t: handle(handle_), size(size_), vcHandle(vcHandle_)\n+\t\t{\n+\t\t}\n+\n+\t\tint handle;\n+\t\tint size;\n+\t\tuint32_t vcHandle;\n+\t};\n+\n+\t/* Map of all allocations that have been requested. */\n+\tusing AllocMap = std::map<void *, AllocInfo>;\n+\n+\tAllocMap::iterator remove(void *user_ptr)\n+\t{\n+\t\tauto it = allocMap_.find(user_ptr);\n+\t\tif (it != allocMap_.end()) {\n+\t\t\tint handle = it->second.handle;\n+\t\t\tint size = it->second.size;\n+\t\t\t::munmap(user_ptr, size);\n+\t\t\t::close(handle);\n+\t\t\t/*\n+\t\t\t * Remove the allocation from the map. This returns\n+\t\t\t * an iterator to the next element.\n+\t\t\t */\n+\t\t\tit = allocMap_.erase(it);\n+\t\t}\n+\n+\t\t/* Returns an iterator to the next element. */\n+\t\treturn it;\n+\t}\n+\n+\tAllocMap allocMap_;\n+\tint vcsmHandle_;\n+\tstd::mutex lock_;\n+};\n+\n+} /* namespace RPi */\n",
    "prefixes": [
        "libcamera-devel",
        "3/6"
    ]
}