diff --git a/src/libcamera/pipeline/ipu3/cio2.cpp b/src/libcamera/pipeline/ipu3/cio2.cpp
index 895848d2b3a1fba9..bbf18910c0e7abd4 100644
--- a/src/libcamera/pipeline/ipu3/cio2.cpp
+++ b/src/libcamera/pipeline/ipu3/cio2.cpp
@@ -205,31 +205,16 @@ int CIO2Device::exportBuffers(unsigned int count,
 	return output_->exportBuffers(count, buffers);
 }
 
-FrameBuffer *CIO2Device::getBuffer()
-{
-	if (availableBuffers_.empty()) {
-		LOG(IPU3, Error) << "CIO2 buffer underrun";
-		return nullptr;
-	}
-
-	FrameBuffer *buffer = availableBuffers_.front();
-
-	availableBuffers_.pop();
-
-	return buffer;
-}
-
-void CIO2Device::putBuffer(FrameBuffer *buffer)
-{
-	availableBuffers_.push(buffer);
-}
-
 int CIO2Device::start()
 {
-	int ret = output_->allocateBuffers(CIO2_BUFFER_COUNT, &buffers_);
+	int ret = output_->exportBuffers(CIO2_BUFFER_COUNT, &buffers_);
 	if (ret < 0)
 		return ret;
 
+	ret = output_->importBuffers(CIO2_BUFFER_COUNT);
+	if (ret)
+		LOG(IPU3, Error) << "Failed to import CIO2 buffers";
+
 	for (std::unique_ptr<FrameBuffer> &buffer : buffers_)
 		availableBuffers_.push(buffer.get());
 
@@ -253,11 +238,36 @@ int CIO2Device::stop()
 	return ret;
 }
 
-int CIO2Device::queueBuffer(FrameBuffer *buffer)
+int CIO2Device::queueBuffer(Request *request, FrameBuffer *rawBuffer)
 {
+	FrameBuffer *buffer = rawBuffer;
+
+	/* If no buffer provided in request, use an internal one. */
+	if (!buffer) {
+		if (availableBuffers_.empty()) {
+			LOG(IPU3, Error) << "CIO2 buffer underrun";
+			return -EINVAL;
+		}
+
+		buffer = availableBuffers_.front();
+		availableBuffers_.pop();
+	}
+
+	buffer->setRequest(request);
+
 	return output_->queueBuffer(buffer);
 }
 
+void CIO2Device::tryReturnBuffer(FrameBuffer *buffer)
+{
+	for (const std::unique_ptr<FrameBuffer> &buf : buffers_) {
+		if (buf.get() == buffer) {
+			availableBuffers_.push(buffer);
+			break;
+		}
+	}
+}
+
 V4L2PixelFormat CIO2Device::mediaBusToFormat(unsigned int code)
 {
 	switch (code) {
diff --git a/src/libcamera/pipeline/ipu3/cio2.h b/src/libcamera/pipeline/ipu3/cio2.h
index 032b91c082889a63..1a9b700bf4e7e15c 100644
--- a/src/libcamera/pipeline/ipu3/cio2.h
+++ b/src/libcamera/pipeline/ipu3/cio2.h
@@ -47,9 +47,6 @@ public:
 	int exportBuffers(unsigned int count,
 			  std::vector<std::unique_ptr<FrameBuffer>> *buffers);
 
-	FrameBuffer *getBuffer();
-	void putBuffer(FrameBuffer *buffer);
-
 	int start();
 	int stop();
 
@@ -57,7 +54,8 @@ public:
 	Size resolution() const { return sensor_->resolution(); }
 	ControlList properties() const { return sensor_->properties(); }
 
-	int queueBuffer(FrameBuffer *buffer);
+	int queueBuffer(Request *request, FrameBuffer *rawBuffer);
+	void tryReturnBuffer(FrameBuffer *buffer);
 	Signal<FrameBuffer *> bufferReady;
 
 private:
diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp
index 636c1c54627b5777..03e954d0f30d39cc 100644
--- a/src/libcamera/pipeline/ipu3/ipu3.cpp
+++ b/src/libcamera/pipeline/ipu3/ipu3.cpp
@@ -121,7 +121,6 @@ public:
 	}
 
 	void imguOutputBufferReady(FrameBuffer *buffer);
-	void imguInputBufferReady(FrameBuffer *buffer);
 	void cio2BufferReady(FrameBuffer *buffer);
 
 	CIO2Device cio2_;
@@ -730,25 +729,31 @@ void PipelineHandlerIPU3::stop(Camera *camera)
 int PipelineHandlerIPU3::queueRequestDevice(Camera *camera, Request *request)
 {
 	IPU3CameraData *data = cameraData(camera);
-	FrameBuffer *buffer;
 	int error = 0;
 
-	/* Get a CIO2 buffer, associate it with the request and queue it. */
-	buffer = data->cio2_.getBuffer();
-	if (!buffer)
-		return -EINVAL;
+	/* Search for a RAW buffer in the request, if any. */
+	FrameBuffer *rawBuffer = nullptr;
+	for (auto it : request->buffers()) {
+		IPU3Stream *stream = static_cast<IPU3Stream *>(it.first);
+		if (!stream->raw_)
+			continue;
 
-	buffer->setRequest(request);
-	data->cio2_.queueBuffer(buffer);
+		rawBuffer = it.second;
+		break;
+	}
 
+	/* Queue a buffer on the CIO2, the buffer may come from the request. */
+	error = data->cio2_.queueBuffer(request, rawBuffer);
+	if (error)
+		return error;
+
+	/* Queue all buffers from the request aimed for the ImgU. */
 	for (auto it : request->buffers()) {
 		IPU3Stream *stream = static_cast<IPU3Stream *>(it.first);
-		buffer = it.second;
-
-		/* Skip raw streams, they are copied from the CIO2 buffer. */
 		if (stream->raw_)
 			continue;
 
+		FrameBuffer *buffer = it.second;
 		int ret = stream->device_->dev->queueBuffer(buffer);
 		if (ret < 0)
 			error = ret;
@@ -878,8 +883,8 @@ int PipelineHandlerIPU3::registerCameras()
 		 */
 		data->cio2_.bufferReady.connect(data.get(),
 					&IPU3CameraData::cio2BufferReady);
-		data->imgu_->input_->bufferReady.connect(data.get(),
-					&IPU3CameraData::imguInputBufferReady);
+		data->imgu_->input_->bufferReady.connect(&data->cio2_,
+					&CIO2Device::tryReturnBuffer);
 		data->imgu_->output_.dev->bufferReady.connect(data.get(),
 					&IPU3CameraData::imguOutputBufferReady);
 		data->imgu_->viewfinder_.dev->bufferReady.connect(data.get(),
@@ -908,22 +913,6 @@ int PipelineHandlerIPU3::registerCameras()
  * Buffer Ready slots
  */
 
-/**
- * \brief Handle buffers completion at the ImgU input
- * \param[in] buffer The completed buffer
- *
- * Buffers completed from the ImgU input are immediately queued back to the
- * CIO2 unit to continue frame capture.
- */
-void IPU3CameraData::imguInputBufferReady(FrameBuffer *buffer)
-{
-	/* \todo Handle buffer failures when state is set to BufferError. */
-	if (buffer->metadata().status == FrameMetadata::FrameCancelled)
-		return;
-
-	cio2_.putBuffer(buffer);
-}
-
 /**
  * \brief Handle buffers completion at the ImgU output
  * \param[in] buffer The completed buffer
@@ -956,27 +945,20 @@ void IPU3CameraData::cio2BufferReady(FrameBuffer *buffer)
 		return;
 
 	Request *request = buffer->request();
-	FrameBuffer *raw = request->findBuffer(&rawStream_);
 
-	if (!raw) {
-		/* No RAW buffers present, just queue to IMGU. */
-		imgu_->input_->queueBuffer(buffer);
-		return;
-	}
-
-	/* RAW buffers present, special care is needed. */
-	if (request->buffers().size() > 1)
-		imgu_->input_->queueBuffer(buffer);
-
-	if (raw->copyFrom(buffer))
-		LOG(IPU3, Debug) << "Copy of FrameBuffer failed";
-
-	pipe_->completeBuffer(camera_, request, raw);
-
-	if (request->buffers().size() == 1) {
-		cio2_.putBuffer(buffer);
+	/*
+	 * If the requests contains a buffer for the RAW stream and the buffer
+	 * completed by the CIO2 satisfy all pending buffers in the request
+	 * complete the request now, there is no need for this request to be
+	 * processed by the ImGU.
+	 */
+	if (request->findBuffer(&rawStream_) &&
+	    pipe_->completeBuffer(camera_, request, buffer)) {
 		pipe_->completeRequest(camera_, request);
+		return;
 	}
+
+	imgu_->input_->queueBuffer(buffer);
 }
 
 /* -----------------------------------------------------------------------------
