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