From patchwork Fri Dec 5 14:52:11 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 25375 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id DF2EFC32AF for ; Fri, 5 Dec 2025 14:52:40 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 314DF613EB; Fri, 5 Dec 2025 15:52:37 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="vJFfz/Es"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 941A8606A0 for ; Fri, 5 Dec 2025 15:52:31 +0100 (CET) Received: from [192.168.1.4] (net-93-65-100-155.cust.vodafonedsl.it [93.65.100.155]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 6C47D1340; Fri, 5 Dec 2025 15:50:15 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1764946215; bh=A/dnpEvqJ410WD7m1lspdx6fWRD2/0pPEEWUV8JhJ4g=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=vJFfz/EsYaCCrP8FlAgr7r7I19X0nKe3n6syEKGNepa5J2+8I5L+8/s3FhoQkFpJI HuzSu6ohYRSLSdzJuTqcfmlgLPWO4auYcaltfktvzRsHlcOoku/jKr+LsCsYlP1Bbg /XmPDaHxZ07COlY1Ed6vUADTHic1em7PHgiacDOU= From: Jacopo Mondi Date: Fri, 05 Dec 2025 15:52:11 +0100 Subject: [PATCH 5/7] libcamera: mali-c55: Implement capture for memory-to-memory MIME-Version: 1.0 Message-Id: <20251205-mali-cru-v1-5-d81bb5ffe73a@ideasonboard.com> References: <20251205-mali-cru-v1-0-d81bb5ffe73a@ideasonboard.com> In-Reply-To: <20251205-mali-cru-v1-0-d81bb5ffe73a@ideasonboard.com> To: Daniel Scally , libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=9311; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=4UcvgR4scTrxJYrijGo+f5eFnqOvmZWIz+luVNrEiCo=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBpMvGtixsOn0mJ0Y4bp6ty0X9Tc4GwwAdWkIxwZ Y0oiQgUhh+JAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaTLxrQAKCRByNAaPFqFW PMx4D/0TYj/sO5NvolfcYCGsm+IJZ1PnM+Tz+HvGaPO3WC4hQtRhAUvWpwXu71PidXzqiOHXTe/ 0Md/DwMSzJ1wmjuFi9UNwF+hTGN2Eek3pjL8yV2n7HmgV1WP99WcFsZdksKSoeiqWPOTI54PBlr WXURmugqHHU1eEUNFpRhC3eZ1iZQsoLQOcfZDfyGsakr+9GUpT5WTidfsred9QMToItGJUO2LCX IfPv2skq2TTRTq7/nsWxl8XxhP3xgj5Et0SI1S+znE4pFYzsY6YU9/EwFQQ7MMLqxQTfNtVJUBh 1FGFPXRN1VF/FnGRyyTFnUmZ/bOZfI0S2ja2N5Qn67tKGYVxC+ctXe0ePRQEYGOyDg+DuVns7og ChAwh/+tuGyPxV28upf03XJfAaw6dP58AMTd7T+ChhuBV8+u6g70Em9QLJAIeNwt+4Hup/wZCQk jqXdvfK05CXojcS7GewSIEW03Z2rV9x9kUp/mBjIakLajrqlwcFuQuVWLxz6bco7HqcrYQkDMF4 e0N82BIiQIj2fBBTBFOarjt/3Qv3j3cnxWjmA/jA9K7dPoTzhr8vzB7cHRqNA4xIifFXzurkvJx a1PNlE0AEEGv49UIuZUy84cuoSf9yw3ZGmkY+wA4MxcHPmQ2eGnl1w7v4GCATeC55i2qCkhSxkn lfcZPHhYzgLbMeg== X-Developer-Key: i=jacopo.mondi@ideasonboard.com; a=openpgp; fpr=72392EDC88144A65C701EA9BA5826A2587AD026B X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" From: Daniel Scally Plumb in the MaliC55 pipeline handler support for capturing frames from memory using the CRU. Introduce a data flow which uses the CRU to feed the ISP through the IVC. In detail: - push incoming request to a pending queue until a buffer from the CRU is available - delay the call to ipa_->fillParams() to the CRU buffer ready even - once the IPA has computed parameters feed the ISP through the IVC with buffers from the CRU, params and statistics. Signed-off-by: Daniel Scally Signed-off-by: Jacopo Mondi --- src/libcamera/pipeline/mali-c55/mali-c55.cpp | 164 ++++++++++++++++++++++++++- 1 file changed, 159 insertions(+), 5 deletions(-) diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp index 6dce315c1c82db3554e8c0eae727cba9d632ca82..46ef5e7735a30a0d4ae9bb6f8d671bbd2dff3f51 100644 --- a/src/libcamera/pipeline/mali-c55/mali-c55.cpp +++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -86,6 +87,7 @@ struct MaliC55FrameInfo { FrameBuffer *paramBuffer; FrameBuffer *statBuffer; + FrameBuffer *rawBuffer; bool paramsDone; bool statsDone; @@ -692,11 +694,14 @@ public: int start(Camera *camera, const ControlList *controls) override; void stopDevice(Camera *camera) override; + int queuePendingRequests(); + void cancelPendingRequests(); int queueRequestDevice(Camera *camera, Request *request) override; void imageBufferReady(FrameBuffer *buffer); void paramsBufferReady(FrameBuffer *buffer); void statsBufferReady(FrameBuffer *buffer); + void cruBufferReady(FrameBuffer *buffer); void paramsComputed(unsigned int requestId, uint32_t bytesused); void statsProcessed(unsigned int requestId, const ControlList &metadata); @@ -778,6 +783,11 @@ private: std::map frameInfoMap_; + /* Requests for which no buffer has been queued to the CRU device yet. */ + std::queue pendingRequests_; + /* Requests queued to the CRU device but not yet processed by the ISP. */ + std::queue processingRequests_; + std::array pipes_; bool dsFitted_; @@ -1235,6 +1245,11 @@ void PipelineHandlerMaliC55::freeBuffers(Camera *camera) if (params_->releaseBuffers()) LOG(MaliC55, Error) << "Failed to release params buffers"; + if (data->input_ == MaliC55CameraData::Memory) { + if (input_->releaseBuffers()) + LOG(MaliC55, Error) << "Failed to release input buffers"; + } + return; } @@ -1264,6 +1279,12 @@ int PipelineHandlerMaliC55::allocateBuffers(Camera *camera) } }; + if (input_) { + ret = input_->importBuffers(RZG2LCRU::kBufferCount); + if (ret < 0) + return ret; + } + ret = stats_->allocateBuffers(bufferCount, &statsBuffers_); if (ret < 0) return ret; @@ -1295,6 +1316,24 @@ int PipelineHandlerMaliC55::start(Camera *camera, [[maybe_unused]] const Control if (ret) return ret; + if (data->input_ == MaliC55CameraData::Memory) { + ret = data->memoryInput.cru_->start(); + if (ret) { + LOG(MaliC55, Error) + << "Failed to start CRU " << camera->id(); + freeBuffers(camera); + return ret; + } + + ret = input_->streamOn(); + if (ret) { + LOG(MaliC55, Error) + << "Failed to start IVC" << camera->id(); + freeBuffers(camera); + return ret; + } + } + if (data->ipa_) { ret = data->ipa_->start(); if (ret) { @@ -1384,6 +1423,12 @@ void PipelineHandlerMaliC55::stopDevice(Camera *camera) pipe.cap->releaseBuffers(); } + if (data->input_ == MaliC55CameraData::Memory) { + cancelPendingRequests(); + input_->streamOff(); + data->memoryInput.cru_->stop(); + } + stats_->streamOff(); params_->streamOff(); if (data->ipa_) @@ -1488,10 +1533,87 @@ void PipelineHandlerMaliC55::applyScalerCrop(Camera *camera, } } +void PipelineHandlerMaliC55::cancelPendingRequests() +{ + processingRequests_ = {}; + + while (!pendingRequests_.empty()) { + Request *request = pendingRequests_.front(); + + completeRequest(request); + pendingRequests_.pop(); + } +} + +int PipelineHandlerMaliC55::queuePendingRequests() +{ + while (!pendingRequests_.empty()) { + Request *request = pendingRequests_.front(); + + if (availableStatsBuffers_.empty()) { + LOG(MaliC55, Error) << "Stats buffer underrun"; + return -ENOENT; + } + + if (availableParamsBuffers_.empty()) { + LOG(MaliC55, Error) << "Params buffer underrun"; + return -ENOENT; + } + + MaliC55FrameInfo frameInfo; + frameInfo.request = request; + + MaliC55CameraData *data = cameraData(request->_d()->camera()); + frameInfo.rawBuffer = data->memoryInput.cru_->queueBuffer(request); + if (!frameInfo.rawBuffer) + return -ENOENT; + + frameInfo.statBuffer = availableStatsBuffers_.front(); + availableStatsBuffers_.pop(); + frameInfo.paramBuffer = availableParamsBuffers_.front(); + availableParamsBuffers_.pop(); + + frameInfo.paramsDone = false; + frameInfo.statsDone = false; + + frameInfoMap_[request->sequence()] = frameInfo; + + for (auto &[stream, buffer] : request->buffers()) { + MaliC55Pipe *pipe = pipeFromStream(data, stream); + + pipe->cap->queueBuffer(buffer); + } + + data->ipa_->queueRequest(request->sequence(), request->controls()); + + pendingRequests_.pop(); + processingRequests_.push(request); + } + + return 0; +} + int PipelineHandlerMaliC55::queueRequestDevice(Camera *camera, Request *request) { MaliC55CameraData *data = cameraData(camera); + /* + * If we're in memory input mode, we need to pop the requests onto the + * pending list until a CRU buffer is ready...otherwise we can just do + * everything immediately. + */ + if (data->input_ == MaliC55CameraData::Memory) { + pendingRequests_.push(request); + + int ret = queuePendingRequests(); + if (ret) { + pendingRequests_.pop(); + return ret; + } + + return 0; + } + /* Do not run the IPA if the TPG is in use. */ if (!data->ipa_) { MaliC55FrameInfo frameInfo; @@ -1556,7 +1678,8 @@ MaliC55FrameInfo *PipelineHandlerMaliC55::findFrameInfo(FrameBuffer *buffer) { for (auto &[sequence, info] : frameInfoMap_) { if (info.paramBuffer == buffer || - info.statBuffer == buffer) + info.statBuffer == buffer || + info.rawBuffer == buffer) return &info; } @@ -1618,6 +1741,26 @@ void PipelineHandlerMaliC55::statsBufferReady(FrameBuffer *buffer) sensorControls); } +void PipelineHandlerMaliC55::cruBufferReady(FrameBuffer *buffer) +{ + MaliC55FrameInfo *info = findFrameInfo(buffer); + Request *request = info->request; + ASSERT(info); + + if (buffer->metadata().status == FrameMetadata::FrameCancelled) { + frameInfoMap_.erase(request->sequence()); + completeRequest(request); + return; + } + + request->metadata().set(controls::SensorTimestamp, + buffer->metadata().timestamp); + + /* Ought we do something with the sensor's controls here...? */ + MaliC55CameraData *data = cameraData(request->_d()->camera()); + data->ipa_->fillParams(request->sequence(), info->paramBuffer->cookie()); +} + void PipelineHandlerMaliC55::paramsComputed(unsigned int requestId, uint32_t bytesused) { MaliC55FrameInfo &frameInfo = frameInfoMap_[requestId]; @@ -1626,18 +1769,27 @@ void PipelineHandlerMaliC55::paramsComputed(unsigned int requestId, uint32_t byt /* * Queue buffers for stats and params, then queue buffers to the capture - * video devices. + * video devices if we're running in Inline mode or with the TPG. + * + * If we're running in M2M buffers have been queued to the capture + * devices at queuePendingRequests() time and here we only have to queue + * buffers to the IVC input to start a transfer. */ frameInfo.paramBuffer->_d()->metadata().planes()[0].bytesused = bytesused; params_->queueBuffer(frameInfo.paramBuffer); stats_->queueBuffer(frameInfo.statBuffer); - for (auto &[stream, buffer] : request->buffers()) { - MaliC55Pipe *pipe = pipeFromStream(data, stream); + if (data->input_ != MaliC55CameraData::Memory) { + for (auto &[stream, buffer] : request->buffers()) { + MaliC55Pipe *pipe = pipeFromStream(data, stream); - pipe->cap->queueBuffer(buffer); + pipe->cap->queueBuffer(buffer); + } } + + if (data->input_ == MaliC55CameraData::Memory) + input_->queueBuffer(frameInfo.rawBuffer); } void PipelineHandlerMaliC55::statsProcessed(unsigned int requestId, @@ -1768,6 +1920,8 @@ bool PipelineHandlerMaliC55::registerMemoryInputCamera() isp_->frameStart.connect(data->delayedCtrls_.get(), &DelayedControls::applyControls); + V4L2VideoDevice *cruOutput = data->memoryInput.cru_->output(); + cruOutput->bufferReady.connect(this, &PipelineHandlerMaliC55::cruBufferReady); input_->bufferReady.connect(data->memoryInput.cru_.get(), &RZG2LCRU::cruReturnBuffer);