From patchwork Fri Jul 30 01:03:04 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 13152 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 2E2FEC3234 for ; Fri, 30 Jul 2021 01:03:28 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8F4C968811; Fri, 30 Jul 2021 03:03:27 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="DpxieMhg"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id ADEC0687BE for ; Fri, 30 Jul 2021 03:03:20 +0200 (CEST) Received: from pendragon.lan (62-78-145-57.bb.dnainternet.fi [62.78.145.57]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 43BDB9FB for ; Fri, 30 Jul 2021 03:03:20 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1627607000; bh=lKsht/tckm4R7RvSQf5+sJ8RSPNPRLnckLFWl3g+fFU=; h=From:To:Subject:Date:In-Reply-To:References:From; b=DpxieMhgpQqUNtJW8V9/D9YR7pFbXoVADXxrMr5Bbyfofl4HmD1FXMUU2h9tteZYs CZazCFWHemXk8ZJbj1BdtiOWAxGZlD0LDknYs8kju6homK6aTnaEbGvZG7OpUJyDrO M7g07duggnromqOdnPoYG8M7EJ2/fifqODnzOeKU= From: Laurent Pinchart To: libcamera-devel@lists.libcamera.org Date: Fri, 30 Jul 2021 04:03:04 +0300 Message-Id: <20210730010306.19956-7-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210730010306.19956-1-laurent.pinchart@ideasonboard.com> References: <20210730010306.19956-1-laurent.pinchart@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 6/8] cam: Add KMS sink class 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" Add a KMSSink class to display framebuffers through the DRM/KMS API. Signed-off-by: Laurent Pinchart --- Changes since v1: - Use formats:: namespace --- src/cam/kms_sink.cpp | 298 +++++++++++++++++++++++++++++++++++++++++++ src/cam/kms_sink.h | 76 +++++++++++ src/cam/meson.build | 1 + 3 files changed, 375 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 000000000000..b8f86dcb6f4e --- /dev/null +++ b/src/cam/kms_sink.cpp @@ -0,0 +1,298 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020, 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 connector is requested, pick the + * first connected connector. + */ + for (const DRM::Connector &conn : dev_.connectors()) { + if (conn.name() == connectorName) { + connector_ = &conn; + break; + } + + if (conn.status() != DRM::Connector::Disconnected) { + if (!connector_ || + (connector_->status() == DRM::Connector::Unknown && + conn.status() == DRM::Connector::Connected)) + 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) +{ + crtc_ = nullptr; + plane_ = nullptr; + mode_ = nullptr; + + const libcamera::StreamConfiguration &cfg = config.at(0); + int ret = configurePipeline(cfg.pixelFormat); + if (ret < 0) + return ret; + + 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; + } + + 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::consumeRequest(libcamera::Request *camRequest) +{ + 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; + 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_) + requestReleased.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 000000000000..7b6ffcede28c --- /dev/null +++ b/src/cam/kms_sink.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020, 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); + + bool isValid() const { return connector_ != nullptr; } + + void mapBuffer(libcamera::FrameBuffer *buffer) override; + + int configure(const libcamera::CameraConfiguration &config) override; + int start() override; + int stop() override; + + bool consumeRequest(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 b47add55b0cb..ea36aaa5c514 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