[{"id":18532,"web_url":"https://patchwork.libcamera.org/comment/18532/","msgid":"<20210804054844.GC2167@pyrite.rasen.tech>","date":"2021-08-04T05:48:44","subject":"Re: [libcamera-devel] [PATCH v2 5/8] cam: Add DRM helper classes","submitter":{"id":17,"url":"https://patchwork.libcamera.org/api/people/17/","name":"Paul Elder","email":"paul.elder@ideasonboard.com"},"content":"Hi Laurent,\n\nOn Fri, Jul 30, 2021 at 04:03:03AM +0300, Laurent Pinchart wrote:\n> To prepare for viewfinder operation through the DRM/KMS API, add a set\n> of helper classes that encapsulate the libdrm functions.\n> \n> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n\noof, that was long... isn't there a library for this or something? :p\n\nReviewed-by: Paul Elder <paul.elder@ideasonboard.com>\n\n> ---\n> Changes 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\n> \n> diff --git a/src/cam/drm.cpp b/src/cam/drm.cpp\n> new file mode 100644\n> index 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 */\n> diff --git a/src/cam/drm.h b/src/cam/drm.h\n> new file mode 100644\n> index 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__ */\n> diff --git a/src/cam/meson.build b/src/cam/meson.build\n> index 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> -- \n> Regards,\n> \n> Laurent Pinchart\n>","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 1CE56C3232\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed,  4 Aug 2021 05:48:55 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 53FC768810;\n\tWed,  4 Aug 2021 07:48:54 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 8DA7560268\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed,  4 Aug 2021 07:48:52 +0200 (CEST)","from pyrite.rasen.tech (unknown\n\t[IPv6:2400:4051:61:600:2c71:1b79:d06d:5032])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 9378524F;\n\tWed,  4 Aug 2021 07:48:50 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"FqfQWnRb\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1628056132;\n\tbh=WAXqMH05sBCYxnVMJ5jicMkMu0C2V+h5ZkMOgMKTHkE=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=FqfQWnRb4G+ZpZ7wchlogXAAPiJvbBg2z4a4vsCDEXObwQcWVgL0/kk0j6OnSFiut\n\t/qY0ICBCczFZm0mf4ac+udLssQ1N8kJnTCLnwYv6ylkDhoq9BcEqsZ12TLk39r+9dZ\n\tVOaFM1CEkXfM3TqJZj0Zm2K9T6IdCenjhvzAWJSI=","Date":"Wed, 4 Aug 2021 14:48:44 +0900","From":"paul.elder@ideasonboard.com","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Message-ID":"<20210804054844.GC2167@pyrite.rasen.tech>","References":"<20210730010306.19956-1-laurent.pinchart@ideasonboard.com>\n\t<20210730010306.19956-6-laurent.pinchart@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=us-ascii","Content-Disposition":"inline","In-Reply-To":"<20210730010306.19956-6-laurent.pinchart@ideasonboard.com>","Subject":"Re: [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>","Cc":"libcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":18533,"web_url":"https://patchwork.libcamera.org/comment/18533/","msgid":"<20210804055253.GD2167@pyrite.rasen.tech>","date":"2021-08-04T05:52:53","subject":"Re: [libcamera-devel] [PATCH v2 5/8] cam: Add DRM helper classes","submitter":{"id":17,"url":"https://patchwork.libcamera.org/api/people/17/","name":"Paul Elder","email":"paul.elder@ideasonboard.com"},"content":"Hi Laurent,\n\nOn Wed, Aug 04, 2021 at 02:48:44PM +0900, paul.elder@ideasonboard.com wrote:\n> Hi Laurent,\n> \n> On Fri, Jul 30, 2021 at 04:03:03AM +0300, Laurent Pinchart wrote:\n> > To prepare for viewfinder operation through the DRM/KMS API, add a set\n> > of helper classes that encapsulate the libdrm functions.\n> > \n> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> \n> oof, that was long... isn't there a library for this or something? :p\n> \n> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>\n> \n> > ---\n> > Changes 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\n> > \n> > diff --git a/src/cam/drm.cpp b/src/cam/drm.cpp\n> > new file mode 100644\n> > index 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\ns/2020/2021 ?\n\nAlso for the header.\n\n\nPaul\n\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 */\n> > diff --git a/src/cam/drm.h b/src/cam/drm.h\n> > new file mode 100644\n> > index 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__ */\n> > diff --git a/src/cam/meson.build b/src/cam/meson.build\n> > index 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> > -- \n> > Regards,\n> > \n> > Laurent Pinchart\n> >","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 22F7FC3235\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed,  4 Aug 2021 05:53:04 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 8C7CB6881B;\n\tWed,  4 Aug 2021 07:53:03 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 4269D60268\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed,  4 Aug 2021 07:53:02 +0200 (CEST)","from pyrite.rasen.tech (unknown\n\t[IPv6:2400:4051:61:600:2c71:1b79:d06d:5032])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 6C7C724F;\n\tWed,  4 Aug 2021 07:53:00 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"Q9l2lLDo\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1628056382;\n\tbh=oaPJZCVqeWZPTHfrKxhfyKEuEPOfyUYdBHqIIDJksZY=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=Q9l2lLDo/xmxujNYP2hMFGrAVQkh1VneCNd5Ew4f8ENwsixfjD/ozMos2cmNhNm5a\n\t6M1DYecRB7KxNcl+N/aV/UQFziARCbaGEZXhjTpNCmKOwR9a7y2UKYgHF2+Fm/19O2\n\tzDLVYpI5aH56vna8qw7a8po5lMnhg35cp9oFbg0g=","Date":"Wed, 4 Aug 2021 14:52:53 +0900","From":"paul.elder@ideasonboard.com","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Message-ID":"<20210804055253.GD2167@pyrite.rasen.tech>","References":"<20210730010306.19956-1-laurent.pinchart@ideasonboard.com>\n\t<20210730010306.19956-6-laurent.pinchart@ideasonboard.com>\n\t<20210804054844.GC2167@pyrite.rasen.tech>","MIME-Version":"1.0","Content-Type":"text/plain; charset=us-ascii","Content-Disposition":"inline","In-Reply-To":"<20210804054844.GC2167@pyrite.rasen.tech>","Subject":"Re: [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>","Cc":"libcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":18541,"web_url":"https://patchwork.libcamera.org/comment/18541/","msgid":"<YQpSFp4xj1Co44W8@pendragon.ideasonboard.com>","date":"2021-08-04T08:38:46","subject":"Re: [libcamera-devel] [PATCH v2 5/8] cam: Add DRM helper classes","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Paul,\n\nOn Wed, Aug 04, 2021 at 02:48:44PM +0900, paul.elder@ideasonboard.com wrote:\n> On Fri, Jul 30, 2021 at 04:03:03AM +0300, Laurent Pinchart wrote:\n> > To prepare for viewfinder operation through the DRM/KMS API, add a set\n> > of helper classes that encapsulate the libdrm functions.\n> > \n> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> \n> oof, that was long... isn't there a library for this or something? :p\n\nFunny you mention that, no later than yesterday I happened to discuss\nhttps://github.com/tomba/kmsxx.git in this context. I've considered it,\nbut wanted to minimize the external dependencies for cam. kmsxx also\ndoesn't have a stable ABI yet, but that could possibly be worked around\nwith static linking.\n\n> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>\n> \n> > ---\n> > Changes 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\n> > \n> > diff --git a/src/cam/drm.cpp b/src/cam/drm.cpp\n> > new file mode 100644\n> > index 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 */\n> > diff --git a/src/cam/drm.h b/src/cam/drm.h\n> > new file mode 100644\n> > index 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__ */\n> > diff --git a/src/cam/meson.build b/src/cam/meson.build\n> > index 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)","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 35A84C3232\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed,  4 Aug 2021 08:39:01 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 9F9376881B;\n\tWed,  4 Aug 2021 10:39:00 +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 26E206026C\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed,  4 Aug 2021 10:38:59 +0200 (CEST)","from pendragon.ideasonboard.com (62-78-145-57.bb.dnainternet.fi\n\t[62.78.145.57])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 8643524F;\n\tWed,  4 Aug 2021 10:38:58 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"H7Qk6ZfA\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1628066338;\n\tbh=p0290aWTR6Dk8Im1aQMzhyDo7JhBg2OOlSyEqdj8TYY=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=H7Qk6ZfAvDctm1fKYHfzKq/XqY4mcY70/VLjIJEb4IixdAP0GSQUHReXoDiao5R8T\n\tto36vID2RPIK/jt60MTrxc0sw1ZFju0yjpStn1XT3i7MrDL0ij0/MB1ay9+3eAOxVP\n\t5PNzlU9yj3XvnfgeZbSrINw1O593rJ4tuxuxcWBg=","Date":"Wed, 4 Aug 2021 11:38:46 +0300","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"paul.elder@ideasonboard.com","Message-ID":"<YQpSFp4xj1Co44W8@pendragon.ideasonboard.com>","References":"<20210730010306.19956-1-laurent.pinchart@ideasonboard.com>\n\t<20210730010306.19956-6-laurent.pinchart@ideasonboard.com>\n\t<20210804054844.GC2167@pyrite.rasen.tech>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20210804054844.GC2167@pyrite.rasen.tech>","Subject":"Re: [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>","Cc":"libcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]