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"