From cf7cc0d43ebf42e493e53e571d80cbc9e7367df6 Mon Sep 17 00:00:00 2001 From: Xaver Hugl Date: Fri, 5 Feb 2021 19:45:34 +0100 Subject: [PATCH] Implement DRM leasing --- src/plugins/platforms/drm/CMakeLists.txt | 1 + src/plugins/platforms/drm/drm_gpu.cpp | 151 +++++++++++++++++- src/plugins/platforms/drm/drm_gpu.h | 16 ++ .../platforms/drm/drm_lease_output.cpp | 70 ++++++++ src/plugins/platforms/drm/drm_lease_output.h | 52 ++++++ 5 files changed, 285 insertions(+), 5 deletions(-) create mode 100644 src/plugins/platforms/drm/drm_lease_output.cpp create mode 100644 src/plugins/platforms/drm/drm_lease_output.h diff --git a/src/plugins/platforms/drm/CMakeLists.txt b/src/plugins/platforms/drm/CMakeLists.txt index 2a59269acc..5594a189d5 100644 --- a/src/plugins/platforms/drm/CMakeLists.txt +++ b/src/plugins/platforms/drm/CMakeLists.txt @@ -17,6 +17,7 @@ set(DRM_SOURCES drm_pipeline.cpp drm_abstract_output.cpp drm_virtual_output.cpp + drm_lease_output.cpp ) if (HAVE_GBM) diff --git a/src/plugins/platforms/drm/drm_gpu.cpp b/src/plugins/platforms/drm/drm_gpu.cpp index 0564c3cc3e..5c92d42b83 100644 --- a/src/plugins/platforms/drm/drm_gpu.cpp +++ b/src/plugins/platforms/drm/drm_gpu.cpp @@ -20,6 +20,8 @@ #include "main.h" #include "drm_pipeline.h" #include "drm_virtual_output.h" +#include "wayland_server.h" +#include "drm_lease_output.h" #if HAVE_GBM #include "egl_gbm_backend.h" @@ -31,11 +33,14 @@ #include #include #include +#include // drm #include #include #include #include +// KWaylandServer +#include "KWaylandServer/drmleasedevice_v1_interface.h" namespace KWin { @@ -84,10 +89,40 @@ DrmGpu::DrmGpu(DrmBackend *backend, const QString &devNode, int fd, dev_t device if (atomicModesetting) { initDrmResources(); } + + m_leaseDevice = new KWaylandServer::DrmLeaseDeviceV1Interface(waylandServer()->display(), [this]{ + char *path = drmGetDeviceNameFromFd2(m_fd); + int fd = open(path, O_RDWR | O_CLOEXEC); + if (fd < 0) { + qCWarning(KWIN_DRM) << "Could not open DRM fd for leasing!" << strerror(errno); + } else { + if (drmIsMaster(fd)) { + if (drmDropMaster(fd) != 0) { + close(fd); + qCWarning(KWIN_DRM) << "Could not create a non-master DRM fd for leasing!" << strerror(errno); + return -1; + } + } + } + return fd; + }); + connect(m_leaseDevice, &KWaylandServer::DrmLeaseDeviceV1Interface::leaseRequested, this, &DrmGpu::handleLeaseRequest); + connect(m_leaseDevice, &KWaylandServer::DrmLeaseDeviceV1Interface::leaseRevoked, this, &DrmGpu::handleLeaseRevoked); + connect(m_backend->session(), &Session::activeChanged, m_leaseDevice, [this](bool active){ + if (!active) { + // when we gain drm master we want to update outputs first and only then notify the lease device + m_leaseDevice->setDrmMaster(active); + } + }); } DrmGpu::~DrmGpu() { + const auto leaseOutputs = m_leaseOutputs; + for (const auto &output : leaseOutputs) { + removeLeaseOutput(output); + } + delete m_leaseDevice; waitIdle(); const auto outputs = m_outputs; for (const auto &output : outputs) { @@ -173,6 +208,24 @@ bool DrmGpu::updateOutputs() return false; } + // In principle these things are supposed to be detected through the wayland protocol. + // In practice SteamVR doesn't always behave correctly + auto lessees = drmModeListLessees(m_fd); + for (const auto &leaseOutput : qAsConst(m_leaseOutputs)) { + if (leaseOutput->lease()) { + bool leaseActive = false; + for (uint i = 0; i < lessees->count; i++) { + if (lessees->lessees[i] == leaseOutput->lease()->lesseeId()) { + leaseActive = true; + break; + } + } + if (!leaseActive) { + leaseOutput->lease()->deny(); + } + } + } + // check for added and removed connectors QVector removedConnectors = m_connectors; for (int i = 0; i < resources->count_connectors; ++i) { @@ -180,7 +233,7 @@ bool DrmGpu::updateOutputs() auto it = std::find_if(m_connectors.constBegin(), m_connectors.constEnd(), [currentConnector] (DrmConnector *c) { return c->id() == currentConnector; }); if (it == m_connectors.constEnd()) { auto c = new DrmConnector(this, currentConnector); - if (!c->init() || !c->isConnected() || c->isNonDesktop()) { + if (!c->init() || !c->isConnected()) { delete c; continue; } @@ -195,6 +248,8 @@ bool DrmGpu::updateOutputs() for (const auto &connector : qAsConst(removedConnectors)) { if (auto output = findOutput(connector->id())) { removeOutput(output); + } else if (auto leaseOutput = findLeaseOutput(connector->id())) { + removeLeaseOutput(leaseOutput); } m_connectors.removeOne(connector); } @@ -203,13 +258,15 @@ bool DrmGpu::updateOutputs() QVector connectedConnectors; for (const auto &conn : qAsConst(m_connectors)) { auto output = findOutput(conn->id()); - if (conn->isConnected() && !conn->isNonDesktop()) { + if (conn->isConnected()) { connectedConnectors << conn; if (output) { output->updateModes(); } } else if (output) { removeOutput(output); + } else if (const auto leaseOutput = findLeaseOutput(conn->id())) { + removeLeaseOutput(leaseOutput); } } @@ -235,12 +292,31 @@ bool DrmGpu::updateOutputs() return c1->getProp(DrmConnector::PropertyIndex::CrtcId)->current() > c2->getProp(DrmConnector::PropertyIndex::CrtcId)->current(); }); } - const auto config = findWorkingCombination({}, connectedConnectors, m_crtcs, m_planes); + auto connectors = connectedConnectors; + auto crtcs = m_crtcs; + auto planes = m_planes; + // don't touch resources that are leased + for (const auto &output : qAsConst(m_leaseOutputs)) { + if (output->lease()) { + connectors.removeOne(output->pipeline()->connector()); + crtcs.removeOne(output->pipeline()->crtc()); + planes.removeOne(output->pipeline()->primaryPlane()); + } + } + const auto config = findWorkingCombination({}, connectors, crtcs, planes); m_pipelines << config; for (const auto &pipeline : config) { auto output = pipeline->output(); - if (m_outputs.contains(output)) { + if (pipeline->connector()->isNonDesktop()) { + if (const auto &leaseOutput = findLeaseOutput(pipeline->connector()->id())) { + leaseOutput->setPipeline(pipeline); + } else { + qCDebug(KWIN_DRM, "New non-desktop output on GPU %s: %s", qPrintable(m_devNode), qPrintable(pipeline->connector()->modelName())); + m_leaseOutputs << new DrmLeaseOutput(pipeline, m_leaseDevice); + } + pipeline->setActive(false); + } else if (m_outputs.contains(output)) { // try setting hardware rotation output->updateTransform(output->transform()); } else { @@ -254,6 +330,7 @@ bool DrmGpu::updateOutputs() } } + m_leaseDevice->setDrmMaster(true); return true; } @@ -325,7 +402,7 @@ bool DrmGpu::commitCombination(const QVector &pipelines) if (output) { output->setPipeline(pipeline); pipeline->setOutput(output); - } else { + } else if (!pipeline->connector()->isNonDesktop()) { output = new DrmOutput(this, pipeline); Q_EMIT outputEnabled(output);// create render resources for the test } @@ -520,4 +597,68 @@ bool DrmGpu::isFormatSupported(uint32_t drmFormat) const } } +DrmLeaseOutput *DrmGpu::findLeaseOutput(quint32 connector) +{ + auto it = std::find_if(m_leaseOutputs.constBegin(), m_leaseOutputs.constEnd(), [connector] (DrmLeaseOutput *o) { + return o->pipeline()->connector()->id() == connector; + }); + if (it != m_leaseOutputs.constEnd()) { + return *it; + } + return nullptr; +} + +void DrmGpu::handleLeaseRequest(KWaylandServer::DrmLeaseV1Interface *leaseRequest) +{ + QVector objects; + QVector outputs; + for (const auto &connector : leaseRequest->connectors()) { + auto output = qobject_cast(connector); + if (m_leaseOutputs.contains(output) && !output->lease()) { + output->addLeaseObjects(objects); + outputs << output; + } + } + uint32_t lesseeId; + int fd = drmModeCreateLease(m_fd, objects.constData(), objects.count(), 0, &lesseeId); + if (fd < 0) { + qCWarning(KWIN_DRM) << "Could not create DRM lease!" << strerror(errno); + qCWarning(KWIN_DRM, "Tried to lease the following %d resources:", objects.count()); + for (const auto &res : qAsConst(objects)) { + qCWarning(KWIN_DRM) << res; + } + leaseRequest->deny(); + } else { + qCDebug(KWIN_DRM, "Created lease with leaseFd %d and lesseeId %d for %d resources:", fd, lesseeId, objects.count()); + for (const auto &res : qAsConst(objects)) { + qCDebug(KWIN_DRM) << res; + } + leaseRequest->grant(fd, lesseeId); + for (const auto &output : qAsConst(outputs)) { + output->leased(leaseRequest); + } + } +} + +void DrmGpu::handleLeaseRevoked(KWaylandServer::DrmLeaseV1Interface *lease) +{ + for (const auto &connector : lease->connectors()) { + auto output = qobject_cast(connector); + if (m_leaseOutputs.contains(output)) { + output->leaseEnded(); + } + } + qCDebug(KWIN_DRM, "Revoking lease with leaseID %d", lease->lesseeId()); + drmModeRevokeLease(m_fd, lease->lesseeId()); +} + +void DrmGpu::removeLeaseOutput(DrmLeaseOutput *output) +{ + m_leaseOutputs.removeOne(output); + auto pipeline = output->pipeline(); + delete output; + m_pipelines.removeOne(pipeline); + delete pipeline; +} + } diff --git a/src/plugins/platforms/drm/drm_gpu.h b/src/plugins/platforms/drm/drm_gpu.h index 6142ecf035..7eaed5ad31 100644 --- a/src/plugins/platforms/drm/drm_gpu.h +++ b/src/plugins/platforms/drm/drm_gpu.h @@ -22,6 +22,12 @@ struct gbm_device; +namespace KWaylandServer +{ +class DrmLeaseDeviceV1Interface; +class DrmLeaseV1Interface; +} + namespace KWin { @@ -33,6 +39,7 @@ class AbstractEglDrmBackend; class DrmPipeline; class DrmAbstractOutput; class DrmVirtualOutput; +class DrmLeaseOutput; class DrmGpu : public QObject { @@ -116,12 +123,17 @@ protected: private: void dispatchEvents(); DrmOutput *findOutput(quint32 connector); + DrmLeaseOutput *findLeaseOutput(quint32 connector); void removeOutput(DrmOutput *output); + void removeLeaseOutput(DrmLeaseOutput *output); void initDrmResources(); QVector findWorkingCombination(const QVector &pipelines, QVector connectors, QVector crtcs, const QVector &planes); bool commitCombination(const QVector &pipelines); + void handleLeaseRequest(KWaylandServer::DrmLeaseV1Interface *leaseRequest); + void handleLeaseRevoked(KWaylandServer::DrmLeaseV1Interface *lease); + DrmBackend* const m_backend; QPointer m_eglBackend; @@ -145,6 +157,10 @@ private: QVector m_drmOutputs; // includes virtual outputs QVector m_outputs; + + // for DRM leasing: + QVector m_leaseOutputs; + KWaylandServer::DrmLeaseDeviceV1Interface *m_leaseDevice = nullptr; }; } diff --git a/src/plugins/platforms/drm/drm_lease_output.cpp b/src/plugins/platforms/drm/drm_lease_output.cpp new file mode 100644 index 0000000000..eca6eed282 --- /dev/null +++ b/src/plugins/platforms/drm/drm_lease_output.cpp @@ -0,0 +1,70 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2021 Xaver Hugl + + SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "drm_lease_output.h" + +#include "KWaylandServer/drmleasedevice_v1_interface.h" +#include "drm_object_connector.h" +#include "drm_object_crtc.h" +#include "drm_object_plane.h" +#include "drm_pipeline.h" + +#include "logging.h" + +#include + +namespace KWin +{ + +DrmLeaseOutput::DrmLeaseOutput(DrmPipeline *pipeline, KWaylandServer::DrmLeaseDeviceV1Interface *leaseDevice) + : KWaylandServer::DrmLeaseConnectorV1Interface( + leaseDevice, + pipeline->connector()->id(), + pipeline->connector()->modelName(), + QStringLiteral("%1 %2").arg(pipeline->connector()->edid()->manufacturerString()).arg(pipeline->connector()->modelName()) + ) + , m_pipeline(pipeline) +{ + qCDebug(KWIN_DRM) << "offering connector" << m_pipeline->connector()->id() << "for lease"; +} + +DrmLeaseOutput::~DrmLeaseOutput() +{ + qCDebug(KWIN_DRM) << "revoking lease offer for connector" << m_pipeline->connector()->id(); +} + +void DrmLeaseOutput::addLeaseObjects(QVector &objectList) +{ + qCDebug(KWIN_DRM) << "adding connector" << m_pipeline->connector()->id() << "to lease"; + objectList << m_pipeline->connector()->id(); + objectList << m_pipeline->crtc()->id(); + if (m_pipeline->primaryPlane()) { + objectList << m_pipeline->primaryPlane()->id(); + } +} + +void DrmLeaseOutput::leased(KWaylandServer::DrmLeaseV1Interface *lease) +{ + m_lease = lease; +} + +void DrmLeaseOutput::leaseEnded() +{ + qCDebug(KWIN_DRM) << "ended lease for connector" << m_pipeline->connector()->id(); + m_lease = nullptr; +} + +void DrmLeaseOutput::setPipeline(DrmPipeline *pipeline) +{ + Q_ASSERT_X(pipeline->connector() == m_pipeline->connector(), "DrmLeaseOutput::setPipeline", "Pipeline with wrong connector set!"); + delete m_pipeline; + m_pipeline = pipeline; +} + +} diff --git a/src/plugins/platforms/drm/drm_lease_output.h b/src/plugins/platforms/drm/drm_lease_output.h new file mode 100644 index 0000000000..b4bf2b7f28 --- /dev/null +++ b/src/plugins/platforms/drm/drm_lease_output.h @@ -0,0 +1,52 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2021 Xaver Hugl + + SPDX-License-Identifier: GPL-2.0-or-later + */ + +#pragma once + +#include +#include +#include + +namespace KWin +{ + +class DrmConnector; +class DrmPipeline; + +/** + * a DrmLeaseOutput represents a non-desktop output (usually a VR headset) + * that is not used directly by the compositor but is instead leased out to + * applications (usually VR compositors) that drive the output themselves + */ +class DrmLeaseOutput : public KWaylandServer::DrmLeaseConnectorV1Interface +{ + Q_OBJECT +public: + DrmLeaseOutput(DrmPipeline *pipeline, KWaylandServer::DrmLeaseDeviceV1Interface *leaseDevice); + ~DrmLeaseOutput() override; + + void addLeaseObjects(QVector &objectList); + void leased(KWaylandServer::DrmLeaseV1Interface *lease); + void leaseEnded(); + + KWaylandServer::DrmLeaseV1Interface *lease() const { + return m_lease; + } + + DrmPipeline *pipeline() const { + return m_pipeline; + } + void setPipeline(DrmPipeline *pipeline); + +private: + DrmPipeline *m_pipeline; + KWaylandServer::DrmLeaseV1Interface *m_lease = nullptr; +}; + +}