{"id":3816,"url":"https://patchwork.libcamera.org/api/patches/3816/?format=json","web_url":"https://patchwork.libcamera.org/patch/3816/","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":"<20200519032505.17307-6-laurent.pinchart@ideasonboard.com>","date":"2020-05-19T03:25:02","name":"[libcamera-devel,5/8] cam: Add DRM helper classes","commit_ref":null,"pull_url":null,"state":"changes-requested","archived":false,"hash":"0b0c265ee50e309aa6855dcc4ec6f5c3d33efdb7","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/?format=json","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"delegate":{"id":14,"url":"https://patchwork.libcamera.org/api/users/14/?format=json","username":"pinchartl","first_name":"Laurent","last_name":"Pinchart","email":"laurent.pinchart@ideasonboard.com"},"mbox":"https://patchwork.libcamera.org/patch/3816/mbox/","series":[{"id":912,"url":"https://patchwork.libcamera.org/api/series/912/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=912","date":"2020-05-19T03:24:57","name":"libcamera: Add DRM/KMS viewfinder display to cam","version":1,"mbox":"https://patchwork.libcamera.org/series/912/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/3816/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/3816/checks/","tags":{},"headers":{"Return-Path":"<laurent.pinchart@ideasonboard.com>","Received":["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 61A2B60E3A\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 19 May 2020 05:25:20 +0200 (CEST)","from pendragon.bb.dnainternet.fi (81-175-216-236.bb.dnainternet.fi\n\t[81.175.216.236])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id CAFF49CD\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 19 May 2020 05:25:19 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"BVKl8yYR\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1589858720;\n\tbh=pVnkRRDLRmwusKRSntd0NNVubZZda3Fh7S1LinbKcGU=;\n\th=From:To:Subject:Date:In-Reply-To:References:From;\n\tb=BVKl8yYRdIReLxK4/DYRYpwG8zvQCfWwen4ip6UXjuzgJTAZzyHj3vpzlM89pdVCx\n\toCnyileKlvvCR7RjtTZwsIUy5dcEX5HHTHXURs1FyTIPmLncUJZkC6UT9Xkbzcxt5a\n\t7FfixeA1sPQVbmvz06MKf1EciAg+YGV7TrlSre8o=","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"libcamera-devel@lists.libcamera.org","Date":"Tue, 19 May 2020 06:25:02 +0300","Message-Id":"<20200519032505.17307-6-laurent.pinchart@ideasonboard.com>","X-Mailer":"git-send-email 2.26.2","In-Reply-To":"<20200519032505.17307-1-laurent.pinchart@ideasonboard.com>","References":"<20200519032505.17307-1-laurent.pinchart@ideasonboard.com>","MIME-Version":"1.0","Content-Transfer-Encoding":"8bit","Subject":"[libcamera-devel] [PATCH 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>","X-List-Received-Date":"Tue, 19 May 2020 03:25:21 -0000"},"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---\n src/cam/drm.cpp     | 661 ++++++++++++++++++++++++++++++++++++++++++++\n src/cam/drm.h       | 334 ++++++++++++++++++++++\n src/cam/meson.build |  14 +-\n 3 files changed, 1008 insertions(+), 1 deletion(-)\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..c0677b1337d7\n--- /dev/null\n+++ b/src/cam/drm.cpp\n@@ -0,0 +1,661 @@\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 our own copy of drm_mode.h first. */\n+#include <linux/drm_mode.h>\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/buffer.h>\n+#include <libcamera/event_notifier.h>\n+#include <libcamera/geometry.h>\n+#include <libcamera/pixelformats.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+\t{ DRM_MODE_CONNECTOR_WRITEBACK, \"writeback\" },\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 &encoder) {\n+\t\t\t\t\t\t    return encoder.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+\tnotifier_ = new libcamera::EventNotifier(fd_, libcamera::EventNotifier::Read);\n+\tnotifier_->activated.connect(this, &Device::drmEvent);\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(libcamera::EventNotifier *notifier)\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(int fd, unsigned int sequence, unsigned int tv_sec,\n+\t\t\t      unsigned int tv_usec, 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..c1c2bab4b121\n--- /dev/null\n+++ b/src/cam/drm.h\n@@ -0,0 +1,334 @@\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/signal.h>\n+#include <libcamera/span.h>\n+\n+#include <drm/drm.h>\n+#include <xf86drm.h>\n+#include <xf86drmMode.h>\n+\n+namespace libcamera {\n+class EventNotifier;\n+class FrameBuffer;\n+class PixelFormat;\n+struct 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(libcamera::EventNotifier *notifier);\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+\tlibcamera::EventNotifier *notifier_;\n+};\n+\n+} /* namespace DRM */\n+\n+#endif /* __CAM_DRM_H__ */\ndiff --git a/src/cam/meson.build b/src/cam/meson.build\nindex 6ba49e82fbd1..b0a8a7580dcc 100644\n--- a/src/cam/meson.build\n+++ b/src/cam/meson.build\n@@ -10,6 +10,18 @@ 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 : [ libatomic, libcamera_dep ],\n+                  dependencies : [ libatomic, libcamera_dep, libdrm ],\n+                  cpp_args : cam_cpp_args,\n                   install : true)\n","prefixes":["libcamera-devel","5/8"]}