From 6d7ddace52782a5a977cd9c37558c9078948e53e Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Sun, 17 May 2020 23:56:26 +0300 Subject: cam: Add KMS sink class Add a KMSSink class to display framebuffers through the DRM/KMS API. Signed-off-by: Laurent Pinchart Reviewed-by: Paul Elder Reviewed-by: Umang Jain Reviewed-by: Kieran Bingham --- src/cam/kms_sink.cpp | 315 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/cam/kms_sink.h | 74 ++++++++++++ src/cam/meson.build | 1 + 3 files changed, 390 insertions(+) create mode 100644 src/cam/kms_sink.cpp create mode 100644 src/cam/kms_sink.h 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 +#include +#include +#include +#include + +#include +#include +#include +#include + +#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 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 &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 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(&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(&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(drmRequest, camRequest); + + std::lock_guard 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 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 +#include +#include +#include +#include + +#include + +#include +#include + +#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 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> buffers_; + + std::mutex lock_; + std::unique_ptr pending_; + std::unique_ptr queued_; + std::unique_ptr 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 -- cgit v1.2.1