File indexing completed on 2024-11-10 04:56:26

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/session.h"
0016 #include "drm_egl_backend.h"
0017 #include "drm_gpu.h"
0018 #include "drm_logging.h"
0019 #include "drm_output.h"
0020 #include "drm_pipeline.h"
0021 #include "drm_qpainter_backend.h"
0022 #include "drm_render_backend.h"
0023 #include "drm_virtual_output.h"
0024 #include "utils/udev.h"
0025 // KF5
0026 #include <KCoreAddons>
0027 #include <KLocalizedString>
0028 // Qt
0029 #include <QCoreApplication>
0030 #include <QFileInfo>
0031 #include <QSocketNotifier>
0032 #include <QStringBuilder>
0033 // system
0034 #include <algorithm>
0035 #include <cerrno>
0036 #include <sys/stat.h>
0037 #include <unistd.h>
0038 // drm
0039 #include <gbm.h>
0040 #include <libdrm/drm_mode.h>
0041 #include <xf86drm.h>
0042 
0043 namespace KWin
0044 {
0045 
0046 static QStringList splitPathList(const QString &input, const QChar delimiter)
0047 {
0048     QStringList ret;
0049     QString tmp;
0050     for (int i = 0; i < input.size(); i++) {
0051         if (input[i] == delimiter) {
0052             if (i > 0 && input[i - 1] == '\\') {
0053                 tmp[tmp.size() - 1] = delimiter;
0054             } else if (!tmp.isEmpty()) {
0055                 ret.append(tmp);
0056                 tmp = QString();
0057             }
0058         } else {
0059             tmp.append(input[i]);
0060         }
0061     }
0062     if (!tmp.isEmpty()) {
0063         ret.append(tmp);
0064     }
0065     return ret;
0066 }
0067 
0068 DrmBackend::DrmBackend(Session *session, QObject *parent)
0069     : OutputBackend(parent)
0070     , m_udev(std::make_unique<Udev>())
0071     , m_udevMonitor(m_udev->monitor())
0072     , m_session(session)
0073     , m_explicitGpus(splitPathList(qEnvironmentVariable("KWIN_DRM_DEVICES"), ':'))
0074 {
0075 }
0076 
0077 DrmBackend::~DrmBackend() = default;
0078 
0079 Session *DrmBackend::session() const
0080 {
0081     return m_session;
0082 }
0083 
0084 Outputs DrmBackend::outputs() const
0085 {
0086     return m_outputs;
0087 }
0088 
0089 bool DrmBackend::initialize()
0090 {
0091     connect(m_session, &Session::devicePaused, this, [this](dev_t deviceId) {
0092         if (const auto gpu = findGpu(deviceId)) {
0093             gpu->setActive(false);
0094         }
0095     });
0096     connect(m_session, &Session::deviceResumed, this, [this](dev_t deviceId) {
0097         if (const auto gpu = findGpu(deviceId)) {
0098             gpu->setActive(true);
0099         }
0100     });
0101 
0102     if (!m_explicitGpus.isEmpty()) {
0103         for (const QString &fileName : m_explicitGpus) {
0104             addGpu(fileName);
0105         }
0106     } else {
0107         const auto devices = m_udev->listGPUs();
0108         for (const auto &device : devices) {
0109             if (device->seat() == m_session->seat()) {
0110                 addGpu(device->devNode());
0111             }
0112         }
0113     }
0114 
0115     if (m_gpus.empty()) {
0116         qCWarning(KWIN_DRM) << "No suitable DRM devices have been found";
0117         return false;
0118     }
0119 
0120     // setup udevMonitor
0121     if (m_udevMonitor) {
0122         m_udevMonitor->filterSubsystemDevType("drm");
0123         const int fd = m_udevMonitor->fd();
0124         if (fd != -1) {
0125             m_socketNotifier = std::make_unique<QSocketNotifier>(fd, QSocketNotifier::Read);
0126             connect(m_socketNotifier.get(), &QSocketNotifier::activated, this, &DrmBackend::handleUdevEvent);
0127             m_udevMonitor->enable();
0128         }
0129     }
0130     return true;
0131 }
0132 
0133 void DrmBackend::handleUdevEvent()
0134 {
0135     while (auto device = m_udevMonitor->getDevice()) {
0136         // Ignore the device seat if the KWIN_DRM_DEVICES envvar is set.
0137         if (!m_explicitGpus.isEmpty()) {
0138             const auto canonicalPath = QFileInfo(device->devNode()).canonicalPath();
0139             const bool foundMatch = std::any_of(m_explicitGpus.begin(), m_explicitGpus.end(), [&canonicalPath](const QString &explicitPath) {
0140                 return QFileInfo(explicitPath).canonicalPath() == canonicalPath;
0141             });
0142             if (!foundMatch) {
0143                 continue;
0144             }
0145         } else {
0146             if (device->seat() != m_session->seat()) {
0147                 continue;
0148             }
0149         }
0150 
0151         if (device->action() == QStringLiteral("add")) {
0152             DrmGpu *gpu = findGpu(device->devNum());
0153             if (gpu) {
0154                 qCWarning(KWIN_DRM) << "Received unexpected add udev event for:" << device->devNode();
0155                 continue;
0156             }
0157             if (addGpu(device->devNode())) {
0158                 updateOutputs();
0159             }
0160         } else if (device->action() == QStringLiteral("remove")) {
0161             DrmGpu *gpu = findGpu(device->devNum());
0162             if (gpu) {
0163                 if (primaryGpu() == gpu) {
0164                     qCCritical(KWIN_DRM) << "Primary gpu has been removed! Quitting...";
0165                     QCoreApplication::exit(1);
0166                     return;
0167                 } else {
0168                     gpu->setRemoved();
0169                     updateOutputs();
0170                 }
0171             }
0172         } else if (device->action() == QStringLiteral("change")) {
0173             DrmGpu *gpu = findGpu(device->devNum());
0174             if (!gpu) {
0175                 gpu = addGpu(device->devNode());
0176             }
0177             if (gpu && gpu->isActive()) {
0178                 qCDebug(KWIN_DRM) << "Received change event for monitored drm device" << gpu->devNode();
0179                 updateOutputs();
0180             }
0181         }
0182     }
0183 }
0184 
0185 DrmGpu *DrmBackend::addGpu(const QString &fileName)
0186 {
0187     int fd = m_session->openRestricted(fileName);
0188     if (fd < 0) {
0189         qCWarning(KWIN_DRM) << "failed to open drm device at" << fileName;
0190         return nullptr;
0191     }
0192 
0193     if (!drmIsKMS(fd)) {
0194         qCDebug(KWIN_DRM) << "Skipping KMS incapable drm device node at" << fileName;
0195         m_session->closeRestricted(fd);
0196         return nullptr;
0197     }
0198 
0199     struct stat buf;
0200     if (fstat(fd, &buf) == -1) {
0201         qCDebug(KWIN_DRM, "Failed to fstat %s: %s", qPrintable(fileName), strerror(errno));
0202         m_session->closeRestricted(fd);
0203         return nullptr;
0204     }
0205 
0206     qCDebug(KWIN_DRM, "adding GPU %s", qPrintable(fileName));
0207     m_gpus.push_back(std::make_unique<DrmGpu>(this, fileName, fd, buf.st_rdev));
0208     auto gpu = m_gpus.back().get();
0209     connect(gpu, &DrmGpu::outputAdded, this, &DrmBackend::addOutput);
0210     connect(gpu, &DrmGpu::outputRemoved, this, &DrmBackend::removeOutput);
0211     Q_EMIT gpuAdded(gpu);
0212     return gpu;
0213 }
0214 
0215 void DrmBackend::addOutput(DrmAbstractOutput *o)
0216 {
0217     const bool allOff = std::all_of(m_outputs.begin(), m_outputs.end(), [](Output *output) {
0218         return output->dpmsMode() != Output::DpmsMode::On;
0219     });
0220     if (allOff && m_recentlyUnpluggedDpmsOffOutputs.contains(o->uuid())) {
0221         if (DrmOutput *drmOutput = qobject_cast<DrmOutput *>(o)) {
0222             // When the system is in dpms power saving mode, KWin turns on all outputs if the user plugs a new output in
0223             // as that's an intentional action and they expect to see the output light up.
0224             // Some outputs however temporarily disconnect in some situations, most often shortly after they go into standby.
0225             // To not turn on outputs in that case, restore the previous dpms state
0226             drmOutput->updateDpmsMode(Output::DpmsMode::Off);
0227             drmOutput->pipeline()->setActive(false);
0228             drmOutput->renderLoop()->inhibit();
0229             m_recentlyUnpluggedDpmsOffOutputs.removeOne(drmOutput->uuid());
0230         }
0231     }
0232     m_outputs.append(o);
0233     Q_EMIT outputAdded(o);
0234     o->updateEnabled(true);
0235 }
0236 
0237 void DrmBackend::removeOutput(DrmAbstractOutput *o)
0238 {
0239     if (o->dpmsMode() == Output::DpmsMode::Off) {
0240         const QUuid id = o->uuid();
0241         m_recentlyUnpluggedDpmsOffOutputs.push_back(id);
0242         QTimer::singleShot(1000, this, [this, id]() {
0243             m_recentlyUnpluggedDpmsOffOutputs.removeOne(id);
0244         });
0245     }
0246     o->updateEnabled(false);
0247     m_outputs.removeOne(o);
0248     Q_EMIT outputRemoved(o);
0249 }
0250 
0251 void DrmBackend::updateOutputs()
0252 {
0253     for (auto it = m_gpus.begin(); it != m_gpus.end(); ++it) {
0254         if ((*it)->isRemoved()) {
0255             (*it)->removeOutputs();
0256         } else {
0257             (*it)->updateOutputs();
0258         }
0259     }
0260 
0261     Q_EMIT outputsQueried();
0262 
0263     for (auto it = m_gpus.begin(); it != m_gpus.end();) {
0264         DrmGpu *gpu = it->get();
0265         if (gpu->isRemoved() || (gpu != primaryGpu() && gpu->drmOutputs().isEmpty())) {
0266             qCDebug(KWIN_DRM) << "Removing GPU" << (*it)->devNode();
0267             const std::unique_ptr<DrmGpu> keepAlive = std::move(*it);
0268             it = m_gpus.erase(it);
0269             Q_EMIT gpuRemoved(keepAlive.get());
0270         } else {
0271             it++;
0272         }
0273     }
0274 }
0275 
0276 std::unique_ptr<InputBackend> DrmBackend::createInputBackend()
0277 {
0278     return std::make_unique<LibinputBackend>(m_session);
0279 }
0280 
0281 std::unique_ptr<QPainterBackend> DrmBackend::createQPainterBackend()
0282 {
0283     return std::make_unique<DrmQPainterBackend>(this);
0284 }
0285 
0286 std::unique_ptr<OpenGLBackend> DrmBackend::createOpenGLBackend()
0287 {
0288     return std::make_unique<EglGbmBackend>(this);
0289 }
0290 
0291 void DrmBackend::sceneInitialized()
0292 {
0293     if (m_outputs.isEmpty()) {
0294         updateOutputs();
0295     } else {
0296         for (const auto &gpu : std::as_const(m_gpus)) {
0297             gpu->recreateSurfaces();
0298         }
0299     }
0300 }
0301 
0302 QList<CompositingType> DrmBackend::supportedCompositors() const
0303 {
0304     return QList<CompositingType>{OpenGLCompositing, QPainterCompositing};
0305 }
0306 
0307 QString DrmBackend::supportInformation() const
0308 {
0309     QString supportInfo;
0310     QDebug s(&supportInfo);
0311     s.nospace();
0312     s << "Name: "
0313       << "DRM" << Qt::endl;
0314     for (size_t g = 0; g < m_gpus.size(); g++) {
0315         s << "Atomic Mode Setting on GPU " << g << ": " << m_gpus.at(g)->atomicModeSetting() << Qt::endl;
0316     }
0317     return supportInfo;
0318 }
0319 
0320 Output *DrmBackend::createVirtualOutput(const QString &name, const QSize &size, double scale)
0321 {
0322     auto output = primaryGpu()->createVirtualOutput(name, size * scale, scale);
0323     Q_EMIT outputsQueried();
0324     return output;
0325 }
0326 
0327 void DrmBackend::removeVirtualOutput(Output *output)
0328 {
0329     auto virtualOutput = qobject_cast<DrmVirtualOutput *>(output);
0330     if (!virtualOutput) {
0331         return;
0332     }
0333     primaryGpu()->removeVirtualOutput(virtualOutput);
0334     Q_EMIT outputsQueried();
0335 }
0336 
0337 DrmGpu *DrmBackend::primaryGpu() const
0338 {
0339     return m_gpus.empty() ? nullptr : m_gpus.front().get();
0340 }
0341 
0342 DrmGpu *DrmBackend::findGpu(dev_t deviceId) const
0343 {
0344     auto it = std::find_if(m_gpus.begin(), m_gpus.end(), [deviceId](const auto &gpu) {
0345         return gpu->deviceId() == deviceId;
0346     });
0347     return it == m_gpus.end() ? nullptr : it->get();
0348 }
0349 
0350 size_t DrmBackend::gpuCount() const
0351 {
0352     return m_gpus.size();
0353 }
0354 
0355 bool DrmBackend::applyOutputChanges(const OutputConfiguration &config)
0356 {
0357     QList<DrmOutput *> toBeEnabled;
0358     QList<DrmOutput *> toBeDisabled;
0359     for (const auto &gpu : std::as_const(m_gpus)) {
0360         const auto &outputs = gpu->drmOutputs();
0361         for (const auto &output : outputs) {
0362             if (output->isNonDesktop()) {
0363                 continue;
0364             }
0365             if (const auto changeset = config.constChangeSet(output)) {
0366                 output->queueChanges(changeset);
0367                 if (changeset->enabled) {
0368                     toBeEnabled << output;
0369                 } else {
0370                     toBeDisabled << output;
0371                 }
0372             }
0373         }
0374         if (gpu->testPendingConfiguration() != DrmPipeline::Error::None) {
0375             for (const auto &output : std::as_const(toBeEnabled)) {
0376                 output->revertQueuedChanges();
0377             }
0378             for (const auto &output : std::as_const(toBeDisabled)) {
0379                 output->revertQueuedChanges();
0380             }
0381             return false;
0382         }
0383     }
0384     // first, apply changes to drm outputs.
0385     // This may remove the placeholder output and thus change m_outputs!
0386     for (const auto &output : std::as_const(toBeEnabled)) {
0387         if (const auto changeset = config.constChangeSet(output)) {
0388             output->applyQueuedChanges(changeset);
0389         }
0390     }
0391     for (const auto &output : std::as_const(toBeDisabled)) {
0392         if (const auto changeset = config.constChangeSet(output)) {
0393             output->applyQueuedChanges(changeset);
0394         }
0395     }
0396     // only then apply changes to the virtual outputs
0397     for (const auto &gpu : std::as_const(m_gpus)) {
0398         const auto &outputs = gpu->virtualOutputs();
0399         for (const auto &output : outputs) {
0400             output->applyChanges(config);
0401         }
0402     }
0403     return true;
0404 }
0405 
0406 void DrmBackend::setRenderBackend(DrmRenderBackend *backend)
0407 {
0408     m_renderBackend = backend;
0409 }
0410 
0411 DrmRenderBackend *DrmBackend::renderBackend() const
0412 {
0413     return m_renderBackend;
0414 }
0415 
0416 void DrmBackend::releaseBuffers()
0417 {
0418     for (const auto &gpu : std::as_const(m_gpus)) {
0419         gpu->releaseBuffers();
0420     }
0421 }
0422 
0423 const std::vector<std::unique_ptr<DrmGpu>> &DrmBackend::gpus() const
0424 {
0425     return m_gpus;
0426 }
0427 
0428 EglDisplay *DrmBackend::sceneEglDisplayObject() const
0429 {
0430     return m_gpus.front()->eglDisplay();
0431 }
0432 }
0433 
0434 #include "moc_drm_backend.cpp"