{"id":13149,"url":"https://patchwork.libcamera.org/api/patches/13149/?format=json","web_url":"https://patchwork.libcamera.org/patch/13149/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20210730010306.19956-6-laurent.pinchart@ideasonboard.com>","date":"2021-07-30T01:03:03","name":"[libcamera-devel,v2,5/8] cam: Add DRM helper classes","commit_ref":null,"pull_url":null,"state":"accepted","archived":false,"hash":"4cbfa7cecd4db972a9b685fbc471f54145776039","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/?format=json","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/13149/mbox/","series":[{"id":2289,"url":"https://patchwork.libcamera.org/api/series/2289/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=2289","date":"2021-07-30T01:02:59","name":"libcamera: Add DRM/KMS viewfinder display to cam","version":2,"mbox":"https://patchwork.libcamera.org/series/2289/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/13149/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/13149/checks/","tags":{},"headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 897F7C3233\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 30 Jul 2021 01:03:27 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 210C068810;\n\tFri, 30 Jul 2021 03:03:27 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 4C295687BE\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 30 Jul 2021 03:03:20 +0200 (CEST)","from pendragon.lan (62-78-145-57.bb.dnainternet.fi [62.78.145.57])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id DC75C101E\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 30 Jul 2021 03:03:19 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"vwEb+1/4\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1627607000;\n\tbh=ILEMf8QJSzjnk8IdnvOF16d72j5T/nfRdWIg3cwOAl8=;\n\th=From:To:Subject:Date:In-Reply-To:References:From;\n\tb=vwEb+1/4fSf+dZNoCst/xmuFeJDeff9QHNSCGDoRiworjvk701VFxU/ogAdbu40MB\n\thpJSR7CGE7Y1uX7JTYBOgXNIbDLNx7aL6CdRhC02no1rXfbK5SmF7m/9pOZlDu+G1w\n\tWpgaisBNl20vINtadpbbqoYNt/HSAp7eD24Bejg4=","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"libcamera-devel@lists.libcamera.org","Date":"Fri, 30 Jul 2021 04:03:03 +0300","Message-Id":"<20210730010306.19956-6-laurent.pinchart@ideasonboard.com>","X-Mailer":"git-send-email 2.31.1","In-Reply-To":"<20210730010306.19956-1-laurent.pinchart@ideasonboard.com>","References":"<20210730010306.19956-1-laurent.pinchart@ideasonboard.com>","MIME-Version":"1.0","Content-Transfer-Encoding":"8bit","Subject":"[libcamera-devel] [PATCH v2 5/8] cam: Add DRM helper classes","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"},"content":"To prepare for viewfinder operation through the DRM/KMS API, add a set\nof helper classes that encapsulate the libdrm functions.\n\nSigned-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n---\nChanges since v1:\n\n- Use drm.h and drm_mode.h from libdrm\n- Drop writeback connector\n---\n src/cam/drm.cpp     | 663 ++++++++++++++++++++++++++++++++++++++++++++\n src/cam/drm.h       | 331 ++++++++++++++++++++++\n src/cam/meson.build |  13 +\n 3 files changed, 1007 insertions(+)\n create mode 100644 src/cam/drm.cpp\n create mode 100644 src/cam/drm.h","diff":"diff --git a/src/cam/drm.cpp b/src/cam/drm.cpp\nnew file mode 100644\nindex 000000000000..56a6cbd2442e\n--- /dev/null\n+++ b/src/cam/drm.cpp\n@@ -0,0 +1,663 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/*\n+ * Copyright (C) 2020, Ideas on Board Oy\n+ *\n+ * drm.cpp - DRM/KMS Helpers\n+ */\n+\n+#include \"drm.h\"\n+\n+#include <algorithm>\n+#include <errno.h>\n+#include <fcntl.h>\n+#include <iostream>\n+#include <set>\n+#include <string.h>\n+#include <sys/ioctl.h>\n+#include <sys/stat.h>\n+#include <sys/types.h>\n+\n+#include <libcamera/framebuffer.h>\n+#include <libcamera/geometry.h>\n+#include <libcamera/pixel_format.h>\n+\n+#include <libdrm/drm_mode.h>\n+\n+#include \"event_loop.h\"\n+\n+namespace DRM {\n+\n+Object::Object(Device *dev, uint32_t id, Type type)\n+\t: id_(id), dev_(dev), type_(type)\n+{\n+\t/* Retrieve properties from the objects that support them. */\n+\tif (type != TypeConnector && type != TypeCrtc &&\n+\t    type != TypeEncoder && type != TypePlane)\n+\t\treturn;\n+\n+\t/*\n+\t * We can't distinguish between failures due to the object having no\n+\t * property and failures due to other conditions. Assume we use the API\n+\t * correctly and consider the object has no property.\n+\t */\n+\tdrmModeObjectProperties *properties = drmModeObjectGetProperties(dev->fd(), id, type);\n+\tif (!properties)\n+\t\treturn;\n+\n+\tproperties_.reserve(properties->count_props);\n+\tfor (uint32_t i = 0; i < properties->count_props; ++i)\n+\t\tproperties_.emplace_back(properties->props[i],\n+\t\t\t\t\t properties->prop_values[i]);\n+\n+\tdrmModeFreeObjectProperties(properties);\n+}\n+\n+Object::~Object()\n+{\n+}\n+\n+const Property *Object::property(const std::string &name) const\n+{\n+\tfor (const PropertyValue &pv : properties_) {\n+\t\tconst Property *property = static_cast<const Property *>(dev_->object(pv.id()));\n+\t\tif (property && property->name() == name)\n+\t\t\treturn property;\n+\t}\n+\n+\treturn nullptr;\n+}\n+\n+const PropertyValue *Object::propertyValue(const std::string &name) const\n+{\n+\tfor (const PropertyValue &pv : properties_) {\n+\t\tconst Property *property = static_cast<const Property *>(dev_->object(pv.id()));\n+\t\tif (property && property->name() == name)\n+\t\t\treturn &pv;\n+\t}\n+\n+\treturn nullptr;\n+}\n+\n+Property::Property(Device *dev, drmModePropertyRes *property)\n+\t: Object(dev, property->prop_id, TypeProperty),\n+\t  name_(property->name), flags_(property->flags),\n+\t  values_(property->values, property->values + property->count_values),\n+\t  blobs_(property->blob_ids, property->blob_ids + property->count_blobs)\n+{\n+\tif (drm_property_type_is(property, DRM_MODE_PROP_RANGE))\n+\t\ttype_ = TypeRange;\n+\telse if (drm_property_type_is(property, DRM_MODE_PROP_ENUM))\n+\t\ttype_ = TypeEnum;\n+\telse if (drm_property_type_is(property, DRM_MODE_PROP_BLOB))\n+\t\ttype_ = TypeBlob;\n+\telse if (drm_property_type_is(property, DRM_MODE_PROP_BITMASK))\n+\t\ttype_ = TypeBitmask;\n+\telse if (drm_property_type_is(property, DRM_MODE_PROP_OBJECT))\n+\t\ttype_ = TypeObject;\n+\telse if (drm_property_type_is(property, DRM_MODE_PROP_SIGNED_RANGE))\n+\t\ttype_ = TypeSignedRange;\n+\telse\n+\t\ttype_ = TypeUnknown;\n+\n+\tfor (int i = 0; i < property->count_enums; ++i)\n+\t\tenums_[property->enums[i].value] = property->enums[i].name;\n+}\n+\n+Blob::Blob(Device *dev, const libcamera::Span<const uint8_t> &data)\n+\t: Object(dev, 0, Object::TypeBlob)\n+{\n+\tdrmModeCreatePropertyBlob(dev->fd(), data.data(), data.size(), &id_);\n+}\n+\n+Blob::~Blob()\n+{\n+\tif (isValid())\n+\t\tdrmModeDestroyPropertyBlob(device()->fd(), id());\n+}\n+\n+Mode::Mode(const drmModeModeInfo &mode)\n+\t: drmModeModeInfo(mode)\n+{\n+}\n+\n+std::unique_ptr<Blob> Mode::toBlob(Device *dev) const\n+{\n+\tlibcamera::Span<const uint8_t> data{ reinterpret_cast<const uint8_t *>(this),\n+\t\t\t\t\t     sizeof(*this) };\n+\treturn std::make_unique<Blob>(dev, data);\n+}\n+\n+Crtc::Crtc(Device *dev, const drmModeCrtc *crtc, unsigned int index)\n+\t: Object(dev, crtc->crtc_id, Object::TypeCrtc), index_(index)\n+{\n+}\n+\n+Encoder::Encoder(Device *dev, const drmModeEncoder *encoder)\n+\t: Object(dev, encoder->encoder_id, Object::TypeEncoder),\n+\t  type_(encoder->encoder_type)\n+{\n+\tconst std::list<Crtc> &crtcs = dev->crtcs();\n+\tpossibleCrtcs_.reserve(crtcs.size());\n+\n+\tfor (const Crtc &crtc : crtcs) {\n+\t\tif (encoder->possible_crtcs & (1 << crtc.index()))\n+\t\t\tpossibleCrtcs_.push_back(&crtc);\n+\t}\n+\n+\tpossibleCrtcs_.shrink_to_fit();\n+}\n+\n+namespace {\n+\n+const std::map<uint32_t, const char *> connectorTypeNames{\n+\t{ DRM_MODE_CONNECTOR_Unknown, \"Unknown\" },\n+\t{ DRM_MODE_CONNECTOR_VGA, \"VGA\" },\n+\t{ DRM_MODE_CONNECTOR_DVII, \"DVI-I\" },\n+\t{ DRM_MODE_CONNECTOR_DVID, \"DVI-D\" },\n+\t{ DRM_MODE_CONNECTOR_DVIA, \"DVI-A\" },\n+\t{ DRM_MODE_CONNECTOR_Composite, \"Composite\" },\n+\t{ DRM_MODE_CONNECTOR_SVIDEO, \"S-Video\" },\n+\t{ DRM_MODE_CONNECTOR_LVDS, \"LVDS\" },\n+\t{ DRM_MODE_CONNECTOR_Component, \"Component\" },\n+\t{ DRM_MODE_CONNECTOR_9PinDIN, \"9-Pin-DIN\" },\n+\t{ DRM_MODE_CONNECTOR_DisplayPort, \"DP\" },\n+\t{ DRM_MODE_CONNECTOR_HDMIA, \"HDMI-A\" },\n+\t{ DRM_MODE_CONNECTOR_HDMIB, \"HDMI-B\" },\n+\t{ DRM_MODE_CONNECTOR_TV, \"TV\" },\n+\t{ DRM_MODE_CONNECTOR_eDP, \"eDP\" },\n+\t{ DRM_MODE_CONNECTOR_VIRTUAL, \"Virtual\" },\n+\t{ DRM_MODE_CONNECTOR_DSI, \"DSI\" },\n+\t{ DRM_MODE_CONNECTOR_DPI, \"DPI\" },\n+};\n+\n+} /* namespace */\n+\n+Connector::Connector(Device *dev, const drmModeConnector *connector)\n+\t: Object(dev, connector->connector_id, Object::TypeConnector),\n+\t  type_(connector->connector_type)\n+{\n+\tauto typeName = connectorTypeNames.find(connector->connector_type);\n+\tif (typeName == connectorTypeNames.end()) {\n+\t\tstd::cerr\n+\t\t\t<< \"Invalid connector type \"\n+\t\t\t<< connector->connector_type << std::endl;\n+\t\ttypeName = connectorTypeNames.find(DRM_MODE_CONNECTOR_Unknown);\n+\t}\n+\n+\tname_ = std::string(typeName->second) + \"-\"\n+\t      + std::to_string(connector->connector_type_id);\n+\n+\tswitch (connector->connection) {\n+\tcase DRM_MODE_CONNECTED:\n+\t\tstatus_ = Status::Connected;\n+\t\tbreak;\n+\n+\tcase DRM_MODE_DISCONNECTED:\n+\t\tstatus_ = Status::Disconnected;\n+\t\tbreak;\n+\n+\tcase DRM_MODE_UNKNOWNCONNECTION:\n+\tdefault:\n+\t\tstatus_ = Status::Unknown;\n+\t\tbreak;\n+\t}\n+\n+\tconst std::list<Encoder> &encoders = dev->encoders();\n+\n+\tencoders_.reserve(connector->count_encoders);\n+\n+\tfor (int i = 0; i < connector->count_encoders; ++i) {\n+\t\tuint32_t encoderId = connector->encoders[i];\n+\t\tauto encoder = std::find_if(encoders.begin(), encoders.end(),\n+\t\t\t\t\t    [=](const Encoder &e) {\n+\t\t\t\t\t\t    return e.id() == encoderId;\n+\t\t\t\t\t    });\n+\t\tif (encoder == encoders.end()) {\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"Encoder \" << encoderId << \" not found\"\n+\t\t\t\t<< std::endl;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tencoders_.push_back(&*encoder);\n+\t}\n+\n+\tencoders_.shrink_to_fit();\n+\n+\tmodes_ = { connector->modes, connector->modes + connector->count_modes };\n+}\n+\n+Plane::Plane(Device *dev, const drmModePlane *plane)\n+\t: Object(dev, plane->plane_id, Object::TypePlane),\n+\t  possibleCrtcsMask_(plane->possible_crtcs)\n+{\n+\tformats_ = { plane->formats, plane->formats + plane->count_formats };\n+\n+\tconst std::list<Crtc> &crtcs = dev->crtcs();\n+\tpossibleCrtcs_.reserve(crtcs.size());\n+\n+\tfor (const Crtc &crtc : crtcs) {\n+\t\tif (plane->possible_crtcs & (1 << crtc.index()))\n+\t\t\tpossibleCrtcs_.push_back(&crtc);\n+\t}\n+\n+\tpossibleCrtcs_.shrink_to_fit();\n+}\n+\n+bool Plane::supportsFormat(const libcamera::PixelFormat &format) const\n+{\n+\treturn std::find(formats_.begin(), formats_.end(), format.fourcc())\n+\t\t!= formats_.end();\n+}\n+\n+int Plane::setup()\n+{\n+\tconst PropertyValue *pv = propertyValue(\"type\");\n+\tif (!pv)\n+\t\treturn -EINVAL;\n+\n+\tswitch (pv->value()) {\n+\tcase DRM_PLANE_TYPE_OVERLAY:\n+\t\ttype_ = TypeOverlay;\n+\t\tbreak;\n+\n+\tcase DRM_PLANE_TYPE_PRIMARY:\n+\t\ttype_ = TypePrimary;\n+\t\tbreak;\n+\n+\tcase DRM_PLANE_TYPE_CURSOR:\n+\t\ttype_ = TypeCursor;\n+\t\tbreak;\n+\n+\tdefault:\n+\t\treturn -EINVAL;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+FrameBuffer::FrameBuffer(Device *dev)\n+\t: Object(dev, 0, Object::TypeFb)\n+{\n+}\n+\n+FrameBuffer::~FrameBuffer()\n+{\n+\tfor (FrameBuffer::Plane &plane : planes_) {\n+\t\tstruct drm_gem_close gem_close = {\n+\t\t\t.handle = plane.handle,\n+\t\t\t.pad = 0,\n+\t\t};\n+\t\tint ret;\n+\n+\t\tdo {\n+\t\t\tret = ioctl(device()->fd(), DRM_IOCTL_GEM_CLOSE, &gem_close);\n+\t\t} while (ret == -1 && (errno == EINTR || errno == EAGAIN));\n+\n+\t\tif (ret == -1) {\n+\t\t\tret = -errno;\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"Failed to close GEM object: \"\n+\t\t\t\t<< strerror(-ret) << std::endl;\n+\t\t}\n+\t}\n+\n+\tdrmModeRmFB(device()->fd(), id());\n+}\n+\n+AtomicRequest::AtomicRequest(Device *dev)\n+\t: dev_(dev), valid_(true)\n+{\n+\trequest_ = drmModeAtomicAlloc();\n+\tif (!request_)\n+\t\tvalid_ = false;\n+}\n+\n+AtomicRequest::~AtomicRequest()\n+{\n+\tif (request_)\n+\t\tdrmModeAtomicFree(request_);\n+}\n+\n+int AtomicRequest::addProperty(const Object *object, const std::string &property,\n+\t\t\t       uint64_t value)\n+{\n+\tif (!valid_)\n+\t\treturn -EINVAL;\n+\n+\tconst Property *prop = object->property(property);\n+\tif (!prop) {\n+\t\tvalid_ = false;\n+\t\treturn -EINVAL;\n+\t}\n+\n+\treturn addProperty(object->id(), prop->id(), value);\n+}\n+\n+int AtomicRequest::addProperty(const Object *object, const std::string &property,\n+\t\t\t       std::unique_ptr<Blob> blob)\n+{\n+\tif (!valid_)\n+\t\treturn -EINVAL;\n+\n+\tconst Property *prop = object->property(property);\n+\tif (!prop) {\n+\t\tvalid_ = false;\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tint ret = addProperty(object->id(), prop->id(), blob->id());\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\tblobs_.emplace_back(std::move(blob));\n+\n+\treturn 0;\n+}\n+\n+int AtomicRequest::addProperty(uint32_t object, uint32_t property, uint64_t value)\n+{\n+\tint ret = drmModeAtomicAddProperty(request_, object, property, value);\n+\tif (ret < 0) {\n+\t\tvalid_ = false;\n+\t\treturn ret;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int AtomicRequest::commit(unsigned int flags)\n+{\n+\tif (!valid_)\n+\t\treturn -EINVAL;\n+\n+\tuint32_t drmFlags = 0;\n+\tif (flags & FlagAllowModeset)\n+\t\tdrmFlags |= DRM_MODE_ATOMIC_ALLOW_MODESET;\n+\tif (flags & FlagAsync)\n+\t\tdrmFlags |= DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK;\n+\n+\treturn drmModeAtomicCommit(dev_->fd(), request_, drmFlags, this);\n+}\n+\n+Device::Device()\n+\t: fd_(-1)\n+{\n+}\n+\n+Device::~Device()\n+{\n+\tif (fd_ != -1)\n+\t\tdrmClose(fd_);\n+}\n+\n+int Device::init()\n+{\n+\tconstexpr size_t NODE_NAME_MAX = sizeof(\"/dev/dri/card255\");\n+\tchar name[NODE_NAME_MAX];\n+\tint ret;\n+\n+\t/*\n+\t * Open the first DRM/KMS device. The libdrm drmOpen*() functions\n+\t * require either a module name or a bus ID, which we don't have, so\n+\t * bypass them. The automatic module loading and device node creation\n+\t * from drmOpen() is of no practical use as any modern system will\n+\t * handle that through udev or an equivalent component.\n+\t */\n+\tsnprintf(name, sizeof(name), \"/dev/dri/card%u\", 0);\n+\tfd_ = open(name, O_RDWR | O_CLOEXEC);\n+\tif (fd_ < 0) {\n+\t\tret = -errno;\n+\t\tstd::cerr\n+\t\t\t<< \"Failed to open DRM/KMS device \" << name << \": \"\n+\t\t\t<< strerror(-ret) << std::endl;\n+\t\treturn ret;\n+\t}\n+\n+\t/*\n+\t * Enable the atomic APIs. This also enables automatically the\n+\t * universal planes API.\n+\t */\n+\tret = drmSetClientCap(fd_, DRM_CLIENT_CAP_ATOMIC, 1);\n+\tif (ret < 0) {\n+\t\tret = -errno;\n+\t\tstd::cerr\n+\t\t\t<< \"Failed to enable atomic capability: \"\n+\t\t\t<< strerror(-ret) << std::endl;\n+\t\treturn ret;\n+\t}\n+\n+\t/* List all the resources. */\n+\tret = getResources();\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\tEventLoop::instance()->addEvent(fd_, EventLoop::Read,\n+\t\t\t\t\tstd::bind(&Device::drmEvent, this));\n+\n+\treturn 0;\n+}\n+\n+int Device::getResources()\n+{\n+\tint ret;\n+\n+\tstd::unique_ptr<drmModeRes, decltype(&drmModeFreeResources)> resources{\n+\t\tdrmModeGetResources(fd_),\n+\t\t&drmModeFreeResources\n+\t};\n+\tif (!resources) {\n+\t\tret = -errno;\n+\t\tstd::cerr\n+\t\t\t<< \"Failed to get DRM/KMS resources: \"\n+\t\t\t<< strerror(-ret) << std::endl;\n+\t\treturn ret;\n+\t}\n+\n+\tfor (int i = 0; i < resources->count_crtcs; ++i) {\n+\t\tdrmModeCrtc *crtc = drmModeGetCrtc(fd_, resources->crtcs[i]);\n+\t\tif (!crtc) {\n+\t\t\tret = -errno;\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"Failed to get CRTC: \" << strerror(-ret)\n+\t\t\t\t<< std::endl;\n+\t\t\treturn ret;\n+\t\t}\n+\n+\t\tcrtcs_.emplace_back(this, crtc, i);\n+\t\tdrmModeFreeCrtc(crtc);\n+\n+\t\tCrtc &obj = crtcs_.back();\n+\t\tobjects_[obj.id()] = &obj;\n+\t}\n+\n+\tfor (int i = 0; i < resources->count_encoders; ++i) {\n+\t\tdrmModeEncoder *encoder =\n+\t\t\tdrmModeGetEncoder(fd_, resources->encoders[i]);\n+\t\tif (!encoder) {\n+\t\t\tret = -errno;\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"Failed to get encoder: \" << strerror(-ret)\n+\t\t\t\t<< std::endl;\n+\t\t\treturn ret;\n+\t\t}\n+\n+\t\tencoders_.emplace_back(this, encoder);\n+\t\tdrmModeFreeEncoder(encoder);\n+\n+\t\tEncoder &obj = encoders_.back();\n+\t\tobjects_[obj.id()] = &obj;\n+\t}\n+\n+\tfor (int i = 0; i < resources->count_connectors; ++i) {\n+\t\tdrmModeConnector *connector =\n+\t\t\tdrmModeGetConnector(fd_, resources->connectors[i]);\n+\t\tif (!connector) {\n+\t\t\tret = -errno;\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"Failed to get connector: \" << strerror(-ret)\n+\t\t\t\t<< std::endl;\n+\t\t\treturn ret;\n+\t\t}\n+\n+\t\tconnectors_.emplace_back(this, connector);\n+\t\tdrmModeFreeConnector(connector);\n+\n+\t\tConnector &obj = connectors_.back();\n+\t\tobjects_[obj.id()] = &obj;\n+\t}\n+\n+\tstd::unique_ptr<drmModePlaneRes, decltype(&drmModeFreePlaneResources)> planes{\n+\t\tdrmModeGetPlaneResources(fd_),\n+\t\t&drmModeFreePlaneResources\n+\t};\n+\tif (!planes) {\n+\t\tret = -errno;\n+\t\tstd::cerr\n+\t\t\t<< \"Failed to get DRM/KMS planes: \"\n+\t\t\t<< strerror(-ret) << std::endl;\n+\t\treturn ret;\n+\t}\n+\n+\tfor (uint32_t i = 0; i < planes->count_planes; ++i) {\n+\t\tdrmModePlane *plane =\n+\t\t\tdrmModeGetPlane(fd_, planes->planes[i]);\n+\t\tif (!plane) {\n+\t\t\tret = -errno;\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"Failed to get plane: \" << strerror(-ret)\n+\t\t\t\t<< std::endl;\n+\t\t\treturn ret;\n+\t\t}\n+\n+\t\tplanes_.emplace_back(this, plane);\n+\t\tdrmModeFreePlane(plane);\n+\n+\t\tPlane &obj = planes_.back();\n+\t\tobjects_[obj.id()] = &obj;\n+\t}\n+\n+\t/* Set the possible planes for each CRTC. */\n+\tfor (Crtc &crtc : crtcs_) {\n+\t\tfor (const Plane &plane : planes_) {\n+\t\t\tif (plane.possibleCrtcsMask_ & (1 << crtc.index()))\n+\t\t\t\tcrtc.planes_.push_back(&plane);\n+\t\t}\n+\t}\n+\n+\t/* Collect all property IDs and create Property instances. */\n+\tstd::set<uint32_t> properties;\n+\tfor (const auto &object : objects_) {\n+\t\tfor (const PropertyValue &value : object.second->properties())\n+\t\t\tproperties.insert(value.id());\n+\t}\n+\n+\tfor (uint32_t id : properties) {\n+\t\tdrmModePropertyRes *property = drmModeGetProperty(fd_, id);\n+\t\tif (!property) {\n+\t\t\tret = -errno;\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"Failed to get property: \" << strerror(-ret)\n+\t\t\t\t<< std::endl;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tproperties_.emplace_back(this, property);\n+\t\tdrmModeFreeProperty(property);\n+\n+\t\tProperty &obj = properties_.back();\n+\t\tobjects_[obj.id()] = &obj;\n+\t}\n+\n+\t/* Finally, perform all delayed setup of mode objects. */\n+\tfor (auto &object : objects_) {\n+\t\tret = object.second->setup();\n+\t\tif (ret < 0) {\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"Failed to setup object \" << object.second->id()\n+\t\t\t\t<< \": \" << strerror(-ret) << std::endl;\n+\t\t\treturn ret;\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+const Object *Device::object(uint32_t id)\n+{\n+\tconst auto iter = objects_.find(id);\n+\tif (iter == objects_.end())\n+\t\treturn nullptr;\n+\n+\treturn iter->second;\n+}\n+\n+std::unique_ptr<FrameBuffer> Device::createFrameBuffer(\n+\tconst libcamera::FrameBuffer &buffer,\n+\tconst libcamera::PixelFormat &format,\n+\tconst libcamera::Size &size, unsigned int stride)\n+{\n+\tstd::unique_ptr<FrameBuffer> fb{ new FrameBuffer(this) };\n+\n+\tuint32_t handles[4] = {};\n+\tuint32_t pitches[4] = {};\n+\tuint32_t offsets[4] = {};\n+\tint ret;\n+\n+\tconst std::vector<libcamera::FrameBuffer::Plane> &planes = buffer.planes();\n+\tfb->planes_.reserve(planes.size());\n+\n+\tunsigned int i = 0;\n+\tfor (const libcamera::FrameBuffer::Plane &plane : planes) {\n+\t\tuint32_t handle;\n+\n+\t\tret = drmPrimeFDToHandle(fd_, plane.fd.fd(), &handle);\n+\t\tif (ret < 0) {\n+\t\t\tret = -errno;\n+\t\t\tstd::cerr\n+\t\t\t\t<< \"Unable to import framebuffer dmabuf: \"\n+\t\t\t\t<< strerror(-ret) << std::endl;\n+\t\t\treturn nullptr;\n+\t\t}\n+\n+\t\tfb->planes_.push_back({ handle });\n+\n+\t\thandles[i] = handle;\n+\t\tpitches[i] = stride;\n+\t\toffsets[i] = 0; /* TODO */\n+\t\t++i;\n+\t}\n+\n+\tret = drmModeAddFB2(fd_, size.width, size.height, format.fourcc(), handles,\n+\t\t\t    pitches, offsets, &fb->id_, 0);\n+\tif (ret < 0) {\n+\t\tret = -errno;\n+\t\tstd::cerr\n+\t\t\t<< \"Failed to add framebuffer: \"\n+\t\t\t<< strerror(-ret) << std::endl;\n+\t\treturn nullptr;\n+\t}\n+\n+\treturn fb;\n+}\n+\n+void Device::drmEvent()\n+{\n+\tdrmEventContext ctx{};\n+\tctx.version = DRM_EVENT_CONTEXT_VERSION;\n+\tctx.page_flip_handler = &Device::pageFlipComplete;\n+\n+\tdrmHandleEvent(fd_, &ctx);\n+}\n+\n+void Device::pageFlipComplete([[maybe_unused]] int fd,\n+\t\t\t      [[maybe_unused]] unsigned int sequence,\n+\t\t\t      [[maybe_unused]] unsigned int tv_sec,\n+\t\t\t      [[maybe_unused]] unsigned int tv_usec,\n+\t\t\t      void *user_data)\n+{\n+\tAtomicRequest *request = static_cast<AtomicRequest *>(user_data);\n+\trequest->device()->requestComplete.emit(request);\n+}\n+\n+} /* namespace DRM */\ndiff --git a/src/cam/drm.h b/src/cam/drm.h\nnew file mode 100644\nindex 000000000000..e1395bc9c7e6\n--- /dev/null\n+++ b/src/cam/drm.h\n@@ -0,0 +1,331 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/*\n+ * Copyright (C) 2020, Ideas on Board Oy\n+ *\n+ * drm.h - DRM/KMS Helpers\n+ */\n+#ifndef __CAM_DRM_H__\n+#define __CAM_DRM_H__\n+\n+#include <list>\n+#include <map>\n+#include <memory>\n+#include <string>\n+#include <vector>\n+\n+#include <libcamera/base/signal.h>\n+#include <libcamera/base/span.h>\n+\n+#include <libdrm/drm.h>\n+#include <xf86drm.h>\n+#include <xf86drmMode.h>\n+\n+namespace libcamera {\n+class FrameBuffer;\n+class PixelFormat;\n+class Size;\n+} /* namespace libcamera */\n+\n+namespace DRM {\n+\n+class Device;\n+class Plane;\n+class Property;\n+class PropertyValue;\n+\n+class Object\n+{\n+public:\n+\tenum Type {\n+\t\tTypeCrtc = DRM_MODE_OBJECT_CRTC,\n+\t\tTypeConnector = DRM_MODE_OBJECT_CONNECTOR,\n+\t\tTypeEncoder = DRM_MODE_OBJECT_ENCODER,\n+\t\tTypeMode = DRM_MODE_OBJECT_MODE,\n+\t\tTypeProperty = DRM_MODE_OBJECT_PROPERTY,\n+\t\tTypeFb = DRM_MODE_OBJECT_FB,\n+\t\tTypeBlob = DRM_MODE_OBJECT_BLOB,\n+\t\tTypePlane = DRM_MODE_OBJECT_PLANE,\n+\t\tTypeAny = DRM_MODE_OBJECT_ANY,\n+\t};\n+\n+\tObject(Device *dev, uint32_t id, Type type);\n+\tvirtual ~Object();\n+\n+\tDevice *device() const { return dev_; }\n+\tuint32_t id() const { return id_; }\n+\tType type() const { return type_; }\n+\n+\tconst Property *property(const std::string &name) const;\n+\tconst PropertyValue *propertyValue(const std::string &name) const;\n+\tconst std::vector<PropertyValue> &properties() const { return properties_; }\n+\n+protected:\n+\tvirtual int setup()\n+\t{\n+\t\treturn 0;\n+\t}\n+\n+\tuint32_t id_;\n+\n+private:\n+\tfriend Device;\n+\n+\tDevice *dev_;\n+\tType type_;\n+\tstd::vector<PropertyValue> properties_;\n+};\n+\n+class Property : public Object\n+{\n+public:\n+\tenum Type {\n+\t\tTypeUnknown = 0,\n+\t\tTypeRange,\n+\t\tTypeEnum,\n+\t\tTypeBlob,\n+\t\tTypeBitmask,\n+\t\tTypeObject,\n+\t\tTypeSignedRange,\n+\t};\n+\n+\tProperty(Device *dev, drmModePropertyRes *property);\n+\n+\tType type() const { return type_; }\n+\tconst std::string &name() const { return name_; }\n+\n+\tbool isImmutable() const { return flags_ & DRM_MODE_PROP_IMMUTABLE; }\n+\n+\tconst std::vector<uint64_t> values() const { return values_; }\n+\tconst std::map<uint32_t, std::string> &enums() const { return enums_; }\n+\tconst std::vector<uint32_t> blobs() const { return blobs_; }\n+\n+private:\n+\tType type_;\n+\tstd::string name_;\n+\tuint32_t flags_;\n+\tstd::vector<uint64_t> values_;\n+\tstd::map<uint32_t, std::string> enums_;\n+\tstd::vector<uint32_t> blobs_;\n+};\n+\n+class PropertyValue\n+{\n+public:\n+\tPropertyValue(uint32_t id, uint64_t value)\n+\t\t: id_(id), value_(value)\n+\t{\n+\t}\n+\n+\tuint32_t id() const { return id_; }\n+\tuint32_t value() const { return value_; }\n+\n+private:\n+\tuint32_t id_;\n+\tuint64_t value_;\n+};\n+\n+class Blob : public Object\n+{\n+public:\n+\tBlob(Device *dev, const libcamera::Span<const uint8_t> &data);\n+\t~Blob();\n+\n+\tbool isValid() const { return id() != 0; }\n+};\n+\n+class Mode : public drmModeModeInfo\n+{\n+public:\n+\tMode(const drmModeModeInfo &mode);\n+\n+\tstd::unique_ptr<Blob> toBlob(Device *dev) const;\n+};\n+\n+class Crtc : public Object\n+{\n+public:\n+\tCrtc(Device *dev, const drmModeCrtc *crtc, unsigned int index);\n+\n+\tunsigned int index() const { return index_; }\n+\tconst std::vector<const Plane *> &planes() const { return planes_; }\n+\n+private:\n+\tfriend Device;\n+\n+\tunsigned int index_;\n+\tstd::vector<const Plane *> planes_;\n+};\n+\n+class Encoder : public Object\n+{\n+public:\n+\tEncoder(Device *dev, const drmModeEncoder *encoder);\n+\n+\tuint32_t type() const { return type_; }\n+\n+\tconst std::vector<const Crtc *> &possibleCrtcs() const { return possibleCrtcs_; }\n+\n+private:\n+\tuint32_t type_;\n+\tstd::vector<const Crtc *> possibleCrtcs_;\n+};\n+\n+class Connector : public Object\n+{\n+public:\n+\tenum Status {\n+\t\tConnected,\n+\t\tDisconnected,\n+\t\tUnknown,\n+\t};\n+\n+\tConnector(Device *dev, const drmModeConnector *connector);\n+\n+\tuint32_t type() const { return type_; }\n+\tconst std::string &name() const { return name_; }\n+\n+\tStatus status() const { return status_; }\n+\n+\tconst std::vector<const Encoder *> &encoders() const { return encoders_; }\n+\tconst std::vector<Mode> &modes() const { return modes_; }\n+\n+private:\n+\tuint32_t type_;\n+\tstd::string name_;\n+\tStatus status_;\n+\tstd::vector<const Encoder *> encoders_;\n+\tstd::vector<Mode> modes_;\n+};\n+\n+class Plane : public Object\n+{\n+public:\n+\tenum Type {\n+\t\tTypeOverlay,\n+\t\tTypePrimary,\n+\t\tTypeCursor,\n+\t};\n+\n+\tPlane(Device *dev, const drmModePlane *plane);\n+\n+\tType type() const { return type_; }\n+\tconst std::vector<uint32_t> &formats() const { return formats_; }\n+\tconst std::vector<const Crtc *> &possibleCrtcs() const { return possibleCrtcs_; }\n+\n+\tbool supportsFormat(const libcamera::PixelFormat &format) const;\n+\n+protected:\n+\tint setup() override;\n+\n+private:\n+\tfriend class Device;\n+\n+\tType type_;\n+\tstd::vector<uint32_t> formats_;\n+\tstd::vector<const Crtc *> possibleCrtcs_;\n+\tuint32_t possibleCrtcsMask_;\n+};\n+\n+class FrameBuffer : public Object\n+{\n+public:\n+\tstruct Plane {\n+\t\tuint32_t handle;\n+\t};\n+\n+\t~FrameBuffer();\n+\n+private:\n+\tfriend class Device;\n+\n+\tFrameBuffer(Device *dev);\n+\n+\tstd::vector<Plane> planes_;\n+};\n+\n+class AtomicRequest\n+{\n+public:\n+\tenum Flags {\n+\t\tFlagAllowModeset = (1 << 0),\n+\t\tFlagAsync = (1 << 1),\n+\t};\n+\n+\tAtomicRequest(Device *dev);\n+\t~AtomicRequest();\n+\n+\tDevice *device() const { return dev_; }\n+\tbool isValid() const { return valid_; }\n+\n+\tint addProperty(const Object *object, const std::string &property,\n+\t\t\tuint64_t value);\n+\tint addProperty(const Object *object, const std::string &property,\n+\t\t\tstd::unique_ptr<Blob> blob);\n+\tint commit(unsigned int flags = 0);\n+\n+private:\n+\tAtomicRequest(const AtomicRequest &) = delete;\n+\tAtomicRequest(const AtomicRequest &&) = delete;\n+\tAtomicRequest &operator=(const AtomicRequest &) = delete;\n+\tAtomicRequest &operator=(const AtomicRequest &&) = delete;\n+\n+\tint addProperty(uint32_t object, uint32_t property, uint64_t value);\n+\n+\tDevice *dev_;\n+\tbool valid_;\n+\tdrmModeAtomicReq *request_;\n+\tstd::list<std::unique_ptr<Blob>> blobs_;\n+};\n+\n+class Device\n+{\n+public:\n+\tDevice();\n+\t~Device();\n+\n+\tint init();\n+\n+\tint fd() const { return fd_; }\n+\n+\tconst std::list<Crtc> &crtcs() const { return crtcs_; }\n+\tconst std::list<Encoder> &encoders() const { return encoders_; }\n+\tconst std::list<Connector> &connectors() const { return connectors_; }\n+\tconst std::list<Plane> &planes() const { return planes_; }\n+\tconst std::list<Property> &properties() const { return properties_; }\n+\n+\tconst Object *object(uint32_t id);\n+\n+\tstd::unique_ptr<FrameBuffer> createFrameBuffer(\n+\t\tconst libcamera::FrameBuffer &buffer,\n+\t\tconst libcamera::PixelFormat &format,\n+\t\tconst libcamera::Size &size, unsigned int stride);\n+\n+\tlibcamera::Signal<AtomicRequest *> requestComplete;\n+\n+private:\n+\tDevice(const Device &) = delete;\n+\tDevice(const Device &&) = delete;\n+\tDevice &operator=(const Device &) = delete;\n+\tDevice &operator=(const Device &&) = delete;\n+\n+\tint getResources();\n+\n+\tvoid drmEvent();\n+\tstatic void pageFlipComplete(int fd, unsigned int sequence,\n+\t\t\t\t     unsigned int tv_sec, unsigned int tv_usec,\n+\t\t\t\t     void *user_data);\n+\n+\tint fd_;\n+\n+\tstd::list<Crtc> crtcs_;\n+\tstd::list<Encoder> encoders_;\n+\tstd::list<Connector> connectors_;\n+\tstd::list<Plane> planes_;\n+\tstd::list<Property> properties_;\n+\n+\tstd::map<uint32_t, Object *> objects_;\n+};\n+\n+} /* namespace DRM */\n+\n+#endif /* __CAM_DRM_H__ */\ndiff --git a/src/cam/meson.build b/src/cam/meson.build\nindex e692ea351987..b47add55b0cb 100644\n--- a/src/cam/meson.build\n+++ b/src/cam/meson.build\n@@ -19,10 +19,23 @@ cam_sources = files([\n     'stream_options.cpp',\n ])\n \n+cam_cpp_args = []\n+\n+libdrm = dependency('libdrm', required : false)\n+\n+if libdrm.found()\n+cam_cpp_args += [ '-DHAVE_KMS' ]\n+cam_sources += files([\n+    'drm.cpp',\n+])\n+endif\n+\n cam  = executable('cam', cam_sources,\n                   dependencies : [\n                       libatomic,\n                       libcamera_public,\n+                      libdrm,\n                       libevent,\n                   ],\n+                  cpp_args : cam_cpp_args,\n                   install : true)\n","prefixes":["libcamera-devel","v2","5/8"]}