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 }