summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurent Pinchart <laurent.pinchart@ideasonboard.com>2020-05-17 23:56:26 +0300
committerLaurent Pinchart <laurent.pinchart@ideasonboard.com>2021-08-05 16:23:11 +0300
commit6d7ddace52782a5a977cd9c37558c9078948e53e (patch)
tree4839a9e922e08a3942c00b14589c6470f6604482
parent910b5253cb2bc81d0aee3efb98af5334f96e2c9c (diff)
cam: Add KMS sink class
Add a KMSSink class to display framebuffers through the DRM/KMS API. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com> Reviewed-by: Umang Jain <umang.jain@ideasonboard.com> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
-rw-r--r--src/cam/kms_sink.cpp315
-rw-r--r--src/cam/kms_sink.h74
-rw-r--r--src/cam/meson.build1
3 files changed, 390 insertions, 0 deletions
diff --git a/src/cam/kms_sink.cpp b/src/cam/kms_sink.cpp
new file mode 100644
index 00000000..35d2a242
--- /dev/null
+++ b/src/cam/kms_sink.cpp
@@ -0,0 +1,315 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2021, Ideas on Board Oy
+ *
+ * kms_sink.cpp - KMS Sink
+ */
+
+#include "kms_sink.h"
+
+#include <algorithm>
+#include <assert.h>
+#include <iostream>
+#include <memory>
+#include <string.h>
+
+#include <libcamera/camera.h>
+#include <libcamera/formats.h>
+#include <libcamera/framebuffer.h>
+#include <libcamera/stream.h>
+
+#include "drm.h"
+
+KMSSink::KMSSink(const std::string &connectorName)
+ : connector_(nullptr), crtc_(nullptr), plane_(nullptr), mode_(nullptr)
+{
+ int ret = dev_.init();
+ if (ret < 0)
+ return;
+
+ /*
+ * Find the requested connector. If no specific connector is requested,
+ * pick the first connected connector or, if no connector is connected,
+ * the first connector with unknown status.
+ */
+ for (const DRM::Connector &conn : dev_.connectors()) {
+ if (!connectorName.empty()) {
+ if (conn.name() != connectorName)
+ continue;
+
+ connector_ = &conn;
+ break;
+ }
+
+ if (conn.status() == DRM::Connector::Connected) {
+ connector_ = &conn;
+ break;
+ }
+
+ if (!connector_ && conn.status() == DRM::Connector::Unknown)
+ connector_ = &conn;
+ }
+
+ if (!connector_) {
+ if (!connectorName.empty())
+ std::cerr
+ << "Connector " << connectorName << " not found"
+ << std::endl;
+ else
+ std::cerr << "No connected connector found" << std::endl;
+ return;
+ }
+
+ dev_.requestComplete.connect(this, &KMSSink::requestComplete);
+}
+
+void KMSSink::mapBuffer(libcamera::FrameBuffer *buffer)
+{
+ std::unique_ptr<DRM::FrameBuffer> drmBuffer =
+ dev_.createFrameBuffer(*buffer, format_, size_, stride_);
+ if (!drmBuffer)
+ return;
+
+ buffers_.emplace(std::piecewise_construct,
+ std::forward_as_tuple(buffer),
+ std::forward_as_tuple(std::move(drmBuffer)));
+}
+
+int KMSSink::configure(const libcamera::CameraConfiguration &config)
+{
+ if (!connector_)
+ return -EINVAL;
+
+ crtc_ = nullptr;
+ plane_ = nullptr;
+ mode_ = nullptr;
+
+ const libcamera::StreamConfiguration &cfg = config.at(0);
+
+ const std::vector<DRM::Mode> &modes = connector_->modes();
+ const auto iter = std::find_if(modes.begin(), modes.end(),
+ [&](const DRM::Mode &mode) {
+ return mode.hdisplay == cfg.size.width &&
+ mode.vdisplay == cfg.size.height;
+ });
+ if (iter == modes.end()) {
+ std::cerr
+ << "No mode matching " << cfg.size.toString()
+ << std::endl;
+ return -EINVAL;
+ }
+
+ int ret = configurePipeline(cfg.pixelFormat);
+ if (ret < 0)
+ return ret;
+
+ mode_ = &*iter;
+ size_ = cfg.size;
+ stride_ = cfg.stride;
+
+ return 0;
+}
+
+int KMSSink::configurePipeline(const libcamera::PixelFormat &format)
+{
+ /*
+ * If the requested format has an alpha channel, also consider the X
+ * variant.
+ */
+ libcamera::PixelFormat xFormat;
+
+ switch (format) {
+ case libcamera::formats::ABGR8888:
+ xFormat = libcamera::formats::XBGR8888;
+ break;
+ case libcamera::formats::ARGB8888:
+ xFormat = libcamera::formats::XRGB8888;
+ break;
+ case libcamera::formats::BGRA8888:
+ xFormat = libcamera::formats::BGRX8888;
+ break;
+ case libcamera::formats::RGBA8888:
+ xFormat = libcamera::formats::RGBX8888;
+ break;
+ }
+
+ /*
+ * Find a CRTC and plane suitable for the request format and the
+ * connector at the end of the pipeline. Restrict the search to primary
+ * planes for now.
+ */
+ for (const DRM::Encoder *encoder : connector_->encoders()) {
+ for (const DRM::Crtc *crtc : encoder->possibleCrtcs()) {
+ for (const DRM::Plane *plane : crtc->planes()) {
+ if (plane->type() != DRM::Plane::TypePrimary)
+ continue;
+
+ if (plane->supportsFormat(format)) {
+ crtc_ = crtc;
+ plane_ = plane;
+ format_ = format;
+ return 0;
+ }
+
+ if (plane->supportsFormat(xFormat)) {
+ crtc_ = crtc;
+ plane_ = plane;
+ format_ = xFormat;
+ return 0;
+ }
+ }
+ }
+ }
+
+ std::cerr
+ << "Unable to find display pipeline for format "
+ << format.toString() << std::endl;
+
+ return -EPIPE;
+}
+
+int KMSSink::start()
+{
+ std::unique_ptr<DRM::AtomicRequest> request;
+
+ int ret = FrameSink::start();
+ if (ret < 0)
+ return ret;
+
+ /* Disable all CRTCs and planes to start from a known valid state. */
+ request = std::make_unique<DRM::AtomicRequest>(&dev_);
+
+ for (const DRM::Crtc &crtc : dev_.crtcs())
+ request->addProperty(&crtc, "ACTIVE", 0);
+
+ for (const DRM::Plane &plane : dev_.planes()) {
+ request->addProperty(&plane, "CRTC_ID", 0);
+ request->addProperty(&plane, "FB_ID", 0);
+ }
+
+ ret = request->commit(DRM::AtomicRequest::FlagAllowModeset);
+ if (ret < 0) {
+ std::cerr
+ << "Failed to disable CRTCs and planes: "
+ << strerror(-ret) << std::endl;
+ return ret;
+ }
+
+ /* Enable the display pipeline with no plane to start with. */
+ request = std::make_unique<DRM::AtomicRequest>(&dev_);
+
+ request->addProperty(connector_, "CRTC_ID", crtc_->id());
+ request->addProperty(crtc_, "ACTIVE", 1);
+ request->addProperty(crtc_, "MODE_ID", mode_->toBlob(&dev_));
+
+ ret = request->commit(DRM::AtomicRequest::FlagAllowModeset);
+ if (ret < 0) {
+ std::cerr
+ << "Failed to enable display pipeline: "
+ << strerror(-ret) << std::endl;
+ return ret;
+ }
+
+ planeInitialized_ = false;
+
+ return 0;
+}
+
+int KMSSink::stop()
+{
+ /* Display pipeline. */
+ DRM::AtomicRequest request(&dev_);
+
+ request.addProperty(connector_, "CRTC_ID", 0);
+ request.addProperty(crtc_, "ACTIVE", 0);
+ request.addProperty(crtc_, "MODE_ID", 0);
+ request.addProperty(plane_, "CRTC_ID", 0);
+ request.addProperty(plane_, "FB_ID", 0);
+
+ int ret = request.commit(DRM::AtomicRequest::FlagAllowModeset);
+ if (ret < 0) {
+ std::cerr
+ << "Failed to stop display pipeline: "
+ << strerror(-ret) << std::endl;
+ return ret;
+ }
+
+ /* Free all buffers. */
+ pending_.reset();
+ queued_.reset();
+ active_.reset();
+ buffers_.clear();
+
+ return FrameSink::stop();
+}
+
+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())
+ return true;
+
+ DRM::FrameBuffer *drmBuffer = iter->second.get();
+
+ DRM::AtomicRequest *drmRequest = new DRM::AtomicRequest(&dev_);
+ drmRequest->addProperty(plane_, "FB_ID", drmBuffer->id());
+
+ if (!planeInitialized_) {
+ drmRequest->addProperty(plane_, "CRTC_ID", crtc_->id());
+ drmRequest->addProperty(plane_, "SRC_X", 0 << 16);
+ drmRequest->addProperty(plane_, "SRC_Y", 0 << 16);
+ drmRequest->addProperty(plane_, "SRC_W", mode_->hdisplay << 16);
+ drmRequest->addProperty(plane_, "SRC_H", mode_->vdisplay << 16);
+ drmRequest->addProperty(plane_, "CRTC_X", 0);
+ drmRequest->addProperty(plane_, "CRTC_Y", 0);
+ drmRequest->addProperty(plane_, "CRTC_W", mode_->hdisplay);
+ drmRequest->addProperty(plane_, "CRTC_H", mode_->vdisplay);
+ planeInitialized_ = true;
+ }
+
+ pending_ = std::make_unique<Request>(drmRequest, camRequest);
+
+ std::lock_guard<std::mutex> lock(lock_);
+
+ if (!queued_) {
+ int ret = drmRequest->commit(DRM::AtomicRequest::FlagAsync);
+ if (ret < 0) {
+ std::cerr
+ << "Failed to commit atomic request: "
+ << strerror(-ret) << std::endl;
+ /* \todo Implement error handling */
+ }
+
+ queued_ = std::move(pending_);
+ }
+
+ return false;
+}
+
+void KMSSink::requestComplete(DRM::AtomicRequest *request)
+{
+ std::lock_guard<std::mutex> lock(lock_);
+
+ assert(queued_ && queued_->drmRequest_.get() == request);
+
+ /* Complete the active request, if any. */
+ if (active_)
+ requestProcessed.emit(active_->camRequest_);
+
+ /* The queued request becomes active. */
+ active_ = std::move(queued_);
+
+ /* Queue the pending request, if any. */
+ if (pending_) {
+ pending_->drmRequest_->commit(DRM::AtomicRequest::FlagAsync);
+ queued_ = std::move(pending_);
+ }
+}
diff --git a/src/cam/kms_sink.h b/src/cam/kms_sink.h
new file mode 100644
index 00000000..0c01df40
--- /dev/null
+++ b/src/cam/kms_sink.h
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2021, Ideas on Board Oy
+ *
+ * kms_sink.h - KMS Sink
+ */
+#ifndef __CAM_KMS_SINK_H__
+#define __CAM_KMS_SINK_H__
+
+#include <list>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <utility>
+
+#include <libcamera/base/signal.h>
+
+#include <libcamera/geometry.h>
+#include <libcamera/pixel_format.h>
+
+#include "drm.h"
+#include "frame_sink.h"
+
+class KMSSink : public FrameSink
+{
+public:
+ KMSSink(const std::string &connectorName);
+
+ void mapBuffer(libcamera::FrameBuffer *buffer) override;
+
+ int configure(const libcamera::CameraConfiguration &config) override;
+ int start() override;
+ int stop() override;
+
+ bool processRequest(libcamera::Request *request) override;
+
+private:
+ class Request
+ {
+ public:
+ Request(DRM::AtomicRequest *drmRequest, libcamera::Request *camRequest)
+ : drmRequest_(drmRequest), camRequest_(camRequest)
+ {
+ }
+
+ std::unique_ptr<DRM::AtomicRequest> drmRequest_;
+ libcamera::Request *camRequest_;
+ };
+
+ int configurePipeline(const libcamera::PixelFormat &format);
+ void requestComplete(DRM::AtomicRequest *request);
+
+ DRM::Device dev_;
+
+ const DRM::Connector *connector_;
+ const DRM::Crtc *crtc_;
+ const DRM::Plane *plane_;
+ const DRM::Mode *mode_;
+
+ libcamera::PixelFormat format_;
+ libcamera::Size size_;
+ unsigned int stride_;
+
+ bool planeInitialized_;
+
+ std::map<libcamera::FrameBuffer *, std::unique_ptr<DRM::FrameBuffer>> buffers_;
+
+ std::mutex lock_;
+ std::unique_ptr<Request> pending_;
+ std::unique_ptr<Request> queued_;
+ std::unique_ptr<Request> active_;
+};
+
+#endif /* __CAM_KMS_SINK_H__ */
diff --git a/src/cam/meson.build b/src/cam/meson.build
index b47add55..ea36aaa5 100644
--- a/src/cam/meson.build
+++ b/src/cam/meson.build
@@ -27,6 +27,7 @@ if libdrm.found()
cam_cpp_args += [ '-DHAVE_KMS' ]
cam_sources += files([
'drm.cpp',
+ 'kms_sink.cpp'
])
endif