[libcamera-devel] [PATCH 3/3] apps: cam: kms_sink: Introduce a requests tracking queue

Umang Jain umang.jain at ideasonboard.com
Sun Apr 23 22:39:31 CEST 2023


Currently the queue depth tracking DRM completed requests is
effectively 2, via queued_ and pending_ class members in KMSSink.
This patch introduces a queue which can track more requests thus giving
a higher queue depth.

The reason to introduce a higher queue depth is to avoid use-after-free
on KMSSink::stop() in cases where KMSSink class is frequently operated
under: start() -> configure() -> stop() cycles. As soon as the
KMSSink::stop() is called, it used to free the queued_ and pending_
requests, but a DRM request can still complete asynchronously (and
after the queued_ and pending_ are freed). This led to use-after-free
segfault in Device::pageFlipComplete() while emitting the
`requestComplete` signal on a (already freed) request.

In the design introduced in this patch, the requests already in the queue
are marked as 'expired' and not freed in KMSSink::stop(). This prevents
the use-after-free segfault in Device::pageFlipComplete(). The expired
requests are dropped from the queue when new requests come into the
queue and gets completed in the KMSSink::requestComplete() handler.

Signed-off-by: Umang Jain <umang.jain at ideasonboard.com>
---
 src/apps/cam/kms_sink.cpp | 73 +++++++++++++++++++++------------------
 src/apps/cam/kms_sink.h   | 11 +++---
 2 files changed, 47 insertions(+), 37 deletions(-)

diff --git a/src/apps/cam/kms_sink.cpp b/src/apps/cam/kms_sink.cpp
index 2aefec06..8305e6de 100644
--- a/src/apps/cam/kms_sink.cpp
+++ b/src/apps/cam/kms_sink.cpp
@@ -24,7 +24,8 @@
 #include "drm.h"
 
 KMSSink::KMSSink(const std::string &connectorName)
-	: connector_(nullptr), crtc_(nullptr), plane_(nullptr), mode_(nullptr)
+	: connector_(nullptr), crtc_(nullptr), plane_(nullptr), mode_(nullptr),
+	  firstFrame_(false)
 {
 	int ret = dev_.init();
 	if (ret < 0)
@@ -327,6 +328,8 @@ int KMSSink::start()
 
 	dev_.requestComplete.connect(this, &KMSSink::requestComplete);
 
+	firstFrame_ = true;
+
 	return 0;
 }
 
@@ -334,6 +337,13 @@ int KMSSink::stop()
 {
 	dev_.requestComplete.disconnect();
 
+	{
+		std::lock_guard<std::mutex> lock(lock_);
+		/* Expire all the DRM requests in the queue */
+		for (std::unique_ptr<Request> &req : requests_)
+			req->expired_ = true;
+	}
+
 	/* Display pipeline. */
 	DRM::AtomicRequest request(&dev_);
 
@@ -352,9 +362,6 @@ int KMSSink::stop()
 	}
 
 	/* Free all buffers. */
-	pending_.reset();
-	queued_.reset();
-	active_.reset();
 	buffers_.clear();
 
 	return FrameSink::stop();
@@ -450,13 +457,6 @@ bool KMSSink::setupComposition(DRM::FrameBuffer *drmBuffer)
 
 bool KMSSink::processRequest(libcamera::Request *camRequest)
 {
-	/*
-	 * Perform a very crude rate adaptation by simply dropping the request
-	 * if the display queue is full.
-	 */
-	if (pending_)
-		return true;
-
 	libcamera::FrameBuffer *buffer = camRequest->buffers().begin()->second;
 	auto iter = buffers_.find(buffer);
 	if (iter == buffers_.end())
@@ -469,7 +469,7 @@ bool KMSSink::processRequest(libcamera::Request *camRequest)
 		std::make_unique<DRM::AtomicRequest>(&dev_);
 	drmRequest->addProperty(plane_, "FB_ID", drmBuffer->id());
 
-	if (!active_ && !queued_) {
+	if (firstFrame_) {
 		/* Enable the display pipeline on the first frame. */
 		if (!setupComposition(drmBuffer)) {
 			std::cerr << "Failed to setup composition" << std::endl;
@@ -497,22 +497,22 @@ bool KMSSink::processRequest(libcamera::Request *camRequest)
 			drmRequest->addProperty(plane_, "COLOR_RANGE", *colorRange_);
 
 		flags |= DRM::AtomicRequest::FlagAllowModeset;
+		firstFrame_ = false;
 	}
 
-	pending_ = std::make_unique<Request>(std::move(drmRequest), camRequest);
+	std::unique_ptr<Request> pending =
+		std::make_unique<Request>(std::move(drmRequest), camRequest);
 
 	std::lock_guard<std::mutex> lock(lock_);
 
-	if (!queued_) {
-		int ret = pending_->drmRequest_->commit(flags);
-		if (ret < 0) {
-			std::cerr
-				<< "Failed to commit atomic request: "
-				<< strerror(-ret) << std::endl;
-			/* \todo Implement error handling */
-		}
-
-		queued_ = std::move(pending_);
+	int ret = pending->drmRequest_->commit(flags);
+	if (ret < 0) {
+		std::cerr
+			<< "Failed to commit atomic request: "
+			<< strerror(-ret) << std::endl;
+		/* \todo Implement error handling */
+	} else {
+		requests_.push_back(std::move(pending));
 	}
 
 	return false;
@@ -522,18 +522,25 @@ void KMSSink::requestComplete([[maybe_unused]] DRM::AtomicRequest *request)
 {
 	std::lock_guard<std::mutex> lock(lock_);
 
-	assert(queued_ && queued_->drmRequest_.get() == request);
+	std::unique_ptr<Request> &headReq = requests_.front();
 
-	/* Complete the active request, if any. */
-	if (active_)
-		requestProcessed.emit(active_->camRequest_);
+	assert(headReq->drmRequest_.get() == request);
 
-	/* The queued request becomes active. */
-	active_ = std::move(queued_);
+	if (!headReq->expired_) {
+		requestProcessed.emit(headReq->camRequest_);
+		requests_.pop_front();
+	} else {
+		/* Remove candidates which are expired */
+		while (requests_.size() > 0) {
+			if (requests_.front()->expired_)
+				requests_.pop_front();
+			else
+				break;
+		}
 
-	/* Queue the pending request, if any. */
-	if (pending_) {
-		pending_->drmRequest_->commit(DRM::AtomicRequest::FlagAsync);
-		queued_ = std::move(pending_);
+		return;
 	}
+
+	if (requests_.size())
+		requests_.front()->drmRequest_->commit(DRM::AtomicRequest::FlagAsync);
 }
diff --git a/src/apps/cam/kms_sink.h b/src/apps/cam/kms_sink.h
index e2c618a1..a6b418aa 100644
--- a/src/apps/cam/kms_sink.h
+++ b/src/apps/cam/kms_sink.h
@@ -7,6 +7,7 @@
 
 #pragma once
 
+#include <deque>
 #include <list>
 #include <memory>
 #include <mutex>
@@ -41,12 +42,14 @@ private:
 	public:
 		Request(std::unique_ptr<DRM::AtomicRequest> drmRequest,
 			libcamera::Request *camRequest)
-			: drmRequest_(std::move(drmRequest)), camRequest_(camRequest)
+			: drmRequest_(std::move(drmRequest)), camRequest_(camRequest),
+			  expired_(false)
 		{
 		}
 
 		std::unique_ptr<DRM::AtomicRequest> drmRequest_;
 		libcamera::Request *camRequest_;
+		bool expired_;
 	};
 
 	int selectPipeline(const libcamera::PixelFormat &format);
@@ -76,8 +79,8 @@ private:
 
 	std::map<libcamera::FrameBuffer *, std::unique_ptr<DRM::FrameBuffer>> buffers_;
 
+	bool firstFrame_;
+
 	std::mutex lock_;
-	std::unique_ptr<Request> pending_;
-	std::unique_ptr<Request> queued_;
-	std::unique_ptr<Request> active_;
+	std::deque<std::unique_ptr<Request>> requests_;
 };
-- 
2.39.1



More information about the libcamera-devel mailing list