File indexing completed on 2025-04-20 10:57:32

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #include "drm_backend.h"
0010 
0011 #include <config-kwin.h>
0012 
0013 #include "backends/libinput/libinputbackend.h"
0014 #include "core/outputconfiguration.h"
0015 #include "core/renderloop.h"
0016 #include "core/session.h"
0017 #include "drm_connector.h"
0018 #include "drm_crtc.h"
0019 #include "drm_egl_backend.h"
0020 #include "drm_gpu.h"
0021 #include "drm_logging.h"
0022 #include "drm_output.h"
0023 #include "drm_pipeline.h"
0024 #include "drm_plane.h"
0025 #include "drm_qpainter_backend.h"
0026 #include "drm_render_backend.h"
0027 #include "drm_virtual_output.h"
0028 #include "gbm_dmabuf.h"
0029 #include "utils/udev.h"
0030 // KF5
0031 #include <KCoreAddons>
0032 #include <KLocalizedString>
0033 // Qt
0034 #include <QCoreApplication>
0035 #include <QFileInfo>
0036 #include <QSocketNotifier>
0037 #include <QStringBuilder>
0038 // system
0039 #include <algorithm>
0040 #include <cerrno>
0041 #include <sys/stat.h>
0042 #include <unistd.h>
0043 // drm
0044 #include <gbm.h>
0045 #include <libdrm/drm_mode.h>
0046 #include <xf86drm.h>
0047 
0048 namespace KWin
0049 {
0050 
0051 static QStringList splitPathList(const QString &input, const QChar delimiter)
0052 {
0053     QStringList ret;
0054     QString tmp;
0055     for (int i = 0; i < input.size(); i++) {
0056         if (input[i] == delimiter) {
0057             if (i > 0 && input[i - 1] == '\\') {
0058                 tmp[tmp.size() - 1] = delimiter;
0059             } else if (!tmp.isEmpty()) {
0060                 ret.append(tmp);
0061                 tmp = QString();
0062             }
0063         } else {
0064             tmp.append(input[i]);
0065         }
0066     }
0067     if (!tmp.isEmpty()) {
0068         ret.append(tmp);
0069     }
0070     return ret;
0071 }
0072 
0073 DrmBackend::DrmBackend(Session *session, QObject *parent)
0074     : OutputBackend(parent)
0075     , m_udev(std::make_unique<Udev>())
0076     , m_udevMonitor(m_udev->monitor())
0077     , m_session(session)
0078     , m_explicitGpus(splitPathList(qEnvironmentVariable("KWIN_DRM_DEVICES"), ':'))
0079     , m_dpmsFilter()
0080 {
0081 }
0082 
0083 DrmBackend::~DrmBackend() = default;
0084 
0085 Session *DrmBackend::session() const
0086 {
0087     return m_session;
0088 }
0089 
0090 bool DrmBackend::isActive() const
0091 {
0092     return m_active;
0093 }
0094 
0095 Outputs DrmBackend::outputs() const
0096 {
0097     return m_outputs;
0098 }
0099 
0100 void DrmBackend::createDpmsFilter()
0101 {
0102     if (m_dpmsFilter) {
0103         // already another output is off
0104         return;
0105     }
0106     m_dpmsFilter = std::make_unique<DpmsInputEventFilter>();
0107     input()->prependInputEventFilter(m_dpmsFilter.get());
0108 }
0109 
0110 void DrmBackend::turnOutputsOn()
0111 {
0112     m_dpmsFilter.reset();
0113     for (Output *output : std::as_const(m_outputs)) {
0114         if (output->isEnabled()) {
0115             output->setDpmsMode(Output::DpmsMode::On);
0116         }
0117     }
0118 }
0119 
0120 void DrmBackend::checkOutputsAreOn()
0121 {
0122     if (!m_dpmsFilter) {
0123         // already disabled, all outputs are on
0124         return;
0125     }
0126     for (Output *output : std::as_const(m_outputs)) {
0127         if (output->isEnabled() && output->dpmsMode() != Output::DpmsMode::On) {
0128             // dpms still disabled, need to keep the filter
0129             return;
0130         }
0131     }
0132     // all outputs are on, disable the filter
0133     m_dpmsFilter.reset();
0134 }
0135 
0136 void DrmBackend::activate(bool active)
0137 {
0138     if (active) {
0139         qCDebug(KWIN_DRM) << "Activating session.";
0140         reactivate();
0141     } else {
0142         qCDebug(KWIN_DRM) << "Deactivating session.";
0143         deactivate();
0144     }
0145 }
0146 
0147 void DrmBackend::reactivate()
0148 {
0149     if (m_active) {
0150         return;
0151     }
0152     m_active = true;
0153 
0154     for (const auto &output : std::as_const(m_outputs)) {
0155         output->renderLoop()->uninhibit();
0156         output->renderLoop()->scheduleRepaint();
0157     }
0158 
0159     // While the session had been inactive, an output could have been added or
0160     // removed, we need to re-scan outputs.
0161     updateOutputs();
0162     Q_EMIT activeChanged();
0163 }
0164 
0165 void DrmBackend::deactivate()
0166 {
0167     if (!m_active) {
0168         return;
0169     }
0170 
0171     for (const auto &output : std::as_const(m_outputs)) {
0172         output->renderLoop()->inhibit();
0173     }
0174 
0175     m_active = false;
0176     Q_EMIT activeChanged();
0177 }
0178 
0179 bool DrmBackend::initialize()
0180 {
0181     // TODO: Pause/Resume individual GPU devices instead.
0182     connect(m_session, &Session::devicePaused, this, [this](dev_t deviceId) {
0183         if (primaryGpu()->deviceId() == deviceId) {
0184             deactivate();
0185         }
0186     });
0187     connect(m_session, &Session::deviceResumed, this, [this](dev_t deviceId) {
0188         if (primaryGpu()->deviceId() == deviceId) {
0189             reactivate();
0190         }
0191     });
0192     connect(m_session, &Session::awoke, this, &DrmBackend::turnOutputsOn);
0193 
0194     if (!m_explicitGpus.isEmpty()) {
0195         for (const QString &fileName : m_explicitGpus) {
0196             addGpu(fileName);
0197         }
0198     } else {
0199         const auto devices = m_udev->listGPUs();
0200         for (const UdevDevice::Ptr &device : devices) {
0201             if (device->seat() == m_session->seat()) {
0202                 addGpu(device->devNode());
0203             }
0204         }
0205     }
0206 
0207     if (m_gpus.empty()) {
0208         qCWarning(KWIN_DRM) << "No suitable DRM devices have been found";
0209         return false;
0210     }
0211 
0212     // setup udevMonitor
0213     if (m_udevMonitor) {
0214         m_udevMonitor->filterSubsystemDevType("drm");
0215         const int fd = m_udevMonitor->fd();
0216         if (fd != -1) {
0217             m_socketNotifier = std::make_unique<QSocketNotifier>(fd, QSocketNotifier::Read);
0218             connect(m_socketNotifier.get(), &QSocketNotifier::activated, this, &DrmBackend::handleUdevEvent);
0219             m_udevMonitor->enable();
0220         }
0221     }
0222     return true;
0223 }
0224 
0225 void DrmBackend::handleUdevEvent()
0226 {
0227     while (auto device = m_udevMonitor->getDevice()) {
0228         if (!m_active) {
0229             continue;
0230         }
0231 
0232         // Ignore the device seat if the KWIN_DRM_DEVICES envvar is set.
0233         if (!m_explicitGpus.isEmpty()) {
0234             const bool foundMatch = std::any_of(m_explicitGpus.begin(), m_explicitGpus.end(), [&device](const QString &explicitPath) {
0235                 return QFileInfo(explicitPath).canonicalPath() == QFileInfo(device->devNode()).canonicalPath();
0236             });
0237             if (!foundMatch) {
0238                 continue;
0239             }
0240         } else {
0241             if (device->seat() != m_session->seat()) {
0242                 continue;
0243             }
0244         }
0245 
0246         if (device->action() == QStringLiteral("add")) {
0247             if (addGpu(device->devNode())) {
0248                 updateOutputs();
0249             }
0250         } else if (device->action() == QStringLiteral("remove")) {
0251             DrmGpu *gpu = findGpu(device->devNum());
0252             if (gpu) {
0253                 if (primaryGpu() == gpu) {
0254                     qCCritical(KWIN_DRM) << "Primary gpu has been removed! Quitting...";
0255                     QCoreApplication::exit(1);
0256                     return;
0257                 } else {
0258                     gpu->setRemoved();
0259                     updateOutputs();
0260                 }
0261             }
0262         } else if (device->action() == QStringLiteral("change")) {
0263             DrmGpu *gpu = findGpu(device->devNum());
0264             if (!gpu) {
0265                 gpu = addGpu(device->devNode());
0266             }
0267             if (gpu) {
0268                 qCDebug(KWIN_DRM) << "Received change event for monitored drm device" << gpu->devNode();
0269                 updateOutputs();
0270             }
0271         }
0272     }
0273 }
0274 
0275 DrmGpu *DrmBackend::addGpu(const QString &fileName)
0276 {
0277     int fd = m_session->openRestricted(fileName);
0278     if (fd < 0) {
0279         qCWarning(KWIN_DRM) << "failed to open drm device at" << fileName;
0280         return nullptr;
0281     }
0282 
0283     // try to make a simple drm get resource call, if it fails it is not useful for us
0284     drmModeRes *resources = drmModeGetResources(fd);
0285     if (!resources) {
0286         qCDebug(KWIN_DRM) << "Skipping KMS incapable drm device node at" << fileName;
0287         m_session->closeRestricted(fd);
0288         return nullptr;
0289     }
0290     drmModeFreeResources(resources);
0291 
0292     struct stat buf;
0293     if (fstat(fd, &buf) == -1) {
0294         qCDebug(KWIN_DRM, "Failed to fstat %s: %s", qPrintable(fileName), strerror(errno));
0295         m_session->closeRestricted(fd);
0296         return nullptr;
0297     }
0298 
0299     qCDebug(KWIN_DRM, "adding GPU %s", qPrintable(fileName));
0300     m_gpus.push_back(std::make_unique<DrmGpu>(this, fileName, fd, buf.st_rdev));
0301     auto gpu = m_gpus.back().get();
0302     m_active = true;
0303     connect(gpu, &DrmGpu::outputAdded, this, &DrmBackend::addOutput);
0304     connect(gpu, &DrmGpu::outputRemoved, this, &DrmBackend::removeOutput);
0305     Q_EMIT gpuAdded(gpu);
0306     return gpu;
0307 }
0308 
0309 void DrmBackend::addOutput(DrmAbstractOutput *o)
0310 {
0311     m_outputs.append(o);
0312     Q_EMIT outputAdded(o);
0313     o->updateEnabled(true);
0314 }
0315 
0316 void DrmBackend::removeOutput(DrmAbstractOutput *o)
0317 {
0318     o->updateEnabled(false);
0319     m_outputs.removeOne(o);
0320     Q_EMIT outputRemoved(o);
0321 }
0322 
0323 void DrmBackend::updateOutputs()
0324 {
0325     for (auto it = m_gpus.begin(); it != m_gpus.end(); ++it) {
0326         if ((*it)->isRemoved()) {
0327             (*it)->removeOutputs();
0328         } else {
0329             (*it)->updateOutputs();
0330         }
0331     }
0332 
0333     Q_EMIT outputsQueried();
0334 
0335     for (auto it = m_gpus.begin(); it != m_gpus.end();) {
0336         DrmGpu *gpu = it->get();
0337         if (gpu->isRemoved() || (gpu != primaryGpu() && gpu->drmOutputs().isEmpty())) {
0338             qCDebug(KWIN_DRM) << "Removing GPU" << (*it)->devNode();
0339             const std::unique_ptr<DrmGpu> keepAlive = std::move(*it);
0340             it = m_gpus.erase(it);
0341             Q_EMIT gpuRemoved(keepAlive.get());
0342         } else {
0343             it++;
0344         }
0345     }
0346 }
0347 
0348 std::unique_ptr<InputBackend> DrmBackend::createInputBackend()
0349 {
0350     return std::make_unique<LibinputBackend>(m_session);
0351 }
0352 
0353 std::unique_ptr<QPainterBackend> DrmBackend::createQPainterBackend()
0354 {
0355     return std::make_unique<DrmQPainterBackend>(this);
0356 }
0357 
0358 std::unique_ptr<OpenGLBackend> DrmBackend::createOpenGLBackend()
0359 {
0360     return std::make_unique<EglGbmBackend>(this);
0361 }
0362 
0363 void DrmBackend::sceneInitialized()
0364 {
0365     if (m_outputs.isEmpty()) {
0366         updateOutputs();
0367     } else {
0368         for (const auto &gpu : std::as_const(m_gpus)) {
0369             gpu->recreateSurfaces();
0370         }
0371     }
0372 }
0373 
0374 QVector<CompositingType> DrmBackend::supportedCompositors() const
0375 {
0376     return QVector<CompositingType>{OpenGLCompositing, QPainterCompositing};
0377 }
0378 
0379 QString DrmBackend::supportInformation() const
0380 {
0381     QString supportInfo;
0382     QDebug s(&supportInfo);
0383     s.nospace();
0384     s << "Name: "
0385       << "DRM" << Qt::endl;
0386     s << "Active: " << m_active << Qt::endl;
0387     for (size_t g = 0; g < m_gpus.size(); g++) {
0388         s << "Atomic Mode Setting on GPU " << g << ": " << m_gpus.at(g)->atomicModeSetting() << Qt::endl;
0389     }
0390     return supportInfo;
0391 }
0392 
0393 Output *DrmBackend::createVirtualOutput(const QString &name, const QSize &size, double scale)
0394 {
0395     auto output = primaryGpu()->createVirtualOutput(name, size * scale, scale);
0396     Q_EMIT outputsQueried();
0397     return output;
0398 }
0399 
0400 void DrmBackend::removeVirtualOutput(Output *output)
0401 {
0402     auto virtualOutput = qobject_cast<DrmVirtualOutput *>(output);
0403     if (!virtualOutput) {
0404         return;
0405     }
0406     primaryGpu()->removeVirtualOutput(virtualOutput);
0407     Q_EMIT outputsQueried();
0408 }
0409 
0410 gbm_bo *DrmBackend::createBo(const QSize &size, quint32 format, const QVector<uint64_t> &modifiers)
0411 {
0412     const auto eglBackend = dynamic_cast<EglGbmBackend *>(m_renderBackend);
0413     if (!eglBackend || !primaryGpu()->gbmDevice()) {
0414         return nullptr;
0415     }
0416 
0417     return createGbmBo(primaryGpu()->gbmDevice(), size, format, modifiers);
0418 }
0419 
0420 std::optional<DmaBufParams> DrmBackend::testCreateDmaBuf(const QSize &size, quint32 format, const QVector<uint64_t> &modifiers)
0421 {
0422     gbm_bo *bo = createBo(size, format, modifiers);
0423     if (!bo) {
0424         return {};
0425     }
0426 
0427     auto ret = dmaBufParamsForBo(bo);
0428     gbm_bo_destroy(bo);
0429     return ret;
0430 }
0431 
0432 std::shared_ptr<DmaBufTexture> DrmBackend::createDmaBufTexture(const QSize &size, quint32 format, uint64_t modifier)
0433 {
0434     QVector<uint64_t> mods = {modifier};
0435     gbm_bo *bo = createBo(size, format, mods);
0436     if (!bo) {
0437         return {};
0438     }
0439 
0440     // The bo will be kept around until the last fd is closed.
0441     DmaBufAttributes attributes = dmaBufAttributesForBo(bo);
0442     gbm_bo_destroy(bo);
0443     const auto eglBackend = static_cast<EglGbmBackend *>(m_renderBackend);
0444     eglBackend->makeCurrent();
0445     if (auto texture = eglBackend->importDmaBufAsTexture(attributes)) {
0446         return std::make_shared<DmaBufTexture>(texture, std::move(attributes));
0447     } else {
0448         return nullptr;
0449     }
0450 }
0451 
0452 DrmGpu *DrmBackend::primaryGpu() const
0453 {
0454     return m_gpus.empty() ? nullptr : m_gpus.front().get();
0455 }
0456 
0457 DrmGpu *DrmBackend::findGpu(dev_t deviceId) const
0458 {
0459     auto it = std::find_if(m_gpus.begin(), m_gpus.end(), [deviceId](const auto &gpu) {
0460         return gpu->deviceId() == deviceId;
0461     });
0462     return it == m_gpus.end() ? nullptr : it->get();
0463 }
0464 
0465 size_t DrmBackend::gpuCount() const
0466 {
0467     return m_gpus.size();
0468 }
0469 
0470 bool DrmBackend::applyOutputChanges(const OutputConfiguration &config)
0471 {
0472     QVector<DrmOutput *> toBeEnabled;
0473     QVector<DrmOutput *> toBeDisabled;
0474     for (const auto &gpu : std::as_const(m_gpus)) {
0475         const auto &outputs = gpu->drmOutputs();
0476         for (const auto &output : outputs) {
0477             if (output->isNonDesktop()) {
0478                 continue;
0479             }
0480             output->queueChanges(config);
0481             if (config.constChangeSet(output)->enabled) {
0482                 toBeEnabled << output;
0483             } else {
0484                 toBeDisabled << output;
0485             }
0486         }
0487         if (gpu->testPendingConfiguration() != DrmPipeline::Error::None) {
0488             for (const auto &output : std::as_const(toBeEnabled)) {
0489                 output->revertQueuedChanges();
0490             }
0491             for (const auto &output : std::as_const(toBeDisabled)) {
0492                 output->revertQueuedChanges();
0493             }
0494             return false;
0495         }
0496     }
0497     // first, apply changes to drm outputs.
0498     // This may remove the placeholder output and thus change m_outputs!
0499     for (const auto &output : std::as_const(toBeEnabled)) {
0500         output->applyQueuedChanges(config);
0501     }
0502     for (const auto &output : std::as_const(toBeDisabled)) {
0503         output->applyQueuedChanges(config);
0504     }
0505     // only then apply changes to the virtual outputs
0506     for (const auto &gpu : std::as_const(m_gpus)) {
0507         const auto &outputs = gpu->virtualOutputs();
0508         for (const auto &output : outputs) {
0509             output->applyChanges(config);
0510         }
0511     }
0512     return true;
0513 }
0514 
0515 void DrmBackend::setRenderBackend(DrmRenderBackend *backend)
0516 {
0517     m_renderBackend = backend;
0518 }
0519 
0520 DrmRenderBackend *DrmBackend::renderBackend() const
0521 {
0522     return m_renderBackend;
0523 }
0524 
0525 void DrmBackend::releaseBuffers()
0526 {
0527     for (const auto &gpu : std::as_const(m_gpus)) {
0528         gpu->releaseBuffers();
0529     }
0530 }
0531 
0532 const std::vector<std::unique_ptr<DrmGpu>> &DrmBackend::gpus() const
0533 {
0534     return m_gpus;
0535 }
0536 }