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