[libcamera-devel,v2,6/8] cam: Add KMS sink class
diff mbox series

Message ID 20210730010306.19956-7-laurent.pinchart@ideasonboard.com
State Accepted
Headers show
Series
  • libcamera: Add DRM/KMS viewfinder display to cam
Related show

Commit Message

Laurent Pinchart July 30, 2021, 1:03 a.m. UTC
Add a KMSSink class to display framebuffers through the DRM/KMS API.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
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

Comments

Paul Elder Aug. 4, 2021, 6:56 a.m. UTC | #1
Hi Laurent,

On Fri, Jul 30, 2021 at 04:03:04AM +0300, Laurent Pinchart wrote:
> Add a KMSSink class to display framebuffers through the DRM/KMS API.
> 
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
> 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

s/2020/2021 ?

Same for the header.

> + *
> + * 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 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;
> +		}

I think this is the only part I'm confused about. If no connector is
requested, then the first candidate connector can be chosen even if its
status is Unknown.


Paul

> +	}
> +
> +	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)
> +{
> +	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<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;
> +	}
> +
> +	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::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<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;
> +		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_)
> +		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 <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);
> +
> +	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<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 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
>  
> -- 
> Regards,
> 
> Laurent Pinchart
>
Laurent Pinchart Aug. 4, 2021, 8:53 a.m. UTC | #2
Hi Paul,

On Wed, Aug 04, 2021 at 03:56:18PM +0900, paul.elder@ideasonboard.com wrote:
> On Fri, Jul 30, 2021 at 04:03:04AM +0300, Laurent Pinchart wrote:
> > Add a KMSSink class to display framebuffers through the DRM/KMS API.
> > 
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > ---
> > 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
> 
> s/2020/2021 ?
> 
> Same for the header.
> 
> > + *
> > + * 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 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;
> > +		}
> 
> I think this is the only part I'm confused about. If no connector is
> requested, then the first candidate connector can be chosen even if its
> status is Unknown.

First of all, a bit of background information. DRM/KMS isn't always able
to tell if a connector is connected, for instance VGA often lacks hot
plug detection support. That's why a connector can have three states:
disconnected, unknown or connected.

Then, the logic. If no suitable connector has been found yet
(!connector_), then we pick any connector that is not marked as
disconnected. Otherwise, we replace the currently (tentatively) selected
connector if it has state unknown, and conn.status() is connected. This
results in picking the first connected connector, with a fallback on the
last connector in unknown state if no connector is known to be
connected.

The logic is a bit convoluted, I'll try to do better.

> > +	}
> > +
> > +	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)
> > +{
> > +	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<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;
> > +	}
> > +
> > +	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::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<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;
> > +		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_)
> > +		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 <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);
> > +
> > +	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<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 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
> >  
> > -- 
> > Regards,
> > 
> > Laurent Pinchart
> >
Laurent Pinchart Aug. 4, 2021, 9:18 a.m. UTC | #3
On Wed, Aug 04, 2021 at 11:53:01AM +0300, Laurent Pinchart wrote:
> On Wed, Aug 04, 2021 at 03:56:18PM +0900, paul.elder@ideasonboard.com wrote:
> > On Fri, Jul 30, 2021 at 04:03:04AM +0300, Laurent Pinchart wrote:
> > > Add a KMSSink class to display framebuffers through the DRM/KMS API.
> > > 
> > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > ---
> > > 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
> > 
> > s/2020/2021 ?
> > 
> > Same for the header.
> > 
> > > + *
> > > + * 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 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;
> > > +		}
> > 
> > I think this is the only part I'm confused about. If no connector is
> > requested, then the first candidate connector can be chosen even if its
> > status is Unknown.
> 
> First of all, a bit of background information. DRM/KMS isn't always able
> to tell if a connector is connected, for instance VGA often lacks hot
> plug detection support. That's why a connector can have three states:
> disconnected, unknown or connected.
> 
> Then, the logic. If no suitable connector has been found yet
> (!connector_), then we pick any connector that is not marked as
> disconnected. Otherwise, we replace the currently (tentatively) selected
> connector if it has state unknown, and conn.status() is connected. This
> results in picking the first connected connector, with a fallback on the
> last connector in unknown state if no connector is known to be
> connected.

Actually the fallback is on the first connector with unknown state. I'll
document this properly.

> The logic is a bit convoluted, I'll try to do better.
> 
> > > +	}
> > > +
> > > +	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)
> > > +{
> > > +	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<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;
> > > +	}
> > > +
> > > +	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::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<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;
> > > +		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_)
> > > +		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 <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);
> > > +
> > > +	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<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 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

Patch
diff mbox series

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 <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 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<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)
+{
+	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<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;
+	}
+
+	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::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<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;
+		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_)
+		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 <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);
+
+	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<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 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