Patch Detail
Show a patch.
GET /api/patches/26587/?format=api
{ "id": 26587, "url": "https://patchwork.libcamera.org/api/patches/26587/?format=api", "web_url": "https://patchwork.libcamera.org/patch/26587/", "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": "<20260428133952.6582-4-david.plowman@raspberrypi.com>", "date": "2026-04-28T13:26:39", "name": "[v3,3/3] pipeline: rpi: Make control lists in requests properly atomic", "commit_ref": null, "pull_url": null, "state": "superseded", "archived": false, "hash": "d8b11ca89e1db5ae586f7790c9e8670a9ade6b39", "submitter": { "id": 42, "url": "https://patchwork.libcamera.org/api/people/42/?format=api", "name": "David Plowman", "email": "david.plowman@raspberrypi.com" }, "delegate": null, "mbox": "https://patchwork.libcamera.org/patch/26587/mbox/", "series": [ { "id": 5891, "url": "https://patchwork.libcamera.org/api/series/5891/?format=api", "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=5891", "date": "2026-04-28T13:26:36", "name": "Atomic control lists on Raspberry Pi", "version": 3, "mbox": "https://patchwork.libcamera.org/series/5891/mbox/" } ], "comments": "https://patchwork.libcamera.org/api/patches/26587/comments/", "check": "pending", "checks": "https://patchwork.libcamera.org/api/patches/26587/checks/", "tags": {}, "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 1F432C32F7\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 28 Apr 2026 13:40:06 +0000 (UTC)", "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id CE6AE62FE2;\n\tTue, 28 Apr 2026 15:40:03 +0200 (CEST)", "from mail-wm1-x335.google.com (mail-wm1-x335.google.com\n\t[IPv6:2a00:1450:4864:20::335])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 3F4FF62FD2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 28 Apr 2026 15:39:59 +0200 (CEST)", "by mail-wm1-x335.google.com with SMTP id\n\t5b1f17b1804b1-4891c00e7aeso87679225e9.2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 28 Apr 2026 06:39:59 -0700 (PDT)", "from davidp-pi5.pitowers.org\n\t([2a00:1098:3142:1f:88ea:c658:5b20:5e46])\n\tby smtp.gmail.com with ESMTPSA id\n\t5b1f17b1804b1-48a77c24ca0sm51374525e9.14.2026.04.28.06.39.58\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tTue, 28 Apr 2026 06:39:58 -0700 (PDT)" ], "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (2048-bit key;\n\tunprotected) header.d=raspberrypi.com header.i=@raspberrypi.com\n\theader.b=\"WYxaUeRp\"; dkim-atps=neutral", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google; t=1777383599; x=1777988399;\n\tdarn=lists.libcamera.org; \n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:from:to:cc:subject:date\n\t:message-id:reply-to;\n\tbh=aX68pVIfwS/iqGU8xne8xIfWg+LTMMw5YrnovQVx2yY=;\n\tb=WYxaUeRpP3QnFdHivheoEPFknhNeocSGoPS+H/BG0Em71D9v1GDzd1vk6t2F2OdrAu\n\tJGEnKZpHIHquO/LZcJ9yhGzPW99NKlUAvuxXrw1ubyxWjS+Lm7lG3+m4ktR4kNiZzlte\n\t7kAnY8nnX6SoYtD8F5qD4AE1bTfe3mBbDtQW7aW3pglyHWo6MjwFPGHy/YBO8epqlGkp\n\tph4LAHSf6Vjg/HTvKTlX5PtwgAJTAxtIjjN6AgdAo2/r3b8hv8FpIXQThNSh583XheXU\n\tfDaX9anyFzrTKtdCxbtIPZYmkn8U3mH+4OBMMxsJ7LRiqP0K8M28rW6E+9D6Dr9mUEJ+\n\tbT/Q==", "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20251104; t=1777383599; x=1777988399;\n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from\n\t:to:cc:subject:date:message-id:reply-to;\n\tbh=aX68pVIfwS/iqGU8xne8xIfWg+LTMMw5YrnovQVx2yY=;\n\tb=rqnHvhlq7d6bIhbkBht6Xxdg96YbiYvmJER0Ds1ApaIEv/Gr7sna7wGeNOjHmP/9DQ\n\tbL9zqtjkJyLWiAVCSyPe4SLRnRNdlDv1YzGizjqhd+CoeBQooYUe+hUw9SeyS6V4h6l6\n\tKRkBawVzqOBUPDY1AuoSw7ieFtyJLBWqI6CRid8T/Saevqqj1gGO9wE5RtBU7MGV0pWG\n\tjdHmtk3itFFoPksEoZIHJ+Ais/EEjqa/pIz6DR3gLorpNi0fEYVzN/MIb6sw4bHanqhS\n\tAAbPuJhteMnZC30rH/i9JRCyE7W8DkjXR+ePFkRX1PdtoAgFhFK5Poi56ikbIf+lZUJl\n\tsUyA==", "X-Gm-Message-State": "AOJu0YzEP50Nt7S6eEQ4KAcTznEI00k1QKi5dgJUOEfuy/jnQvui6GGs\n\tYm0X5EVa7MFGJz4G9TB+ypWd6pRCz4jpaBtw+xl8O/PST1lEqwXxyKAaIMNmIASni7TysnwPBBH\n\tbOihw", "X-Gm-Gg": "AeBDieul1LLvF63S2ODhh5PfbanT9/4eXKNpGbXvz1LGgoMOVKrcjo73X4HNb9BbeE/\n\txvqEUKMPtmvWI+ujjxvaziIsrilWV+ktv5ca7YX6nxJzKBbgOK9IJnHDVTWOPwKLmj2mOcLIhVV\n\tBpEbUgDxoobRDER8K1vVQ2vYAwfbf3dzu85ebeKqkrIvGMQgv7rtAQh8cOuLq8l2wVMWIsfALma\n\tf6EX0oDRh6x0ui1qdUQMPfyKfzf6aQuekcUU+7o8x4QFImZ1mSsXqsG2UNut85Vod6wxHVkrY0R\n\tWxeOuKcpRSHKlH/WiFLNXaASLP+66CxMSh/97nxSUl+cRUFG65LyypWW3pb19lPoO4YylOdGlNR\n\tHDXUqqSGVRcFJ19JMl92Dm6FOeA9m2WB7yFqNQyJNiY9No75qgwbaaMQaF+3hJew7e1JbngsdAC\n\tI8IxqQA7K+YvSk9s3P35JFZv8UTIb0oERMb4bTACMeDneaYSwr8v7WUHy/mCmOqVp/r/NvVAbOn\n\tQwfqXeW3FFr2jfSv94LkRmbMEUBjCwCThHUMzn3fdnlKmIbGCuE", "X-Received": "by 2002:a05:600c:3b0f:b0:487:20ee:bef6 with SMTP id\n\t5b1f17b1804b1-48a77afec2amr51180015e9.11.1777383598474; \n\tTue, 28 Apr 2026 06:39:58 -0700 (PDT)", "From": "David Plowman <david.plowman@raspberrypi.com>", "To": "libcamera-devel@lists.libcamera.org", "Cc": "David Plowman <david.plowman@raspberrypi.com>", "Subject": "[PATCH v3 3/3] pipeline: rpi: Make control lists in requests\n\tproperly atomic", "Date": "Tue, 28 Apr 2026 14:26:39 +0100", "Message-ID": "<20260428133952.6582-4-david.plowman@raspberrypi.com>", "X-Mailer": "git-send-email 2.47.3", "In-Reply-To": "<20260428133952.6582-1-david.plowman@raspberrypi.com>", "References": "<20260428133952.6582-1-david.plowman@raspberrypi.com>", "MIME-Version": "1.0", "Content-Transfer-Encoding": "8bit", "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>", "Errors-To": "libcamera-devel-bounces@lists.libcamera.org", "Sender": "\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>" }, "content": "When a request is about to be processed, this commit separates\n\"delayed controls\" (camera-related ones that take \"a few frames\" to\napply) from \"immediate controls\" (ISP-related controls) that will\nhappen instantly.\n\nThe immediate controls are held back until the delayed controls that\nthey were submitted with, have happened. This means all the controls\nsubmitted in a request happen atomically.\n\nWe therefore already have the sequence number of the request whose\ncontrols have just been applied, so we additionally attach this to the\ncurrent request as \"ControlId\" metadata.\n\nSigned-off-by: David Plowman <david.plowman@raspberrypi.com>\n---\n src/ipa/rpi/common/ipa_base.cpp | 24 ++++++----\n .../pipeline/rpi/common/pipeline_base.cpp | 46 +++++++++++++++++++\n .../pipeline/rpi/common/pipeline_base.h | 8 ++++\n src/libcamera/pipeline/rpi/pisp/pisp.cpp | 3 ++\n src/libcamera/pipeline/rpi/vc4/vc4.cpp | 3 ++\n 5 files changed, 76 insertions(+), 8 deletions(-)", "diff": "diff --git a/src/ipa/rpi/common/ipa_base.cpp b/src/ipa/rpi/common/ipa_base.cpp\nindex faa77719..dacafa57 100644\n--- a/src/ipa/rpi/common/ipa_base.cpp\n+++ b/src/ipa/rpi/common/ipa_base.cpp\n@@ -431,6 +431,15 @@ void IpaBase::prepareIsp(const PrepareParams ¶ms)\n \trpiMetadata.clear();\n \tfillDeviceStatus(params.sensorControls, ipaContext);\n \n+\t/*\n+\t * When there are controls, it's important that we don't skip running the\n+\t * IPAs, as that can mess with synchronisation. Crucially though, we need\n+\t * to know whether there were controls when this comes back as the\n+\t * _delayed_ metadata, hence why we flag this in the metadata itself.\n+\t */\n+\tif (!params.requestControls.empty())\n+\t\trpiMetadata.set(\"ipa.request_controls\", true);\n+\n \tif (params.buffers.embedded) {\n \t\t/*\n \t\t * Pipeline handler has supplied us with an embedded data buffer,\n@@ -451,7 +460,7 @@ void IpaBase::prepareIsp(const PrepareParams ¶ms)\n \t */\n \tAgcStatus agcStatus;\n \tbool hdrChange = false;\n-\tRPiController::Metadata &delayedMetadata = rpiMetadata_[params.delayContext];\n+\tRPiController::Metadata &delayedMetadata = rpiMetadata_[params.delayContext % rpiMetadata_.size()];\n \tif (!delayedMetadata.get<AgcStatus>(\"agc.status\", agcStatus)) {\n \t\trpiMetadata.set(\"agc.delayed_status\", agcStatus);\n \t\thdrChange = agcStatus.hdr.mode != hdrStatus_.mode;\n@@ -464,9 +473,13 @@ void IpaBase::prepareIsp(const PrepareParams ¶ms)\n \t */\n \thelper_->prepare(embeddedBuffer, rpiMetadata);\n \n+\tbool delayedRequestControls = false;\n+\tdelayedMetadata.get<bool>(\"ipa.request_controls\", delayedRequestControls);\n+\n \t/* Allow a 10% margin on the comparison below. */\n \tDuration delta = (frameTimestamp - lastRunTimestamp_) * 1.0ns;\n-\tif (lastRunTimestamp_ && frameCount_ > invalidCount_ &&\n+\tif (!delayedRequestControls && params.requestControls.empty() &&\n+\t lastRunTimestamp_ && frameCount_ > invalidCount_ &&\n \t delta < controllerMinFrameDuration_ * 0.9 && !hdrChange) {\n \t\t/*\n \t\t * Ensure we merge the previous frame's metadata with the current\n@@ -535,7 +548,7 @@ void IpaBase::processStats(const ProcessParams ¶ms)\n \t\tControlList ctrls(sensorCtrls_);\n \t\tapplyAGC(&agcStatus, ctrls);\n \t\trpiMetadata.set(\"agc.status\", agcStatus);\n-\t\tsetDelayedControls.emit(ctrls, ipaContext);\n+\t\tsetDelayedControls.emit(ctrls, params.ipaContext);\n \t\tsetCameraTimeoutValue();\n \t}\n \n@@ -951,8 +964,6 @@ void IpaBase::applyControls(const ControlList &controls)\n \n \t\t\t/* The control provides units of microseconds. */\n \t\t\tagc->setFixedExposureTime(0, ctrl.second.get<int32_t>() * 1.0us);\n-\n-\t\t\tlibcameraMetadata_.set(controls::ExposureTime, ctrl.second.get<int32_t>());\n \t\t\tbreak;\n \t\t}\n \n@@ -976,9 +987,6 @@ void IpaBase::applyControls(const ControlList &controls)\n \t\t\t\tbreak;\n \n \t\t\tagc->setFixedGain(0, ctrl.second.get<float>());\n-\n-\t\t\tlibcameraMetadata_.set(controls::AnalogueGain,\n-\t\t\t\t\t ctrl.second.get<float>());\n \t\t\tbreak;\n \t\t}\n \ndiff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp\nindex f6ef8a3b..28f44753 100644\n--- a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp\n+++ b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp\n@@ -1528,4 +1528,50 @@ void CameraData::fillRequestMetadata(const ControlList &bufferControls, Request\n \t}\n }\n \n+static bool isControlDelayed(unsigned int id)\n+{\n+\treturn id == controls::ExposureTime ||\n+\t id == controls::AnalogueGain ||\n+\t id == controls::FrameDurationLimits ||\n+\t id == controls::AeEnable ||\n+\t id == controls::ExposureTimeMode ||\n+\t id == controls::AnalogueGainMode;\n+}\n+\n+void CameraData::handleControlLists(uint32_t delayContext)\n+{\n+\t/*\n+\t * The delayContext is the sequence number after it's gone through the various\n+\t * pipeline delays, so that's what gets reported as the \"ControlListSequence\"\n+\t * in the metadata, being the sequence number of the request whose ControlList\n+\t * has just been applied.\n+\t */\n+\tRequest *request = requestQueue_.front();\n+\trequest->_d()->metadata().set(controls::rpi::ControlListSequence, delayContext);\n+\n+\t/*\n+\t * Controls that take effect immediately (typically ISP controls) have to be\n+\t * delayed so as to synchronise with those controls that do get delayed. So we\n+\t * must remove them from the current request, and push them onto a queue so\n+\t * that they can be used later.\n+\t */\n+\tControlList controls = std::move(request->controls());\n+\trequest->controls().clear();\n+\timmediateControls_.push({ request->sequence(), {} });\n+\tfor (const auto &ctrl : controls) {\n+\t\tif (isControlDelayed(ctrl.first))\n+\t\t\trequest->controls().set(ctrl.first, ctrl.second);\n+\t\telse\n+\t\t\timmediateControls_.back().controls.set(ctrl.first, ctrl.second);\n+\t}\n+\n+\t/* \"Immediate\" controls that have become due are now merged back into this request. */\n+\twhile (!immediateControls_.empty() &&\n+\t immediateControls_.front().controlListId <= delayContext) {\n+\t\trequest->controls().merge(immediateControls_.front().controls,\n+\t\t\t\t\t ControlList::MergePolicy::OverwriteExisting);\n+\t\timmediateControls_.pop();\n+\t}\n+}\n+\n } /* namespace libcamera */\ndiff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.h b/src/libcamera/pipeline/rpi/common/pipeline_base.h\nindex 7bfac33e..d3f044a4 100644\n--- a/src/libcamera/pipeline/rpi/common/pipeline_base.h\n+++ b/src/libcamera/pipeline/rpi/common/pipeline_base.h\n@@ -180,10 +180,18 @@ public:\n \n \tClockRecovery wallClockRecovery_;\n \n+\tstruct ImmediateControlsEntry {\n+\t\tuint64_t controlListId;\n+\t\tControlList controls;\n+\t};\n+\tstd::queue<ImmediateControlsEntry> immediateControls_;\n+\n protected:\n \tvoid fillRequestMetadata(const ControlList &bufferControls,\n \t\t\t\t Request *request);\n \n+\tvoid handleControlLists(uint32_t delayContext);\n+\n \tvirtual void tryRunPipeline() = 0;\n \n private:\ndiff --git a/src/libcamera/pipeline/rpi/pisp/pisp.cpp b/src/libcamera/pipeline/rpi/pisp/pisp.cpp\nindex c7799f2b..70bbac5a 100644\n--- a/src/libcamera/pipeline/rpi/pisp/pisp.cpp\n+++ b/src/libcamera/pipeline/rpi/pisp/pisp.cpp\n@@ -2322,6 +2322,9 @@ void PiSPCameraData::tryRunPipeline()\n \n \tfillRequestMetadata(job.sensorControls, request);\n \n+\t/* This sorts out synchronisation with ControlLists in earlier requests. */\n+\thandleControlLists(job.delayContext);\n+\n \t/* Set our state to say the pipeline is active. */\n \tstate_ = State::Busy;\n \ndiff --git a/src/libcamera/pipeline/rpi/vc4/vc4.cpp b/src/libcamera/pipeline/rpi/vc4/vc4.cpp\nindex f99cfdbc..a1b44af1 100644\n--- a/src/libcamera/pipeline/rpi/vc4/vc4.cpp\n+++ b/src/libcamera/pipeline/rpi/vc4/vc4.cpp\n@@ -939,6 +939,9 @@ void Vc4CameraData::tryRunPipeline()\n \n \tfillRequestMetadata(bayerFrame.controls, request);\n \n+\t/* This sorts out synchronisation with ControlLists in earlier requests. */\n+\thandleControlLists(bayerFrame.delayContext);\n+\n \t/* Set our state to say the pipeline is active. */\n \tstate_ = State::Busy;\n \n", "prefixes": [ "v3", "3/3" ] }