[libcamera-devel,v3,5/8] cam: Add DRM helper classes
diff mbox series

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

Commit Message

Laurent Pinchart Aug. 4, 2021, 12:43 p.m. UTC
To prepare for viewfinder operation through the DRM/KMS API, add a set
of helper classes that encapsulate the libdrm functions.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
---
Changes since v2:

- Update copyright years

Changes since v1:

- Use drm.h and drm_mode.h from libdrm
- Drop writeback connector
---
 src/cam/drm.cpp     | 663 ++++++++++++++++++++++++++++++++++++++++++++
 src/cam/drm.h       | 331 ++++++++++++++++++++++
 src/cam/meson.build |  13 +
 3 files changed, 1007 insertions(+)
 create mode 100644 src/cam/drm.cpp
 create mode 100644 src/cam/drm.h

Comments

Kieran Bingham Aug. 5, 2021, 11:52 a.m. UTC | #1
Hi Laurent,

On 04/08/2021 13:43, Laurent Pinchart wrote:
> To prepare for viewfinder operation through the DRM/KMS API, add a set
> of helper classes that encapsulate the libdrm functions.
> 
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
> ---
> Changes since v2:
> 
> - Update copyright years
> 
> Changes since v1:
> 
> - Use drm.h and drm_mode.h from libdrm
> - Drop writeback connector
> ---
>  src/cam/drm.cpp     | 663 ++++++++++++++++++++++++++++++++++++++++++++
>  src/cam/drm.h       | 331 ++++++++++++++++++++++
>  src/cam/meson.build |  13 +
>  3 files changed, 1007 insertions(+)
>  create mode 100644 src/cam/drm.cpp
>  create mode 100644 src/cam/drm.h
> 
> diff --git a/src/cam/drm.cpp b/src/cam/drm.cpp
> new file mode 100644
> index 000000000000..09c52b2ed64f
> --- /dev/null
> +++ b/src/cam/drm.cpp
> @@ -0,0 +1,663 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * Copyright (C) 2021, Ideas on Board Oy
> + *
> + * drm.cpp - DRM/KMS Helpers
> + */
> +
> +#include "drm.h"
> +
> +#include <algorithm>
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <iostream>
> +#include <set>
> +#include <string.h>
> +#include <sys/ioctl.h>
> +#include <sys/stat.h>
> +#include <sys/types.h>
> +
> +#include <libcamera/framebuffer.h>
> +#include <libcamera/geometry.h>
> +#include <libcamera/pixel_format.h>
> +
> +#include <libdrm/drm_mode.h>
> +
> +#include "event_loop.h"
> +
> +namespace DRM {
> +
> +Object::Object(Device *dev, uint32_t id, Type type)
> +	: id_(id), dev_(dev), type_(type)
> +{
> +	/* Retrieve properties from the objects that support them. */
> +	if (type != TypeConnector && type != TypeCrtc &&
> +	    type != TypeEncoder && type != TypePlane)
> +		return;
> +
> +	/*
> +	 * We can't distinguish between failures due to the object having no
> +	 * property and failures due to other conditions. Assume we use the API
> +	 * correctly and consider the object has no property.
> +	 */
> +	drmModeObjectProperties *properties = drmModeObjectGetProperties(dev->fd(), id, type);
> +	if (!properties)
> +		return;
> +
> +	properties_.reserve(properties->count_props);
> +	for (uint32_t i = 0; i < properties->count_props; ++i)
> +		properties_.emplace_back(properties->props[i],
> +					 properties->prop_values[i]);
> +
> +	drmModeFreeObjectProperties(properties);
> +}
> +
> +Object::~Object()
> +{
> +}
> +
> +const Property *Object::property(const std::string &name) const
> +{
> +	for (const PropertyValue &pv : properties_) {
> +		const Property *property = static_cast<const Property *>(dev_->object(pv.id()));
> +		if (property && property->name() == name)
> +			return property;
> +	}
> +
> +	return nullptr;
> +}
> +
> +const PropertyValue *Object::propertyValue(const std::string &name) const
> +{
> +	for (const PropertyValue &pv : properties_) {
> +		const Property *property = static_cast<const Property *>(dev_->object(pv.id()));
> +		if (property && property->name() == name)
> +			return &pv;
> +	}
> +
> +	return nullptr;
> +}
> +
> +Property::Property(Device *dev, drmModePropertyRes *property)
> +	: Object(dev, property->prop_id, TypeProperty),
> +	  name_(property->name), flags_(property->flags),
> +	  values_(property->values, property->values + property->count_values),
> +	  blobs_(property->blob_ids, property->blob_ids + property->count_blobs)
> +{
> +	if (drm_property_type_is(property, DRM_MODE_PROP_RANGE))
> +		type_ = TypeRange;
> +	else if (drm_property_type_is(property, DRM_MODE_PROP_ENUM))
> +		type_ = TypeEnum;
> +	else if (drm_property_type_is(property, DRM_MODE_PROP_BLOB))
> +		type_ = TypeBlob;
> +	else if (drm_property_type_is(property, DRM_MODE_PROP_BITMASK))
> +		type_ = TypeBitmask;
> +	else if (drm_property_type_is(property, DRM_MODE_PROP_OBJECT))
> +		type_ = TypeObject;
> +	else if (drm_property_type_is(property, DRM_MODE_PROP_SIGNED_RANGE))
> +		type_ = TypeSignedRange;
> +	else
> +		type_ = TypeUnknown;
> +
> +	for (int i = 0; i < property->count_enums; ++i)
> +		enums_[property->enums[i].value] = property->enums[i].name;
> +}
> +
> +Blob::Blob(Device *dev, const libcamera::Span<const uint8_t> &data)
> +	: Object(dev, 0, Object::TypeBlob)
> +{
> +	drmModeCreatePropertyBlob(dev->fd(), data.data(), data.size(), &id_);
> +}
> +
> +Blob::~Blob()
> +{
> +	if (isValid())
> +		drmModeDestroyPropertyBlob(device()->fd(), id());
> +}
> +
> +Mode::Mode(const drmModeModeInfo &mode)
> +	: drmModeModeInfo(mode)
> +{
> +}
> +
> +std::unique_ptr<Blob> Mode::toBlob(Device *dev) const
> +{
> +	libcamera::Span<const uint8_t> data{ reinterpret_cast<const uint8_t *>(this),
> +					     sizeof(*this) };
> +	return std::make_unique<Blob>(dev, data);
> +}
> +
> +Crtc::Crtc(Device *dev, const drmModeCrtc *crtc, unsigned int index)
> +	: Object(dev, crtc->crtc_id, Object::TypeCrtc), index_(index)
> +{
> +}
> +
> +Encoder::Encoder(Device *dev, const drmModeEncoder *encoder)
> +	: Object(dev, encoder->encoder_id, Object::TypeEncoder),
> +	  type_(encoder->encoder_type)
> +{
> +	const std::list<Crtc> &crtcs = dev->crtcs();
> +	possibleCrtcs_.reserve(crtcs.size());
> +
> +	for (const Crtc &crtc : crtcs) {
> +		if (encoder->possible_crtcs & (1 << crtc.index()))
> +			possibleCrtcs_.push_back(&crtc);
> +	}
> +
> +	possibleCrtcs_.shrink_to_fit();
> +}
> +
> +namespace {
> +
> +const std::map<uint32_t, const char *> connectorTypeNames{
> +	{ DRM_MODE_CONNECTOR_Unknown, "Unknown" },
> +	{ DRM_MODE_CONNECTOR_VGA, "VGA" },
> +	{ DRM_MODE_CONNECTOR_DVII, "DVI-I" },
> +	{ DRM_MODE_CONNECTOR_DVID, "DVI-D" },
> +	{ DRM_MODE_CONNECTOR_DVIA, "DVI-A" },
> +	{ DRM_MODE_CONNECTOR_Composite, "Composite" },
> +	{ DRM_MODE_CONNECTOR_SVIDEO, "S-Video" },
> +	{ DRM_MODE_CONNECTOR_LVDS, "LVDS" },
> +	{ DRM_MODE_CONNECTOR_Component, "Component" },
> +	{ DRM_MODE_CONNECTOR_9PinDIN, "9-Pin-DIN" },
> +	{ DRM_MODE_CONNECTOR_DisplayPort, "DP" },
> +	{ DRM_MODE_CONNECTOR_HDMIA, "HDMI-A" },
> +	{ DRM_MODE_CONNECTOR_HDMIB, "HDMI-B" },
> +	{ DRM_MODE_CONNECTOR_TV, "TV" },
> +	{ DRM_MODE_CONNECTOR_eDP, "eDP" },
> +	{ DRM_MODE_CONNECTOR_VIRTUAL, "Virtual" },
> +	{ DRM_MODE_CONNECTOR_DSI, "DSI" },
> +	{ DRM_MODE_CONNECTOR_DPI, "DPI" },
> +};
> +
> +} /* namespace */
> +
> +Connector::Connector(Device *dev, const drmModeConnector *connector)
> +	: Object(dev, connector->connector_id, Object::TypeConnector),
> +	  type_(connector->connector_type)
> +{
> +	auto typeName = connectorTypeNames.find(connector->connector_type);
> +	if (typeName == connectorTypeNames.end()) {
> +		std::cerr
> +			<< "Invalid connector type "
> +			<< connector->connector_type << std::endl;
> +		typeName = connectorTypeNames.find(DRM_MODE_CONNECTOR_Unknown);
> +	}
> +
> +	name_ = std::string(typeName->second) + "-"
> +	      + std::to_string(connector->connector_type_id);
> +
> +	switch (connector->connection) {
> +	case DRM_MODE_CONNECTED:
> +		status_ = Status::Connected;
> +		break;
> +
> +	case DRM_MODE_DISCONNECTED:
> +		status_ = Status::Disconnected;
> +		break;
> +
> +	case DRM_MODE_UNKNOWNCONNECTION:
> +	default:
> +		status_ = Status::Unknown;
> +		break;
> +	}
> +
> +	const std::list<Encoder> &encoders = dev->encoders();
> +
> +	encoders_.reserve(connector->count_encoders);
> +
> +	for (int i = 0; i < connector->count_encoders; ++i) {
> +		uint32_t encoderId = connector->encoders[i];
> +		auto encoder = std::find_if(encoders.begin(), encoders.end(),
> +					    [=](const Encoder &e) {
> +						    return e.id() == encoderId;
> +					    });
> +		if (encoder == encoders.end()) {
> +			std::cerr
> +				<< "Encoder " << encoderId << " not found"
> +				<< std::endl;
> +			continue;
> +		}
> +
> +		encoders_.push_back(&*encoder);
> +	}
> +
> +	encoders_.shrink_to_fit();
> +
> +	modes_ = { connector->modes, connector->modes + connector->count_modes };
> +}
> +
> +Plane::Plane(Device *dev, const drmModePlane *plane)
> +	: Object(dev, plane->plane_id, Object::TypePlane),
> +	  possibleCrtcsMask_(plane->possible_crtcs)
> +{
> +	formats_ = { plane->formats, plane->formats + plane->count_formats };
> +
> +	const std::list<Crtc> &crtcs = dev->crtcs();
> +	possibleCrtcs_.reserve(crtcs.size());
> +
> +	for (const Crtc &crtc : crtcs) {
> +		if (plane->possible_crtcs & (1 << crtc.index()))
> +			possibleCrtcs_.push_back(&crtc);
> +	}
> +
> +	possibleCrtcs_.shrink_to_fit();
> +}
> +
> +bool Plane::supportsFormat(const libcamera::PixelFormat &format) const
> +{
> +	return std::find(formats_.begin(), formats_.end(), format.fourcc())
> +		!= formats_.end();
> +}
> +
> +int Plane::setup()
> +{
> +	const PropertyValue *pv = propertyValue("type");
> +	if (!pv)
> +		return -EINVAL;
> +
> +	switch (pv->value()) {
> +	case DRM_PLANE_TYPE_OVERLAY:
> +		type_ = TypeOverlay;
> +		break;
> +
> +	case DRM_PLANE_TYPE_PRIMARY:
> +		type_ = TypePrimary;
> +		break;
> +
> +	case DRM_PLANE_TYPE_CURSOR:
> +		type_ = TypeCursor;
> +		break;
> +
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +FrameBuffer::FrameBuffer(Device *dev)
> +	: Object(dev, 0, Object::TypeFb)
> +{
> +}
> +
> +FrameBuffer::~FrameBuffer()
> +{
> +	for (FrameBuffer::Plane &plane : planes_) {
> +		struct drm_gem_close gem_close = {
> +			.handle = plane.handle,
> +			.pad = 0,
> +		};
> +		int ret;
> +
> +		do {
> +			ret = ioctl(device()->fd(), DRM_IOCTL_GEM_CLOSE, &gem_close);
> +		} while (ret == -1 && (errno == EINTR || errno == EAGAIN));
> +
> +		if (ret == -1) {
> +			ret = -errno;
> +			std::cerr
> +				<< "Failed to close GEM object: "
> +				<< strerror(-ret) << std::endl;
> +		}
> +	}
> +
> +	drmModeRmFB(device()->fd(), id());
> +}
> +
> +AtomicRequest::AtomicRequest(Device *dev)
> +	: dev_(dev), valid_(true)
> +{
> +	request_ = drmModeAtomicAlloc();
> +	if (!request_)
> +		valid_ = false;
> +}
> +
> +AtomicRequest::~AtomicRequest()
> +{
> +	if (request_)
> +		drmModeAtomicFree(request_);
> +}
> +
> +int AtomicRequest::addProperty(const Object *object, const std::string &property,
> +			       uint64_t value)
> +{
> +	if (!valid_)
> +		return -EINVAL;
> +
> +	const Property *prop = object->property(property);
> +	if (!prop) {
> +		valid_ = false;
> +		return -EINVAL;
> +	}
> +
> +	return addProperty(object->id(), prop->id(), value);
> +}
> +
> +int AtomicRequest::addProperty(const Object *object, const std::string &property,
> +			       std::unique_ptr<Blob> blob)
> +{
> +	if (!valid_)
> +		return -EINVAL;
> +
> +	const Property *prop = object->property(property);
> +	if (!prop) {
> +		valid_ = false;
> +		return -EINVAL;
> +	}
> +
> +	int ret = addProperty(object->id(), prop->id(), blob->id());
> +	if (ret < 0)
> +		return ret;
> +
> +	blobs_.emplace_back(std::move(blob));
> +
> +	return 0;
> +}
> +
> +int AtomicRequest::addProperty(uint32_t object, uint32_t property, uint64_t value)
> +{
> +	int ret = drmModeAtomicAddProperty(request_, object, property, value);
> +	if (ret < 0) {
> +		valid_ = false;
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +int AtomicRequest::commit(unsigned int flags)
> +{
> +	if (!valid_)
> +		return -EINVAL;
> +
> +	uint32_t drmFlags = 0;
> +	if (flags & FlagAllowModeset)
> +		drmFlags |= DRM_MODE_ATOMIC_ALLOW_MODESET;
> +	if (flags & FlagAsync)
> +		drmFlags |= DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK;
> +
> +	return drmModeAtomicCommit(dev_->fd(), request_, drmFlags, this);
> +}
> +
> +Device::Device()
> +	: fd_(-1)
> +{
> +}
> +
> +Device::~Device()
> +{
> +	if (fd_ != -1)
> +		drmClose(fd_);
> +}
> +
> +int Device::init()
> +{
> +	constexpr size_t NODE_NAME_MAX = sizeof("/dev/dri/card255");
> +	char name[NODE_NAME_MAX];
> +	int ret;
> +
> +	/*
> +	 * Open the first DRM/KMS device. The libdrm drmOpen*() functions
> +	 * require either a module name or a bus ID, which we don't have, so
> +	 * bypass them. The automatic module loading and device node creation
> +	 * from drmOpen() is of no practical use as any modern system will
> +	 * handle that through udev or an equivalent component.
> +	 */
> +	snprintf(name, sizeof(name), "/dev/dri/card%u", 0);
> +	fd_ = open(name, O_RDWR | O_CLOEXEC);
> +	if (fd_ < 0) {
> +		ret = -errno;
> +		std::cerr
> +			<< "Failed to open DRM/KMS device " << name << ": "
> +			<< strerror(-ret) << std::endl;
> +		return ret;
> +	}
> +
> +	/*
> +	 * Enable the atomic APIs. This also enables automatically the

exceptionally minor nit
s/enables automatically/automatically enables/

> +	 * universal planes API.
> +	 */
> +	ret = drmSetClientCap(fd_, DRM_CLIENT_CAP_ATOMIC, 1);
> +	if (ret < 0) {
> +		ret = -errno;
> +		std::cerr
> +			<< "Failed to enable atomic capability: "
> +			<< strerror(-ret) << std::endl;
> +		return ret;
> +	}
> +
> +	/* List all the resources. */
> +	ret = getResources();
> +	if (ret < 0)
> +		return ret;
> +
> +	EventLoop::instance()->addEvent(fd_, EventLoop::Read,
> +					std::bind(&Device::drmEvent, this));
> +
> +	return 0;
> +}
> +
> +int Device::getResources()
> +{
> +	int ret;
> +
> +	std::unique_ptr<drmModeRes, decltype(&drmModeFreeResources)> resources{
> +		drmModeGetResources(fd_),
> +		&drmModeFreeResources
> +	};
> +	if (!resources) {
> +		ret = -errno;
> +		std::cerr
> +			<< "Failed to get DRM/KMS resources: "
> +			<< strerror(-ret) << std::endl;
> +		return ret;
> +	}
> +
> +	for (int i = 0; i < resources->count_crtcs; ++i) {
> +		drmModeCrtc *crtc = drmModeGetCrtc(fd_, resources->crtcs[i]);
> +		if (!crtc) {
> +			ret = -errno;
> +			std::cerr
> +				<< "Failed to get CRTC: " << strerror(-ret)
> +				<< std::endl;
> +			return ret;
> +		}
> +
> +		crtcs_.emplace_back(this, crtc, i);
> +		drmModeFreeCrtc(crtc);
> +
> +		Crtc &obj = crtcs_.back();
> +		objects_[obj.id()] = &obj;
> +	}
> +
> +	for (int i = 0; i < resources->count_encoders; ++i) {
> +		drmModeEncoder *encoder =
> +			drmModeGetEncoder(fd_, resources->encoders[i]);
> +		if (!encoder) {
> +			ret = -errno;
> +			std::cerr
> +				<< "Failed to get encoder: " << strerror(-ret)
> +				<< std::endl;
> +			return ret;
> +		}
> +
> +		encoders_.emplace_back(this, encoder);
> +		drmModeFreeEncoder(encoder);
> +
> +		Encoder &obj = encoders_.back();
> +		objects_[obj.id()] = &obj;
> +	}
> +
> +	for (int i = 0; i < resources->count_connectors; ++i) {
> +		drmModeConnector *connector =
> +			drmModeGetConnector(fd_, resources->connectors[i]);
> +		if (!connector) {
> +			ret = -errno;
> +			std::cerr
> +				<< "Failed to get connector: " << strerror(-ret)
> +				<< std::endl;
> +			return ret;
> +		}
> +
> +		connectors_.emplace_back(this, connector);
> +		drmModeFreeConnector(connector);
> +
> +		Connector &obj = connectors_.back();
> +		objects_[obj.id()] = &obj;
> +	}
> +
> +	std::unique_ptr<drmModePlaneRes, decltype(&drmModeFreePlaneResources)> planes{
> +		drmModeGetPlaneResources(fd_),
> +		&drmModeFreePlaneResources
> +	};
> +	if (!planes) {
> +		ret = -errno;
> +		std::cerr
> +			<< "Failed to get DRM/KMS planes: "
> +			<< strerror(-ret) << std::endl;
> +		return ret;
> +	}
> +
> +	for (uint32_t i = 0; i < planes->count_planes; ++i) {
> +		drmModePlane *plane =
> +			drmModeGetPlane(fd_, planes->planes[i]);
> +		if (!plane) {
> +			ret = -errno;
> +			std::cerr
> +				<< "Failed to get plane: " << strerror(-ret)
> +				<< std::endl;
> +			return ret;
> +		}
> +
> +		planes_.emplace_back(this, plane);
> +		drmModeFreePlane(plane);
> +
> +		Plane &obj = planes_.back();
> +		objects_[obj.id()] = &obj;
> +	}
> +
> +	/* Set the possible planes for each CRTC. */
> +	for (Crtc &crtc : crtcs_) {
> +		for (const Plane &plane : planes_) {
> +			if (plane.possibleCrtcsMask_ & (1 << crtc.index()))
> +				crtc.planes_.push_back(&plane);
> +		}
> +	}
> +
> +	/* Collect all property IDs and create Property instances. */
> +	std::set<uint32_t> properties;
> +	for (const auto &object : objects_) {
> +		for (const PropertyValue &value : object.second->properties())
> +			properties.insert(value.id());
> +	}
> +
> +	for (uint32_t id : properties) {
> +		drmModePropertyRes *property = drmModeGetProperty(fd_, id);
> +		if (!property) {
> +			ret = -errno;
> +			std::cerr
> +				<< "Failed to get property: " << strerror(-ret)
> +				<< std::endl;
> +			continue;
> +		}
> +
> +		properties_.emplace_back(this, property);
> +		drmModeFreeProperty(property);
> +
> +		Property &obj = properties_.back();
> +		objects_[obj.id()] = &obj;
> +	}
> +
> +	/* Finally, perform all delayed setup of mode objects. */
> +	for (auto &object : objects_) {
> +		ret = object.second->setup();
> +		if (ret < 0) {
> +			std::cerr
> +				<< "Failed to setup object " << object.second->id()
> +				<< ": " << strerror(-ret) << std::endl;
> +			return ret;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +const Object *Device::object(uint32_t id)
> +{
> +	const auto iter = objects_.find(id);
> +	if (iter == objects_.end())
> +		return nullptr;
> +
> +	return iter->second;
> +}
> +
> +std::unique_ptr<FrameBuffer> Device::createFrameBuffer(
> +	const libcamera::FrameBuffer &buffer,
> +	const libcamera::PixelFormat &format,
> +	const libcamera::Size &size, unsigned int stride)
> +{
> +	std::unique_ptr<FrameBuffer> fb{ new FrameBuffer(this) };
> +
> +	uint32_t handles[4] = {};
> +	uint32_t pitches[4] = {};
> +	uint32_t offsets[4] = {};
> +	int ret;
> +
> +	const std::vector<libcamera::FrameBuffer::Plane> &planes = buffer.planes();
> +	fb->planes_.reserve(planes.size());
> +
> +	unsigned int i = 0;
> +	for (const libcamera::FrameBuffer::Plane &plane : planes) {
> +		uint32_t handle;
> +
> +		ret = drmPrimeFDToHandle(fd_, plane.fd.fd(), &handle);
> +		if (ret < 0) {
> +			ret = -errno;
> +			std::cerr
> +				<< "Unable to import framebuffer dmabuf: "
> +				<< strerror(-ret) << std::endl;
> +			return nullptr;
> +		}
> +
> +		fb->planes_.push_back({ handle });
> +
> +		handles[i] = handle;
> +		pitches[i] = stride;
> +		offsets[i] = 0; /* TODO */

Is this an issue? Is this for supporting semi-planar formats or such?

> +		++i;
> +	}
> +
> +	ret = drmModeAddFB2(fd_, size.width, size.height, format.fourcc(), handles,
> +			    pitches, offsets, &fb->id_, 0);
> +	if (ret < 0) {
> +		ret = -errno;
> +		std::cerr
> +			<< "Failed to add framebuffer: "
> +			<< strerror(-ret) << std::endl;
> +		return nullptr;
> +	}
> +
> +	return fb;
> +}
> +
> +void Device::drmEvent()
> +{
> +	drmEventContext ctx{};
> +	ctx.version = DRM_EVENT_CONTEXT_VERSION;
> +	ctx.page_flip_handler = &Device::pageFlipComplete;
> +
> +	drmHandleEvent(fd_, &ctx);
> +}
> +
> +void Device::pageFlipComplete([[maybe_unused]] int fd,
> +			      [[maybe_unused]] unsigned int sequence,
> +			      [[maybe_unused]] unsigned int tv_sec,
> +			      [[maybe_unused]] unsigned int tv_usec,
> +			      void *user_data)
> +{
> +	AtomicRequest *request = static_cast<AtomicRequest *>(user_data);
> +	request->device()->requestComplete.emit(request);
> +}
> +
> +} /* namespace DRM */
> diff --git a/src/cam/drm.h b/src/cam/drm.h
> new file mode 100644
> index 000000000000..ee2304025208
> --- /dev/null
> +++ b/src/cam/drm.h
> @@ -0,0 +1,331 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * Copyright (C) 2021, Ideas on Board Oy
> + *
> + * drm.h - DRM/KMS Helpers
> + */
> +#ifndef __CAM_DRM_H__
> +#define __CAM_DRM_H__
> +
> +#include <list>
> +#include <map>
> +#include <memory>
> +#include <string>
> +#include <vector>
> +
> +#include <libcamera/base/signal.h>
> +#include <libcamera/base/span.h>
> +
> +#include <libdrm/drm.h>
> +#include <xf86drm.h>
> +#include <xf86drmMode.h>
> +
> +namespace libcamera {
> +class FrameBuffer;
> +class PixelFormat;
> +class Size;
> +} /* namespace libcamera */
> +
> +namespace DRM {
> +
> +class Device;
> +class Plane;
> +class Property;
> +class PropertyValue;
> +
> +class Object
> +{
> +public:
> +	enum Type {
> +		TypeCrtc = DRM_MODE_OBJECT_CRTC,
> +		TypeConnector = DRM_MODE_OBJECT_CONNECTOR,
> +		TypeEncoder = DRM_MODE_OBJECT_ENCODER,
> +		TypeMode = DRM_MODE_OBJECT_MODE,
> +		TypeProperty = DRM_MODE_OBJECT_PROPERTY,
> +		TypeFb = DRM_MODE_OBJECT_FB,
> +		TypeBlob = DRM_MODE_OBJECT_BLOB,
> +		TypePlane = DRM_MODE_OBJECT_PLANE,
> +		TypeAny = DRM_MODE_OBJECT_ANY,
> +	};
> +
> +	Object(Device *dev, uint32_t id, Type type);
> +	virtual ~Object();
> +
> +	Device *device() const { return dev_; }
> +	uint32_t id() const { return id_; }
> +	Type type() const { return type_; }
> +
> +	const Property *property(const std::string &name) const;
> +	const PropertyValue *propertyValue(const std::string &name) const;
> +	const std::vector<PropertyValue> &properties() const { return properties_; }
> +
> +protected:
> +	virtual int setup()
> +	{
> +		return 0;
> +	}
> +
> +	uint32_t id_;
> +
> +private:
> +	friend Device;
> +
> +	Device *dev_;
> +	Type type_;
> +	std::vector<PropertyValue> properties_;
> +};
> +
> +class Property : public Object
> +{
> +public:
> +	enum Type {
> +		TypeUnknown = 0,
> +		TypeRange,
> +		TypeEnum,
> +		TypeBlob,
> +		TypeBitmask,
> +		TypeObject,
> +		TypeSignedRange,
> +	};
> +
> +	Property(Device *dev, drmModePropertyRes *property);
> +
> +	Type type() const { return type_; }
> +	const std::string &name() const { return name_; }
> +
> +	bool isImmutable() const { return flags_ & DRM_MODE_PROP_IMMUTABLE; }
> +
> +	const std::vector<uint64_t> values() const { return values_; }
> +	const std::map<uint32_t, std::string> &enums() const { return enums_; }
> +	const std::vector<uint32_t> blobs() const { return blobs_; }
> +
> +private:
> +	Type type_;
> +	std::string name_;
> +	uint32_t flags_;
> +	std::vector<uint64_t> values_;
> +	std::map<uint32_t, std::string> enums_;
> +	std::vector<uint32_t> blobs_;
> +};
> +
> +class PropertyValue
> +{
> +public:
> +	PropertyValue(uint32_t id, uint64_t value)
> +		: id_(id), value_(value)
> +	{
> +	}
> +
> +	uint32_t id() const { return id_; }
> +	uint32_t value() const { return value_; }
> +
> +private:
> +	uint32_t id_;
> +	uint64_t value_;
> +};
> +
> +class Blob : public Object
> +{
> +public:
> +	Blob(Device *dev, const libcamera::Span<const uint8_t> &data);
> +	~Blob();
> +
> +	bool isValid() const { return id() != 0; }
> +};
> +
> +class Mode : public drmModeModeInfo
> +{
> +public:
> +	Mode(const drmModeModeInfo &mode);
> +
> +	std::unique_ptr<Blob> toBlob(Device *dev) const;
> +};
> +
> +class Crtc : public Object
> +{
> +public:
> +	Crtc(Device *dev, const drmModeCrtc *crtc, unsigned int index);
> +
> +	unsigned int index() const { return index_; }
> +	const std::vector<const Plane *> &planes() const { return planes_; }
> +
> +private:
> +	friend Device;
> +
> +	unsigned int index_;
> +	std::vector<const Plane *> planes_;
> +};
> +
> +class Encoder : public Object
> +{
> +public:
> +	Encoder(Device *dev, const drmModeEncoder *encoder);
> +
> +	uint32_t type() const { return type_; }
> +
> +	const std::vector<const Crtc *> &possibleCrtcs() const { return possibleCrtcs_; }
> +
> +private:
> +	uint32_t type_;
> +	std::vector<const Crtc *> possibleCrtcs_;
> +};
> +
> +class Connector : public Object
> +{
> +public:
> +	enum Status {
> +		Connected,
> +		Disconnected,
> +		Unknown,
> +	};
> +
> +	Connector(Device *dev, const drmModeConnector *connector);
> +
> +	uint32_t type() const { return type_; }
> +	const std::string &name() const { return name_; }
> +
> +	Status status() const { return status_; }
> +
> +	const std::vector<const Encoder *> &encoders() const { return encoders_; }
> +	const std::vector<Mode> &modes() const { return modes_; }
> +
> +private:
> +	uint32_t type_;
> +	std::string name_;
> +	Status status_;
> +	std::vector<const Encoder *> encoders_;
> +	std::vector<Mode> modes_;
> +};
> +
> +class Plane : public Object
> +{
> +public:
> +	enum Type {
> +		TypeOverlay,
> +		TypePrimary,
> +		TypeCursor,
> +	};
> +
> +	Plane(Device *dev, const drmModePlane *plane);
> +
> +	Type type() const { return type_; }
> +	const std::vector<uint32_t> &formats() const { return formats_; }
> +	const std::vector<const Crtc *> &possibleCrtcs() const { return possibleCrtcs_; }
> +
> +	bool supportsFormat(const libcamera::PixelFormat &format) const;
> +
> +protected:
> +	int setup() override;
> +
> +private:
> +	friend class Device;
> +
> +	Type type_;
> +	std::vector<uint32_t> formats_;
> +	std::vector<const Crtc *> possibleCrtcs_;
> +	uint32_t possibleCrtcsMask_;
> +};
> +
> +class FrameBuffer : public Object
> +{
> +public:
> +	struct Plane {
> +		uint32_t handle;
> +	};
> +
> +	~FrameBuffer();
> +
> +private:
> +	friend class Device;
> +
> +	FrameBuffer(Device *dev);
> +
> +	std::vector<Plane> planes_;
> +};
> +
> +class AtomicRequest
> +{
> +public:
> +	enum Flags {
> +		FlagAllowModeset = (1 << 0),
> +		FlagAsync = (1 << 1),
> +	};
> +
> +	AtomicRequest(Device *dev);
> +	~AtomicRequest();
> +
> +	Device *device() const { return dev_; }
> +	bool isValid() const { return valid_; }
> +
> +	int addProperty(const Object *object, const std::string &property,
> +			uint64_t value);
> +	int addProperty(const Object *object, const std::string &property,
> +			std::unique_ptr<Blob> blob);
> +	int commit(unsigned int flags = 0);
> +
> +private:
> +	AtomicRequest(const AtomicRequest &) = delete;
> +	AtomicRequest(const AtomicRequest &&) = delete;
> +	AtomicRequest &operator=(const AtomicRequest &) = delete;
> +	AtomicRequest &operator=(const AtomicRequest &&) = delete;
> +
> +	int addProperty(uint32_t object, uint32_t property, uint64_t value);
> +
> +	Device *dev_;
> +	bool valid_;
> +	drmModeAtomicReq *request_;
> +	std::list<std::unique_ptr<Blob>> blobs_;
> +};
> +
> +class Device
> +{
> +public:
> +	Device();
> +	~Device();
> +
> +	int init();
> +
> +	int fd() const { return fd_; }
> +
> +	const std::list<Crtc> &crtcs() const { return crtcs_; }
> +	const std::list<Encoder> &encoders() const { return encoders_; }
> +	const std::list<Connector> &connectors() const { return connectors_; }
> +	const std::list<Plane> &planes() const { return planes_; }
> +	const std::list<Property> &properties() const { return properties_; }
> +
> +	const Object *object(uint32_t id);
> +
> +	std::unique_ptr<FrameBuffer> createFrameBuffer(
> +		const libcamera::FrameBuffer &buffer,
> +		const libcamera::PixelFormat &format,
> +		const libcamera::Size &size, unsigned int stride);
> +
> +	libcamera::Signal<AtomicRequest *> requestComplete;
> +
> +private:
> +	Device(const Device &) = delete;
> +	Device(const Device &&) = delete;
> +	Device &operator=(const Device &) = delete;
> +	Device &operator=(const Device &&) = delete;
> +
> +	int getResources();
> +
> +	void drmEvent();
> +	static void pageFlipComplete(int fd, unsigned int sequence,
> +				     unsigned int tv_sec, unsigned int tv_usec,
> +				     void *user_data);
> +
> +	int fd_;
> +
> +	std::list<Crtc> crtcs_;
> +	std::list<Encoder> encoders_;
> +	std::list<Connector> connectors_;
> +	std::list<Plane> planes_;
> +	std::list<Property> properties_;
> +
> +	std::map<uint32_t, Object *> objects_;
> +};
> +
> +} /* namespace DRM */
> +
> +#endif /* __CAM_DRM_H__ */
> diff --git a/src/cam/meson.build b/src/cam/meson.build
> index e692ea351987..b47add55b0cb 100644
> --- a/src/cam/meson.build
> +++ b/src/cam/meson.build
> @@ -19,10 +19,23 @@ cam_sources = files([
>      'stream_options.cpp',
>  ])
>  
> +cam_cpp_args = []
> +
> +libdrm = dependency('libdrm', required : false)
> +
> +if libdrm.found()
> +cam_cpp_args += [ '-DHAVE_KMS' ]
> +cam_sources += files([
> +    'drm.cpp',


From what I can tell - this could be a helpful re-usable component for
other applications too.

The only thing that ties it to cam currently is the event loop - so
perhaps in the future that could be parameterised somehow - but that's
not needed here so.

Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>

But ... ouch - so much work to be able to get an image on the screen.

And this isnt' even all of it yet...

Can we bring back /dev/fb0 ;-)

--
Kieran


> +])
> +endif
> +
>  cam  = executable('cam', cam_sources,
>                    dependencies : [
>                        libatomic,
>                        libcamera_public,
> +                      libdrm,
>                        libevent,
>                    ],
> +                  cpp_args : cam_cpp_args,
>                    install : true)
>
Laurent Pinchart Aug. 5, 2021, 11:58 a.m. UTC | #2
Hi Kieran,

On Thu, Aug 05, 2021 at 12:52:02PM +0100, Kieran Bingham wrote:
> On 04/08/2021 13:43, Laurent Pinchart wrote:
> > To prepare for viewfinder operation through the DRM/KMS API, add a set
> > of helper classes that encapsulate the libdrm functions.
> > 
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
> > ---
> > Changes since v2:
> > 
> > - Update copyright years
> > 
> > Changes since v1:
> > 
> > - Use drm.h and drm_mode.h from libdrm
> > - Drop writeback connector
> > ---
> >  src/cam/drm.cpp     | 663 ++++++++++++++++++++++++++++++++++++++++++++
> >  src/cam/drm.h       | 331 ++++++++++++++++++++++
> >  src/cam/meson.build |  13 +
> >  3 files changed, 1007 insertions(+)
> >  create mode 100644 src/cam/drm.cpp
> >  create mode 100644 src/cam/drm.h
> > 
> > diff --git a/src/cam/drm.cpp b/src/cam/drm.cpp
> > new file mode 100644
> > index 000000000000..09c52b2ed64f
> > --- /dev/null
> > +++ b/src/cam/drm.cpp
> > @@ -0,0 +1,663 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > +/*
> > + * Copyright (C) 2021, Ideas on Board Oy
> > + *
> > + * drm.cpp - DRM/KMS Helpers
> > + */
> > +
> > +#include "drm.h"
> > +
> > +#include <algorithm>
> > +#include <errno.h>
> > +#include <fcntl.h>
> > +#include <iostream>
> > +#include <set>
> > +#include <string.h>
> > +#include <sys/ioctl.h>
> > +#include <sys/stat.h>
> > +#include <sys/types.h>
> > +
> > +#include <libcamera/framebuffer.h>
> > +#include <libcamera/geometry.h>
> > +#include <libcamera/pixel_format.h>
> > +
> > +#include <libdrm/drm_mode.h>
> > +
> > +#include "event_loop.h"
> > +
> > +namespace DRM {
> > +
> > +Object::Object(Device *dev, uint32_t id, Type type)
> > +	: id_(id), dev_(dev), type_(type)
> > +{
> > +	/* Retrieve properties from the objects that support them. */
> > +	if (type != TypeConnector && type != TypeCrtc &&
> > +	    type != TypeEncoder && type != TypePlane)
> > +		return;
> > +
> > +	/*
> > +	 * We can't distinguish between failures due to the object having no
> > +	 * property and failures due to other conditions. Assume we use the API
> > +	 * correctly and consider the object has no property.
> > +	 */
> > +	drmModeObjectProperties *properties = drmModeObjectGetProperties(dev->fd(), id, type);
> > +	if (!properties)
> > +		return;
> > +
> > +	properties_.reserve(properties->count_props);
> > +	for (uint32_t i = 0; i < properties->count_props; ++i)
> > +		properties_.emplace_back(properties->props[i],
> > +					 properties->prop_values[i]);
> > +
> > +	drmModeFreeObjectProperties(properties);
> > +}
> > +
> > +Object::~Object()
> > +{
> > +}
> > +
> > +const Property *Object::property(const std::string &name) const
> > +{
> > +	for (const PropertyValue &pv : properties_) {
> > +		const Property *property = static_cast<const Property *>(dev_->object(pv.id()));
> > +		if (property && property->name() == name)
> > +			return property;
> > +	}
> > +
> > +	return nullptr;
> > +}
> > +
> > +const PropertyValue *Object::propertyValue(const std::string &name) const
> > +{
> > +	for (const PropertyValue &pv : properties_) {
> > +		const Property *property = static_cast<const Property *>(dev_->object(pv.id()));
> > +		if (property && property->name() == name)
> > +			return &pv;
> > +	}
> > +
> > +	return nullptr;
> > +}
> > +
> > +Property::Property(Device *dev, drmModePropertyRes *property)
> > +	: Object(dev, property->prop_id, TypeProperty),
> > +	  name_(property->name), flags_(property->flags),
> > +	  values_(property->values, property->values + property->count_values),
> > +	  blobs_(property->blob_ids, property->blob_ids + property->count_blobs)
> > +{
> > +	if (drm_property_type_is(property, DRM_MODE_PROP_RANGE))
> > +		type_ = TypeRange;
> > +	else if (drm_property_type_is(property, DRM_MODE_PROP_ENUM))
> > +		type_ = TypeEnum;
> > +	else if (drm_property_type_is(property, DRM_MODE_PROP_BLOB))
> > +		type_ = TypeBlob;
> > +	else if (drm_property_type_is(property, DRM_MODE_PROP_BITMASK))
> > +		type_ = TypeBitmask;
> > +	else if (drm_property_type_is(property, DRM_MODE_PROP_OBJECT))
> > +		type_ = TypeObject;
> > +	else if (drm_property_type_is(property, DRM_MODE_PROP_SIGNED_RANGE))
> > +		type_ = TypeSignedRange;
> > +	else
> > +		type_ = TypeUnknown;
> > +
> > +	for (int i = 0; i < property->count_enums; ++i)
> > +		enums_[property->enums[i].value] = property->enums[i].name;
> > +}
> > +
> > +Blob::Blob(Device *dev, const libcamera::Span<const uint8_t> &data)
> > +	: Object(dev, 0, Object::TypeBlob)
> > +{
> > +	drmModeCreatePropertyBlob(dev->fd(), data.data(), data.size(), &id_);
> > +}
> > +
> > +Blob::~Blob()
> > +{
> > +	if (isValid())
> > +		drmModeDestroyPropertyBlob(device()->fd(), id());
> > +}
> > +
> > +Mode::Mode(const drmModeModeInfo &mode)
> > +	: drmModeModeInfo(mode)
> > +{
> > +}
> > +
> > +std::unique_ptr<Blob> Mode::toBlob(Device *dev) const
> > +{
> > +	libcamera::Span<const uint8_t> data{ reinterpret_cast<const uint8_t *>(this),
> > +					     sizeof(*this) };
> > +	return std::make_unique<Blob>(dev, data);
> > +}
> > +
> > +Crtc::Crtc(Device *dev, const drmModeCrtc *crtc, unsigned int index)
> > +	: Object(dev, crtc->crtc_id, Object::TypeCrtc), index_(index)
> > +{
> > +}
> > +
> > +Encoder::Encoder(Device *dev, const drmModeEncoder *encoder)
> > +	: Object(dev, encoder->encoder_id, Object::TypeEncoder),
> > +	  type_(encoder->encoder_type)
> > +{
> > +	const std::list<Crtc> &crtcs = dev->crtcs();
> > +	possibleCrtcs_.reserve(crtcs.size());
> > +
> > +	for (const Crtc &crtc : crtcs) {
> > +		if (encoder->possible_crtcs & (1 << crtc.index()))
> > +			possibleCrtcs_.push_back(&crtc);
> > +	}
> > +
> > +	possibleCrtcs_.shrink_to_fit();
> > +}
> > +
> > +namespace {
> > +
> > +const std::map<uint32_t, const char *> connectorTypeNames{
> > +	{ DRM_MODE_CONNECTOR_Unknown, "Unknown" },
> > +	{ DRM_MODE_CONNECTOR_VGA, "VGA" },
> > +	{ DRM_MODE_CONNECTOR_DVII, "DVI-I" },
> > +	{ DRM_MODE_CONNECTOR_DVID, "DVI-D" },
> > +	{ DRM_MODE_CONNECTOR_DVIA, "DVI-A" },
> > +	{ DRM_MODE_CONNECTOR_Composite, "Composite" },
> > +	{ DRM_MODE_CONNECTOR_SVIDEO, "S-Video" },
> > +	{ DRM_MODE_CONNECTOR_LVDS, "LVDS" },
> > +	{ DRM_MODE_CONNECTOR_Component, "Component" },
> > +	{ DRM_MODE_CONNECTOR_9PinDIN, "9-Pin-DIN" },
> > +	{ DRM_MODE_CONNECTOR_DisplayPort, "DP" },
> > +	{ DRM_MODE_CONNECTOR_HDMIA, "HDMI-A" },
> > +	{ DRM_MODE_CONNECTOR_HDMIB, "HDMI-B" },
> > +	{ DRM_MODE_CONNECTOR_TV, "TV" },
> > +	{ DRM_MODE_CONNECTOR_eDP, "eDP" },
> > +	{ DRM_MODE_CONNECTOR_VIRTUAL, "Virtual" },
> > +	{ DRM_MODE_CONNECTOR_DSI, "DSI" },
> > +	{ DRM_MODE_CONNECTOR_DPI, "DPI" },
> > +};
> > +
> > +} /* namespace */
> > +
> > +Connector::Connector(Device *dev, const drmModeConnector *connector)
> > +	: Object(dev, connector->connector_id, Object::TypeConnector),
> > +	  type_(connector->connector_type)
> > +{
> > +	auto typeName = connectorTypeNames.find(connector->connector_type);
> > +	if (typeName == connectorTypeNames.end()) {
> > +		std::cerr
> > +			<< "Invalid connector type "
> > +			<< connector->connector_type << std::endl;
> > +		typeName = connectorTypeNames.find(DRM_MODE_CONNECTOR_Unknown);
> > +	}
> > +
> > +	name_ = std::string(typeName->second) + "-"
> > +	      + std::to_string(connector->connector_type_id);
> > +
> > +	switch (connector->connection) {
> > +	case DRM_MODE_CONNECTED:
> > +		status_ = Status::Connected;
> > +		break;
> > +
> > +	case DRM_MODE_DISCONNECTED:
> > +		status_ = Status::Disconnected;
> > +		break;
> > +
> > +	case DRM_MODE_UNKNOWNCONNECTION:
> > +	default:
> > +		status_ = Status::Unknown;
> > +		break;
> > +	}
> > +
> > +	const std::list<Encoder> &encoders = dev->encoders();
> > +
> > +	encoders_.reserve(connector->count_encoders);
> > +
> > +	for (int i = 0; i < connector->count_encoders; ++i) {
> > +		uint32_t encoderId = connector->encoders[i];
> > +		auto encoder = std::find_if(encoders.begin(), encoders.end(),
> > +					    [=](const Encoder &e) {
> > +						    return e.id() == encoderId;
> > +					    });
> > +		if (encoder == encoders.end()) {
> > +			std::cerr
> > +				<< "Encoder " << encoderId << " not found"
> > +				<< std::endl;
> > +			continue;
> > +		}
> > +
> > +		encoders_.push_back(&*encoder);
> > +	}
> > +
> > +	encoders_.shrink_to_fit();
> > +
> > +	modes_ = { connector->modes, connector->modes + connector->count_modes };
> > +}
> > +
> > +Plane::Plane(Device *dev, const drmModePlane *plane)
> > +	: Object(dev, plane->plane_id, Object::TypePlane),
> > +	  possibleCrtcsMask_(plane->possible_crtcs)
> > +{
> > +	formats_ = { plane->formats, plane->formats + plane->count_formats };
> > +
> > +	const std::list<Crtc> &crtcs = dev->crtcs();
> > +	possibleCrtcs_.reserve(crtcs.size());
> > +
> > +	for (const Crtc &crtc : crtcs) {
> > +		if (plane->possible_crtcs & (1 << crtc.index()))
> > +			possibleCrtcs_.push_back(&crtc);
> > +	}
> > +
> > +	possibleCrtcs_.shrink_to_fit();
> > +}
> > +
> > +bool Plane::supportsFormat(const libcamera::PixelFormat &format) const
> > +{
> > +	return std::find(formats_.begin(), formats_.end(), format.fourcc())
> > +		!= formats_.end();
> > +}
> > +
> > +int Plane::setup()
> > +{
> > +	const PropertyValue *pv = propertyValue("type");
> > +	if (!pv)
> > +		return -EINVAL;
> > +
> > +	switch (pv->value()) {
> > +	case DRM_PLANE_TYPE_OVERLAY:
> > +		type_ = TypeOverlay;
> > +		break;
> > +
> > +	case DRM_PLANE_TYPE_PRIMARY:
> > +		type_ = TypePrimary;
> > +		break;
> > +
> > +	case DRM_PLANE_TYPE_CURSOR:
> > +		type_ = TypeCursor;
> > +		break;
> > +
> > +	default:
> > +		return -EINVAL;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +FrameBuffer::FrameBuffer(Device *dev)
> > +	: Object(dev, 0, Object::TypeFb)
> > +{
> > +}
> > +
> > +FrameBuffer::~FrameBuffer()
> > +{
> > +	for (FrameBuffer::Plane &plane : planes_) {
> > +		struct drm_gem_close gem_close = {
> > +			.handle = plane.handle,
> > +			.pad = 0,
> > +		};
> > +		int ret;
> > +
> > +		do {
> > +			ret = ioctl(device()->fd(), DRM_IOCTL_GEM_CLOSE, &gem_close);
> > +		} while (ret == -1 && (errno == EINTR || errno == EAGAIN));
> > +
> > +		if (ret == -1) {
> > +			ret = -errno;
> > +			std::cerr
> > +				<< "Failed to close GEM object: "
> > +				<< strerror(-ret) << std::endl;
> > +		}
> > +	}
> > +
> > +	drmModeRmFB(device()->fd(), id());
> > +}
> > +
> > +AtomicRequest::AtomicRequest(Device *dev)
> > +	: dev_(dev), valid_(true)
> > +{
> > +	request_ = drmModeAtomicAlloc();
> > +	if (!request_)
> > +		valid_ = false;
> > +}
> > +
> > +AtomicRequest::~AtomicRequest()
> > +{
> > +	if (request_)
> > +		drmModeAtomicFree(request_);
> > +}
> > +
> > +int AtomicRequest::addProperty(const Object *object, const std::string &property,
> > +			       uint64_t value)
> > +{
> > +	if (!valid_)
> > +		return -EINVAL;
> > +
> > +	const Property *prop = object->property(property);
> > +	if (!prop) {
> > +		valid_ = false;
> > +		return -EINVAL;
> > +	}
> > +
> > +	return addProperty(object->id(), prop->id(), value);
> > +}
> > +
> > +int AtomicRequest::addProperty(const Object *object, const std::string &property,
> > +			       std::unique_ptr<Blob> blob)
> > +{
> > +	if (!valid_)
> > +		return -EINVAL;
> > +
> > +	const Property *prop = object->property(property);
> > +	if (!prop) {
> > +		valid_ = false;
> > +		return -EINVAL;
> > +	}
> > +
> > +	int ret = addProperty(object->id(), prop->id(), blob->id());
> > +	if (ret < 0)
> > +		return ret;
> > +
> > +	blobs_.emplace_back(std::move(blob));
> > +
> > +	return 0;
> > +}
> > +
> > +int AtomicRequest::addProperty(uint32_t object, uint32_t property, uint64_t value)
> > +{
> > +	int ret = drmModeAtomicAddProperty(request_, object, property, value);
> > +	if (ret < 0) {
> > +		valid_ = false;
> > +		return ret;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +int AtomicRequest::commit(unsigned int flags)
> > +{
> > +	if (!valid_)
> > +		return -EINVAL;
> > +
> > +	uint32_t drmFlags = 0;
> > +	if (flags & FlagAllowModeset)
> > +		drmFlags |= DRM_MODE_ATOMIC_ALLOW_MODESET;
> > +	if (flags & FlagAsync)
> > +		drmFlags |= DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK;
> > +
> > +	return drmModeAtomicCommit(dev_->fd(), request_, drmFlags, this);
> > +}
> > +
> > +Device::Device()
> > +	: fd_(-1)
> > +{
> > +}
> > +
> > +Device::~Device()
> > +{
> > +	if (fd_ != -1)
> > +		drmClose(fd_);
> > +}
> > +
> > +int Device::init()
> > +{
> > +	constexpr size_t NODE_NAME_MAX = sizeof("/dev/dri/card255");
> > +	char name[NODE_NAME_MAX];
> > +	int ret;
> > +
> > +	/*
> > +	 * Open the first DRM/KMS device. The libdrm drmOpen*() functions
> > +	 * require either a module name or a bus ID, which we don't have, so
> > +	 * bypass them. The automatic module loading and device node creation
> > +	 * from drmOpen() is of no practical use as any modern system will
> > +	 * handle that through udev or an equivalent component.
> > +	 */
> > +	snprintf(name, sizeof(name), "/dev/dri/card%u", 0);
> > +	fd_ = open(name, O_RDWR | O_CLOEXEC);
> > +	if (fd_ < 0) {
> > +		ret = -errno;
> > +		std::cerr
> > +			<< "Failed to open DRM/KMS device " << name << ": "
> > +			<< strerror(-ret) << std::endl;
> > +		return ret;
> > +	}
> > +
> > +	/*
> > +	 * Enable the atomic APIs. This also enables automatically the
> 
> exceptionally minor nit
> s/enables automatically/automatically enables/
> 
> > +	 * universal planes API.
> > +	 */
> > +	ret = drmSetClientCap(fd_, DRM_CLIENT_CAP_ATOMIC, 1);
> > +	if (ret < 0) {
> > +		ret = -errno;
> > +		std::cerr
> > +			<< "Failed to enable atomic capability: "
> > +			<< strerror(-ret) << std::endl;
> > +		return ret;
> > +	}
> > +
> > +	/* List all the resources. */
> > +	ret = getResources();
> > +	if (ret < 0)
> > +		return ret;
> > +
> > +	EventLoop::instance()->addEvent(fd_, EventLoop::Read,
> > +					std::bind(&Device::drmEvent, this));
> > +
> > +	return 0;
> > +}
> > +
> > +int Device::getResources()
> > +{
> > +	int ret;
> > +
> > +	std::unique_ptr<drmModeRes, decltype(&drmModeFreeResources)> resources{
> > +		drmModeGetResources(fd_),
> > +		&drmModeFreeResources
> > +	};
> > +	if (!resources) {
> > +		ret = -errno;
> > +		std::cerr
> > +			<< "Failed to get DRM/KMS resources: "
> > +			<< strerror(-ret) << std::endl;
> > +		return ret;
> > +	}
> > +
> > +	for (int i = 0; i < resources->count_crtcs; ++i) {
> > +		drmModeCrtc *crtc = drmModeGetCrtc(fd_, resources->crtcs[i]);
> > +		if (!crtc) {
> > +			ret = -errno;
> > +			std::cerr
> > +				<< "Failed to get CRTC: " << strerror(-ret)
> > +				<< std::endl;
> > +			return ret;
> > +		}
> > +
> > +		crtcs_.emplace_back(this, crtc, i);
> > +		drmModeFreeCrtc(crtc);
> > +
> > +		Crtc &obj = crtcs_.back();
> > +		objects_[obj.id()] = &obj;
> > +	}
> > +
> > +	for (int i = 0; i < resources->count_encoders; ++i) {
> > +		drmModeEncoder *encoder =
> > +			drmModeGetEncoder(fd_, resources->encoders[i]);
> > +		if (!encoder) {
> > +			ret = -errno;
> > +			std::cerr
> > +				<< "Failed to get encoder: " << strerror(-ret)
> > +				<< std::endl;
> > +			return ret;
> > +		}
> > +
> > +		encoders_.emplace_back(this, encoder);
> > +		drmModeFreeEncoder(encoder);
> > +
> > +		Encoder &obj = encoders_.back();
> > +		objects_[obj.id()] = &obj;
> > +	}
> > +
> > +	for (int i = 0; i < resources->count_connectors; ++i) {
> > +		drmModeConnector *connector =
> > +			drmModeGetConnector(fd_, resources->connectors[i]);
> > +		if (!connector) {
> > +			ret = -errno;
> > +			std::cerr
> > +				<< "Failed to get connector: " << strerror(-ret)
> > +				<< std::endl;
> > +			return ret;
> > +		}
> > +
> > +		connectors_.emplace_back(this, connector);
> > +		drmModeFreeConnector(connector);
> > +
> > +		Connector &obj = connectors_.back();
> > +		objects_[obj.id()] = &obj;
> > +	}
> > +
> > +	std::unique_ptr<drmModePlaneRes, decltype(&drmModeFreePlaneResources)> planes{
> > +		drmModeGetPlaneResources(fd_),
> > +		&drmModeFreePlaneResources
> > +	};
> > +	if (!planes) {
> > +		ret = -errno;
> > +		std::cerr
> > +			<< "Failed to get DRM/KMS planes: "
> > +			<< strerror(-ret) << std::endl;
> > +		return ret;
> > +	}
> > +
> > +	for (uint32_t i = 0; i < planes->count_planes; ++i) {
> > +		drmModePlane *plane =
> > +			drmModeGetPlane(fd_, planes->planes[i]);
> > +		if (!plane) {
> > +			ret = -errno;
> > +			std::cerr
> > +				<< "Failed to get plane: " << strerror(-ret)
> > +				<< std::endl;
> > +			return ret;
> > +		}
> > +
> > +		planes_.emplace_back(this, plane);
> > +		drmModeFreePlane(plane);
> > +
> > +		Plane &obj = planes_.back();
> > +		objects_[obj.id()] = &obj;
> > +	}
> > +
> > +	/* Set the possible planes for each CRTC. */
> > +	for (Crtc &crtc : crtcs_) {
> > +		for (const Plane &plane : planes_) {
> > +			if (plane.possibleCrtcsMask_ & (1 << crtc.index()))
> > +				crtc.planes_.push_back(&plane);
> > +		}
> > +	}
> > +
> > +	/* Collect all property IDs and create Property instances. */
> > +	std::set<uint32_t> properties;
> > +	for (const auto &object : objects_) {
> > +		for (const PropertyValue &value : object.second->properties())
> > +			properties.insert(value.id());
> > +	}
> > +
> > +	for (uint32_t id : properties) {
> > +		drmModePropertyRes *property = drmModeGetProperty(fd_, id);
> > +		if (!property) {
> > +			ret = -errno;
> > +			std::cerr
> > +				<< "Failed to get property: " << strerror(-ret)
> > +				<< std::endl;
> > +			continue;
> > +		}
> > +
> > +		properties_.emplace_back(this, property);
> > +		drmModeFreeProperty(property);
> > +
> > +		Property &obj = properties_.back();
> > +		objects_[obj.id()] = &obj;
> > +	}
> > +
> > +	/* Finally, perform all delayed setup of mode objects. */
> > +	for (auto &object : objects_) {
> > +		ret = object.second->setup();
> > +		if (ret < 0) {
> > +			std::cerr
> > +				<< "Failed to setup object " << object.second->id()
> > +				<< ": " << strerror(-ret) << std::endl;
> > +			return ret;
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +const Object *Device::object(uint32_t id)
> > +{
> > +	const auto iter = objects_.find(id);
> > +	if (iter == objects_.end())
> > +		return nullptr;
> > +
> > +	return iter->second;
> > +}
> > +
> > +std::unique_ptr<FrameBuffer> Device::createFrameBuffer(
> > +	const libcamera::FrameBuffer &buffer,
> > +	const libcamera::PixelFormat &format,
> > +	const libcamera::Size &size, unsigned int stride)
> > +{
> > +	std::unique_ptr<FrameBuffer> fb{ new FrameBuffer(this) };
> > +
> > +	uint32_t handles[4] = {};
> > +	uint32_t pitches[4] = {};
> > +	uint32_t offsets[4] = {};
> > +	int ret;
> > +
> > +	const std::vector<libcamera::FrameBuffer::Plane> &planes = buffer.planes();
> > +	fb->planes_.reserve(planes.size());
> > +
> > +	unsigned int i = 0;
> > +	for (const libcamera::FrameBuffer::Plane &plane : planes) {
> > +		uint32_t handle;
> > +
> > +		ret = drmPrimeFDToHandle(fd_, plane.fd.fd(), &handle);
> > +		if (ret < 0) {
> > +			ret = -errno;
> > +			std::cerr
> > +				<< "Unable to import framebuffer dmabuf: "
> > +				<< strerror(-ret) << std::endl;
> > +			return nullptr;
> > +		}
> > +
> > +		fb->planes_.push_back({ handle });
> > +
> > +		handles[i] = handle;
> > +		pitches[i] = stride;
> > +		offsets[i] = 0; /* TODO */
> 
> Is this an issue? Is this for supporting semi-planar formats or such?

That's correct, it's to support the multiplanar *formats* that use the
single-planar *API* in V4L2 (all planes in the same dmabuf).

> > +		++i;
> > +	}
> > +
> > +	ret = drmModeAddFB2(fd_, size.width, size.height, format.fourcc(), handles,
> > +			    pitches, offsets, &fb->id_, 0);
> > +	if (ret < 0) {
> > +		ret = -errno;
> > +		std::cerr
> > +			<< "Failed to add framebuffer: "
> > +			<< strerror(-ret) << std::endl;
> > +		return nullptr;
> > +	}
> > +
> > +	return fb;
> > +}
> > +
> > +void Device::drmEvent()
> > +{
> > +	drmEventContext ctx{};
> > +	ctx.version = DRM_EVENT_CONTEXT_VERSION;
> > +	ctx.page_flip_handler = &Device::pageFlipComplete;
> > +
> > +	drmHandleEvent(fd_, &ctx);
> > +}
> > +
> > +void Device::pageFlipComplete([[maybe_unused]] int fd,
> > +			      [[maybe_unused]] unsigned int sequence,
> > +			      [[maybe_unused]] unsigned int tv_sec,
> > +			      [[maybe_unused]] unsigned int tv_usec,
> > +			      void *user_data)
> > +{
> > +	AtomicRequest *request = static_cast<AtomicRequest *>(user_data);
> > +	request->device()->requestComplete.emit(request);
> > +}
> > +
> > +} /* namespace DRM */
> > diff --git a/src/cam/drm.h b/src/cam/drm.h
> > new file mode 100644
> > index 000000000000..ee2304025208
> > --- /dev/null
> > +++ b/src/cam/drm.h
> > @@ -0,0 +1,331 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > +/*
> > + * Copyright (C) 2021, Ideas on Board Oy
> > + *
> > + * drm.h - DRM/KMS Helpers
> > + */
> > +#ifndef __CAM_DRM_H__
> > +#define __CAM_DRM_H__
> > +
> > +#include <list>
> > +#include <map>
> > +#include <memory>
> > +#include <string>
> > +#include <vector>
> > +
> > +#include <libcamera/base/signal.h>
> > +#include <libcamera/base/span.h>
> > +
> > +#include <libdrm/drm.h>
> > +#include <xf86drm.h>
> > +#include <xf86drmMode.h>
> > +
> > +namespace libcamera {
> > +class FrameBuffer;
> > +class PixelFormat;
> > +class Size;
> > +} /* namespace libcamera */
> > +
> > +namespace DRM {
> > +
> > +class Device;
> > +class Plane;
> > +class Property;
> > +class PropertyValue;
> > +
> > +class Object
> > +{
> > +public:
> > +	enum Type {
> > +		TypeCrtc = DRM_MODE_OBJECT_CRTC,
> > +		TypeConnector = DRM_MODE_OBJECT_CONNECTOR,
> > +		TypeEncoder = DRM_MODE_OBJECT_ENCODER,
> > +		TypeMode = DRM_MODE_OBJECT_MODE,
> > +		TypeProperty = DRM_MODE_OBJECT_PROPERTY,
> > +		TypeFb = DRM_MODE_OBJECT_FB,
> > +		TypeBlob = DRM_MODE_OBJECT_BLOB,
> > +		TypePlane = DRM_MODE_OBJECT_PLANE,
> > +		TypeAny = DRM_MODE_OBJECT_ANY,
> > +	};
> > +
> > +	Object(Device *dev, uint32_t id, Type type);
> > +	virtual ~Object();
> > +
> > +	Device *device() const { return dev_; }
> > +	uint32_t id() const { return id_; }
> > +	Type type() const { return type_; }
> > +
> > +	const Property *property(const std::string &name) const;
> > +	const PropertyValue *propertyValue(const std::string &name) const;
> > +	const std::vector<PropertyValue> &properties() const { return properties_; }
> > +
> > +protected:
> > +	virtual int setup()
> > +	{
> > +		return 0;
> > +	}
> > +
> > +	uint32_t id_;
> > +
> > +private:
> > +	friend Device;
> > +
> > +	Device *dev_;
> > +	Type type_;
> > +	std::vector<PropertyValue> properties_;
> > +};
> > +
> > +class Property : public Object
> > +{
> > +public:
> > +	enum Type {
> > +		TypeUnknown = 0,
> > +		TypeRange,
> > +		TypeEnum,
> > +		TypeBlob,
> > +		TypeBitmask,
> > +		TypeObject,
> > +		TypeSignedRange,
> > +	};
> > +
> > +	Property(Device *dev, drmModePropertyRes *property);
> > +
> > +	Type type() const { return type_; }
> > +	const std::string &name() const { return name_; }
> > +
> > +	bool isImmutable() const { return flags_ & DRM_MODE_PROP_IMMUTABLE; }
> > +
> > +	const std::vector<uint64_t> values() const { return values_; }
> > +	const std::map<uint32_t, std::string> &enums() const { return enums_; }
> > +	const std::vector<uint32_t> blobs() const { return blobs_; }
> > +
> > +private:
> > +	Type type_;
> > +	std::string name_;
> > +	uint32_t flags_;
> > +	std::vector<uint64_t> values_;
> > +	std::map<uint32_t, std::string> enums_;
> > +	std::vector<uint32_t> blobs_;
> > +};
> > +
> > +class PropertyValue
> > +{
> > +public:
> > +	PropertyValue(uint32_t id, uint64_t value)
> > +		: id_(id), value_(value)
> > +	{
> > +	}
> > +
> > +	uint32_t id() const { return id_; }
> > +	uint32_t value() const { return value_; }
> > +
> > +private:
> > +	uint32_t id_;
> > +	uint64_t value_;
> > +};
> > +
> > +class Blob : public Object
> > +{
> > +public:
> > +	Blob(Device *dev, const libcamera::Span<const uint8_t> &data);
> > +	~Blob();
> > +
> > +	bool isValid() const { return id() != 0; }
> > +};
> > +
> > +class Mode : public drmModeModeInfo
> > +{
> > +public:
> > +	Mode(const drmModeModeInfo &mode);
> > +
> > +	std::unique_ptr<Blob> toBlob(Device *dev) const;
> > +};
> > +
> > +class Crtc : public Object
> > +{
> > +public:
> > +	Crtc(Device *dev, const drmModeCrtc *crtc, unsigned int index);
> > +
> > +	unsigned int index() const { return index_; }
> > +	const std::vector<const Plane *> &planes() const { return planes_; }
> > +
> > +private:
> > +	friend Device;
> > +
> > +	unsigned int index_;
> > +	std::vector<const Plane *> planes_;
> > +};
> > +
> > +class Encoder : public Object
> > +{
> > +public:
> > +	Encoder(Device *dev, const drmModeEncoder *encoder);
> > +
> > +	uint32_t type() const { return type_; }
> > +
> > +	const std::vector<const Crtc *> &possibleCrtcs() const { return possibleCrtcs_; }
> > +
> > +private:
> > +	uint32_t type_;
> > +	std::vector<const Crtc *> possibleCrtcs_;
> > +};
> > +
> > +class Connector : public Object
> > +{
> > +public:
> > +	enum Status {
> > +		Connected,
> > +		Disconnected,
> > +		Unknown,
> > +	};
> > +
> > +	Connector(Device *dev, const drmModeConnector *connector);
> > +
> > +	uint32_t type() const { return type_; }
> > +	const std::string &name() const { return name_; }
> > +
> > +	Status status() const { return status_; }
> > +
> > +	const std::vector<const Encoder *> &encoders() const { return encoders_; }
> > +	const std::vector<Mode> &modes() const { return modes_; }
> > +
> > +private:
> > +	uint32_t type_;
> > +	std::string name_;
> > +	Status status_;
> > +	std::vector<const Encoder *> encoders_;
> > +	std::vector<Mode> modes_;
> > +};
> > +
> > +class Plane : public Object
> > +{
> > +public:
> > +	enum Type {
> > +		TypeOverlay,
> > +		TypePrimary,
> > +		TypeCursor,
> > +	};
> > +
> > +	Plane(Device *dev, const drmModePlane *plane);
> > +
> > +	Type type() const { return type_; }
> > +	const std::vector<uint32_t> &formats() const { return formats_; }
> > +	const std::vector<const Crtc *> &possibleCrtcs() const { return possibleCrtcs_; }
> > +
> > +	bool supportsFormat(const libcamera::PixelFormat &format) const;
> > +
> > +protected:
> > +	int setup() override;
> > +
> > +private:
> > +	friend class Device;
> > +
> > +	Type type_;
> > +	std::vector<uint32_t> formats_;
> > +	std::vector<const Crtc *> possibleCrtcs_;
> > +	uint32_t possibleCrtcsMask_;
> > +};
> > +
> > +class FrameBuffer : public Object
> > +{
> > +public:
> > +	struct Plane {
> > +		uint32_t handle;
> > +	};
> > +
> > +	~FrameBuffer();
> > +
> > +private:
> > +	friend class Device;
> > +
> > +	FrameBuffer(Device *dev);
> > +
> > +	std::vector<Plane> planes_;
> > +};
> > +
> > +class AtomicRequest
> > +{
> > +public:
> > +	enum Flags {
> > +		FlagAllowModeset = (1 << 0),
> > +		FlagAsync = (1 << 1),
> > +	};
> > +
> > +	AtomicRequest(Device *dev);
> > +	~AtomicRequest();
> > +
> > +	Device *device() const { return dev_; }
> > +	bool isValid() const { return valid_; }
> > +
> > +	int addProperty(const Object *object, const std::string &property,
> > +			uint64_t value);
> > +	int addProperty(const Object *object, const std::string &property,
> > +			std::unique_ptr<Blob> blob);
> > +	int commit(unsigned int flags = 0);
> > +
> > +private:
> > +	AtomicRequest(const AtomicRequest &) = delete;
> > +	AtomicRequest(const AtomicRequest &&) = delete;
> > +	AtomicRequest &operator=(const AtomicRequest &) = delete;
> > +	AtomicRequest &operator=(const AtomicRequest &&) = delete;
> > +
> > +	int addProperty(uint32_t object, uint32_t property, uint64_t value);
> > +
> > +	Device *dev_;
> > +	bool valid_;
> > +	drmModeAtomicReq *request_;
> > +	std::list<std::unique_ptr<Blob>> blobs_;
> > +};
> > +
> > +class Device
> > +{
> > +public:
> > +	Device();
> > +	~Device();
> > +
> > +	int init();
> > +
> > +	int fd() const { return fd_; }
> > +
> > +	const std::list<Crtc> &crtcs() const { return crtcs_; }
> > +	const std::list<Encoder> &encoders() const { return encoders_; }
> > +	const std::list<Connector> &connectors() const { return connectors_; }
> > +	const std::list<Plane> &planes() const { return planes_; }
> > +	const std::list<Property> &properties() const { return properties_; }
> > +
> > +	const Object *object(uint32_t id);
> > +
> > +	std::unique_ptr<FrameBuffer> createFrameBuffer(
> > +		const libcamera::FrameBuffer &buffer,
> > +		const libcamera::PixelFormat &format,
> > +		const libcamera::Size &size, unsigned int stride);
> > +
> > +	libcamera::Signal<AtomicRequest *> requestComplete;
> > +
> > +private:
> > +	Device(const Device &) = delete;
> > +	Device(const Device &&) = delete;
> > +	Device &operator=(const Device &) = delete;
> > +	Device &operator=(const Device &&) = delete;
> > +
> > +	int getResources();
> > +
> > +	void drmEvent();
> > +	static void pageFlipComplete(int fd, unsigned int sequence,
> > +				     unsigned int tv_sec, unsigned int tv_usec,
> > +				     void *user_data);
> > +
> > +	int fd_;
> > +
> > +	std::list<Crtc> crtcs_;
> > +	std::list<Encoder> encoders_;
> > +	std::list<Connector> connectors_;
> > +	std::list<Plane> planes_;
> > +	std::list<Property> properties_;
> > +
> > +	std::map<uint32_t, Object *> objects_;
> > +};
> > +
> > +} /* namespace DRM */
> > +
> > +#endif /* __CAM_DRM_H__ */
> > diff --git a/src/cam/meson.build b/src/cam/meson.build
> > index e692ea351987..b47add55b0cb 100644
> > --- a/src/cam/meson.build
> > +++ b/src/cam/meson.build
> > @@ -19,10 +19,23 @@ cam_sources = files([
> >      'stream_options.cpp',
> >  ])
> >  
> > +cam_cpp_args = []
> > +
> > +libdrm = dependency('libdrm', required : false)
> > +
> > +if libdrm.found()
> > +cam_cpp_args += [ '-DHAVE_KMS' ]
> > +cam_sources += files([
> > +    'drm.cpp',
> 
> From what I can tell - this could be a helpful re-usable component for
> other applications too.

libdrm++ ? ;-)

> The only thing that ties it to cam currently is the event loop - so
> perhaps in the future that could be parameterised somehow - but that's
> not needed here so.
> 
> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> 
> But ... ouch - so much work to be able to get an image on the screen.

Nah, piece of cake :-)

> And this isnt' even all of it yet...
> 
> Can we bring back /dev/fb0 ;-)

Are you missing its memcpy() requirements ?

> > +])
> > +endif
> > +
> >  cam  = executable('cam', cam_sources,
> >                    dependencies : [
> >                        libatomic,
> >                        libcamera_public,
> > +                      libdrm,
> >                        libevent,
> >                    ],
> > +                  cpp_args : cam_cpp_args,
> >                    install : true)
> >
Kieran Bingham Aug. 5, 2021, 12:38 p.m. UTC | #3
On 05/08/2021 12:58, Laurent Pinchart wrote:
> Hi Kieran,>>
>> But ... ouch - so much work to be able to get an image on the screen.
> 
> Nah, piece of cake :-)
> 
>> And this isnt' even all of it yet...
>>
>> Can we bring back /dev/fb0 ;-)
> 
> Are you missing its memcpy() requirements ?

What's wrong with memcpy; I heard it's often one of the most popular
functions to run on CPU's by measure of time ...

That must mean it's really good. ;-)

--
Kieran

Patch
diff mbox series

diff --git a/src/cam/drm.cpp b/src/cam/drm.cpp
new file mode 100644
index 000000000000..09c52b2ed64f
--- /dev/null
+++ b/src/cam/drm.cpp
@@ -0,0 +1,663 @@ 
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2021, Ideas on Board Oy
+ *
+ * drm.cpp - DRM/KMS Helpers
+ */
+
+#include "drm.h"
+
+#include <algorithm>
+#include <errno.h>
+#include <fcntl.h>
+#include <iostream>
+#include <set>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <libcamera/framebuffer.h>
+#include <libcamera/geometry.h>
+#include <libcamera/pixel_format.h>
+
+#include <libdrm/drm_mode.h>
+
+#include "event_loop.h"
+
+namespace DRM {
+
+Object::Object(Device *dev, uint32_t id, Type type)
+	: id_(id), dev_(dev), type_(type)
+{
+	/* Retrieve properties from the objects that support them. */
+	if (type != TypeConnector && type != TypeCrtc &&
+	    type != TypeEncoder && type != TypePlane)
+		return;
+
+	/*
+	 * We can't distinguish between failures due to the object having no
+	 * property and failures due to other conditions. Assume we use the API
+	 * correctly and consider the object has no property.
+	 */
+	drmModeObjectProperties *properties = drmModeObjectGetProperties(dev->fd(), id, type);
+	if (!properties)
+		return;
+
+	properties_.reserve(properties->count_props);
+	for (uint32_t i = 0; i < properties->count_props; ++i)
+		properties_.emplace_back(properties->props[i],
+					 properties->prop_values[i]);
+
+	drmModeFreeObjectProperties(properties);
+}
+
+Object::~Object()
+{
+}
+
+const Property *Object::property(const std::string &name) const
+{
+	for (const PropertyValue &pv : properties_) {
+		const Property *property = static_cast<const Property *>(dev_->object(pv.id()));
+		if (property && property->name() == name)
+			return property;
+	}
+
+	return nullptr;
+}
+
+const PropertyValue *Object::propertyValue(const std::string &name) const
+{
+	for (const PropertyValue &pv : properties_) {
+		const Property *property = static_cast<const Property *>(dev_->object(pv.id()));
+		if (property && property->name() == name)
+			return &pv;
+	}
+
+	return nullptr;
+}
+
+Property::Property(Device *dev, drmModePropertyRes *property)
+	: Object(dev, property->prop_id, TypeProperty),
+	  name_(property->name), flags_(property->flags),
+	  values_(property->values, property->values + property->count_values),
+	  blobs_(property->blob_ids, property->blob_ids + property->count_blobs)
+{
+	if (drm_property_type_is(property, DRM_MODE_PROP_RANGE))
+		type_ = TypeRange;
+	else if (drm_property_type_is(property, DRM_MODE_PROP_ENUM))
+		type_ = TypeEnum;
+	else if (drm_property_type_is(property, DRM_MODE_PROP_BLOB))
+		type_ = TypeBlob;
+	else if (drm_property_type_is(property, DRM_MODE_PROP_BITMASK))
+		type_ = TypeBitmask;
+	else if (drm_property_type_is(property, DRM_MODE_PROP_OBJECT))
+		type_ = TypeObject;
+	else if (drm_property_type_is(property, DRM_MODE_PROP_SIGNED_RANGE))
+		type_ = TypeSignedRange;
+	else
+		type_ = TypeUnknown;
+
+	for (int i = 0; i < property->count_enums; ++i)
+		enums_[property->enums[i].value] = property->enums[i].name;
+}
+
+Blob::Blob(Device *dev, const libcamera::Span<const uint8_t> &data)
+	: Object(dev, 0, Object::TypeBlob)
+{
+	drmModeCreatePropertyBlob(dev->fd(), data.data(), data.size(), &id_);
+}
+
+Blob::~Blob()
+{
+	if (isValid())
+		drmModeDestroyPropertyBlob(device()->fd(), id());
+}
+
+Mode::Mode(const drmModeModeInfo &mode)
+	: drmModeModeInfo(mode)
+{
+}
+
+std::unique_ptr<Blob> Mode::toBlob(Device *dev) const
+{
+	libcamera::Span<const uint8_t> data{ reinterpret_cast<const uint8_t *>(this),
+					     sizeof(*this) };
+	return std::make_unique<Blob>(dev, data);
+}
+
+Crtc::Crtc(Device *dev, const drmModeCrtc *crtc, unsigned int index)
+	: Object(dev, crtc->crtc_id, Object::TypeCrtc), index_(index)
+{
+}
+
+Encoder::Encoder(Device *dev, const drmModeEncoder *encoder)
+	: Object(dev, encoder->encoder_id, Object::TypeEncoder),
+	  type_(encoder->encoder_type)
+{
+	const std::list<Crtc> &crtcs = dev->crtcs();
+	possibleCrtcs_.reserve(crtcs.size());
+
+	for (const Crtc &crtc : crtcs) {
+		if (encoder->possible_crtcs & (1 << crtc.index()))
+			possibleCrtcs_.push_back(&crtc);
+	}
+
+	possibleCrtcs_.shrink_to_fit();
+}
+
+namespace {
+
+const std::map<uint32_t, const char *> connectorTypeNames{
+	{ DRM_MODE_CONNECTOR_Unknown, "Unknown" },
+	{ DRM_MODE_CONNECTOR_VGA, "VGA" },
+	{ DRM_MODE_CONNECTOR_DVII, "DVI-I" },
+	{ DRM_MODE_CONNECTOR_DVID, "DVI-D" },
+	{ DRM_MODE_CONNECTOR_DVIA, "DVI-A" },
+	{ DRM_MODE_CONNECTOR_Composite, "Composite" },
+	{ DRM_MODE_CONNECTOR_SVIDEO, "S-Video" },
+	{ DRM_MODE_CONNECTOR_LVDS, "LVDS" },
+	{ DRM_MODE_CONNECTOR_Component, "Component" },
+	{ DRM_MODE_CONNECTOR_9PinDIN, "9-Pin-DIN" },
+	{ DRM_MODE_CONNECTOR_DisplayPort, "DP" },
+	{ DRM_MODE_CONNECTOR_HDMIA, "HDMI-A" },
+	{ DRM_MODE_CONNECTOR_HDMIB, "HDMI-B" },
+	{ DRM_MODE_CONNECTOR_TV, "TV" },
+	{ DRM_MODE_CONNECTOR_eDP, "eDP" },
+	{ DRM_MODE_CONNECTOR_VIRTUAL, "Virtual" },
+	{ DRM_MODE_CONNECTOR_DSI, "DSI" },
+	{ DRM_MODE_CONNECTOR_DPI, "DPI" },
+};
+
+} /* namespace */
+
+Connector::Connector(Device *dev, const drmModeConnector *connector)
+	: Object(dev, connector->connector_id, Object::TypeConnector),
+	  type_(connector->connector_type)
+{
+	auto typeName = connectorTypeNames.find(connector->connector_type);
+	if (typeName == connectorTypeNames.end()) {
+		std::cerr
+			<< "Invalid connector type "
+			<< connector->connector_type << std::endl;
+		typeName = connectorTypeNames.find(DRM_MODE_CONNECTOR_Unknown);
+	}
+
+	name_ = std::string(typeName->second) + "-"
+	      + std::to_string(connector->connector_type_id);
+
+	switch (connector->connection) {
+	case DRM_MODE_CONNECTED:
+		status_ = Status::Connected;
+		break;
+
+	case DRM_MODE_DISCONNECTED:
+		status_ = Status::Disconnected;
+		break;
+
+	case DRM_MODE_UNKNOWNCONNECTION:
+	default:
+		status_ = Status::Unknown;
+		break;
+	}
+
+	const std::list<Encoder> &encoders = dev->encoders();
+
+	encoders_.reserve(connector->count_encoders);
+
+	for (int i = 0; i < connector->count_encoders; ++i) {
+		uint32_t encoderId = connector->encoders[i];
+		auto encoder = std::find_if(encoders.begin(), encoders.end(),
+					    [=](const Encoder &e) {
+						    return e.id() == encoderId;
+					    });
+		if (encoder == encoders.end()) {
+			std::cerr
+				<< "Encoder " << encoderId << " not found"
+				<< std::endl;
+			continue;
+		}
+
+		encoders_.push_back(&*encoder);
+	}
+
+	encoders_.shrink_to_fit();
+
+	modes_ = { connector->modes, connector->modes + connector->count_modes };
+}
+
+Plane::Plane(Device *dev, const drmModePlane *plane)
+	: Object(dev, plane->plane_id, Object::TypePlane),
+	  possibleCrtcsMask_(plane->possible_crtcs)
+{
+	formats_ = { plane->formats, plane->formats + plane->count_formats };
+
+	const std::list<Crtc> &crtcs = dev->crtcs();
+	possibleCrtcs_.reserve(crtcs.size());
+
+	for (const Crtc &crtc : crtcs) {
+		if (plane->possible_crtcs & (1 << crtc.index()))
+			possibleCrtcs_.push_back(&crtc);
+	}
+
+	possibleCrtcs_.shrink_to_fit();
+}
+
+bool Plane::supportsFormat(const libcamera::PixelFormat &format) const
+{
+	return std::find(formats_.begin(), formats_.end(), format.fourcc())
+		!= formats_.end();
+}
+
+int Plane::setup()
+{
+	const PropertyValue *pv = propertyValue("type");
+	if (!pv)
+		return -EINVAL;
+
+	switch (pv->value()) {
+	case DRM_PLANE_TYPE_OVERLAY:
+		type_ = TypeOverlay;
+		break;
+
+	case DRM_PLANE_TYPE_PRIMARY:
+		type_ = TypePrimary;
+		break;
+
+	case DRM_PLANE_TYPE_CURSOR:
+		type_ = TypeCursor;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+FrameBuffer::FrameBuffer(Device *dev)
+	: Object(dev, 0, Object::TypeFb)
+{
+}
+
+FrameBuffer::~FrameBuffer()
+{
+	for (FrameBuffer::Plane &plane : planes_) {
+		struct drm_gem_close gem_close = {
+			.handle = plane.handle,
+			.pad = 0,
+		};
+		int ret;
+
+		do {
+			ret = ioctl(device()->fd(), DRM_IOCTL_GEM_CLOSE, &gem_close);
+		} while (ret == -1 && (errno == EINTR || errno == EAGAIN));
+
+		if (ret == -1) {
+			ret = -errno;
+			std::cerr
+				<< "Failed to close GEM object: "
+				<< strerror(-ret) << std::endl;
+		}
+	}
+
+	drmModeRmFB(device()->fd(), id());
+}
+
+AtomicRequest::AtomicRequest(Device *dev)
+	: dev_(dev), valid_(true)
+{
+	request_ = drmModeAtomicAlloc();
+	if (!request_)
+		valid_ = false;
+}
+
+AtomicRequest::~AtomicRequest()
+{
+	if (request_)
+		drmModeAtomicFree(request_);
+}
+
+int AtomicRequest::addProperty(const Object *object, const std::string &property,
+			       uint64_t value)
+{
+	if (!valid_)
+		return -EINVAL;
+
+	const Property *prop = object->property(property);
+	if (!prop) {
+		valid_ = false;
+		return -EINVAL;
+	}
+
+	return addProperty(object->id(), prop->id(), value);
+}
+
+int AtomicRequest::addProperty(const Object *object, const std::string &property,
+			       std::unique_ptr<Blob> blob)
+{
+	if (!valid_)
+		return -EINVAL;
+
+	const Property *prop = object->property(property);
+	if (!prop) {
+		valid_ = false;
+		return -EINVAL;
+	}
+
+	int ret = addProperty(object->id(), prop->id(), blob->id());
+	if (ret < 0)
+		return ret;
+
+	blobs_.emplace_back(std::move(blob));
+
+	return 0;
+}
+
+int AtomicRequest::addProperty(uint32_t object, uint32_t property, uint64_t value)
+{
+	int ret = drmModeAtomicAddProperty(request_, object, property, value);
+	if (ret < 0) {
+		valid_ = false;
+		return ret;
+	}
+
+	return 0;
+}
+
+int AtomicRequest::commit(unsigned int flags)
+{
+	if (!valid_)
+		return -EINVAL;
+
+	uint32_t drmFlags = 0;
+	if (flags & FlagAllowModeset)
+		drmFlags |= DRM_MODE_ATOMIC_ALLOW_MODESET;
+	if (flags & FlagAsync)
+		drmFlags |= DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK;
+
+	return drmModeAtomicCommit(dev_->fd(), request_, drmFlags, this);
+}
+
+Device::Device()
+	: fd_(-1)
+{
+}
+
+Device::~Device()
+{
+	if (fd_ != -1)
+		drmClose(fd_);
+}
+
+int Device::init()
+{
+	constexpr size_t NODE_NAME_MAX = sizeof("/dev/dri/card255");
+	char name[NODE_NAME_MAX];
+	int ret;
+
+	/*
+	 * Open the first DRM/KMS device. The libdrm drmOpen*() functions
+	 * require either a module name or a bus ID, which we don't have, so
+	 * bypass them. The automatic module loading and device node creation
+	 * from drmOpen() is of no practical use as any modern system will
+	 * handle that through udev or an equivalent component.
+	 */
+	snprintf(name, sizeof(name), "/dev/dri/card%u", 0);
+	fd_ = open(name, O_RDWR | O_CLOEXEC);
+	if (fd_ < 0) {
+		ret = -errno;
+		std::cerr
+			<< "Failed to open DRM/KMS device " << name << ": "
+			<< strerror(-ret) << std::endl;
+		return ret;
+	}
+
+	/*
+	 * Enable the atomic APIs. This also enables automatically the
+	 * universal planes API.
+	 */
+	ret = drmSetClientCap(fd_, DRM_CLIENT_CAP_ATOMIC, 1);
+	if (ret < 0) {
+		ret = -errno;
+		std::cerr
+			<< "Failed to enable atomic capability: "
+			<< strerror(-ret) << std::endl;
+		return ret;
+	}
+
+	/* List all the resources. */
+	ret = getResources();
+	if (ret < 0)
+		return ret;
+
+	EventLoop::instance()->addEvent(fd_, EventLoop::Read,
+					std::bind(&Device::drmEvent, this));
+
+	return 0;
+}
+
+int Device::getResources()
+{
+	int ret;
+
+	std::unique_ptr<drmModeRes, decltype(&drmModeFreeResources)> resources{
+		drmModeGetResources(fd_),
+		&drmModeFreeResources
+	};
+	if (!resources) {
+		ret = -errno;
+		std::cerr
+			<< "Failed to get DRM/KMS resources: "
+			<< strerror(-ret) << std::endl;
+		return ret;
+	}
+
+	for (int i = 0; i < resources->count_crtcs; ++i) {
+		drmModeCrtc *crtc = drmModeGetCrtc(fd_, resources->crtcs[i]);
+		if (!crtc) {
+			ret = -errno;
+			std::cerr
+				<< "Failed to get CRTC: " << strerror(-ret)
+				<< std::endl;
+			return ret;
+		}
+
+		crtcs_.emplace_back(this, crtc, i);
+		drmModeFreeCrtc(crtc);
+
+		Crtc &obj = crtcs_.back();
+		objects_[obj.id()] = &obj;
+	}
+
+	for (int i = 0; i < resources->count_encoders; ++i) {
+		drmModeEncoder *encoder =
+			drmModeGetEncoder(fd_, resources->encoders[i]);
+		if (!encoder) {
+			ret = -errno;
+			std::cerr
+				<< "Failed to get encoder: " << strerror(-ret)
+				<< std::endl;
+			return ret;
+		}
+
+		encoders_.emplace_back(this, encoder);
+		drmModeFreeEncoder(encoder);
+
+		Encoder &obj = encoders_.back();
+		objects_[obj.id()] = &obj;
+	}
+
+	for (int i = 0; i < resources->count_connectors; ++i) {
+		drmModeConnector *connector =
+			drmModeGetConnector(fd_, resources->connectors[i]);
+		if (!connector) {
+			ret = -errno;
+			std::cerr
+				<< "Failed to get connector: " << strerror(-ret)
+				<< std::endl;
+			return ret;
+		}
+
+		connectors_.emplace_back(this, connector);
+		drmModeFreeConnector(connector);
+
+		Connector &obj = connectors_.back();
+		objects_[obj.id()] = &obj;
+	}
+
+	std::unique_ptr<drmModePlaneRes, decltype(&drmModeFreePlaneResources)> planes{
+		drmModeGetPlaneResources(fd_),
+		&drmModeFreePlaneResources
+	};
+	if (!planes) {
+		ret = -errno;
+		std::cerr
+			<< "Failed to get DRM/KMS planes: "
+			<< strerror(-ret) << std::endl;
+		return ret;
+	}
+
+	for (uint32_t i = 0; i < planes->count_planes; ++i) {
+		drmModePlane *plane =
+			drmModeGetPlane(fd_, planes->planes[i]);
+		if (!plane) {
+			ret = -errno;
+			std::cerr
+				<< "Failed to get plane: " << strerror(-ret)
+				<< std::endl;
+			return ret;
+		}
+
+		planes_.emplace_back(this, plane);
+		drmModeFreePlane(plane);
+
+		Plane &obj = planes_.back();
+		objects_[obj.id()] = &obj;
+	}
+
+	/* Set the possible planes for each CRTC. */
+	for (Crtc &crtc : crtcs_) {
+		for (const Plane &plane : planes_) {
+			if (plane.possibleCrtcsMask_ & (1 << crtc.index()))
+				crtc.planes_.push_back(&plane);
+		}
+	}
+
+	/* Collect all property IDs and create Property instances. */
+	std::set<uint32_t> properties;
+	for (const auto &object : objects_) {
+		for (const PropertyValue &value : object.second->properties())
+			properties.insert(value.id());
+	}
+
+	for (uint32_t id : properties) {
+		drmModePropertyRes *property = drmModeGetProperty(fd_, id);
+		if (!property) {
+			ret = -errno;
+			std::cerr
+				<< "Failed to get property: " << strerror(-ret)
+				<< std::endl;
+			continue;
+		}
+
+		properties_.emplace_back(this, property);
+		drmModeFreeProperty(property);
+
+		Property &obj = properties_.back();
+		objects_[obj.id()] = &obj;
+	}
+
+	/* Finally, perform all delayed setup of mode objects. */
+	for (auto &object : objects_) {
+		ret = object.second->setup();
+		if (ret < 0) {
+			std::cerr
+				<< "Failed to setup object " << object.second->id()
+				<< ": " << strerror(-ret) << std::endl;
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+const Object *Device::object(uint32_t id)
+{
+	const auto iter = objects_.find(id);
+	if (iter == objects_.end())
+		return nullptr;
+
+	return iter->second;
+}
+
+std::unique_ptr<FrameBuffer> Device::createFrameBuffer(
+	const libcamera::FrameBuffer &buffer,
+	const libcamera::PixelFormat &format,
+	const libcamera::Size &size, unsigned int stride)
+{
+	std::unique_ptr<FrameBuffer> fb{ new FrameBuffer(this) };
+
+	uint32_t handles[4] = {};
+	uint32_t pitches[4] = {};
+	uint32_t offsets[4] = {};
+	int ret;
+
+	const std::vector<libcamera::FrameBuffer::Plane> &planes = buffer.planes();
+	fb->planes_.reserve(planes.size());
+
+	unsigned int i = 0;
+	for (const libcamera::FrameBuffer::Plane &plane : planes) {
+		uint32_t handle;
+
+		ret = drmPrimeFDToHandle(fd_, plane.fd.fd(), &handle);
+		if (ret < 0) {
+			ret = -errno;
+			std::cerr
+				<< "Unable to import framebuffer dmabuf: "
+				<< strerror(-ret) << std::endl;
+			return nullptr;
+		}
+
+		fb->planes_.push_back({ handle });
+
+		handles[i] = handle;
+		pitches[i] = stride;
+		offsets[i] = 0; /* TODO */
+		++i;
+	}
+
+	ret = drmModeAddFB2(fd_, size.width, size.height, format.fourcc(), handles,
+			    pitches, offsets, &fb->id_, 0);
+	if (ret < 0) {
+		ret = -errno;
+		std::cerr
+			<< "Failed to add framebuffer: "
+			<< strerror(-ret) << std::endl;
+		return nullptr;
+	}
+
+	return fb;
+}
+
+void Device::drmEvent()
+{
+	drmEventContext ctx{};
+	ctx.version = DRM_EVENT_CONTEXT_VERSION;
+	ctx.page_flip_handler = &Device::pageFlipComplete;
+
+	drmHandleEvent(fd_, &ctx);
+}
+
+void Device::pageFlipComplete([[maybe_unused]] int fd,
+			      [[maybe_unused]] unsigned int sequence,
+			      [[maybe_unused]] unsigned int tv_sec,
+			      [[maybe_unused]] unsigned int tv_usec,
+			      void *user_data)
+{
+	AtomicRequest *request = static_cast<AtomicRequest *>(user_data);
+	request->device()->requestComplete.emit(request);
+}
+
+} /* namespace DRM */
diff --git a/src/cam/drm.h b/src/cam/drm.h
new file mode 100644
index 000000000000..ee2304025208
--- /dev/null
+++ b/src/cam/drm.h
@@ -0,0 +1,331 @@ 
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2021, Ideas on Board Oy
+ *
+ * drm.h - DRM/KMS Helpers
+ */
+#ifndef __CAM_DRM_H__
+#define __CAM_DRM_H__
+
+#include <list>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <libcamera/base/signal.h>
+#include <libcamera/base/span.h>
+
+#include <libdrm/drm.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+namespace libcamera {
+class FrameBuffer;
+class PixelFormat;
+class Size;
+} /* namespace libcamera */
+
+namespace DRM {
+
+class Device;
+class Plane;
+class Property;
+class PropertyValue;
+
+class Object
+{
+public:
+	enum Type {
+		TypeCrtc = DRM_MODE_OBJECT_CRTC,
+		TypeConnector = DRM_MODE_OBJECT_CONNECTOR,
+		TypeEncoder = DRM_MODE_OBJECT_ENCODER,
+		TypeMode = DRM_MODE_OBJECT_MODE,
+		TypeProperty = DRM_MODE_OBJECT_PROPERTY,
+		TypeFb = DRM_MODE_OBJECT_FB,
+		TypeBlob = DRM_MODE_OBJECT_BLOB,
+		TypePlane = DRM_MODE_OBJECT_PLANE,
+		TypeAny = DRM_MODE_OBJECT_ANY,
+	};
+
+	Object(Device *dev, uint32_t id, Type type);
+	virtual ~Object();
+
+	Device *device() const { return dev_; }
+	uint32_t id() const { return id_; }
+	Type type() const { return type_; }
+
+	const Property *property(const std::string &name) const;
+	const PropertyValue *propertyValue(const std::string &name) const;
+	const std::vector<PropertyValue> &properties() const { return properties_; }
+
+protected:
+	virtual int setup()
+	{
+		return 0;
+	}
+
+	uint32_t id_;
+
+private:
+	friend Device;
+
+	Device *dev_;
+	Type type_;
+	std::vector<PropertyValue> properties_;
+};
+
+class Property : public Object
+{
+public:
+	enum Type {
+		TypeUnknown = 0,
+		TypeRange,
+		TypeEnum,
+		TypeBlob,
+		TypeBitmask,
+		TypeObject,
+		TypeSignedRange,
+	};
+
+	Property(Device *dev, drmModePropertyRes *property);
+
+	Type type() const { return type_; }
+	const std::string &name() const { return name_; }
+
+	bool isImmutable() const { return flags_ & DRM_MODE_PROP_IMMUTABLE; }
+
+	const std::vector<uint64_t> values() const { return values_; }
+	const std::map<uint32_t, std::string> &enums() const { return enums_; }
+	const std::vector<uint32_t> blobs() const { return blobs_; }
+
+private:
+	Type type_;
+	std::string name_;
+	uint32_t flags_;
+	std::vector<uint64_t> values_;
+	std::map<uint32_t, std::string> enums_;
+	std::vector<uint32_t> blobs_;
+};
+
+class PropertyValue
+{
+public:
+	PropertyValue(uint32_t id, uint64_t value)
+		: id_(id), value_(value)
+	{
+	}
+
+	uint32_t id() const { return id_; }
+	uint32_t value() const { return value_; }
+
+private:
+	uint32_t id_;
+	uint64_t value_;
+};
+
+class Blob : public Object
+{
+public:
+	Blob(Device *dev, const libcamera::Span<const uint8_t> &data);
+	~Blob();
+
+	bool isValid() const { return id() != 0; }
+};
+
+class Mode : public drmModeModeInfo
+{
+public:
+	Mode(const drmModeModeInfo &mode);
+
+	std::unique_ptr<Blob> toBlob(Device *dev) const;
+};
+
+class Crtc : public Object
+{
+public:
+	Crtc(Device *dev, const drmModeCrtc *crtc, unsigned int index);
+
+	unsigned int index() const { return index_; }
+	const std::vector<const Plane *> &planes() const { return planes_; }
+
+private:
+	friend Device;
+
+	unsigned int index_;
+	std::vector<const Plane *> planes_;
+};
+
+class Encoder : public Object
+{
+public:
+	Encoder(Device *dev, const drmModeEncoder *encoder);
+
+	uint32_t type() const { return type_; }
+
+	const std::vector<const Crtc *> &possibleCrtcs() const { return possibleCrtcs_; }
+
+private:
+	uint32_t type_;
+	std::vector<const Crtc *> possibleCrtcs_;
+};
+
+class Connector : public Object
+{
+public:
+	enum Status {
+		Connected,
+		Disconnected,
+		Unknown,
+	};
+
+	Connector(Device *dev, const drmModeConnector *connector);
+
+	uint32_t type() const { return type_; }
+	const std::string &name() const { return name_; }
+
+	Status status() const { return status_; }
+
+	const std::vector<const Encoder *> &encoders() const { return encoders_; }
+	const std::vector<Mode> &modes() const { return modes_; }
+
+private:
+	uint32_t type_;
+	std::string name_;
+	Status status_;
+	std::vector<const Encoder *> encoders_;
+	std::vector<Mode> modes_;
+};
+
+class Plane : public Object
+{
+public:
+	enum Type {
+		TypeOverlay,
+		TypePrimary,
+		TypeCursor,
+	};
+
+	Plane(Device *dev, const drmModePlane *plane);
+
+	Type type() const { return type_; }
+	const std::vector<uint32_t> &formats() const { return formats_; }
+	const std::vector<const Crtc *> &possibleCrtcs() const { return possibleCrtcs_; }
+
+	bool supportsFormat(const libcamera::PixelFormat &format) const;
+
+protected:
+	int setup() override;
+
+private:
+	friend class Device;
+
+	Type type_;
+	std::vector<uint32_t> formats_;
+	std::vector<const Crtc *> possibleCrtcs_;
+	uint32_t possibleCrtcsMask_;
+};
+
+class FrameBuffer : public Object
+{
+public:
+	struct Plane {
+		uint32_t handle;
+	};
+
+	~FrameBuffer();
+
+private:
+	friend class Device;
+
+	FrameBuffer(Device *dev);
+
+	std::vector<Plane> planes_;
+};
+
+class AtomicRequest
+{
+public:
+	enum Flags {
+		FlagAllowModeset = (1 << 0),
+		FlagAsync = (1 << 1),
+	};
+
+	AtomicRequest(Device *dev);
+	~AtomicRequest();
+
+	Device *device() const { return dev_; }
+	bool isValid() const { return valid_; }
+
+	int addProperty(const Object *object, const std::string &property,
+			uint64_t value);
+	int addProperty(const Object *object, const std::string &property,
+			std::unique_ptr<Blob> blob);
+	int commit(unsigned int flags = 0);
+
+private:
+	AtomicRequest(const AtomicRequest &) = delete;
+	AtomicRequest(const AtomicRequest &&) = delete;
+	AtomicRequest &operator=(const AtomicRequest &) = delete;
+	AtomicRequest &operator=(const AtomicRequest &&) = delete;
+
+	int addProperty(uint32_t object, uint32_t property, uint64_t value);
+
+	Device *dev_;
+	bool valid_;
+	drmModeAtomicReq *request_;
+	std::list<std::unique_ptr<Blob>> blobs_;
+};
+
+class Device
+{
+public:
+	Device();
+	~Device();
+
+	int init();
+
+	int fd() const { return fd_; }
+
+	const std::list<Crtc> &crtcs() const { return crtcs_; }
+	const std::list<Encoder> &encoders() const { return encoders_; }
+	const std::list<Connector> &connectors() const { return connectors_; }
+	const std::list<Plane> &planes() const { return planes_; }
+	const std::list<Property> &properties() const { return properties_; }
+
+	const Object *object(uint32_t id);
+
+	std::unique_ptr<FrameBuffer> createFrameBuffer(
+		const libcamera::FrameBuffer &buffer,
+		const libcamera::PixelFormat &format,
+		const libcamera::Size &size, unsigned int stride);
+
+	libcamera::Signal<AtomicRequest *> requestComplete;
+
+private:
+	Device(const Device &) = delete;
+	Device(const Device &&) = delete;
+	Device &operator=(const Device &) = delete;
+	Device &operator=(const Device &&) = delete;
+
+	int getResources();
+
+	void drmEvent();
+	static void pageFlipComplete(int fd, unsigned int sequence,
+				     unsigned int tv_sec, unsigned int tv_usec,
+				     void *user_data);
+
+	int fd_;
+
+	std::list<Crtc> crtcs_;
+	std::list<Encoder> encoders_;
+	std::list<Connector> connectors_;
+	std::list<Plane> planes_;
+	std::list<Property> properties_;
+
+	std::map<uint32_t, Object *> objects_;
+};
+
+} /* namespace DRM */
+
+#endif /* __CAM_DRM_H__ */
diff --git a/src/cam/meson.build b/src/cam/meson.build
index e692ea351987..b47add55b0cb 100644
--- a/src/cam/meson.build
+++ b/src/cam/meson.build
@@ -19,10 +19,23 @@  cam_sources = files([
     'stream_options.cpp',
 ])
 
+cam_cpp_args = []
+
+libdrm = dependency('libdrm', required : false)
+
+if libdrm.found()
+cam_cpp_args += [ '-DHAVE_KMS' ]
+cam_sources += files([
+    'drm.cpp',
+])
+endif
+
 cam  = executable('cam', cam_sources,
                   dependencies : [
                       libatomic,
                       libcamera_public,
+                      libdrm,
                       libevent,
                   ],
+                  cpp_args : cam_cpp_args,
                   install : true)