Show a patch.

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

{
    "id": 26382,
    "url": "https://patchwork.libcamera.org/api/1.1/patches/26382/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/26382/",
    "project": {
        "id": 1,
        "url": "https://patchwork.libcamera.org/api/1.1/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": "<20260327144726.7983-4-david.plowman@raspberrypi.com>",
    "date": "2026-03-27T14:42:36",
    "name": "[v2,3/3] pipeline: rpi: Make control lists in requests properly atomic",
    "commit_ref": null,
    "pull_url": null,
    "state": "new",
    "archived": false,
    "hash": "d16f401f119d34ab5da747feffb7c2d724431689",
    "submitter": {
        "id": 42,
        "url": "https://patchwork.libcamera.org/api/1.1/people/42/?format=api",
        "name": "David Plowman",
        "email": "david.plowman@raspberrypi.com"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/26382/mbox/",
    "series": [
        {
            "id": 5854,
            "url": "https://patchwork.libcamera.org/api/1.1/series/5854/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=5854",
            "date": "2026-03-27T14:42:33",
            "name": "Atomic control lists on Raspberry Pi",
            "version": 2,
            "mbox": "https://patchwork.libcamera.org/series/5854/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/26382/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/26382/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 4D0F9C32DE\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 27 Mar 2026 14:47:36 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id DA8C362CD3;\n\tFri, 27 Mar 2026 15:47:35 +0100 (CET)",
            "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 7742C62CDD\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 27 Mar 2026 15:47:31 +0100 (CET)",
            "by mail-wm1-x335.google.com with SMTP id\n\t5b1f17b1804b1-48557c8ad47so16364115e9.0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 27 Mar 2026 07:47:31 -0700 (PDT)",
            "from localhost.localdomain ([2a06:61c0:f337:0:9c1f:b517:931a:3b19])\n\tby smtp.gmail.com with ESMTPSA id\n\t5b1f17b1804b1-48722be608bsm125858545e9.0.2026.03.27.07.47.30\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tFri, 27 Mar 2026 07:47:30 -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=\"GQQZlGo8\"; dkim-atps=neutral",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google; t=1774622851; x=1775227651;\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=OzFBxGG9gFDSlVha3fKLwZcDwW2k/RRGuvZ0LossX9k=;\n\tb=GQQZlGo8aYLDKIbxAliUkSG8hxNsCmvOqnevl3cdCCxEZfUcGcLH2FIL6XGKxgFAEP\n\tg5C7gXmOxLZvpI2dkkRCjf2XYAC8G1jOXHSaQn8dJ19Y6nvHLbIkKaI3G9k1hTnvkEE2\n\t5FnwPHlAVAfDD3hZoIEbM5MFJ9smIBowO1Y6PdP0KhMHCV1abO49DKB7aUF7mulmN85q\n\troMBEz9i8/Eajz5LyBEwd/IXFQXZLyh50VtYJP1nIWNnKILol3qCS0IWVEe0VD0ePG0N\n\ttzFAGUf56WOkkilinIWTIOEJXALv3ibpX9Po8b3/U4xudDTYUPc7QxCBqQW9SRXvLWwg\n\tjDzg==",
        "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20251104; t=1774622851; x=1775227651;\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=OzFBxGG9gFDSlVha3fKLwZcDwW2k/RRGuvZ0LossX9k=;\n\tb=lsY6QcNkwriwTIwuwIFKHt0bF2Wqbl/D+90pHxvmRr0JoNBKFlrXh3DkUGlfyby1Rx\n\tszj34kUa+HZ8B0RoZGFZ3B5UJnqN41I7hw+RzZRoLNwyxWm3REEbRFQxGcFHp35KYilO\n\tolhsNZVrtodOwainBYQE3y5SiJXVsHdJzBjp66pZ6pCpvcdwsl+tFmjVFlbSdjJCKWkY\n\tDv2euoy59EELUKBfY76N2MygWpLnQJM4Utz2NESvCjUOIPCRpVZGFrb2LBN1czOt9209\n\tlJ5rqCtPLSHIj6KRwyjUkR/ghpf9mYpS+zyjVT97i4aSsNB1OrV8v1HuToYbYwfSSzpu\n\t9R4g==",
        "X-Gm-Message-State": "AOJu0Yz+OzjP3PtP4g2dHut6dmJj17okprs44lGJIrxQfQ8582xgeQPG\n\tMNEyBm8l6KnSvmN5Oi8N28hGWBUAzw7qYgCAS1YFGlGqJF+0qwVe2cD8SMSSaHVOILa+T00qvSJ\n\tdcsxj",
        "X-Gm-Gg": "ATEYQzyc0Joa2Q+dzFkk3MYKIfp9I8u16N4yX5/MLDTppHEUbnTLzW3Mspl6i0AXANO\n\t37qXUvd+HaM91l1KDP68xUA4cPSqqHYcsVOXnsYlG+x34OH9C3MQ1nsRJJH1aAI32xzh1ZtGgAZ\n\tqHSHBKhUjsHfAX/iyJK0AedreDU8blnTdApr53OscnzAkUP9eZqpno7Yb8rLlR3E4svJIFOrbSv\n\tX43npCHPY6OwXpuuRvqeFRX7rL31G2dXPzAFwbGZlIIqDNd4F6vKPT2ux9WT1snvDhqMv44m27m\n\t6my3iImliFa9wpklIC2uG8OroNEHHfuRGc0qJ9kg8iHa/YY2jywarlzpGcoysE97SGlVzHDWfpP\n\tGV7YZZtCF4c7WuzGMG2pVta4I+Nc5MI0ClU+vevVkKHMuJz2APUHsIqeZn21TrHYgjlW0K9uLwM\n\tGFBnrYuxxL5TRjufFh+HRk6xL6JVPwjrjiP8P8AJRBOtx2jd5k8UnPfi6nHwtzEPS4j2yskeUAc\n\tmn2pKyray697PLfKIBOLaus6BaACm0xi4fDyQ==",
        "X-Received": "by 2002:a05:600c:4e55:b0:485:4533:9c47 with SMTP id\n\t5b1f17b1804b1-48727ec77a9mr50048745e9.22.1774622850459; \n\tFri, 27 Mar 2026 07:47:30 -0700 (PDT)",
        "From": "David Plowman <david.plowman@raspberrypi.com>",
        "To": "libcamera-devel@lists.libcamera.org",
        "Cc": "David Plowman <david.plowman@raspberrypi.com>,\n\tNaushir Patuck <naush@raspberrypi.com>",
        "Subject": "[PATCH v2 3/3] pipeline: rpi: Make control lists in requests\n\tproperly atomic",
        "Date": "Fri, 27 Mar 2026 14:42:36 +0000",
        "Message-ID": "<20260327144726.7983-4-david.plowman@raspberrypi.com>",
        "X-Mailer": "git-send-email 2.47.3",
        "In-Reply-To": "<20260327144726.7983-1-david.plowman@raspberrypi.com>",
        "References": "<20260327144726.7983-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 \"ControlListSequence\" metadata.\n\nSigned-off-by: David Plowman <david.plowman@raspberrypi.com>\nReviewed-by: Naushir Patuck <naush@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 &params)\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 &params)\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 &params)\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 &params)\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 867ecf1b..c6df0934 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 597eb587..65d8efdc 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 dff73a79..cc8aa4d4 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 b734889d..f743c8a7 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": [
        "v2",
        "3/3"
    ]
}